Skip to content
← Public packages

@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.

FollowFork

AGENTS.md

243 lines · 8.9 KB · Markdown
Raw

@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

NameViaRequiredNotes
github-botAccessToken@kentcdodds/github account botYes (github host)Issues/comments on target.repo
cursorApiKey@kentcdodds/cursorYes (daily sweep)Spawn Cloud Agents on sweep
grok-bot wake secrets@kentcdodds/grok-botYes (kody host)Stamp-aligned; do not mint URLs yourself

Qualify principles (soft skip)

Before filing, ./create / ./file soft-skip when any of:

  1. Missing/invalid target (host not github|kody, or repo empty; github needs owner/repo)
  2. Structured filing missing preliminaryInvestigation (and no body)
  3. Missing reproducible gap: no howToReproduce and no body and structured fields too thin
  4. Looks transient/session-bound: heuristic — no howToReproduce, empty/missing cost, whatHappened under ~40 chars (and no body)
  5. 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

ExportImport
overviewkody:@kentcdodds/friction-log
createkody:@kentcdodds/friction-log/create
filekody:@kentcdodds/friction-log/file
scankody:@kentcdodds/friction-log/scan
sweepkody:@kentcdodds/friction-log/sweep
dailykody:@kentcdodds/friction-log/daily
record-outcomekody:@kentcdodds/friction-log/record-outcome
pausekody:@kentcdodds/friction-log/pause
resumekody:@kentcdodds/friction-log/resume
statuskody:@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 assume kody-hq/kody. Never use gh issue create for friction that should hit the daily sweep — missing friction label means the platform sweep never sees it.
  • Prefer ./file for 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 items actually 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'); dryRun reports the same.
  • Daily sweep eligibility: open + friction, not friction-skipped, on kody-hq/kody only (hard-coded queue). Comments are not parsed.
  • At most one automatic Cloud Agent per Denver calendar day.
  • ./pause / ./resume are the kill switch. Prefer pause over disabling the daily job definition.
  • ./record-outcome stores the agent's final report — keep summary short; never include secrets.
  • Kody host: package calls import wake from 'kody:@kentcdodds/grok-bot/wake' with bot: 'patch'. From local CLI execute (or any execute path), also statically import kody:@kentcdodds/grok-bot/wake alongside 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" for api.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-outcome via npx @kodycodes/cli execute --local — never hosted MCP execute.

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.