@kentcdodds/friction-log
Qualify and file friction papercuts to GitHub (any owner/repo) or wake Patch for Kody packages; daily sweep of kody-hq/kody friction issues.
AGENTS.md
243 lines · 8.9 KB · Markdown@kentcdodds/friction-log — agent notes
Human setup and intent live in README.md. This file is for
agents: imports, smoke/dryRun snippets, and edge cases. Secrets by name
only — never paste token values. Do not disable the live daily job or
related webhooks unless the user explicitly asks.
Secrets
| Name | Via | Required | Notes |
|---|---|---|---|
github-botAccessToken | @kentcdodds/github account bot | Yes (github host) | Issues/comments on target.repo |
cursorApiKey | @kentcdodds/cursor | Yes (daily sweep) | Spawn Cloud Agents on sweep |
| grok-bot wake secrets | @kentcdodds/grok-bot | Yes (kody host) | Stamp-aligned; do not mint URLs yourself |
Qualify principles (soft skip)
Before filing, ./create / ./file soft-skip when any of:
- Missing/invalid
target(hostnotgithub|kody, orrepoempty; github needsowner/repo) - Structured filing missing
preliminaryInvestigation(and nobody) - Missing reproducible gap: no
howToReproduceand nobodyand structured fields too thin - Looks transient/session-bound: heuristic — no
howToReproduce, empty/missingcost,whatHappenedunder ~40 chars (and nobody) - For github: title empty after
Friction:normalize
Bar: durable, recurring, clear owner, reproducible contract/API/docs gap.
Do not invent a deny-list of titles. Skip returns
{ ok: true, skipped: true, reason, title } (create) or skipped[] (file).
Structured filings also pass preliminaryInvestigation (short notes on what
was already looked at) and optional relevantFiles (omit when none known). Do
not invent a root cause. Do not paste a full issue thread.
Import paths
| Export | Import |
|---|---|
| overview | kody:@kentcdodds/friction-log |
| create | kody:@kentcdodds/friction-log/create |
| file | kody:@kentcdodds/friction-log/file |
| scan | kody:@kentcdodds/friction-log/scan |
| sweep | kody:@kentcdodds/friction-log/sweep |
| daily | kody:@kentcdodds/friction-log/daily |
| record-outcome | kody:@kentcdodds/friction-log/record-outcome |
| pause | kody:@kentcdodds/friction-log/pause |
| resume | kody:@kentcdodds/friction-log/resume |
| status | kody:@kentcdodds/friction-log/status |
Prefer npx @kodycodes/cli execute --local (Node ≥22) with static
kody:@kentcdodds/friction-log/... imports. Do not use hosted MCP
execute. Do not lead with packages.invoke. If --local cannot run, fix the
environment — Open API / MCP api cannot invoke these package exports.
Target (required)
type FrictionHost = 'github' | 'kody'
type FrictionTarget = {
host: FrictionHost
/** github: "owner/repo"; kody: "@owner/leaf" or "leaf" */
repo: string
}- No aliases, no dual/compat paths, no silent default host.
- Platform GitHub queue: pass
target: { host: 'github', repo: 'kody-hq/kody' }. - Package friction:
target: { host: 'kody', repo: '@kentcdodds/leaf' }→ Patch wake only.
Smoke / dryRun
Operator snapshot (read-only):
import status from 'kody:@kentcdodds/friction-log/status'
export default async function main() {
return await status()
}Create — GitHub dryRun:
import create from 'kody:@kentcdodds/friction-log/create'
export default async function main() {
return await create({
dryRun: true,
target: { host: 'github', repo: 'kody-hq/kody' },
title: 'Friction: example papercut',
whatHappened: '…',
whatYouWanted: '…',
howToReproduce: '…',
cost: '…',
preliminaryInvestigation: '…',
relevantFiles: ['path/when/known.ts'],
})
// => { ok, dryRun: true, routed: 'github', title, body, labels: ['friction'], created: false, target }
}Create — would-skip qualify (no network write beyond soft skip):
import create from 'kody:@kentcdodds/friction-log/create'
export default async function main() {
return await create({
target: { host: 'github', repo: 'kody-hq/kody' },
title: 'confused once',
whatHappened: 'brief confusion',
})
// => { ok: true, skipped: true, reason, title }
}Create — Kody Patch wake dryRun:
import create from 'kody:@kentcdodds/friction-log/create'
// Local-execute callers should also statically import wake so bundler provenance
// includes the nested grok-bot dependency.
import 'kody:@kentcdodds/grok-bot/wake'
export default async function main() {
return await create({
dryRun: true,
target: { host: 'kody', repo: '@kentcdodds/friction-log' },
title: 'export map stale',
whatHappened: 'docs still imply a silent default host',
whatYouWanted: 'required target documented everywhere',
howToReproduce: 'read create without target in AGENTS',
cost: 'wrong routing',
preliminaryInvestigation:
'Compared create export JSDoc with AGENTS.md smoke examples.',
relevantFiles: ['AGENTS.md', 'src/create.ts'],
})
// => { ok, routed: 'patch-wake', woke: false, dryRun: true, title, target }
}File (end-of-ship)
One top-level target applies to all items. Papercuts do not carry their
own target. Prefer ./file for batches.
import file from 'kody:@kentcdodds/friction-log/file'
export default async function main() {
return await file({
dryRun: true,
target: { host: 'github', repo: 'kody-hq/kody' },
items: [
{
title: 'Friction: example papercut',
whatHappened: '…',
whatYouWanted: '…',
howToReproduce: '…',
cost: '…',
preliminaryInvestigation: '…',
relevantFiles: ['path/when/known.ts'],
},
],
})
// => { ok, dryRun, filed, reused, woke, skipped }
}Kody batch (results in woke):
import file from 'kody:@kentcdodds/friction-log/file'
export default async function main() {
return await file({
dryRun: true,
target: { host: 'kody', repo: '@kentcdodds/friction-log' },
items: [
{
title: 'qualify soft-skip unclear',
whatHappened: '…',
whatYouWanted: '…',
howToReproduce: '…',
cost: '…',
preliminaryInvestigation: '…',
},
],
})
// => { ok, dryRun, filed: [], reused: [], woke: [{ action:'woke', bot:'patch', woke, title, target }], skipped }
}Edge cases
- Always pass
target. Never assumekody-hq/kody. Never usegh issue createfor friction that should hit the daily sweep — missingfrictionlabel means the platform sweep never sees it. - Prefer
./filefor end-of-ship batches; empty input / bad target →skipped. - GitHub reuse matches the exact normalized title, and the "Seen again" comment
is built only from the item you passed. If a reuse lists a title you did not
send, the call received stale params: check the
itemsactually passed (for example the file behind--params "$(cat items.json)"). Re-filing a papercut whose body equals the issue body reuses without commenting (commentSkipped: 'same-as-issue-body');dryRunreports the same. - Daily sweep eligibility: open +
friction, notfriction-skipped, on kody-hq/kody only (hard-coded queue). Comments are not parsed. - At most one automatic Cloud Agent per Denver calendar day.
./pause/./resumeare the kill switch. Prefer pause over disabling thedailyjob definition../record-outcomestores the agent's final report — keep summary short; never include secrets.- Kody host: package calls
import wake from 'kody:@kentcdodds/grok-bot/wake'withbot: 'patch'. From local CLI execute (or any execute path), also statically importkody:@kentcdodds/grok-bot/wakealongside the friction-log export so bundler provenance includes the nested grok-bot dependency. This is a bundler-provenance requirement, not a secret-visibility or Discord workaround. Cole does not need to arm a separate routine for this path. - Issue text forwarded into the Cloud Agent prompt goes through
sanitizeForPrompt, which breaks any literal{{secret:…}}placeholder ({ {secret:…}}). Kody expands placeholders in outbound request bodies, so a quoted placeholder would otherwise fail the run with "Secrets require host approval" forapi.cursor.com. Never approve a secret for that host to "fix" it; that would leak the secret into the prompt. - Daily Cloud Agents call
./record-outcomevianpx @kodycodes/cli execute --local— never hosted MCPexecute.
Mention safety
Friction issues and comments are public. Before posting, ./create and
./file run a deterministic pass (src/mention-safe.ts): scoped package names
(@scope/name, kody:@scope/name/export) go in inline code and credit
phrases like "by @someone" are dropped. Same-title dedupe matches both the
original and the sanitized title, so issues filed before this pass still
reuse. @kentcdodds/github refuses any remaining @-mention.
The daily agent's skip comment is the only intended mention. It pings Kent
with allowedMentions: ['kentcdodds'] on that one comment and nowhere else.
Dependency note
Uses @kentcdodds/github (createAuthenticatedFetch on github-bot /
github-kent OAuth integrations) and @kentcdodds/grok-bot for Patch wake.