@kolisachint/hoocode-agent 0.5.13 → 0.5.14

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (53) hide show
  1. package/CHANGELOG.md +71 -0
  2. package/dist/config.d.ts +9 -0
  3. package/dist/config.d.ts.map +1 -1
  4. package/dist/config.js +16 -0
  5. package/dist/config.js.map +1 -1
  6. package/dist/core/context-files.d.ts +16 -3
  7. package/dist/core/context-files.d.ts.map +1 -1
  8. package/dist/core/context-files.js +77 -6
  9. package/dist/core/context-files.js.map +1 -1
  10. package/dist/core/learn/digest.d.ts +17 -0
  11. package/dist/core/learn/digest.d.ts.map +1 -0
  12. package/dist/core/learn/digest.js +131 -0
  13. package/dist/core/learn/digest.js.map +1 -0
  14. package/dist/core/learn/extract.d.ts +178 -0
  15. package/dist/core/learn/extract.d.ts.map +1 -0
  16. package/dist/core/learn/extract.js +705 -0
  17. package/dist/core/learn/extract.js.map +1 -0
  18. package/dist/core/learn/normalize.d.ts +65 -0
  19. package/dist/core/learn/normalize.d.ts.map +1 -0
  20. package/dist/core/learn/normalize.js +245 -0
  21. package/dist/core/learn/normalize.js.map +1 -0
  22. package/dist/core/learn/state.d.ts +115 -0
  23. package/dist/core/learn/state.d.ts.map +1 -0
  24. package/dist/core/learn/state.js +151 -0
  25. package/dist/core/learn/state.js.map +1 -0
  26. package/dist/core/settings-defaults.d.ts +5 -0
  27. package/dist/core/settings-defaults.d.ts.map +1 -1
  28. package/dist/core/settings-defaults.js +5 -0
  29. package/dist/core/settings-defaults.js.map +1 -1
  30. package/dist/core/settings-manager.d.ts +14 -0
  31. package/dist/core/settings-manager.d.ts.map +1 -1
  32. package/dist/core/settings-manager.js +17 -0
  33. package/dist/core/settings-manager.js.map +1 -1
  34. package/dist/core/settings-types.d.ts +5 -0
  35. package/dist/core/settings-types.d.ts.map +1 -1
  36. package/dist/core/settings-types.js.map +1 -1
  37. package/dist/extensions/core/hoo-core.d.ts.map +1 -1
  38. package/dist/extensions/core/hoo-core.js +2 -0
  39. package/dist/extensions/core/hoo-core.js.map +1 -1
  40. package/dist/extensions/core/learn.d.ts +22 -0
  41. package/dist/extensions/core/learn.d.ts.map +1 -0
  42. package/dist/extensions/core/learn.js +167 -0
  43. package/dist/extensions/core/learn.js.map +1 -0
  44. package/dist/modes/interactive/resource-display.d.ts.map +1 -1
  45. package/dist/modes/interactive/resource-display.js +6 -1
  46. package/dist/modes/interactive/resource-display.js.map +1 -1
  47. package/docs/settings.md +31 -0
  48. package/docs/usage.md +64 -2
  49. package/examples/extensions/custom-provider-anthropic/package.json +1 -1
  50. package/examples/extensions/custom-provider-gitlab-duo/package.json +1 -1
  51. package/examples/extensions/sandbox/package.json +1 -1
  52. package/examples/extensions/with-deps/package.json +1 -1
  53. package/package.json +4 -4
@@ -0,0 +1 @@
1
+ {"version":3,"file":"extract.js","sourceRoot":"","sources":["../../../src/core/learn/extract.ts"],"names":[],"mappings":"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","sourcesContent":["/**\n * Session mining for `/learn`.\n *\n * Reads session `.jsonl` files straight off disk rather than the live context.\n * That is the whole point: the on-disk transcript is complete even when the\n * in-context one has been compacted away, and it spans every past session\n * instead of only this one. Cross-session repetition is the signal that decides\n * whether something is a durable rule or a one-off, and it is the one thing a\n * prompt reading its own context cannot see.\n *\n * The split of labour is deliberate. This module is entirely deterministic: it\n * parses, filters, normalizes, counts and ranks. Judgement — is this a rule, how\n * should it be phrased, which scope owns it — belongs to the model reading the\n * digest, which is why the output carries evidence (counts, sessions, dates)\n * rather than conclusions.\n */\n\nimport { existsSync, readdirSync, readFileSync, statSync } from \"node:fs\";\nimport { dirname, join, resolve } from \"node:path\";\nimport type { AgentMessage } from \"@kolisachint/hoocode-agent-core\";\nimport type { TextContent, ToolCall } from \"@kolisachint/hoocode-ai\";\nimport { getUserAgentsDir } from \"../../config.js\";\nimport { getDefaultSessionDir } from \"../session-manager.js\";\nimport { loadSkills } from \"../skills.js\";\nimport {\n\tcommandHead,\n\tcontentWords,\n\textractErrorRegion,\n\tisBenignFailure,\n\tisRuleShapedDirective,\n\tisUninformativeFailure,\n\tnormalizeCommand,\n\tnormalizeDirective,\n\tnormalizeErrorSignature,\n\twordOverlap,\n} from \"./normalize.js\";\nimport { judge, type LearnState } from \"./state.js\";\n\n/**\n * Prefix on the message `/learn` injects. The digest is persisted like any user\n * turn, so without this marker the next `/learn` would mine its own output and\n * every proposal would compound its own count.\n */\nexport const LEARN_DIGEST_MARKER = \"[learn-digest]\";\n\n/** Sessions considered, newest first. */\nconst DEFAULT_MAX_SESSIONS = 20;\n/** Sessions older than this are ignored — a pattern that stopped is not a rule. */\nconst DEFAULT_MAX_AGE_DAYS = 30;\n/** Entries parsed per session file, as a guard against pathological transcripts. */\nconst MAX_ENTRIES_PER_SESSION = 8000;\n/** Tool calls per session fed to the workflow detector. */\nconst MAX_TOOL_CALLS_PER_SESSION = 400;\n/** How far forward the fix extractor looks for the same command succeeding. */\nconst FIX_LOOKAHEAD = 40;\n/** Word overlap against an existing rule above which a directive counts as covered. */\nconst COVERED_OVERLAP = 0.6;\n/**\n * The same bar for skills, set higher on purpose.\n *\n * A rule is one line, so overlap against it is a sharp signal. A skill is a name\n * plus a description written to attract matches, which is a far larger haystack\n * — a short directive's words turn up in it by chance much more readily. The\n * higher bar and the truncation below keep \"you already have a skill for this\"\n * from being said on a coincidence.\n */\nconst SKILL_COVERED_OVERLAP = 0.75;\n/** Description characters considered. The opening says what a skill does; the rest is trigger bait. */\nconst SKILL_DESCRIPTION_CHARS = 300;\n/** Directives must reach this many occurrences to be reported at all. */\nconst DEFAULT_MIN_DIRECTIVE_COUNT = 2;\n/** Tool sequence lengths considered as workflow candidates. */\nconst WORKFLOW_MIN_LEN = 3;\nconst WORKFLOW_MAX_LEN = 5;\n/** Repeats before a tool sequence is worth proposing as a skill. */\nconst DEFAULT_MIN_WORKFLOW_COUNT = 3;\n/** Cap on each list in the digest, so the model's budget goes to the top signals. */\nconst DEFAULT_MAX_PER_CATEGORY = 8;\n\n/**\n * Where a repeated directive already lives, if anywhere.\n *\n * A directive covered by a rule and said only once is simply dropped — the rule\n * exists and is working. What survives is one of three cases, and they want\n * different responses:\n *\n * - `new` — not written down anywhere. Propose it.\n * - `restated` — a context-file rule covers it and you said it anyway, so the\n * rule is not working. Rewrite it; do not add a second one.\n * - `has-skill` — a *skill* covers it and you asked by hand anyway, which\n * usually means the skill's `description` is not triggering. Sharpen the\n * description rather than writing a rule that duplicates the skill.\n */\nexport type DirectiveStatus = \"new\" | \"restated\" | \"has-skill\";\n\n/** Fields every proposable item shares, so suppression can be applied uniformly. */\ninterface Proposable {\n\t/** Stable identity across runs — what the state file remembers. */\n\tkey: string;\n\t/** Newest occurrence in the window, ISO. */\n\tlastSeen: string;\n}\n\nexport interface DirectiveCluster extends Proposable {\n\t/** Representative raw text, the longest seen in the cluster. */\n\ttext: string;\n\tnormalized: string;\n\t/** Total times said. */\n\tcount: number;\n\t/** Distinct sessions it was said in — the stronger of the two counts. */\n\tsessions: number;\n\tstatus: DirectiveStatus;\n\t/** The existing rule line matched, when status is `restated`. */\n\texistingRule?: string;\n\t/** The skill that already covers this, when status is `has-skill`. */\n\texistingSkill?: string;\n\t/**\n\t * Shown before and still not written down anywhere — neither as a rule nor as\n\t * a skill — so you saw this proposal and passed on it. Only meaningful for\n\t * directives, which are the only items with a real coverage signal.\n\t */\n\tpreviouslyDeclined: boolean;\n}\n\nexport interface FixCandidate extends Proposable {\n\t/** Normalized failing command. */\n\tcommand: string;\n\t/** Normalized error signature, the dedupe key. */\n\tsignature: string;\n\t/** Short raw excerpt, so the model sees the real error text. */\n\terrorExcerpt: string;\n\t/** Commands run between the failure and the pass. */\n\tinterveningCommands: string[];\n\t/** Files edited between the failure and the pass. */\n\teditedFiles: string[];\n\t/** Times this signature failed and was resolved across the window. */\n\tcount: number;\n\tsessions: number;\n}\n\nexport interface WorkflowCandidate extends Proposable {\n\t/** Tool-call signatures in order. */\n\tsteps: string[];\n\tcount: number;\n\tsessions: number;\n}\n\nexport interface LearnDigest {\n\tscannedSessions: number;\n\tskippedSessions: number;\n\toldestSession?: string;\n\tnewestSession?: string;\n\tagentsFilePath?: string;\n\tagentsFileTokens?: number;\n\tdirectives: DirectiveCluster[];\n\tfixes: FixCandidate[];\n\tworkflows: WorkflowCandidate[];\n\t/** Items held back because nothing new has happened since they were last shown. */\n\tsuppressed: number;\n\t/** Everything this run put on screen, for the caller to persist. */\n\tsurfaced: Array<{ key: string; lastSeen: string; covered: boolean }>;\n}\n\nexport interface ExtractOptions {\n\tcwd: string;\n\tagentDir: string;\n\t/** Override the directory scanned. Defaults to the per-cwd session dir. */\n\tsessionDir?: string;\n\tmaxSessions?: number;\n\tmaxAgeDays?: number;\n\t/** Occurrences a directive needs before it is proposed. The signal/noise dial. */\n\tminRepeats?: number;\n\t/** Non-overlapping repeats a tool sequence needs before it is proposed as a skill. */\n\tminWorkflowRepeats?: number;\n\t/** Cap on each list in the digest. */\n\tmaxProposals?: number;\n\t/**\n\t * What previous runs already showed. Items with no new occurrences since are\n\t * held back. Omit (or pass `ignoreState`) to propose everything in the window.\n\t */\n\tstate?: LearnState;\n\t/** Re-propose everything, ignoring what previous runs surfaced (`/learn all`). */\n\tignoreState?: boolean;\n\t/**\n\t * Skills a directive can already be covered by. Defaults to the ones loaded\n\t * from disk; injectable so tests do not read the developer's real skills.\n\t */\n\tskills?: Array<{ name: string; description: string }>;\n\t/** Injectable clock, for tests. */\n\tnow?: Date;\n}\n\ninterface SessionHeaderLike {\n\ttype: \"session\";\n\tid?: string;\n\ttimestamp?: string;\n\tcwd?: string;\n}\n\ninterface EntryLike {\n\ttype: string;\n\tid?: string;\n\tparentId?: string | null;\n\ttimestamp?: string;\n\tmessage?: AgentMessage;\n}\n\n/** One session, reduced to the branch that was actually taken. */\ninterface ParsedSession {\n\tfile: string;\n\tid: string;\n\ttimestamp: string;\n\tentries: EntryLike[];\n}\n\nfunction textOf(content: unknown): string {\n\tif (typeof content === \"string\") return content;\n\tif (!Array.isArray(content)) return \"\";\n\treturn content\n\t\t.map((block) =>\n\t\t\tblock && typeof block === \"object\" && (block as TextContent).type === \"text\"\n\t\t\t\t? ((block as TextContent).text ?? \"\")\n\t\t\t\t: \"\",\n\t\t)\n\t\t.join(\"\\n\")\n\t\t.trim();\n}\n\nfunction isToolCall(block: unknown): block is ToolCall {\n\treturn !!block && typeof block === \"object\" && (block as ToolCall).type === \"toolCall\";\n}\n\n/**\n * Reduce a session's raw entries to the branch that was actually taken.\n *\n * Session files are trees — forks and clones append entries that were never\n * part of the same conversation. Walking parent links back from the last entry\n * keeps the extractor from stitching a \"fix\" out of two turns that never\n * happened in sequence. Sessions written before entry ids existed are flat, and\n * for those file order *is* the branch.\n */\nfunction activeBranch(entries: EntryLike[]): EntryLike[] {\n\tconst withIds = entries.filter((e) => typeof e.id === \"string\");\n\tif (withIds.length === 0) return entries;\n\n\tconst byId = new Map<string, EntryLike>();\n\tfor (const entry of withIds) byId.set(entry.id as string, entry);\n\n\tconst branch: EntryLike[] = [];\n\tconst seen = new Set<string>();\n\tlet cursor: EntryLike | undefined = withIds[withIds.length - 1];\n\twhile (cursor?.id && !seen.has(cursor.id)) {\n\t\tseen.add(cursor.id);\n\t\tbranch.push(cursor);\n\t\tcursor = cursor.parentId ? byId.get(cursor.parentId) : undefined;\n\t}\n\treturn branch.reverse();\n}\n\nfunction parseSessionFile(file: string, cwd: string): ParsedSession | undefined {\n\tlet raw: string;\n\ttry {\n\t\traw = readFileSync(file, \"utf-8\");\n\t} catch {\n\t\treturn undefined;\n\t}\n\n\tconst lines = raw.split(\"\\n\");\n\tlet header: SessionHeaderLike | undefined;\n\tconst entries: EntryLike[] = [];\n\tfor (const line of lines) {\n\t\tif (!line.trim()) continue;\n\t\tif (entries.length >= MAX_ENTRIES_PER_SESSION) break;\n\t\tlet parsed: EntryLike | SessionHeaderLike;\n\t\ttry {\n\t\t\tparsed = JSON.parse(line);\n\t\t} catch {\n\t\t\t// A partially-flushed final line is normal for a live session.\n\t\t\tcontinue;\n\t\t}\n\t\tif (parsed.type === \"session\") {\n\t\t\theader ??= parsed as SessionHeaderLike;\n\t\t\tcontinue;\n\t\t}\n\t\tentries.push(parsed as EntryLike);\n\t}\n\n\t// An explicit `--session` path can put a session for another directory in\n\t// this directory, so trust the header over the file's location.\n\tif (header?.cwd && resolve(header.cwd) !== resolve(cwd)) return undefined;\n\tif (entries.length === 0) return undefined;\n\n\treturn {\n\t\tfile,\n\t\tid: header?.id ?? file,\n\t\ttimestamp: header?.timestamp ?? statSync(file).mtime.toISOString(),\n\t\tentries: activeBranch(entries),\n\t};\n}\n\nfunction listSessions(options: ExtractOptions): { sessions: ParsedSession[]; skipped: number } {\n\tconst dir = options.sessionDir ?? getDefaultSessionDir(options.cwd, options.agentDir);\n\tif (!existsSync(dir)) return { sessions: [], skipped: 0 };\n\n\tconst maxSessions = options.maxSessions ?? DEFAULT_MAX_SESSIONS;\n\tconst maxAgeDays = options.maxAgeDays ?? DEFAULT_MAX_AGE_DAYS;\n\tconst now = options.now ?? new Date();\n\tconst cutoff = now.getTime() - maxAgeDays * 24 * 60 * 60 * 1000;\n\n\tlet files: string[];\n\ttry {\n\t\tfiles = readdirSync(dir)\n\t\t\t.filter((f) => f.endsWith(\".jsonl\"))\n\t\t\t.map((f) => join(dir, f));\n\t} catch {\n\t\treturn { sessions: [], skipped: 0 };\n\t}\n\n\tconst dated = files\n\t\t.map((file) => {\n\t\t\ttry {\n\t\t\t\treturn { file, mtime: statSync(file).mtime.getTime() };\n\t\t\t} catch {\n\t\t\t\treturn undefined;\n\t\t\t}\n\t\t})\n\t\t.filter((f): f is { file: string; mtime: number } => !!f)\n\t\t.sort((a, b) => b.mtime - a.mtime);\n\n\tconst sessions: ParsedSession[] = [];\n\tlet skipped = 0;\n\tfor (const { file, mtime } of dated) {\n\t\tif (sessions.length >= maxSessions) {\n\t\t\tskipped++;\n\t\t\tcontinue;\n\t\t}\n\t\tif (mtime < cutoff) {\n\t\t\tskipped++;\n\t\t\tcontinue;\n\t\t}\n\t\tconst parsed = parseSessionFile(file, options.cwd);\n\t\tif (parsed) sessions.push(parsed);\n\t\telse skipped++;\n\t}\n\treturn { sessions, skipped };\n}\n\n/**\n * Hold back items already shown that have not recurred since, then cap the rest.\n *\n * Order matters: suppression runs *before* the cap, or an item you already\n * decided on would occupy one of the few slots the digest has and push a live\n * signal off the list.\n */\nfunction applySuppression<T extends Proposable>(\n\titems: T[],\n\tstate: LearnState | undefined,\n\tmaxProposals: number,\n\tcovered: (item: T) => boolean,\n\tonDeclined?: (item: T) => void,\n): { kept: T[]; suppressed: number } {\n\tif (!state) return { kept: items.slice(0, maxProposals), suppressed: 0 };\n\n\tconst kept: T[] = [];\n\tlet suppressed = 0;\n\tfor (const item of items) {\n\t\tconst verdict = judge(state, { key: item.key, lastSeen: item.lastSeen, covered: covered(item) });\n\t\tif (verdict.suppressed) {\n\t\t\tsuppressed++;\n\t\t\tcontinue;\n\t\t}\n\t\tif (verdict.previouslyDeclined) onDeclined?.(item);\n\t\tkept.push(item);\n\t}\n\treturn { kept: kept.slice(0, maxProposals), suppressed };\n}\n\n/** Nearest AGENTS.md walking up from cwd, so proposals can be checked against it. */\nfunction findAgentsFile(cwd: string): string | undefined {\n\tlet dir = resolve(cwd);\n\twhile (true) {\n\t\tfor (const name of [\"AGENTS.md\", \"AGENTS.MD\", \"CLAUDE.md\", \"CLAUDE.MD\"]) {\n\t\t\tconst candidate = join(dir, name);\n\t\t\tif (existsSync(candidate)) return candidate;\n\t\t}\n\t\tconst parent = dirname(dir);\n\t\tif (parent === dir) return undefined;\n\t\tdir = parent;\n\t}\n}\n\ninterface ToolEvent {\n\tname: string;\n\targs: Record<string, any>;\n\t/** Set once the matching result is seen. */\n\tisError?: boolean;\n\toutput?: string;\n}\n\n/** Pair tool calls with their results along one branch, in call order. */\nfunction toolEvents(entries: EntryLike[]): ToolEvent[] {\n\tconst byCallId = new Map<string, ToolEvent>();\n\tconst ordered: ToolEvent[] = [];\n\n\tfor (const entry of entries) {\n\t\tconst message = entry.type === \"message\" ? entry.message : undefined;\n\t\tif (!message) continue;\n\t\tif (message.role === \"assistant\") {\n\t\t\tfor (const block of (message.content ?? []) as unknown[]) {\n\t\t\t\tif (!isToolCall(block)) continue;\n\t\t\t\tconst event: ToolEvent = { name: block.name, args: block.arguments ?? {} };\n\t\t\t\tbyCallId.set(block.id, event);\n\t\t\t\tordered.push(event);\n\t\t\t}\n\t\t} else if (message.role === \"toolResult\") {\n\t\t\tconst event = byCallId.get(message.toolCallId);\n\t\t\tif (!event) continue;\n\t\t\tevent.isError = message.isError;\n\t\t\tevent.output = textOf(message.content);\n\t\t}\n\t}\n\treturn ordered;\n}\n\n/** User turns worth mining, in order, with the digest's own output excluded. */\nfunction userDirectives(entries: EntryLike[]): string[] {\n\tconst out: string[] = [];\n\tfor (const entry of entries) {\n\t\tconst message = entry.type === \"message\" ? entry.message : undefined;\n\t\tif (!message || message.role !== \"user\") continue;\n\t\tconst text = textOf(message.content);\n\t\tif (!text || text.startsWith(LEARN_DIGEST_MARKER)) continue;\n\t\tif (!isRuleShapedDirective(text)) continue;\n\t\tout.push(text.trim());\n\t}\n\treturn out;\n}\n\nfunction clusterDirectives(\n\tperSession: Array<{ session: ParsedSession; directives: string[] }>,\n\tcoverage: CoverageIndex,\n\tminRepeats: number,\n): DirectiveCluster[] {\n\tinterface Acc {\n\t\ttext: string;\n\t\tnormalized: string;\n\t\tcount: number;\n\t\tsessions: Set<string>;\n\t\tlastSeen: string;\n\t}\n\tconst acc = new Map<string, Acc>();\n\n\tfor (const { session, directives } of perSession) {\n\t\tfor (const text of directives) {\n\t\t\tconst normalized = normalizeDirective(text);\n\t\t\tif (!normalized) continue;\n\t\t\tconst existing = acc.get(normalized);\n\t\t\tif (existing) {\n\t\t\t\texisting.count++;\n\t\t\t\texisting.sessions.add(session.id);\n\t\t\t\tif (session.timestamp > existing.lastSeen) existing.lastSeen = session.timestamp;\n\t\t\t\tif (text.length > existing.text.length) existing.text = text;\n\t\t\t} else {\n\t\t\t\tacc.set(normalized, {\n\t\t\t\t\ttext,\n\t\t\t\t\tnormalized,\n\t\t\t\t\tcount: 1,\n\t\t\t\t\tsessions: new Set([session.id]),\n\t\t\t\t\tlastSeen: session.timestamp,\n\t\t\t\t});\n\t\t\t}\n\t\t}\n\t}\n\n\tconst clusters: DirectiveCluster[] = [];\n\tfor (const entry of acc.values()) {\n\t\tif (entry.count < minRepeats) continue;\n\n\t\t// Everything reaching here cleared the repeat threshold. Suppression handles\n\t\t// the case that used to make these labels lie — a proposal accepted from a\n\t\t// previous run coming back as \"not working\" when nothing had happened\n\t\t// since. By the time an item survives that filter, a match genuinely means\n\t\t// you repeated yourself after the rule or skill already existed.\n\t\tconst match = matchCoverage(entry.text, coverage);\n\t\tclusters.push({\n\t\t\tkey: `directive:${entry.normalized}`,\n\t\t\ttext: entry.text,\n\t\t\tnormalized: entry.normalized,\n\t\t\tcount: entry.count,\n\t\t\tsessions: entry.sessions.size,\n\t\t\tlastSeen: entry.lastSeen,\n\t\t\tstatus: match.rule ? \"restated\" : match.skill ? \"has-skill\" : \"new\",\n\t\t\texistingRule: match.rule,\n\t\t\texistingSkill: match.skill,\n\t\t\tpreviouslyDeclined: false,\n\t\t});\n\t}\n\n\treturn clusters.sort((a, b) => b.sessions - a.sessions || b.count - a.count || a.text.localeCompare(b.text));\n}\n\n/** Files a mutating tool touched, for the resolution summary. */\nfunction editedFile(event: ToolEvent): string | undefined {\n\tif (![\"edit\", \"write\", \"multi_edit\", \"apply_patch\"].includes(event.name)) return undefined;\n\tconst path = event.args?.path ?? event.args?.file_path ?? event.args?.filePath;\n\treturn typeof path === \"string\" ? path : undefined;\n}\n\nfunction extractFixes(perSession: Array<{ session: ParsedSession; events: ToolEvent[] }>): FixCandidate[] {\n\tinterface Acc {\n\t\tcandidate: FixCandidate;\n\t\tsessions: Set<string>;\n\t}\n\tconst acc = new Map<string, Acc>();\n\n\tfor (const { session, events } of perSession) {\n\t\tfor (let i = 0; i < events.length; i++) {\n\t\t\tconst failure = events[i]!;\n\t\t\tif (failure.name !== \"bash\" || !failure.isError) continue;\n\t\t\tconst command = typeof failure.args?.command === \"string\" ? failure.args.command : \"\";\n\t\t\tif (!command || isBenignFailure(command)) continue;\n\n\t\t\tconst normalized = normalizeCommand(command);\n\t\t\tconst interveningCommands: string[] = [];\n\t\t\tconst editedFiles: string[] = [];\n\t\t\tlet resolved = false;\n\n\t\t\tfor (let j = i + 1; j < Math.min(events.length, i + 1 + FIX_LOOKAHEAD); j++) {\n\t\t\t\tconst next = events[j]!;\n\t\t\t\tconst file = editedFile(next);\n\t\t\t\tif (file) editedFiles.push(file);\n\n\t\t\t\tif (next.name !== \"bash\") continue;\n\t\t\t\tconst nextCommand = typeof next.args?.command === \"string\" ? next.args.command : \"\";\n\t\t\t\tif (!nextCommand) continue;\n\n\t\t\t\t// The same command later succeeding is the only evidence that the\n\t\t\t\t// problem was actually fixed. A *different* command passing says\n\t\t\t\t// nothing, and neither does the model moving on.\n\t\t\t\tif (normalizeCommand(nextCommand) === normalized && !next.isError) {\n\t\t\t\t\tresolved = true;\n\t\t\t\t\tbreak;\n\t\t\t\t}\n\t\t\t\tinterveningCommands.push(nextCommand.trim());\n\t\t\t}\n\n\t\t\tif (!resolved) continue;\n\n\t\t\tconst output = failure.output ?? \"\";\n\t\t\t// An abort is the user changing their mind, not a problem that was\n\t\t\t// solved, and empty output carries nothing to sign or show.\n\t\t\tif (isUninformativeFailure(output)) continue;\n\n\t\t\t// Sign the error region, not the whole output: build tools lead with an\n\t\t\t// identical banner, so signing everything makes unrelated failures of\n\t\t\t// the same command collide on their shared preamble.\n\t\t\tconst errorRegion = extractErrorRegion(output);\n\t\t\tconst signature = normalizeErrorSignature(errorRegion);\n\t\t\tif (!signature) continue;\n\n\t\t\tconst key = `${normalized}\u0000${signature}`;\n\t\t\tconst existing = acc.get(key);\n\t\t\tif (existing) {\n\t\t\t\texisting.candidate.count++;\n\t\t\t\texisting.sessions.add(session.id);\n\t\t\t\tif (session.timestamp > existing.candidate.lastSeen) existing.candidate.lastSeen = session.timestamp;\n\t\t\t} else {\n\t\t\t\tacc.set(key, {\n\t\t\t\t\tsessions: new Set([session.id]),\n\t\t\t\t\tcandidate: {\n\t\t\t\t\t\tkey: `fix:${key}`,\n\t\t\t\t\t\tcommand: normalized,\n\t\t\t\t\t\tsignature,\n\t\t\t\t\t\terrorExcerpt: errorRegion.replace(/\\s+/g, \" \").trim().slice(0, 240),\n\t\t\t\t\t\tinterveningCommands: [...new Set(interveningCommands)].slice(0, 5),\n\t\t\t\t\t\teditedFiles: [...new Set(editedFiles)].slice(0, 5),\n\t\t\t\t\t\tcount: 1,\n\t\t\t\t\t\tsessions: 1,\n\t\t\t\t\t\tlastSeen: session.timestamp,\n\t\t\t\t\t},\n\t\t\t\t});\n\t\t\t}\n\t\t}\n\t}\n\n\tconst out: FixCandidate[] = [];\n\tfor (const { candidate, sessions } of acc.values()) {\n\t\tcandidate.sessions = sessions.size;\n\t\tout.push(candidate);\n\t}\n\treturn out.sort((a, b) => b.count - a.count || b.sessions - a.sessions || a.signature.localeCompare(b.signature));\n}\n\n/** A tool call reduced to a comparable step: the tool, plus what a bash call runs. */\nfunction stepSignature(event: ToolEvent): string {\n\tif (event.name === \"bash\") {\n\t\tconst command = typeof event.args?.command === \"string\" ? event.args.command : \"\";\n\t\tconst head = commandHead(command);\n\t\treturn head ? `bash:${head}` : \"bash\";\n\t}\n\treturn event.name;\n}\n\n/**\n * Commands that are how an agent looks around rather than what the user was\n * doing. A sequence built only from these plus file edits describes \"coding\",\n * not a workflow, and no useful skill has ever come out of one.\n */\nconst PLUMBING_COMMANDS = new Set([\n\t\"cd\",\n\t\"ls\",\n\t\"pwd\",\n\t\"cat\",\n\t\"head\",\n\t\"tail\",\n\t\"wc\",\n\t\"echo\",\n\t\"which\",\n\t\"find\",\n\t\"fd\",\n\t\"grep\",\n\t\"rg\",\n\t\"sed\",\n\t\"awk\",\n\t\"git status\",\n\t\"git diff\",\n\t\"git log\",\n\t\"git show\",\n]);\n\n/**\n * Whether a sequence is a procedure rather than the rhythm of editing code.\n *\n * Two distinct doing-commands is the bar, and it was set by looking at real\n * transcripts. One command is not enough: the edit/test loop\n * (`edit → edit → bash:npm run`) satisfies it, and because a sliding window\n * over a long alternating run produces every rotation of that cycle, it alone\n * filled all eight slots with `edit → npm run → edit`, `npm run → edit → edit`\n * and so on — one habit described eight ways.\n *\n * A procedure worth a skill chains *different* actions: test then commit then\n * push, build then tag then publish. Requiring two distinct ones keeps those and\n * drops the rhythm. The cost is real — a genuine one-command routine with setup\n * is missed — and that is the intended trade, since a missed skill costs nothing\n * while a digest full of noise costs the reader's attention every run.\n */\nfunction isProcedure(steps: string[]): boolean {\n\tconst commands = new Set<string>();\n\tfor (const step of steps) {\n\t\tif (!step.startsWith(\"bash:\")) continue;\n\t\tconst head = step.slice(\"bash:\".length);\n\t\tif (PLUMBING_COMMANDS.has(head) || PLUMBING_COMMANDS.has(head.split(\" \")[0] ?? \"\")) continue;\n\t\tcommands.add(head);\n\t}\n\treturn commands.size >= 2;\n}\n\n/** True when `needle` appears as a contiguous run inside `haystack`. */\nfunction containsSequence(haystack: string[], needle: string[]): boolean {\n\tif (needle.length > haystack.length) return false;\n\tfor (let i = 0; i + needle.length <= haystack.length; i++) {\n\t\tif (needle.every((step, offset) => haystack[i + offset] === step)) return true;\n\t}\n\treturn false;\n}\n\nfunction extractWorkflows(\n\tperSession: Array<{ session: ParsedSession; events: ToolEvent[] }>,\n\tminRepeats: number,\n): WorkflowCandidate[] {\n\tinterface Acc {\n\t\tsteps: string[];\n\t\tcount: number;\n\t\tsessions: Set<string>;\n\t\tlastSeen: string;\n\t}\n\tconst acc = new Map<string, Acc>();\n\n\tfor (const { session, events } of perSession) {\n\t\tconst steps = events.slice(0, MAX_TOOL_CALLS_PER_SESSION).map(stepSignature);\n\n\t\tfor (let len = WORKFLOW_MIN_LEN; len <= WORKFLOW_MAX_LEN; len++) {\n\t\t\t// Collect every position first, then count greedily without overlap.\n\t\t\t// Counting each sliding position separately treats one long stretch of\n\t\t\t// edit/read churn as dozens of repeats: an `edit > read > edit` run of\n\t\t\t// length 12 scores 10 occurrences when it is really one stretch of work.\n\t\t\tconst positions = new Map<string, number[]>();\n\t\t\tfor (let i = 0; i + len <= steps.length; i++) {\n\t\t\t\tconst window = steps.slice(i, i + len);\n\t\t\t\t// A run of one repeated tool is a loop, not a workflow.\n\t\t\t\tif (new Set(window).size < 2) continue;\n\t\t\t\tif (!isProcedure(window)) continue;\n\t\t\t\tconst key = window.join(\" > \");\n\t\t\t\tconst list = positions.get(key);\n\t\t\t\tif (list) list.push(i);\n\t\t\t\telse positions.set(key, [i]);\n\t\t\t}\n\n\t\t\tfor (const [key, occurrences] of positions) {\n\t\t\t\tlet count = 0;\n\t\t\t\tlet nextFree = -1;\n\t\t\t\tfor (const start of occurrences) {\n\t\t\t\t\tif (start < nextFree) continue;\n\t\t\t\t\tcount++;\n\t\t\t\t\tnextFree = start + len;\n\t\t\t\t}\n\n\t\t\t\tconst existing = acc.get(key);\n\t\t\t\tif (existing) {\n\t\t\t\t\texisting.count += count;\n\t\t\t\t\texisting.sessions.add(session.id);\n\t\t\t\t\tif (session.timestamp > existing.lastSeen) existing.lastSeen = session.timestamp;\n\t\t\t\t} else {\n\t\t\t\t\tacc.set(key, {\n\t\t\t\t\t\tsteps: key.split(\" > \"),\n\t\t\t\t\t\tcount,\n\t\t\t\t\t\tsessions: new Set([session.id]),\n\t\t\t\t\t\tlastSeen: session.timestamp,\n\t\t\t\t\t});\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\t}\n\n\tconst ranked = [...acc.values()]\n\t\t.filter((entry) => entry.count >= minRepeats)\n\t\t.map((entry) => ({\n\t\t\tkey: `workflow:${entry.steps.join(\" > \")}`,\n\t\t\tsteps: entry.steps,\n\t\t\tcount: entry.count,\n\t\t\tsessions: entry.sessions.size,\n\t\t\tlastSeen: entry.lastSeen,\n\t\t}))\n\t\t// Sessions first, matching directives: a sequence seen in three sessions is\n\t\t// a workflow, while one repeated ten times in a single session is usually\n\t\t// just the shape of that one task.\n\t\t.sort(\n\t\t\t(a, b) =>\n\t\t\t\tb.sessions - a.sessions ||\n\t\t\t\tb.count - a.count ||\n\t\t\t\tb.steps.length - a.steps.length ||\n\t\t\t\ta.steps.join().localeCompare(b.steps.join()),\n\t\t);\n\n\t// Every n-gram overlaps its own extensions and prefixes, so without this the\n\t// list is one workflow described five slightly different ways. The test runs\n\t// both directions on purpose: a shorter sequence always outranks the longer\n\t// one containing it (it occurs at least as often), so checking only\n\t// shorter-inside-kept would never fire. Keep the best-ranked member of each\n\t// family and drop the rest.\n\tconst distinct: typeof ranked = [];\n\tfor (const candidate of ranked) {\n\t\tconst overlapsKept = distinct.some(\n\t\t\t(kept) => containsSequence(kept.steps, candidate.steps) || containsSequence(candidate.steps, kept.steps),\n\t\t);\n\t\tif (overlapsKept) continue;\n\t\tdistinct.push(candidate);\n\t}\n\treturn distinct;\n}\n\n/**\n * Everything a proposal could already have been written into.\n *\n * Built once and shared, because the same question — is this already written\n * down? — is asked while ranking a run *and* afterwards by `/learn stats`,\n * which reconstructs adoption by comparing coverage now against coverage when\n * the item was shown.\n */\nexport interface CoverageIndex {\n\t/** Candidate rule lines from the repo context file and both user scopes. */\n\truleLines: string[];\n\tskills: Array<{ name: string; description: string }>;\n}\n\nexport interface CoverageMatch {\n\t/** The context-file line that covers this, if any. */\n\trule?: string;\n\t/** The skill that covers this, if any. Only set when no rule matched. */\n\tskill?: string;\n}\n\n/**\n * Where a piece of text is already written down, if anywhere.\n *\n * A rule wins over a skill when both match: it is the more specific answer, and\n * \"rewrite this line\" is more actionable than \"sharpen a description\".\n */\nexport function matchCoverage(text: string, index: CoverageIndex): CoverageMatch {\n\tconst words = contentWords(text);\n\n\tlet bestLine: string | undefined;\n\tlet bestOverlap = 0;\n\tfor (const line of index.ruleLines) {\n\t\tconst overlap = wordOverlap(words, line);\n\t\tif (overlap > bestOverlap) {\n\t\t\tbestOverlap = overlap;\n\t\t\tbestLine = line;\n\t\t}\n\t}\n\tif (bestOverlap >= COVERED_OVERLAP) return { rule: bestLine };\n\n\tlet bestSkill: string | undefined;\n\tlet bestSkillOverlap = 0;\n\tfor (const skill of index.skills) {\n\t\tconst haystack = `${skill.name} ${skill.description.slice(0, SKILL_DESCRIPTION_CHARS)}`;\n\t\tconst overlap = wordOverlap(words, haystack);\n\t\tif (overlap > bestSkillOverlap) {\n\t\t\tbestSkillOverlap = overlap;\n\t\t\tbestSkill = skill.name;\n\t\t}\n\t}\n\tif (bestSkillOverlap >= SKILL_COVERED_OVERLAP) return { skill: bestSkill };\n\n\treturn {};\n}\n\n/** Assemble the coverage index for a directory. */\nexport function buildCoverageIndex(options: {\n\tcwd: string;\n\tagentDir: string;\n\tskills?: Array<{ name: string; description: string }>;\n}): CoverageIndex {\n\tconst corpus = coverageCorpus(options.agentDir, findAgentsFile(options.cwd));\n\treturn {\n\t\truleLines: corpus\n\t\t\t.split(\"\\n\")\n\t\t\t.map((line) => line.trim())\n\t\t\t.filter((line) => line.length > 0 && !line.startsWith(\"#\")),\n\t\tskills: options.skills ?? loadSkillIndex(options.cwd, options.agentDir),\n\t};\n}\n\n/**\n * Text a proposal is checked against to decide whether it is already written\n * down — the nearest repo context file plus both user scopes.\n *\n * All three matter for suppression, because `/learn` can route a rule to the\n * user scope. Checking only the repo file would report a rule you accepted into\n * `~/.agents/AGENTS.md` as declined.\n */\nfunction coverageCorpus(agentDir: string, repoFile: string | undefined): string {\n\tconst parts: string[] = [];\n\tfor (const candidate of [repoFile, join(getUserAgentsDir(), \"AGENTS.md\"), join(agentDir, \"AGENTS.md\")]) {\n\t\tif (!candidate || !existsSync(candidate)) continue;\n\t\ttry {\n\t\t\tparts.push(readFileSync(candidate, \"utf-8\"));\n\t\t} catch {\n\t\t\t// Unreadable context file: treat as absent rather than failing the run.\n\t\t}\n\t}\n\treturn parts.join(\"\\n\");\n}\n\n/**\n * Skills a proposal could already have become.\n *\n * `/learn` routes long or conditional guidance to a skill rather than a rule, so\n * without this a proposal you adopted *as a skill* would read as declined —\n * looking only at context files sees an unchanged `AGENTS.md` and concludes you\n * passed. Reuses the real loader rather than a second SKILL.md scanner so the\n * set of locations cannot drift from what the session actually loads.\n */\nfunction loadSkillIndex(cwd: string, agentDir: string): Array<{ name: string; description: string }> {\n\ttry {\n\t\treturn loadSkills({ cwd, agentDir, skillPaths: [], includeDefaults: true }).skills.map((skill) => ({\n\t\t\tname: skill.name,\n\t\t\tdescription: skill.description ?? \"\",\n\t\t}));\n\t} catch {\n\t\t// Skills are an enrichment here, not the point of the command.\n\t\treturn [];\n\t}\n}\n\n/** Mine the recent sessions for this cwd and return the ranked digest. */\nexport function extractLearnDigest(options: ExtractOptions): LearnDigest {\n\tconst { sessions, skipped } = listSessions(options);\n\n\tconst agentsFilePath = findAgentsFile(options.cwd);\n\tlet agentsContent: string | undefined;\n\tif (agentsFilePath) {\n\t\ttry {\n\t\t\tagentsContent = readFileSync(agentsFilePath, \"utf-8\");\n\t\t} catch {\n\t\t\tagentsContent = undefined;\n\t\t}\n\t}\n\tconst coverage = buildCoverageIndex({ cwd: options.cwd, agentDir: options.agentDir, skills: options.skills });\n\n\tconst withDirectives = sessions.map((session) => ({ session, directives: userDirectives(session.entries) }));\n\tconst withEvents = sessions.map((session) => ({ session, events: toolEvents(session.entries) }));\n\n\tconst timestamps = sessions.map((s) => s.timestamp).sort();\n\tconst state = options.ignoreState ? undefined : options.state;\n\n\t// Directives carry a real coverage signal — is this written down as a rule or\n\t// a skill right now? — which is what separates an adopted proposal from a\n\t// declined one. Fixes and workflows do not: a fix may have become a rule, a\n\t// skill, or a habit, and which one is not recoverable here, so they get\n\t// suppression only and are never labelled declined.\n\tconst maxProposals = options.maxProposals ?? DEFAULT_MAX_PER_CATEGORY;\n\tconst directives = applySuppression(\n\t\tclusterDirectives(withDirectives, coverage, options.minRepeats ?? DEFAULT_MIN_DIRECTIVE_COUNT),\n\t\tstate,\n\t\tmaxProposals,\n\t\t(item) => item.status !== \"new\",\n\t\t(item) => {\n\t\t\titem.previouslyDeclined = true;\n\t\t},\n\t);\n\tconst fixes = applySuppression(extractFixes(withEvents), state, maxProposals, () => false);\n\tconst workflows = applySuppression(\n\t\textractWorkflows(withEvents, options.minWorkflowRepeats ?? DEFAULT_MIN_WORKFLOW_COUNT),\n\t\tstate,\n\t\tmaxProposals,\n\t\t() => false,\n\t);\n\n\tconst surfaced = [\n\t\t...directives.kept.map((d) => ({ key: d.key, lastSeen: d.lastSeen, covered: d.status !== \"new\" })),\n\t\t...fixes.kept.map((f) => ({ key: f.key, lastSeen: f.lastSeen, covered: false })),\n\t\t...workflows.kept.map((w) => ({ key: w.key, lastSeen: w.lastSeen, covered: false })),\n\t];\n\n\treturn {\n\t\tscannedSessions: sessions.length,\n\t\tskippedSessions: skipped,\n\t\toldestSession: timestamps[0],\n\t\tnewestSession: timestamps[timestamps.length - 1],\n\t\tagentsFilePath,\n\t\tagentsFileTokens:\n\t\t\tagentsContent === undefined ? undefined : Math.round(Buffer.byteLength(agentsContent, \"utf-8\") / 4),\n\t\tdirectives: directives.kept,\n\t\tfixes: fixes.kept,\n\t\tworkflows: workflows.kept,\n\t\tsuppressed: directives.suppressed + fixes.suppressed + workflows.suppressed,\n\t\tsurfaced,\n\t};\n}\n"]}
@@ -0,0 +1,65 @@
1
+ /**
2
+ * Normalization and noise filters for the `/learn` extractor.
3
+ *
4
+ * Everything downstream — clustering, dedupe, cross-session counting — is only
5
+ * as good as what happens here. Two runs of the same failing command differ by
6
+ * absolute paths, line numbers, PIDs and timings; two statements of the same
7
+ * rule differ by politeness and word order. Collapsing that variance is what
8
+ * turns a pile of transcripts into counts, and the counts are the product.
9
+ *
10
+ * These are deliberately lexical and cheap. Semantic grouping is left to the
11
+ * model, which sees the deduped candidates and can tell that "run tests with
12
+ * coverage" and "use --coverage on the suite" are one rule. Trying to do that
13
+ * here would cost a dependency and still be worse.
14
+ */
15
+ /**
16
+ * Collapse a raw error or command output into a comparable signature.
17
+ *
18
+ * Case is preserved: `TS2307` and `ERR_MODULE_NOT_FOUND` carry meaning that
19
+ * lowercasing would blur into surrounding prose, and they are exactly the
20
+ * tokens worth matching on.
21
+ */
22
+ export declare function normalizeErrorSignature(text: string): string;
23
+ /**
24
+ * Normalize a shell command so two invocations of "the same" command match.
25
+ *
26
+ * Paths are collapsed but flags are kept — `npm test` and `npm test --coverage`
27
+ * are genuinely different commands, and flags are frequently the fix itself.
28
+ */
29
+ export declare function normalizeCommand(command: string): string;
30
+ /**
31
+ * The first `tokens` words of what a command actually does, with the
32
+ * where-and-how prefixes stripped. `cd <path> && npm run check` becomes
33
+ * `npm run`, which is the part worth comparing across sessions.
34
+ */
35
+ export declare function commandHead(command: string, tokens?: number): string;
36
+ export declare function extractErrorRegion(output: string, maxChars?: number): string;
37
+ /**
38
+ * Output that reports no failure worth learning from: an abort is the user
39
+ * changing their mind, and an empty result says nothing at all.
40
+ */
41
+ export declare function isUninformativeFailure(output: string): boolean;
42
+ /**
43
+ * Normalize a user directive for clustering: lowercase, strip filler and
44
+ * punctuation, collapse whitespace. Unlike error signatures these are compared
45
+ * loosely, so case and symbols are noise.
46
+ */
47
+ export declare function normalizeDirective(text: string): string;
48
+ /** Content words of a directive, for overlap comparisons against existing rules. */
49
+ export declare function contentWords(text: string): string[];
50
+ /**
51
+ * Fraction of `words` that appear in `haystack`, 0 when there is nothing to
52
+ * compare. Used to decide whether a proposed rule is already written down.
53
+ */
54
+ export declare function wordOverlap(words: string[], haystack: string): number;
55
+ /**
56
+ * Whether a user message is worth mining as a potential rule.
57
+ *
58
+ * Deliberately strict. The extractor's output is read by a model with a limited
59
+ * budget, and recall costs precision: every task statement that slips through
60
+ * displaces a real recurring directive in the ranking.
61
+ */
62
+ export declare function isRuleShapedDirective(text: string): boolean;
63
+ /** Whether a non-zero exit from this command should be ignored by the fix extractor. */
64
+ export declare function isBenignFailure(command: string): boolean;
65
+ //# sourceMappingURL=normalize.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"normalize.d.ts","sourceRoot":"","sources":["../../../src/core/learn/normalize.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;GAaG;AAwCH;;;;;;GAMG;AACH,wBAAgB,uBAAuB,CAAC,IAAI,EAAE,MAAM,GAAG,MAAM,CAM5D;AAED;;;;;GAKG;AACH,wBAAgB,gBAAgB,CAAC,OAAO,EAAE,MAAM,GAAG,MAAM,CAMxD;AAYD;;;;GAIG;AACH,wBAAgB,WAAW,CAAC,OAAO,EAAE,MAAM,EAAE,MAAM,SAAI,GAAG,MAAM,CAO/D;AAgBD,wBAAgB,kBAAkB,CAAC,MAAM,EAAE,MAAM,EAAE,QAAQ,SAAM,GAAG,MAAM,CAOzE;AAED;;;GAGG;AACH,wBAAgB,sBAAsB,CAAC,MAAM,EAAE,MAAM,GAAG,OAAO,CAI9D;AAuCD;;;;GAIG;AACH,wBAAgB,kBAAkB,CAAC,IAAI,EAAE,MAAM,GAAG,MAAM,CAWvD;AAED,oFAAoF;AACpF,wBAAgB,YAAY,CAAC,IAAI,EAAE,MAAM,GAAG,MAAM,EAAE,CAEnD;AAED;;;GAGG;AACH,wBAAgB,WAAW,CAAC,KAAK,EAAE,MAAM,EAAE,EAAE,QAAQ,EAAE,MAAM,GAAG,MAAM,CAKrE;AAmBD;;;;;;GAMG;AACH,wBAAgB,qBAAqB,CAAC,IAAI,EAAE,MAAM,GAAG,OAAO,CAU3D;AAYD,wFAAwF;AACxF,wBAAgB,eAAe,CAAC,OAAO,EAAE,MAAM,GAAG,OAAO,CAGxD","sourcesContent":["/**\n * Normalization and noise filters for the `/learn` extractor.\n *\n * Everything downstream — clustering, dedupe, cross-session counting — is only\n * as good as what happens here. Two runs of the same failing command differ by\n * absolute paths, line numbers, PIDs and timings; two statements of the same\n * rule differ by politeness and word order. Collapsing that variance is what\n * turns a pile of transcripts into counts, and the counts are the product.\n *\n * These are deliberately lexical and cheap. Semantic grouping is left to the\n * model, which sees the deduped candidates and can tell that \"run tests with\n * coverage\" and \"use --coverage on the suite\" are one rule. Trying to do that\n * here would cost a dependency and still be worse.\n */\n\n/** Longest text we normalize; error dumps can run to megabytes. */\nconst MAX_SIGNATURE_CHARS = 400;\n\n/**\n * Volatile fragments that make two runs of the same failure look different.\n *\n * Order is load-bearing, because these patterns overlap and the first one to\n * match wins the characters. Longest-and-most-specific therefore runs first:\n * paths before line numbers so `foo.ts:12:3` does not leave a bare `:12:3`, and\n * timestamps before line numbers so the `:00:00` inside `10:00:00` is not\n * mistaken for a line/column pair. Adding a pattern means placing it by\n * specificity, not appending it.\n */\nconst VOLATILE_PATTERNS: Array<[RegExp, string]> = [\n\t// Absolute POSIX and Windows paths, including the temp dirs that change per run.\n\t[/\\/(?:[\\w.@+-]+\\/)+[\\w.@+-]+/g, \"<path>\"],\n\t[/[A-Za-z]:\\\\(?:[\\w.@+-]+\\\\)+[\\w.@+-]+/g, \"<path>\"],\n\t// UUIDs, before the shorter hex rule can eat their first group.\n\t[/\\b[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}\\b/gi, \"<uuid>\"],\n\t// ISO timestamps and clock times, before the line/column rule.\n\t[/\\d{4}-\\d{2}-\\d{2}[T ]\\d{2}:\\d{2}:\\d{2}(?:\\.\\d+)?Z?/g, \"<time>\"],\n\t[/\\b\\d{1,2}:\\d{2}:\\d{2}\\b/g, \"<time>\"],\n\t// Ports, before the line/column rule can claim the colon.\n\t[/\\b(?:127\\.0\\.0\\.1|localhost):\\d+/gi, \"localhost:<port>\"],\n\t// file:line:col and bare line references.\n\t[/:\\d+:\\d+/g, \":<line>\"],\n\t[/\\bline \\d+/gi, \"line <line>\"],\n\t// Hex-ish identifiers: git shas, content hashes, addresses.\n\t[/\\b0x[0-9a-f]+\\b/gi, \"<addr>\"],\n\t[/\\b[0-9a-f]{7,40}\\b/gi, \"<hash>\"],\n\t// Durations and byte counts reported by build tools.\n\t[/\\b\\d+(?:\\.\\d+)?\\s?(?:ms|s|sec|secs|seconds|m|min|mins|kb|mb|gb)\\b/gi, \"<qty>\"],\n\t// PIDs, which vary per machine and per run.\n\t[/\\bpid[= ]\\d+/gi, \"pid <pid>\"],\n\t// Anything left that is a bare multi-digit run.\n\t[/\\b\\d{3,}\\b/g, \"<n>\"],\n];\n\n/**\n * Collapse a raw error or command output into a comparable signature.\n *\n * Case is preserved: `TS2307` and `ERR_MODULE_NOT_FOUND` carry meaning that\n * lowercasing would blur into surrounding prose, and they are exactly the\n * tokens worth matching on.\n */\nexport function normalizeErrorSignature(text: string): string {\n\tlet out = text.slice(0, MAX_SIGNATURE_CHARS * 4);\n\tfor (const [pattern, replacement] of VOLATILE_PATTERNS) {\n\t\tout = out.replace(pattern, replacement);\n\t}\n\treturn out.replace(/\\s+/g, \" \").trim().slice(0, MAX_SIGNATURE_CHARS);\n}\n\n/**\n * Normalize a shell command so two invocations of \"the same\" command match.\n *\n * Paths are collapsed but flags are kept — `npm test` and `npm test --coverage`\n * are genuinely different commands, and flags are frequently the fix itself.\n */\nexport function normalizeCommand(command: string): string {\n\tconst collapsed = command\n\t\t.replace(/\\s+/g, \" \")\n\t\t.replace(/(?:^|\\s)\\/(?:[\\w.@+-]+\\/)+[\\w.@+-]+/g, \" <path>\")\n\t\t.trim();\n\treturn collapsed.slice(0, MAX_SIGNATURE_CHARS);\n}\n\n/**\n * Prefixes that say where or how a command runs rather than what it does.\n *\n * Nearly every command an agent runs is wrapped in `cd <somewhere> && …`, and\n * once paths are collapsed that prefix is identical everywhere. Taking the head\n * of the raw command therefore yields `cd <path>` for the whole session, which\n * makes every bash step look like every other one.\n */\nconst COMMAND_PREFIX = /^(?:cd\\s+\\S+\\s*&&\\s*|(?:sudo|env|time|nice|exec)\\s+)/;\n\n/**\n * The first `tokens` words of what a command actually does, with the\n * where-and-how prefixes stripped. `cd <path> && npm run check` becomes\n * `npm run`, which is the part worth comparing across sessions.\n */\nexport function commandHead(command: string, tokens = 2): string {\n\tlet out = normalizeCommand(command);\n\t// Prefixes stack: `cd x && sudo env FOO=1 cmd`.\n\tfor (let i = 0; i < 4 && COMMAND_PREFIX.test(out); i++) {\n\t\tout = out.replace(COMMAND_PREFIX, \"\");\n\t}\n\treturn out.split(\" \").slice(0, tokens).join(\" \").trim();\n}\n\n/**\n * The part of a command's output that is actually the error.\n *\n * Build tools lead with a banner — npm's `> pkg@1.0 check > biome check …`, a\n * compiler's version line — that is byte-identical across every run. Signing a\n * failure by its whole output therefore signs mostly the banner, and two\n * unrelated failures of the same command look like the same problem. Scanning\n * for the first error-shaped line fixes both the signature and what the reader\n * is shown; failing that, the tail beats the head, because tools print the\n * banner first and the verdict last.\n */\nconst ERROR_LINE =\n\t/\\b(?:error|err!|errors?:|failed|failure|cannot|can't|unable to|not found|no such|unexpected|expected|invalid|denied|refused|timed out|traceback|panic|exception|fatal)\\b|^\\s*(?:×|✖|✗)/i;\n\nexport function extractErrorRegion(output: string, maxChars = 400): string {\n\tconst lines = output.split(\"\\n\");\n\tconst start = lines.findIndex((line) => ERROR_LINE.test(line));\n\tif (start >= 0) {\n\t\treturn lines.slice(start).join(\"\\n\").slice(0, maxChars).trim();\n\t}\n\treturn output.slice(-maxChars).trim();\n}\n\n/**\n * Output that reports no failure worth learning from: an abort is the user\n * changing their mind, and an empty result says nothing at all.\n */\nexport function isUninformativeFailure(output: string): boolean {\n\tconst trimmed = output.trim();\n\tif (trimmed.length === 0) return true;\n\treturn /^(?:command aborted|aborted|cancelled|canceled|interrupted|killed|sigint|sigterm)\\b/i.test(trimmed);\n}\n\n/** Filler that carries no rule content, stripped before directives are compared. */\nconst DIRECTIVE_FILLER =\n\t/^(?:please|pls|hey|ok|okay|now|also|and|so|just|quickly|can you|could you|would you|i want you to|i'd like you to|let's|lets)\\b[\\s,]*/i;\n\n/** Words too common to distinguish one directive from another. */\nconst STOPWORDS = new Set([\n\t\"a\",\n\t\"an\",\n\t\"and\",\n\t\"are\",\n\t\"as\",\n\t\"at\",\n\t\"be\",\n\t\"but\",\n\t\"by\",\n\t\"do\",\n\t\"for\",\n\t\"from\",\n\t\"in\",\n\t\"is\",\n\t\"it\",\n\t\"its\",\n\t\"of\",\n\t\"on\",\n\t\"or\",\n\t\"that\",\n\t\"the\",\n\t\"then\",\n\t\"this\",\n\t\"to\",\n\t\"we\",\n\t\"when\",\n\t\"with\",\n\t\"you\",\n\t\"your\",\n]);\n\n/**\n * Normalize a user directive for clustering: lowercase, strip filler and\n * punctuation, collapse whitespace. Unlike error signatures these are compared\n * loosely, so case and symbols are noise.\n */\nexport function normalizeDirective(text: string): string {\n\tlet out = text.trim();\n\t// Filler can stack (\"ok so please also ...\"), so strip until it stops matching.\n\tfor (let i = 0; i < 4 && DIRECTIVE_FILLER.test(out); i++) {\n\t\tout = out.replace(DIRECTIVE_FILLER, \"\");\n\t}\n\treturn out\n\t\t.toLowerCase()\n\t\t.replace(/[^\\w\\s@./+-]/g, \" \")\n\t\t.replace(/\\s+/g, \" \")\n\t\t.trim();\n}\n\n/** Content words of a directive, for overlap comparisons against existing rules. */\nexport function contentWords(text: string): string[] {\n\treturn [...new Set(normalizeDirective(text).split(\" \"))].filter((w) => w.length > 2 && !STOPWORDS.has(w));\n}\n\n/**\n * Fraction of `words` that appear in `haystack`, 0 when there is nothing to\n * compare. Used to decide whether a proposed rule is already written down.\n */\nexport function wordOverlap(words: string[], haystack: string): number {\n\tif (words.length === 0) return 0;\n\tconst hay = ` ${normalizeDirective(haystack)} `;\n\tconst hits = words.filter((w) => hay.includes(` ${w} `)).length;\n\treturn hits / words.length;\n}\n\n/** Approvals and acknowledgements. These are the bulk of user turns and none are rules. */\nconst ACKNOWLEDGEMENT =\n\t/^(?:y|n|ok|okay|yes|no|yep|yeah|nope|sure|thanks|thank you|ty|go|go ahead|continue|proceed|do it|next|done|good|great|nice|perfect|lgtm|cool|k|fine|stop|wait|hmm|\\?+|\\.+)$/i;\n\n/**\n * Imperative or corrective phrasing — the shape a rule takes when it is spoken\n * aloud. A directive that matches none of these is almost always a task\n * (\"add a button here\"), which is worth doing once and never worth writing down.\n */\nconst RULE_SHAPED =\n\t/\\b(?:always|never|don'?t|do not|avoid|make sure|ensure|must|should|prefer|instead|remember|from now on|going forward|every time|each time|whenever|stop|use|run|keep|only|no need to)\\b/i;\n\n/** Minimum length before a directive is worth considering at all. */\nconst MIN_DIRECTIVE_CHARS = 12;\n/** Above this a \"directive\" is a task description or a pasted spec, not a rule. */\nconst MAX_DIRECTIVE_CHARS = 400;\n\n/**\n * Whether a user message is worth mining as a potential rule.\n *\n * Deliberately strict. The extractor's output is read by a model with a limited\n * budget, and recall costs precision: every task statement that slips through\n * displaces a real recurring directive in the ranking.\n */\nexport function isRuleShapedDirective(text: string): boolean {\n\tconst trimmed = text.trim();\n\tif (trimmed.length < MIN_DIRECTIVE_CHARS || trimmed.length > MAX_DIRECTIVE_CHARS) return false;\n\tif (ACKNOWLEDGEMENT.test(trimmed)) return false;\n\t// Slash commands are invocations, not instructions.\n\tif (trimmed.startsWith(\"/\")) return false;\n\t// Pasted output, diffs and stack traces are not directives.\n\tif (/^(?:```|diff --git|\\s*at\\s|\\{|\\[)/.test(trimmed)) return false;\n\tif (trimmed.split(\"\\n\").length > 6) return false;\n\treturn RULE_SHAPED.test(trimmed);\n}\n\n/**\n * Commands whose non-zero exit is a normal answer rather than a failure.\n *\n * `rg`/`grep` exit 1 on \"no match\", `test`/`diff`/`cmp` exit non-zero to report\n * their result, and a TDD loop is *supposed* to go red. Without this filter the\n * fail→fix extractor is mostly noise, since these are among the most-run\n * commands in any session.\n */\nconst BENIGN_EXIT_COMMANDS = /^(?:rg|grep|egrep|fgrep|ag|ack|test|\\[|diff|cmp|which|command|type|hash|find|fd)\\b/;\n\n/** Whether a non-zero exit from this command should be ignored by the fix extractor. */\nexport function isBenignFailure(command: string): boolean {\n\tconst first = command.trim().replace(/^(?:sudo|env|time|nice)\\s+/, \"\");\n\treturn BENIGN_EXIT_COMMANDS.test(first);\n}\n"]}
@@ -0,0 +1,245 @@
1
+ /**
2
+ * Normalization and noise filters for the `/learn` extractor.
3
+ *
4
+ * Everything downstream — clustering, dedupe, cross-session counting — is only
5
+ * as good as what happens here. Two runs of the same failing command differ by
6
+ * absolute paths, line numbers, PIDs and timings; two statements of the same
7
+ * rule differ by politeness and word order. Collapsing that variance is what
8
+ * turns a pile of transcripts into counts, and the counts are the product.
9
+ *
10
+ * These are deliberately lexical and cheap. Semantic grouping is left to the
11
+ * model, which sees the deduped candidates and can tell that "run tests with
12
+ * coverage" and "use --coverage on the suite" are one rule. Trying to do that
13
+ * here would cost a dependency and still be worse.
14
+ */
15
+ /** Longest text we normalize; error dumps can run to megabytes. */
16
+ const MAX_SIGNATURE_CHARS = 400;
17
+ /**
18
+ * Volatile fragments that make two runs of the same failure look different.
19
+ *
20
+ * Order is load-bearing, because these patterns overlap and the first one to
21
+ * match wins the characters. Longest-and-most-specific therefore runs first:
22
+ * paths before line numbers so `foo.ts:12:3` does not leave a bare `:12:3`, and
23
+ * timestamps before line numbers so the `:00:00` inside `10:00:00` is not
24
+ * mistaken for a line/column pair. Adding a pattern means placing it by
25
+ * specificity, not appending it.
26
+ */
27
+ const VOLATILE_PATTERNS = [
28
+ // Absolute POSIX and Windows paths, including the temp dirs that change per run.
29
+ [/\/(?:[\w.@+-]+\/)+[\w.@+-]+/g, "<path>"],
30
+ [/[A-Za-z]:\\(?:[\w.@+-]+\\)+[\w.@+-]+/g, "<path>"],
31
+ // UUIDs, before the shorter hex rule can eat their first group.
32
+ [/\b[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}\b/gi, "<uuid>"],
33
+ // ISO timestamps and clock times, before the line/column rule.
34
+ [/\d{4}-\d{2}-\d{2}[T ]\d{2}:\d{2}:\d{2}(?:\.\d+)?Z?/g, "<time>"],
35
+ [/\b\d{1,2}:\d{2}:\d{2}\b/g, "<time>"],
36
+ // Ports, before the line/column rule can claim the colon.
37
+ [/\b(?:127\.0\.0\.1|localhost):\d+/gi, "localhost:<port>"],
38
+ // file:line:col and bare line references.
39
+ [/:\d+:\d+/g, ":<line>"],
40
+ [/\bline \d+/gi, "line <line>"],
41
+ // Hex-ish identifiers: git shas, content hashes, addresses.
42
+ [/\b0x[0-9a-f]+\b/gi, "<addr>"],
43
+ [/\b[0-9a-f]{7,40}\b/gi, "<hash>"],
44
+ // Durations and byte counts reported by build tools.
45
+ [/\b\d+(?:\.\d+)?\s?(?:ms|s|sec|secs|seconds|m|min|mins|kb|mb|gb)\b/gi, "<qty>"],
46
+ // PIDs, which vary per machine and per run.
47
+ [/\bpid[= ]\d+/gi, "pid <pid>"],
48
+ // Anything left that is a bare multi-digit run.
49
+ [/\b\d{3,}\b/g, "<n>"],
50
+ ];
51
+ /**
52
+ * Collapse a raw error or command output into a comparable signature.
53
+ *
54
+ * Case is preserved: `TS2307` and `ERR_MODULE_NOT_FOUND` carry meaning that
55
+ * lowercasing would blur into surrounding prose, and they are exactly the
56
+ * tokens worth matching on.
57
+ */
58
+ export function normalizeErrorSignature(text) {
59
+ let out = text.slice(0, MAX_SIGNATURE_CHARS * 4);
60
+ for (const [pattern, replacement] of VOLATILE_PATTERNS) {
61
+ out = out.replace(pattern, replacement);
62
+ }
63
+ return out.replace(/\s+/g, " ").trim().slice(0, MAX_SIGNATURE_CHARS);
64
+ }
65
+ /**
66
+ * Normalize a shell command so two invocations of "the same" command match.
67
+ *
68
+ * Paths are collapsed but flags are kept — `npm test` and `npm test --coverage`
69
+ * are genuinely different commands, and flags are frequently the fix itself.
70
+ */
71
+ export function normalizeCommand(command) {
72
+ const collapsed = command
73
+ .replace(/\s+/g, " ")
74
+ .replace(/(?:^|\s)\/(?:[\w.@+-]+\/)+[\w.@+-]+/g, " <path>")
75
+ .trim();
76
+ return collapsed.slice(0, MAX_SIGNATURE_CHARS);
77
+ }
78
+ /**
79
+ * Prefixes that say where or how a command runs rather than what it does.
80
+ *
81
+ * Nearly every command an agent runs is wrapped in `cd <somewhere> && …`, and
82
+ * once paths are collapsed that prefix is identical everywhere. Taking the head
83
+ * of the raw command therefore yields `cd <path>` for the whole session, which
84
+ * makes every bash step look like every other one.
85
+ */
86
+ const COMMAND_PREFIX = /^(?:cd\s+\S+\s*&&\s*|(?:sudo|env|time|nice|exec)\s+)/;
87
+ /**
88
+ * The first `tokens` words of what a command actually does, with the
89
+ * where-and-how prefixes stripped. `cd <path> && npm run check` becomes
90
+ * `npm run`, which is the part worth comparing across sessions.
91
+ */
92
+ export function commandHead(command, tokens = 2) {
93
+ let out = normalizeCommand(command);
94
+ // Prefixes stack: `cd x && sudo env FOO=1 cmd`.
95
+ for (let i = 0; i < 4 && COMMAND_PREFIX.test(out); i++) {
96
+ out = out.replace(COMMAND_PREFIX, "");
97
+ }
98
+ return out.split(" ").slice(0, tokens).join(" ").trim();
99
+ }
100
+ /**
101
+ * The part of a command's output that is actually the error.
102
+ *
103
+ * Build tools lead with a banner — npm's `> pkg@1.0 check > biome check …`, a
104
+ * compiler's version line — that is byte-identical across every run. Signing a
105
+ * failure by its whole output therefore signs mostly the banner, and two
106
+ * unrelated failures of the same command look like the same problem. Scanning
107
+ * for the first error-shaped line fixes both the signature and what the reader
108
+ * is shown; failing that, the tail beats the head, because tools print the
109
+ * banner first and the verdict last.
110
+ */
111
+ const ERROR_LINE = /\b(?:error|err!|errors?:|failed|failure|cannot|can't|unable to|not found|no such|unexpected|expected|invalid|denied|refused|timed out|traceback|panic|exception|fatal)\b|^\s*(?:×|✖|✗)/i;
112
+ export function extractErrorRegion(output, maxChars = 400) {
113
+ const lines = output.split("\n");
114
+ const start = lines.findIndex((line) => ERROR_LINE.test(line));
115
+ if (start >= 0) {
116
+ return lines.slice(start).join("\n").slice(0, maxChars).trim();
117
+ }
118
+ return output.slice(-maxChars).trim();
119
+ }
120
+ /**
121
+ * Output that reports no failure worth learning from: an abort is the user
122
+ * changing their mind, and an empty result says nothing at all.
123
+ */
124
+ export function isUninformativeFailure(output) {
125
+ const trimmed = output.trim();
126
+ if (trimmed.length === 0)
127
+ return true;
128
+ return /^(?:command aborted|aborted|cancelled|canceled|interrupted|killed|sigint|sigterm)\b/i.test(trimmed);
129
+ }
130
+ /** Filler that carries no rule content, stripped before directives are compared. */
131
+ const DIRECTIVE_FILLER = /^(?:please|pls|hey|ok|okay|now|also|and|so|just|quickly|can you|could you|would you|i want you to|i'd like you to|let's|lets)\b[\s,]*/i;
132
+ /** Words too common to distinguish one directive from another. */
133
+ const STOPWORDS = new Set([
134
+ "a",
135
+ "an",
136
+ "and",
137
+ "are",
138
+ "as",
139
+ "at",
140
+ "be",
141
+ "but",
142
+ "by",
143
+ "do",
144
+ "for",
145
+ "from",
146
+ "in",
147
+ "is",
148
+ "it",
149
+ "its",
150
+ "of",
151
+ "on",
152
+ "or",
153
+ "that",
154
+ "the",
155
+ "then",
156
+ "this",
157
+ "to",
158
+ "we",
159
+ "when",
160
+ "with",
161
+ "you",
162
+ "your",
163
+ ]);
164
+ /**
165
+ * Normalize a user directive for clustering: lowercase, strip filler and
166
+ * punctuation, collapse whitespace. Unlike error signatures these are compared
167
+ * loosely, so case and symbols are noise.
168
+ */
169
+ export function normalizeDirective(text) {
170
+ let out = text.trim();
171
+ // Filler can stack ("ok so please also ..."), so strip until it stops matching.
172
+ for (let i = 0; i < 4 && DIRECTIVE_FILLER.test(out); i++) {
173
+ out = out.replace(DIRECTIVE_FILLER, "");
174
+ }
175
+ return out
176
+ .toLowerCase()
177
+ .replace(/[^\w\s@./+-]/g, " ")
178
+ .replace(/\s+/g, " ")
179
+ .trim();
180
+ }
181
+ /** Content words of a directive, for overlap comparisons against existing rules. */
182
+ export function contentWords(text) {
183
+ return [...new Set(normalizeDirective(text).split(" "))].filter((w) => w.length > 2 && !STOPWORDS.has(w));
184
+ }
185
+ /**
186
+ * Fraction of `words` that appear in `haystack`, 0 when there is nothing to
187
+ * compare. Used to decide whether a proposed rule is already written down.
188
+ */
189
+ export function wordOverlap(words, haystack) {
190
+ if (words.length === 0)
191
+ return 0;
192
+ const hay = ` ${normalizeDirective(haystack)} `;
193
+ const hits = words.filter((w) => hay.includes(` ${w} `)).length;
194
+ return hits / words.length;
195
+ }
196
+ /** Approvals and acknowledgements. These are the bulk of user turns and none are rules. */
197
+ const ACKNOWLEDGEMENT = /^(?:y|n|ok|okay|yes|no|yep|yeah|nope|sure|thanks|thank you|ty|go|go ahead|continue|proceed|do it|next|done|good|great|nice|perfect|lgtm|cool|k|fine|stop|wait|hmm|\?+|\.+)$/i;
198
+ /**
199
+ * Imperative or corrective phrasing — the shape a rule takes when it is spoken
200
+ * aloud. A directive that matches none of these is almost always a task
201
+ * ("add a button here"), which is worth doing once and never worth writing down.
202
+ */
203
+ const RULE_SHAPED = /\b(?:always|never|don'?t|do not|avoid|make sure|ensure|must|should|prefer|instead|remember|from now on|going forward|every time|each time|whenever|stop|use|run|keep|only|no need to)\b/i;
204
+ /** Minimum length before a directive is worth considering at all. */
205
+ const MIN_DIRECTIVE_CHARS = 12;
206
+ /** Above this a "directive" is a task description or a pasted spec, not a rule. */
207
+ const MAX_DIRECTIVE_CHARS = 400;
208
+ /**
209
+ * Whether a user message is worth mining as a potential rule.
210
+ *
211
+ * Deliberately strict. The extractor's output is read by a model with a limited
212
+ * budget, and recall costs precision: every task statement that slips through
213
+ * displaces a real recurring directive in the ranking.
214
+ */
215
+ export function isRuleShapedDirective(text) {
216
+ const trimmed = text.trim();
217
+ if (trimmed.length < MIN_DIRECTIVE_CHARS || trimmed.length > MAX_DIRECTIVE_CHARS)
218
+ return false;
219
+ if (ACKNOWLEDGEMENT.test(trimmed))
220
+ return false;
221
+ // Slash commands are invocations, not instructions.
222
+ if (trimmed.startsWith("/"))
223
+ return false;
224
+ // Pasted output, diffs and stack traces are not directives.
225
+ if (/^(?:```|diff --git|\s*at\s|\{|\[)/.test(trimmed))
226
+ return false;
227
+ if (trimmed.split("\n").length > 6)
228
+ return false;
229
+ return RULE_SHAPED.test(trimmed);
230
+ }
231
+ /**
232
+ * Commands whose non-zero exit is a normal answer rather than a failure.
233
+ *
234
+ * `rg`/`grep` exit 1 on "no match", `test`/`diff`/`cmp` exit non-zero to report
235
+ * their result, and a TDD loop is *supposed* to go red. Without this filter the
236
+ * fail→fix extractor is mostly noise, since these are among the most-run
237
+ * commands in any session.
238
+ */
239
+ const BENIGN_EXIT_COMMANDS = /^(?:rg|grep|egrep|fgrep|ag|ack|test|\[|diff|cmp|which|command|type|hash|find|fd)\b/;
240
+ /** Whether a non-zero exit from this command should be ignored by the fix extractor. */
241
+ export function isBenignFailure(command) {
242
+ const first = command.trim().replace(/^(?:sudo|env|time|nice)\s+/, "");
243
+ return BENIGN_EXIT_COMMANDS.test(first);
244
+ }
245
+ //# sourceMappingURL=normalize.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"normalize.js","sourceRoot":"","sources":["../../../src/core/learn/normalize.ts"],"names":[],"mappings":"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","sourcesContent":["/**\n * Normalization and noise filters for the `/learn` extractor.\n *\n * Everything downstream — clustering, dedupe, cross-session counting — is only\n * as good as what happens here. Two runs of the same failing command differ by\n * absolute paths, line numbers, PIDs and timings; two statements of the same\n * rule differ by politeness and word order. Collapsing that variance is what\n * turns a pile of transcripts into counts, and the counts are the product.\n *\n * These are deliberately lexical and cheap. Semantic grouping is left to the\n * model, which sees the deduped candidates and can tell that \"run tests with\n * coverage\" and \"use --coverage on the suite\" are one rule. Trying to do that\n * here would cost a dependency and still be worse.\n */\n\n/** Longest text we normalize; error dumps can run to megabytes. */\nconst MAX_SIGNATURE_CHARS = 400;\n\n/**\n * Volatile fragments that make two runs of the same failure look different.\n *\n * Order is load-bearing, because these patterns overlap and the first one to\n * match wins the characters. Longest-and-most-specific therefore runs first:\n * paths before line numbers so `foo.ts:12:3` does not leave a bare `:12:3`, and\n * timestamps before line numbers so the `:00:00` inside `10:00:00` is not\n * mistaken for a line/column pair. Adding a pattern means placing it by\n * specificity, not appending it.\n */\nconst VOLATILE_PATTERNS: Array<[RegExp, string]> = [\n\t// Absolute POSIX and Windows paths, including the temp dirs that change per run.\n\t[/\\/(?:[\\w.@+-]+\\/)+[\\w.@+-]+/g, \"<path>\"],\n\t[/[A-Za-z]:\\\\(?:[\\w.@+-]+\\\\)+[\\w.@+-]+/g, \"<path>\"],\n\t// UUIDs, before the shorter hex rule can eat their first group.\n\t[/\\b[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}\\b/gi, \"<uuid>\"],\n\t// ISO timestamps and clock times, before the line/column rule.\n\t[/\\d{4}-\\d{2}-\\d{2}[T ]\\d{2}:\\d{2}:\\d{2}(?:\\.\\d+)?Z?/g, \"<time>\"],\n\t[/\\b\\d{1,2}:\\d{2}:\\d{2}\\b/g, \"<time>\"],\n\t// Ports, before the line/column rule can claim the colon.\n\t[/\\b(?:127\\.0\\.0\\.1|localhost):\\d+/gi, \"localhost:<port>\"],\n\t// file:line:col and bare line references.\n\t[/:\\d+:\\d+/g, \":<line>\"],\n\t[/\\bline \\d+/gi, \"line <line>\"],\n\t// Hex-ish identifiers: git shas, content hashes, addresses.\n\t[/\\b0x[0-9a-f]+\\b/gi, \"<addr>\"],\n\t[/\\b[0-9a-f]{7,40}\\b/gi, \"<hash>\"],\n\t// Durations and byte counts reported by build tools.\n\t[/\\b\\d+(?:\\.\\d+)?\\s?(?:ms|s|sec|secs|seconds|m|min|mins|kb|mb|gb)\\b/gi, \"<qty>\"],\n\t// PIDs, which vary per machine and per run.\n\t[/\\bpid[= ]\\d+/gi, \"pid <pid>\"],\n\t// Anything left that is a bare multi-digit run.\n\t[/\\b\\d{3,}\\b/g, \"<n>\"],\n];\n\n/**\n * Collapse a raw error or command output into a comparable signature.\n *\n * Case is preserved: `TS2307` and `ERR_MODULE_NOT_FOUND` carry meaning that\n * lowercasing would blur into surrounding prose, and they are exactly the\n * tokens worth matching on.\n */\nexport function normalizeErrorSignature(text: string): string {\n\tlet out = text.slice(0, MAX_SIGNATURE_CHARS * 4);\n\tfor (const [pattern, replacement] of VOLATILE_PATTERNS) {\n\t\tout = out.replace(pattern, replacement);\n\t}\n\treturn out.replace(/\\s+/g, \" \").trim().slice(0, MAX_SIGNATURE_CHARS);\n}\n\n/**\n * Normalize a shell command so two invocations of \"the same\" command match.\n *\n * Paths are collapsed but flags are kept — `npm test` and `npm test --coverage`\n * are genuinely different commands, and flags are frequently the fix itself.\n */\nexport function normalizeCommand(command: string): string {\n\tconst collapsed = command\n\t\t.replace(/\\s+/g, \" \")\n\t\t.replace(/(?:^|\\s)\\/(?:[\\w.@+-]+\\/)+[\\w.@+-]+/g, \" <path>\")\n\t\t.trim();\n\treturn collapsed.slice(0, MAX_SIGNATURE_CHARS);\n}\n\n/**\n * Prefixes that say where or how a command runs rather than what it does.\n *\n * Nearly every command an agent runs is wrapped in `cd <somewhere> && …`, and\n * once paths are collapsed that prefix is identical everywhere. Taking the head\n * of the raw command therefore yields `cd <path>` for the whole session, which\n * makes every bash step look like every other one.\n */\nconst COMMAND_PREFIX = /^(?:cd\\s+\\S+\\s*&&\\s*|(?:sudo|env|time|nice|exec)\\s+)/;\n\n/**\n * The first `tokens` words of what a command actually does, with the\n * where-and-how prefixes stripped. `cd <path> && npm run check` becomes\n * `npm run`, which is the part worth comparing across sessions.\n */\nexport function commandHead(command: string, tokens = 2): string {\n\tlet out = normalizeCommand(command);\n\t// Prefixes stack: `cd x && sudo env FOO=1 cmd`.\n\tfor (let i = 0; i < 4 && COMMAND_PREFIX.test(out); i++) {\n\t\tout = out.replace(COMMAND_PREFIX, \"\");\n\t}\n\treturn out.split(\" \").slice(0, tokens).join(\" \").trim();\n}\n\n/**\n * The part of a command's output that is actually the error.\n *\n * Build tools lead with a banner — npm's `> pkg@1.0 check > biome check …`, a\n * compiler's version line — that is byte-identical across every run. Signing a\n * failure by its whole output therefore signs mostly the banner, and two\n * unrelated failures of the same command look like the same problem. Scanning\n * for the first error-shaped line fixes both the signature and what the reader\n * is shown; failing that, the tail beats the head, because tools print the\n * banner first and the verdict last.\n */\nconst ERROR_LINE =\n\t/\\b(?:error|err!|errors?:|failed|failure|cannot|can't|unable to|not found|no such|unexpected|expected|invalid|denied|refused|timed out|traceback|panic|exception|fatal)\\b|^\\s*(?:×|✖|✗)/i;\n\nexport function extractErrorRegion(output: string, maxChars = 400): string {\n\tconst lines = output.split(\"\\n\");\n\tconst start = lines.findIndex((line) => ERROR_LINE.test(line));\n\tif (start >= 0) {\n\t\treturn lines.slice(start).join(\"\\n\").slice(0, maxChars).trim();\n\t}\n\treturn output.slice(-maxChars).trim();\n}\n\n/**\n * Output that reports no failure worth learning from: an abort is the user\n * changing their mind, and an empty result says nothing at all.\n */\nexport function isUninformativeFailure(output: string): boolean {\n\tconst trimmed = output.trim();\n\tif (trimmed.length === 0) return true;\n\treturn /^(?:command aborted|aborted|cancelled|canceled|interrupted|killed|sigint|sigterm)\\b/i.test(trimmed);\n}\n\n/** Filler that carries no rule content, stripped before directives are compared. */\nconst DIRECTIVE_FILLER =\n\t/^(?:please|pls|hey|ok|okay|now|also|and|so|just|quickly|can you|could you|would you|i want you to|i'd like you to|let's|lets)\\b[\\s,]*/i;\n\n/** Words too common to distinguish one directive from another. */\nconst STOPWORDS = new Set([\n\t\"a\",\n\t\"an\",\n\t\"and\",\n\t\"are\",\n\t\"as\",\n\t\"at\",\n\t\"be\",\n\t\"but\",\n\t\"by\",\n\t\"do\",\n\t\"for\",\n\t\"from\",\n\t\"in\",\n\t\"is\",\n\t\"it\",\n\t\"its\",\n\t\"of\",\n\t\"on\",\n\t\"or\",\n\t\"that\",\n\t\"the\",\n\t\"then\",\n\t\"this\",\n\t\"to\",\n\t\"we\",\n\t\"when\",\n\t\"with\",\n\t\"you\",\n\t\"your\",\n]);\n\n/**\n * Normalize a user directive for clustering: lowercase, strip filler and\n * punctuation, collapse whitespace. Unlike error signatures these are compared\n * loosely, so case and symbols are noise.\n */\nexport function normalizeDirective(text: string): string {\n\tlet out = text.trim();\n\t// Filler can stack (\"ok so please also ...\"), so strip until it stops matching.\n\tfor (let i = 0; i < 4 && DIRECTIVE_FILLER.test(out); i++) {\n\t\tout = out.replace(DIRECTIVE_FILLER, \"\");\n\t}\n\treturn out\n\t\t.toLowerCase()\n\t\t.replace(/[^\\w\\s@./+-]/g, \" \")\n\t\t.replace(/\\s+/g, \" \")\n\t\t.trim();\n}\n\n/** Content words of a directive, for overlap comparisons against existing rules. */\nexport function contentWords(text: string): string[] {\n\treturn [...new Set(normalizeDirective(text).split(\" \"))].filter((w) => w.length > 2 && !STOPWORDS.has(w));\n}\n\n/**\n * Fraction of `words` that appear in `haystack`, 0 when there is nothing to\n * compare. Used to decide whether a proposed rule is already written down.\n */\nexport function wordOverlap(words: string[], haystack: string): number {\n\tif (words.length === 0) return 0;\n\tconst hay = ` ${normalizeDirective(haystack)} `;\n\tconst hits = words.filter((w) => hay.includes(` ${w} `)).length;\n\treturn hits / words.length;\n}\n\n/** Approvals and acknowledgements. These are the bulk of user turns and none are rules. */\nconst ACKNOWLEDGEMENT =\n\t/^(?:y|n|ok|okay|yes|no|yep|yeah|nope|sure|thanks|thank you|ty|go|go ahead|continue|proceed|do it|next|done|good|great|nice|perfect|lgtm|cool|k|fine|stop|wait|hmm|\\?+|\\.+)$/i;\n\n/**\n * Imperative or corrective phrasing — the shape a rule takes when it is spoken\n * aloud. A directive that matches none of these is almost always a task\n * (\"add a button here\"), which is worth doing once and never worth writing down.\n */\nconst RULE_SHAPED =\n\t/\\b(?:always|never|don'?t|do not|avoid|make sure|ensure|must|should|prefer|instead|remember|from now on|going forward|every time|each time|whenever|stop|use|run|keep|only|no need to)\\b/i;\n\n/** Minimum length before a directive is worth considering at all. */\nconst MIN_DIRECTIVE_CHARS = 12;\n/** Above this a \"directive\" is a task description or a pasted spec, not a rule. */\nconst MAX_DIRECTIVE_CHARS = 400;\n\n/**\n * Whether a user message is worth mining as a potential rule.\n *\n * Deliberately strict. The extractor's output is read by a model with a limited\n * budget, and recall costs precision: every task statement that slips through\n * displaces a real recurring directive in the ranking.\n */\nexport function isRuleShapedDirective(text: string): boolean {\n\tconst trimmed = text.trim();\n\tif (trimmed.length < MIN_DIRECTIVE_CHARS || trimmed.length > MAX_DIRECTIVE_CHARS) return false;\n\tif (ACKNOWLEDGEMENT.test(trimmed)) return false;\n\t// Slash commands are invocations, not instructions.\n\tif (trimmed.startsWith(\"/\")) return false;\n\t// Pasted output, diffs and stack traces are not directives.\n\tif (/^(?:```|diff --git|\\s*at\\s|\\{|\\[)/.test(trimmed)) return false;\n\tif (trimmed.split(\"\\n\").length > 6) return false;\n\treturn RULE_SHAPED.test(trimmed);\n}\n\n/**\n * Commands whose non-zero exit is a normal answer rather than a failure.\n *\n * `rg`/`grep` exit 1 on \"no match\", `test`/`diff`/`cmp` exit non-zero to report\n * their result, and a TDD loop is *supposed* to go red. Without this filter the\n * fail→fix extractor is mostly noise, since these are among the most-run\n * commands in any session.\n */\nconst BENIGN_EXIT_COMMANDS = /^(?:rg|grep|egrep|fgrep|ag|ack|test|\\[|diff|cmp|which|command|type|hash|find|fd)\\b/;\n\n/** Whether a non-zero exit from this command should be ignored by the fix extractor. */\nexport function isBenignFailure(command: string): boolean {\n\tconst first = command.trim().replace(/^(?:sudo|env|time|nice)\\s+/, \"\");\n\treturn BENIGN_EXIT_COMMANDS.test(first);\n}\n"]}
@@ -0,0 +1,115 @@
1
+ /**
2
+ * Per-directory memory of what `/learn` has already shown you.
3
+ *
4
+ * The extractor computes a sliding-window snapshot — "in your last N sessions
5
+ * you said this M times" — which is stateless by design and correct to
6
+ * recompute from scratch every run. What it cannot know on its own is whether
7
+ * you have already *seen* a given item and made a call on it. Without that, a
8
+ * rule you accepted last week comes back forever (its occurrences are still
9
+ * inside the window), and worse, it comes back flagged `restated` — accusing a
10
+ * rule that is working of not working. An item you declined comes back too,
11
+ * unchanged, every single run.
12
+ *
13
+ * So one bookmark per directory: what was surfaced, and when. An item is shown
14
+ * again only once something new has happened — you said it again *after* it was
15
+ * last put in front of you. That single rule fixes both cases, and it keeps the
16
+ * `restated` label honest, because the only way to earn it is to repeat yourself
17
+ * after the rule already existed.
18
+ *
19
+ * This is a bookmark, not an index. No cards, no retrieval, no embeddings — the
20
+ * transcripts remain the source of truth and this file can be deleted at any
21
+ * time with no loss beyond re-proposing things once.
22
+ */
23
+ export interface SurfacedItem {
24
+ /** When this item was last put in front of the user, ISO. */
25
+ surfacedAt: string;
26
+ /** Newest occurrence the run knew about at that point, ISO. */
27
+ lastOccurrence: string;
28
+ /**
29
+ * Whether the item was covered by a context file the last time it was
30
+ * surfaced. Comparing that against coverage now is how an adopted proposal is
31
+ * told from a declined one, without asking.
32
+ */
33
+ coveredWhenSurfaced: boolean;
34
+ }
35
+ export interface LearnState {
36
+ version: number;
37
+ lastRun?: string;
38
+ surfaced: Record<string, SurfacedItem>;
39
+ }
40
+ /**
41
+ * State file for a directory.
42
+ *
43
+ * Keyed off the session directory's own name rather than re-deriving the cwd
44
+ * encoding, so the two can never drift apart: whatever set of sessions is being
45
+ * mined is the set this state describes.
46
+ */
47
+ export declare function getLearnStatePath(agentDir: string, sessionDir: string): string;
48
+ /** Read state, treating any unreadable or unknown-version file as empty. */
49
+ export declare function readLearnState(path: string): LearnState;
50
+ /** Persist state, pruning entries nothing has referenced in a long time. */
51
+ export declare function writeLearnState(path: string, state: LearnState, now?: Date): void;
52
+ /** What the caller needs to decide whether an item is worth showing again. */
53
+ export interface SuppressionInput {
54
+ key: string;
55
+ /** Newest occurrence of this item in the current window, ISO. */
56
+ lastSeen: string;
57
+ /** Whether a context file covers it right now. */
58
+ covered: boolean;
59
+ }
60
+ export interface SuppressionVerdict {
61
+ /** Drop it: already surfaced, and nothing new has happened since. */
62
+ suppressed: boolean;
63
+ /**
64
+ * Surfaced before, still not covered by any context file — you saw it and
65
+ * chose not to write it down. Worth telling the model so it proposes again
66
+ * tentatively instead of pressing the same case twice.
67
+ */
68
+ previouslyDeclined: boolean;
69
+ }
70
+ /**
71
+ * Decide whether an item should be shown again.
72
+ *
73
+ * An item is suppressed when it was surfaced before and has not recurred since:
74
+ * `lastSeen <= surfacedAt` means every occurrence backing it was already on
75
+ * screen when you made your call. Say it again and `lastSeen` moves past
76
+ * `surfacedAt`, and it returns — which is exactly when it is worth returning.
77
+ */
78
+ export declare function judge(state: LearnState, input: SuppressionInput): SuppressionVerdict;
79
+ export interface LearnStats {
80
+ /** Proposals on record, across all categories. */
81
+ total: number;
82
+ directives: number;
83
+ fixes: number;
84
+ workflows: number;
85
+ /**
86
+ * Directive proposals that were genuinely open questions — not already
87
+ * written down when they were shown. Only these can be adopted or passed
88
+ * over, so they are the denominator of the rate.
89
+ */
90
+ open: number;
91
+ adopted: number;
92
+ declined: number;
93
+ earliest?: string;
94
+ latest?: string;
95
+ lastRun?: string;
96
+ }
97
+ /**
98
+ * Reconstruct what happened to past proposals.
99
+ *
100
+ * Nothing extra is recorded to make this work: each entry already stores whether
101
+ * it was covered when it was shown, and coverage now is recomputable, so the
102
+ * pair is enough to tell an adopted proposal from one that was passed over.
103
+ *
104
+ * Directives only. Fixes and workflows have no coverage signal — a fix may have
105
+ * become a rule, a skill, or a habit, and which one is not recoverable — so
106
+ * counting them here would mean inventing an outcome.
107
+ */
108
+ export declare function summarizeLearnState(state: LearnState, isCoveredNow: (normalized: string) => boolean): LearnStats;
109
+ /** Record everything this run put on screen, so the next run can suppress it. */
110
+ export declare function recordSurfaced(state: LearnState, items: Array<{
111
+ key: string;
112
+ lastSeen: string;
113
+ covered: boolean;
114
+ }>, now?: Date): LearnState;
115
+ //# sourceMappingURL=state.d.ts.map