@davesheffer/hunch 1.31.0 → 1.32.0

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 (228) hide show
  1. package/README.md +3 -6
  2. package/dist/cli/automaticReviewMemory.d.ts +13 -0
  3. package/dist/cli/dna.d.ts +2 -0
  4. package/dist/cli/index.d.ts +16 -0
  5. package/dist/cli/index.js +602 -43
  6. package/dist/cli/integrations.d.ts +2 -0
  7. package/dist/cli/integrations.js +5 -2
  8. package/dist/cli/invocation.d.ts +35 -0
  9. package/dist/cli/preflight.d.ts +1 -0
  10. package/dist/cli/reviewMemory.d.ts +4 -0
  11. package/dist/cli/reviewMemoryProvider.d.ts +14 -0
  12. package/dist/cli/serve.d.ts +2 -0
  13. package/dist/cli/taskReport.d.ts +6 -0
  14. package/dist/cli/taskReport.js +124 -0
  15. package/dist/cli/update.d.ts +14 -0
  16. package/dist/client/state.d.ts +248 -0
  17. package/dist/client/state.js +2 -0
  18. package/dist/constitution/adapters.d.ts +29 -0
  19. package/dist/constitution/behaviorAttestationBinding.d.ts +3 -0
  20. package/dist/constitution/behaviorEvaluator.d.ts +8 -0
  21. package/dist/constitution/behaviorProof.d.ts +9 -0
  22. package/dist/constitution/behaviorWorkspace.d.ts +25 -0
  23. package/dist/constitution/bootstrap.d.ts +30 -0
  24. package/dist/constitution/canonical.d.ts +15 -0
  25. package/dist/constitution/card.d.ts +58 -0
  26. package/dist/constitution/compiler.d.ts +52 -0
  27. package/dist/constitution/composition.d.ts +10 -0
  28. package/dist/constitution/corpus.d.ts +7 -0
  29. package/dist/constitution/correctionPolicyMaterializer.d.ts +57 -0
  30. package/dist/constitution/delta.d.ts +4 -0
  31. package/dist/constitution/disposition.d.ts +20 -0
  32. package/dist/constitution/evaluator.d.ts +47 -0
  33. package/dist/constitution/experiment.d.ts +466 -0
  34. package/dist/constitution/experimentRunner.d.ts +7 -0
  35. package/dist/constitution/g2.d.ts +209 -0
  36. package/dist/constitution/g2BehaviorAttestation.d.ts +45 -0
  37. package/dist/constitution/g2BehaviorCandidates.d.ts +123 -0
  38. package/dist/constitution/g2BehaviorDependencies.d.ts +83 -0
  39. package/dist/constitution/g2BehaviorMaterialization.d.ts +77 -0
  40. package/dist/constitution/g2BehaviorPolicyMaterializer.d.ts +48 -0
  41. package/dist/constitution/g2CandidateAttestation.d.ts +43 -0
  42. package/dist/constitution/g2Candidates.d.ts +80 -0
  43. package/dist/constitution/g2Drills.d.ts +33 -0
  44. package/dist/constitution/g3.d.ts +311 -0
  45. package/dist/constitution/g3Conformance.d.ts +33 -0
  46. package/dist/constitution/lifecycle.d.ts +25 -0
  47. package/dist/constitution/mutation.d.ts +15 -0
  48. package/dist/constitution/nodeTestEvidence.d.ts +13 -0
  49. package/dist/constitution/plan.d.ts +18 -0
  50. package/dist/constitution/policyRuntime.d.ts +5 -0
  51. package/dist/constitution/proof.d.ts +11 -0
  52. package/dist/constitution/repairPolicies.d.ts +34 -0
  53. package/dist/constitution/replacementFreeGit.d.ts +14 -0
  54. package/dist/constitution/replay.d.ts +40 -0
  55. package/dist/constitution/replayCache.d.ts +14 -0
  56. package/dist/constitution/replayWorker.d.ts +1 -0
  57. package/dist/constitution/repository.d.ts +129 -0
  58. package/dist/constitution/safeCheckout.d.ts +9 -0
  59. package/dist/constitution/schema.d.ts +1763 -0
  60. package/dist/constitution/scorecard.d.ts +219 -0
  61. package/dist/constitution/service.d.ts +398 -0
  62. package/dist/constitution/shadow.d.ts +54 -0
  63. package/dist/constitution/sourceMutation.d.ts +15 -0
  64. package/dist/constitution/staticGraphBaseline.d.ts +13 -0
  65. package/dist/constitution/structural.d.ts +64 -0
  66. package/dist/core/agenthook.d.ts +63 -0
  67. package/dist/core/agenthook.js +1 -0
  68. package/dist/core/automaticReviewMemory.d.ts +60 -0
  69. package/dist/core/autoreview.d.ts +66 -0
  70. package/dist/core/canonicalOrder.d.ts +3 -0
  71. package/dist/core/capturetoken.d.ts +19 -0
  72. package/dist/core/changeIdentity.d.ts +22 -0
  73. package/dist/core/changeProof.d.ts +14 -0
  74. package/dist/core/checkreport.d.ts +140 -0
  75. package/dist/core/commitrepair.d.ts +171 -0
  76. package/dist/core/commitrepair.js +238 -0
  77. package/dist/core/compare.d.ts +21 -0
  78. package/dist/core/config.d.ts +20 -0
  79. package/dist/core/conformance.d.ts +35 -0
  80. package/dist/core/constraintmatch.d.ts +54 -0
  81. package/dist/core/correction.d.ts +42 -0
  82. package/dist/core/correctionStage.d.ts +224 -0
  83. package/dist/core/declarationClusters.d.ts +149 -0
  84. package/dist/core/delivery.d.ts +110 -0
  85. package/dist/core/docanchors.d.ts +32 -0
  86. package/dist/core/docscan.d.ts +26 -0
  87. package/dist/core/drift.d.ts +13 -0
  88. package/dist/core/drift.js +31 -1
  89. package/dist/core/dupdetect.d.ts +43 -0
  90. package/dist/core/escalations.d.ts +129 -0
  91. package/dist/core/escalations.js +165 -0
  92. package/dist/core/events.d.ts +29 -0
  93. package/dist/core/evidenceMap.d.ts +164 -0
  94. package/dist/core/externalImports.d.ts +8 -0
  95. package/dist/core/format.d.ts +13 -0
  96. package/dist/core/glob.d.ts +9 -0
  97. package/dist/core/groundingLag.d.ts +84 -0
  98. package/dist/core/groundingLag.js +16 -2
  99. package/dist/core/groundingMerge.d.ts +15 -0
  100. package/dist/core/groundingMerge.js +95 -0
  101. package/dist/core/hookcache.d.ts +10 -0
  102. package/dist/core/hookpolicy.d.ts +47 -0
  103. package/dist/core/ids.d.ts +36 -0
  104. package/dist/core/importReview.d.ts +30 -0
  105. package/dist/core/io.d.ts +23 -0
  106. package/dist/core/jsonc.d.ts +4 -0
  107. package/dist/core/landscapeAdoption.d.ts +66 -0
  108. package/dist/core/landscapeDelivery.d.ts +76 -0
  109. package/dist/core/memorylog.d.ts +37 -0
  110. package/dist/core/migrate.d.ts +28 -0
  111. package/dist/core/outcomeExperience.d.ts +89 -0
  112. package/dist/core/overlaySafety.d.ts +24 -0
  113. package/dist/core/overlaySafety.js +7 -1
  114. package/dist/core/paths.d.ts +31 -0
  115. package/dist/core/pipeline.d.ts +289 -0
  116. package/dist/core/premises.d.ts +43 -0
  117. package/dist/core/provenance.d.ts +21 -0
  118. package/dist/core/publication.d.ts +54 -0
  119. package/dist/core/refrepair.d.ts +24 -0
  120. package/dist/core/refrepair.js +1 -1
  121. package/dist/core/relativeImports.d.ts +11 -0
  122. package/dist/core/repair.d.ts +44 -0
  123. package/dist/core/repairqueue.d.ts +40 -0
  124. package/dist/core/repairqueue.js +133 -0
  125. package/dist/core/reviewMemory.d.ts +40 -0
  126. package/dist/core/reviewqueue.d.ts +54 -0
  127. package/dist/core/safeRepoFile.d.ts +20 -0
  128. package/dist/core/served.d.ts +57 -0
  129. package/dist/core/served.js +14 -0
  130. package/dist/core/stateContract.d.ts +979 -0
  131. package/dist/core/stateContract.js +87 -4
  132. package/dist/core/stateDelivery.d.ts +78 -0
  133. package/dist/core/stateRecords.d.ts +383 -0
  134. package/dist/core/stateRecords.js +16 -1
  135. package/dist/core/stats.d.ts +88 -0
  136. package/dist/core/strictgate.d.ts +33 -0
  137. package/dist/core/taskReport.d.ts +226 -0
  138. package/dist/core/taskReport.js +514 -0
  139. package/dist/core/taskReportCapture.d.ts +8 -0
  140. package/dist/core/taskReportCapture.js +36 -0
  141. package/dist/core/taskReportEvidence.d.ts +26 -0
  142. package/dist/core/taskReportEvidence.js +268 -0
  143. package/dist/core/taskReportHook.d.ts +20 -0
  144. package/dist/core/taskReportHook.js +76 -0
  145. package/dist/core/taskReportPaths.d.ts +3 -0
  146. package/dist/core/taskReportPaths.js +23 -0
  147. package/dist/core/taskReportPublic.d.ts +31 -0
  148. package/dist/core/taskReportPublic.js +45 -0
  149. package/dist/core/taskReportRender.d.ts +6 -0
  150. package/dist/core/taskReportRender.js +97 -0
  151. package/dist/core/topics.d.ts +73 -0
  152. package/dist/core/types.d.ts +1239 -0
  153. package/dist/core/version.d.ts +10 -0
  154. package/dist/eval/guards.d.ts +45 -0
  155. package/dist/eval/harness.d.ts +58 -0
  156. package/dist/extractors/adrImport.d.ts +59 -0
  157. package/dist/extractors/comments.d.ts +7 -0
  158. package/dist/extractors/correctionSources.d.ts +11 -0
  159. package/dist/extractors/diff.d.ts +40 -0
  160. package/dist/extractors/git.d.ts +304 -0
  161. package/dist/extractors/git.js +208 -20
  162. package/dist/extractors/helm.d.ts +31 -0
  163. package/dist/extractors/indexer.d.ts +55 -0
  164. package/dist/extractors/landscapeDiscovery.d.ts +41 -0
  165. package/dist/extractors/languages.d.ts +77 -0
  166. package/dist/extractors/nativeTreeSitter.d.ts +16 -0
  167. package/dist/extractors/parse.d.ts +54 -0
  168. package/dist/extractors/php.d.ts +26 -0
  169. package/dist/extractors/repoSource.d.ts +53 -0
  170. package/dist/extractors/testreport.d.ts +36 -0
  171. package/dist/integrations/ciAction.d.ts +8 -0
  172. package/dist/integrations/claudeConfig.d.ts +44 -0
  173. package/dist/integrations/claudemd.d.ts +13 -0
  174. package/dist/integrations/claudemd.js +11 -2
  175. package/dist/integrations/gitignore.d.ts +16 -0
  176. package/dist/integrations/gitignore.js +7 -0
  177. package/dist/integrations/health.d.ts +74 -0
  178. package/dist/integrations/hooks.d.ts +41 -0
  179. package/dist/integrations/hooks.js +92 -66
  180. package/dist/integrations/madrExport.d.ts +39 -0
  181. package/dist/integrations/madrManifest.d.ts +69 -0
  182. package/dist/integrations/mergeDriver.d.ts +3 -0
  183. package/dist/integrations/mergeDriver.js +25 -8
  184. package/dist/integrations/probe.d.ts +4 -0
  185. package/dist/integrations/providers.d.ts +103 -0
  186. package/dist/integrations/scaffold.d.ts +29 -0
  187. package/dist/integrations/sync.d.ts +30 -0
  188. package/dist/integrations/sync.js +3 -2
  189. package/dist/integrations/team.d.ts +70 -0
  190. package/dist/integrations/worktree.d.ts +8 -0
  191. package/dist/mcp/roots.d.ts +16 -0
  192. package/dist/mcp/server.d.ts +36 -0
  193. package/dist/mcp/server.js +114 -25
  194. package/dist/mcp/taskReportTools.d.ts +3 -0
  195. package/dist/mcp/taskReportTools.js +106 -0
  196. package/dist/serve/app.d.ts +36 -0
  197. package/dist/serve/app.js +20 -0
  198. package/dist/serve/config.d.ts +100 -0
  199. package/dist/serve/writelock.d.ts +22 -0
  200. package/dist/store/changeLedger.d.ts +132 -0
  201. package/dist/store/changeLedger.js +40 -9
  202. package/dist/store/compact.d.ts +33 -0
  203. package/dist/store/db.d.ts +9 -0
  204. package/dist/store/embedder.d.ts +32 -0
  205. package/dist/store/hunchStore.d.ts +624 -0
  206. package/dist/store/hunchStore.js +13 -5
  207. package/dist/store/jsonStore.d.ts +125 -0
  208. package/dist/store/jsonStore.js +15 -0
  209. package/dist/store/merge.d.ts +39 -0
  210. package/dist/store/privateMigrate.d.ts +24 -0
  211. package/dist/store/replay.d.ts +56 -0
  212. package/dist/store/schema.d.ts +17 -0
  213. package/dist/store/stateBinding.d.ts +145 -0
  214. package/dist/store/stateBinding.js +110 -15
  215. package/dist/store/stateCapture.d.ts +15 -0
  216. package/dist/store/stateCapture.js +145 -0
  217. package/dist/synthesis/cliAdapter.d.ts +22 -0
  218. package/dist/synthesis/initiator.d.ts +13 -0
  219. package/dist/synthesis/provider.d.ts +307 -0
  220. package/dist/synthesis/synthesize.d.ts +117 -0
  221. package/dist/synthesis/tripwires.d.ts +9 -0
  222. package/dist/taskReports.d.ts +76 -0
  223. package/dist/taskReports.js +67 -0
  224. package/dist/wiki/adopt.d.ts +22 -0
  225. package/dist/wiki/graph.d.ts +87 -0
  226. package/dist/wiki/wiki.d.ts +242 -0
  227. package/package.json +6 -5
  228. package/server.json +2 -2
@@ -0,0 +1,53 @@
1
+ export type RepoScanSource = {
2
+ kind: "checkout";
3
+ } | {
4
+ kind: "staged";
5
+ } | {
6
+ kind: "working";
7
+ } | {
8
+ kind: "commit";
9
+ ref: string;
10
+ } | {
11
+ kind: "base";
12
+ };
13
+ export type RepoScanSourceKind = RepoScanSource["kind"];
14
+ export interface RepoScanSourceIdentity {
15
+ kind: RepoScanSourceKind;
16
+ /** Canonical commit object used by commit/base scans. */
17
+ revision?: string;
18
+ }
19
+ export type RepoSourceIssueCode = "conflicted" | "unsafe_path" | "unsafe_mode" | "symlink" | "non_regular" | "oversized" | "invalid_encoding" | "read_failed" | "parse_failed";
20
+ export interface RepoSourceIssue {
21
+ path: string;
22
+ code: RepoSourceIssueCode;
23
+ detail: string;
24
+ }
25
+ export interface RepoSourceRead {
26
+ source: string | null;
27
+ mode: string;
28
+ /** Hash of the exact bytes supplied by Git/filesystem, before UTF-8 decode. */
29
+ contentHash?: string;
30
+ /** Missing tracked working files are intentional deletions, not scan failures. */
31
+ absent?: boolean;
32
+ issue?: RepoSourceIssue;
33
+ }
34
+ export interface RepoSourceEntry {
35
+ path: string;
36
+ mode: string;
37
+ read(): RepoSourceRead;
38
+ }
39
+ export interface RepoSourceInventory {
40
+ identity: RepoScanSourceIdentity;
41
+ entries: RepoSourceEntry[];
42
+ }
43
+ export declare function isIndexedCodePath(path: string): boolean;
44
+ export declare function repoSourceInventory(root: string, source?: RepoScanSource): RepoSourceInventory;
45
+ /** Read a known, safe auxiliary manifest from the exact same source selection as
46
+ * the semantic code scan. This is intentionally path-specific rather than a
47
+ * second inventory: manifests inform resolution but never become code files. */
48
+ export declare function repoAuxiliarySource(root: string, source: RepoScanSource | undefined, path: string): RepoSourceRead;
49
+ export declare function dirtyIndexedCodePaths(root: string): string[];
50
+ export declare function assertCleanIndexedCode(root: string): void;
51
+ /** Auxiliary manifests can change graph resolution just like source code. A
52
+ * durable checkout scan must therefore prove these selected paths are clean. */
53
+ export declare function assertCleanAuxiliarySources(root: string, paths: readonly string[]): void;
@@ -0,0 +1,36 @@
1
+ /**
2
+ * Parse a test run's output into pass/fail signals for the failure-learning loop
3
+ * (`hunch test`). Test-framework-agnostic: it recognizes both common shapes that
4
+ * `node:test` (this repo), mocha, ava, vitest, jest, and `prove` emit:
5
+ *
6
+ * - TAP: `ok 12 - name` / `not ok 3 - name`, with an optional indented YAML
7
+ * diagnostic block (error + stack) under a failure.
8
+ * - spec: node:test's default non-TTY reporter — `✔ name (1.2ms)` /
9
+ * `✖ name (1.2ms)`, failures followed by an indented error block.
10
+ * (Also accepts ✓/✗.)
11
+ *
12
+ * Design choices:
13
+ * - A failure's `message` is the test name plus its indented diagnostic block —
14
+ * exactly the context recordFailure() feeds the synthesizer for root-cause and
15
+ * suspect ranking.
16
+ * - If NEITHER shape is recognized, we return empty lists + recognized=false so
17
+ * the caller falls back to a coarse "the suite failed" bug from the raw tail —
18
+ * never silently reports success.
19
+ * - Results are deduped by name (a spec reporter can echo a failing test in its
20
+ * end-of-run recap).
21
+ */
22
+ export interface TestFailure {
23
+ /** Stable test identifier (the description) — seeds the bug id. */
24
+ test: string;
25
+ /** Name + diagnostic block; the root-cause/suspect context for synthesis. */
26
+ message: string;
27
+ }
28
+ export interface TestReport {
29
+ failures: TestFailure[];
30
+ /** Names of tests that passed — used to mark previously-open bugs as fixed. */
31
+ passed: string[];
32
+ /** True iff at least one result line (TAP or spec) was recognized. */
33
+ recognized: boolean;
34
+ }
35
+ /** Parse TAP-or-spec text. Pure + synchronous so it's trivially unit-testable. */
36
+ export declare function parseTestReport(output: string): TestReport;
@@ -0,0 +1,8 @@
1
+ export declare function ciWorkflowYaml(): string;
2
+ export interface CiResult {
3
+ path: string;
4
+ action: "created" | "exists";
5
+ }
6
+ /** Write .github/workflows/hunch-guard.yml. Never overwrites an existing file
7
+ * (respects user edits) — reports "exists" instead. */
8
+ export declare function writeCiWorkflow(root: string): CiResult;
@@ -0,0 +1,44 @@
1
+ /** Absolute path to Claude Code's per-user config (`~/.claude.json`). */
2
+ export declare function claudeConfigPath(): string;
3
+ export interface HealedGroup {
4
+ /** The normalized real path the casing variants collapse to. */
5
+ realPath: string;
6
+ /** The raw project keys that differ only by drive-letter case. */
7
+ casings: string[];
8
+ /** The union of MCP server names now mirrored across every casing. */
9
+ servers: string[];
10
+ }
11
+ export interface HealResult {
12
+ platform: NodeJS.Platform;
13
+ /** False on non-Windows (the bug can't occur there) — the heal is a no-op. */
14
+ applicable: boolean;
15
+ file: string;
16
+ /** Path to the timestamped backup, set only when a write happened. */
17
+ backup?: string;
18
+ /** Whether the file was modified. */
19
+ changed: boolean;
20
+ /** The case-split groups that were merged (empty when nothing to heal). */
21
+ groups: HealedGroup[];
22
+ }
23
+ export interface HealOptions {
24
+ /** Defaults to `claudeConfigPath()`. */
25
+ file?: string;
26
+ /** Defaults to `process.platform`; pass "win32" to exercise the heal in tests. */
27
+ platform?: NodeJS.Platform;
28
+ }
29
+ /**
30
+ * Scan `~/.claude.json` for project keys that collapse to the same real directory
31
+ * but differ by drive-letter case, and HEAL each split by computing the UNION of
32
+ * its casings' MCP config and MIRRORING that union back into EVERY casing.
33
+ *
34
+ * Why mirror (not merge-into-one-canonical-and-delete-the-rest): we cannot predict
35
+ * which casing a given Claude Code session will resolve its cwd to. If we collapsed
36
+ * to a single canonical key, a session that lands on a deleted casing would get a
37
+ * fresh empty block → hunch missing again. Mirroring the union guarantees that
38
+ * whichever casing wins, the server is there — and it deletes nothing Claude made.
39
+ *
40
+ * Safety: no-op on non-Windows; backs up the file (timestamped copy) BEFORE any
41
+ * write; merges only (never clobbers other servers/keys); and THROWS rather than
42
+ * overwrite a non-empty file it cannot parse (mirrors readJsonObj in providers.ts).
43
+ */
44
+ export declare function healClaudeConfigCaseSplit(opts?: HealOptions): HealResult;
@@ -0,0 +1,13 @@
1
+ import type { HunchStore } from "../store/hunchStore.js";
2
+ /** Remove the managed HUNCH section (markers inclusive), leaving only the
3
+ * user-authored surroundings. Lets a caller decide whether two versions of a
4
+ * doc differ ONLY in generated content (the stranded-grounding heal,
5
+ * fnd_b269d5c422): equal outside the block ⇒ regenerating cannot lose prose. */
6
+ export declare function stripManagedSection(text: string): string;
7
+ export declare function renderHunchSection(store: HunchStore, root?: string): string;
8
+ /** Insert/replace the marker-delimited HUNCH section in a markdown doc, preserving
9
+ * all user-authored content outside the markers. Shared by CLAUDE.md, AGENTS.md,
10
+ * and .github/copilot-instructions.md so every assistant gets the same grounding. */
11
+ export declare function upsertSection(file: string, section: string, fallbackTitle: string): string;
12
+ /** Insert/replace the HUNCH section in CLAUDE.md, preserving everything else. */
13
+ export declare function updateClaudeMd(root: string, store: HunchStore): string;
@@ -8,6 +8,7 @@ import { writeFileAtomic } from "../core/io.js";
8
8
  import { basename, join, dirname } from "node:path";
9
9
  import { wikiSummary } from "../wiki/wiki.js";
10
10
  import { PolicyRepository } from "../constitution/repository.js";
11
+ import { renderCountsMatch } from "../core/groundingLag.js";
11
12
  const START = "<!-- HUNCH:START — auto-generated, do not edit by hand -->";
12
13
  const END = "<!-- HUNCH:END -->";
13
14
  /** Remove the managed HUNCH section (markers inclusive), leaving only the
@@ -41,13 +42,14 @@ export function renderHunchSection(store, root) {
41
42
  lines.push("");
42
43
  lines.push("This repo has **Hunch** — a curated graph of *why* the code is the way it is " +
43
44
  "(decisions, bug history, invariants). It currently holds " +
44
- `**${counts.decisions} decisions, ${counts.bugs} bugs, ${counts.constraints} constraints, ${counts.components} components, ${counts.policies} policies${counts.findings ? `, ${counts.findings} open findings` : ""}**.`);
45
+ `${renderCountsMatch(counts)}.`);
45
46
  lines.push("");
46
47
  lines.push("**Consult Hunch via the `hunch_*` MCP tools — pick by MOMENT, not from memory:**");
47
48
  lines.push("");
48
49
  lines.push("**Orient (session/task start):**");
50
+ lines.push("- For a new user task, call `hunch_task(action: \"start\", title: <short task title>)` once and retain its `task_id`. If a native prompt hook already supplied a task ID, reuse its exact start arguments instead of creating another task; each new native prompt has its own ID. Otherwise reuse the ID for follow-up work on the same task; never borrow another task's ID. This is task bookkeeping; `hunch_context` remains the first memory lookup. If reporting fails, continue the work and disclose the gap.");
49
51
  lines.push("- When the user asks to **update Hunch**, run `hunch update` from this repository root. It updates to the latest release and repairs all configured harness pins. Use `hunch update --global` to also update a global CLI alongside a repository dependency; reconnect active MCP sessions afterward.");
50
- lines.push("- `hunch_context(target)` — the minimal relevant slice for what you're about to do; a task phrase falls back to the closest graph matches. **Call FIRST.**");
52
+ lines.push("- `hunch_context(target, task_id)` — the minimal relevant slice for what you're about to do; a task phrase falls back to the closest graph matches. **Call FIRST** for memory. Include the current task ID on each context call so its contribution is inspectable.");
51
53
  lines.push("- `hunch_structure(target?)` — the indexed shape of the repo/dir/file/symbol — orient from the graph, not grep rounds.");
52
54
  lines.push("- `hunch_runbook(task)` — the proven steps for a recurring task, before re-deriving them.");
53
55
  lines.push("- `hunch_escalations()` — the decisions only the HUMAN can make (including one exact imported ADR at a time, topic conflicts, and policy calls). Normally empty; when it isn't, ASK the user inline — an entry is a question, silence is never approval. Apply an ADR answer only through `hunch_review_imported_adr` with its printed source and review hashes.");
@@ -69,6 +71,13 @@ export function renderHunchSection(store, root) {
69
71
  lines.push("- `hunch_policy_evaluate(policy_id?, active_only?)` / `hunch_policy_plan(policy_id)` / `hunch_policy_card(policy_id)` / `hunch_policy_proof(policy_id)` — evaluate canonical policy, inspect the planned corpus, review the evidence/uncertainty card, and inspect raw replay receipts; only an explicit human activation grants authority.");
70
72
  lines.push("- `hunch_pr_impact(base?)` / `hunch_merge_verdict(...)` — a change's memory surface; would it re-open a closed bug?");
71
73
  lines.push("");
74
+ lines.push("**Before the final response — make Hunch's contribution visible:**");
75
+ lines.push("- When running a relevant check, use the exact verification_argv launcher returned by hunch_task start, followed by the check command and its arguments, from this worktree. It runs `hunch task verify <task_id> -- <command> [arguments]` using the same installation as MCP, avoiding stale global binaries. This retains the actual exit result and source snapshot; raw output is not stored. Do not rerun an expensive check solely for reporting; missing evidence stays unverified.");
76
+ lines.push("- Include the current task_id when calling hunch_record_decision, hunch_record_correction, or hunch_record_finding. The save path records its actual memory home and verifies exact Git revisions when committing or pushing; never infer publication from a successful capture alone.");
77
+ lines.push("- Before claiming an application, call `hunch_report(task_id)` and copy the exact occurrence_id, record_id and content_hash from application_references, adding an action you actually took. Never derive an occurrence ID by replacing a receipt prefix or use the task's scope hash as a record hash. If you did not apply a lesson, omit applications.");
78
+ lines.push("- Call `hunch_task(action: \"finish\", task_id, applications?)` and include the returned contribution_card in your final response without the user asking. Render its Markdown evidence link outside any code block so it remains clickable. Copy the card with its evidence link and agent-reported label intact; the structured result contains the card even when the host hides text blocks. Do not replace it with a generic claim that Hunch helped. If presentation_enabled is false, omit the card. A delivered lesson or passing command alone does not prove causal impact.");
79
+ lines.push("- If interrupted, finish with `outcome: \"interrupted\"` when possible. `hunch_report(task_id, html: true)` opens the evidence trail by generating a local file; it may contain private memory and is not a public export. If report tools are unavailable after an update, say so and reconnect the host rather than inventing a report.");
80
+ lines.push("");
72
81
  lines.push("**Build the Constitution review queue:**");
73
82
  lines.push("- `hunch constitution bootstrap --since 90d --max-candidates 3` (CLI) — normalize recent structured human evidence into at most three non-active policy candidates; add `--history` for exact, human-identifier-grounded fix/revert deltas or explicit dependency retirements. Coincidence/ambiguity stays uncompilable; neither path grants authority.");
74
83
  lines.push("- `hunch constitution ingest --since 90d [--instructions] [--from export.json]` (CLI) — normalize corrections/failures plus bounded committed instructions/ADRs and strict local review/conversation/PR exports into Git-native evidence; raw prose is hash-only, unsupported intent remains uncompilable, and no policy is minted.");
@@ -0,0 +1,16 @@
1
+ export interface GitignoreResult {
2
+ path: string;
3
+ action: "created" | "appended" | "unchanged";
4
+ }
5
+ /** Defense in depth for integration files written automatically after a clone.
6
+ * Refuse symlinks, directories/devices, and hard links; require the canonical
7
+ * target to be the expected top-level file inside the canonical repository root. */
8
+ export declare function assertSafeTopLevelConfigFile(root: string, name: string): string;
9
+ export declare function ensureGitignore(root: string): GitignoreResult;
10
+ /** Ignore the engineering-memory tree so a private-migrated repo stays code-only.
11
+ * The kind subdirs the user's records live in (decisions/, bugs/, …) move to the
12
+ * private overlay; this stops git from re-publishing them. The `.hunch/` dir, its
13
+ * manifest, and the local.json pointer are left tracked/managed elsewhere. */
14
+ export declare function ignoreHunchMemory(root: string): GitignoreResult;
15
+ /** The .hunch memory subdirs un-published by a private migration (git pathspecs). */
16
+ export declare const HUNCH_MEMORY_DIRS: string[];
@@ -26,6 +26,13 @@ const ENTRIES = [
26
26
  ".hunch/local.json",
27
27
  // The strict hook's append-only catch-log: clone-local, never a memory record.
28
28
  ".hunch/events.log",
29
+ // The post-merge hook's queue of detected-but-unconfirmed commit-provenance
30
+ // repairs — clone-local scratch state, never committed (`hunch repair-provenance
31
+ // --apply` confirms and clears it).
32
+ ".hunch/pending-commit-repairs.json",
33
+ // Tombstones for commit-repair matches a human rejected via `--drop` — clone-local
34
+ // scratch state, same discipline as the queue above.
35
+ ".hunch/dropped-commit-repairs.json",
29
36
  // A local PRIVATE overlay store (HUNCH_PRIVATE_DIR) for sensitive memory — never
30
37
  // committed. This is the conventional in-repo path; point the env elsewhere for a
31
38
  // fully separate private repo.
@@ -0,0 +1,74 @@
1
+ export declare const CAPABILITIES: readonly ["mcp", "context", "edit-blocking", "failure-capture", "compaction"];
2
+ export type Capability = typeof CAPABILITIES[number];
3
+ export type HealthStatus = "verified" | "advisory-only" | "unsupported" | "untested";
4
+ export declare const HARNESSES: {
5
+ readonly claude: {
6
+ readonly mcp: ".mcp.json";
7
+ readonly hooks: ".claude/settings.json";
8
+ readonly key: "mcpServers";
9
+ readonly events: readonly ["SessionStart", "PreToolUse", "PostToolUseFailure", "PreCompact"];
10
+ };
11
+ readonly codex: {
12
+ readonly mcp: ".codex/config.toml";
13
+ readonly hooks: "";
14
+ readonly key: "";
15
+ readonly events: readonly [];
16
+ };
17
+ readonly cursor: {
18
+ readonly mcp: ".cursor/mcp.json";
19
+ readonly hooks: ".cursor/hooks.json";
20
+ readonly key: "mcpServers";
21
+ readonly events: readonly ["sessionStart", "preToolUse", "postToolUse", ""];
22
+ };
23
+ readonly vscode: {
24
+ readonly mcp: ".vscode/mcp.json";
25
+ readonly hooks: ".github/hooks/hunch.json";
26
+ readonly key: "servers";
27
+ readonly events: readonly ["SessionStart", "PreToolUse", "PostToolUse", ""];
28
+ };
29
+ readonly windsurf: {
30
+ readonly mcp: ".windsurf/mcp_config.json";
31
+ readonly hooks: ".windsurf/hooks.json";
32
+ readonly key: "mcpServers";
33
+ readonly events: readonly ["", "pre_write_code", "post_run_command", ""];
34
+ };
35
+ readonly antigravity: {
36
+ readonly mcp: ".agents/mcp_config.json";
37
+ readonly hooks: ".agents/hooks.json";
38
+ readonly key: "mcpServers";
39
+ readonly events: readonly ["PreInvocation", "PreToolUse", "", ""];
40
+ };
41
+ };
42
+ export type Harness = keyof typeof HARNESSES;
43
+ export interface HealthIssue {
44
+ file: string;
45
+ code: string;
46
+ detail: string;
47
+ }
48
+ export interface HarnessHealth {
49
+ harness: Harness;
50
+ capabilities: Record<Capability, {
51
+ status: HealthStatus;
52
+ detail: string;
53
+ }>;
54
+ }
55
+ export interface IntegrationHealth {
56
+ schema: "hunch.integration-health/1";
57
+ expectedVersion: string;
58
+ scope: "repository-config";
59
+ issues: HealthIssue[];
60
+ harnesses: HarnessHealth[];
61
+ }
62
+ export declare function readLauncher(root: string, harness: Harness): {
63
+ command: string;
64
+ args: string[];
65
+ customEnvironment: boolean;
66
+ };
67
+ export declare function inspectIntegrations(root: string, selected?: Harness): IntegrationHealth;
68
+ /** Repair only exact published pins. Preserve formatting and all other values.
69
+ * Preflight every affected file before writing any; reject malformed JSON/TOML. */
70
+ export declare function repairIntegrationPins(root: string): string[];
71
+ export declare function integrationHealthFails(report: IntegrationHealth, required?: readonly Capability[]): boolean;
72
+ export declare function formatIntegrationHealth(report: IntegrationHealth): string;
73
+ /** Bounded session warning; diagnostics must never break hook execution. */
74
+ export declare function integrationSessionWarning(root: string, harness: Harness): string;
@@ -0,0 +1,41 @@
1
+ export interface HookInstall {
2
+ path: string;
3
+ action: "created" | "appended" | "updated" | "unchanged";
4
+ }
5
+ export declare function installPostCommitHook(root: string, invocation: string, opts?: {
6
+ private?: boolean;
7
+ commit?: boolean;
8
+ localOnly?: boolean;
9
+ }): HookInstall;
10
+ /** Install a pre-commit constraint guard (DESIGN §4 enforcement). Advisory by
11
+ * default (prints invariants in scope, never blocks); pass strict to fail the
12
+ * commit — but even strict only fails on a DIRECT, high-confidence, non-stale
13
+ * blocking invariant (see strictgate.ts), so it's safe on a shared repo.
14
+ * Preserves any existing pre-commit hook. */
15
+ export declare function installPreCommitHook(root: string, invocation: string, strict?: boolean): HookInstall;
16
+ /** Install a post-merge hook carrying TWO independently-managed blocks:
17
+ * re-sync the committed grounding docs when a merge brought memory in behind
18
+ * them (fnd_c402046ac7, HUNCH_SYNC-guarded, foreground — it rewrites five
19
+ * files and can never fail the merge), and opportunistically DETECT a
20
+ * decision's commit provenance going orphaned right after a squash-merged
21
+ * branch lands locally (including a fast-forward from `git pull`) — while
22
+ * the original commits are still fully intact and matchable — queuing the
23
+ * match for a human to confirm via `hunch repair-provenance --apply`
24
+ * (HUNCH_MERGE_SYNC-guarded, backgrounded; own env var since this hook makes
25
+ * no commit of its own and so can't reuse HUNCH_SYNC's re-trigger guard).
26
+ * Each block is keyed by its own marker pair (installManagedBlock), so
27
+ * re-running updates only its own block, preserves the other untouched, and
28
+ * a repo carrying only one half (an older install, or a hand-edited hook)
29
+ * gets the other appended rather than clobbered. */
30
+ export declare function installPostMergeHook(root: string, invocation: string): HookInstall;
31
+ /** Read-only diagnostic (used by `hunch doctor`): which of the three managed
32
+ * hooks are currently present. Never writes anything — a hook counts as
33
+ * installed if its managed marker is present, regardless of whether the
34
+ * invocation inside it happens to be stale. postMerge requires BOTH halves
35
+ * (grounding-refresh and repair-provenance) present — a repo carrying only
36
+ * one is a partial install, same as `installPostMergeHook` self-healing it. */
37
+ export declare function hookStatus(root: string): {
38
+ postCommit: boolean;
39
+ preCommit: boolean;
40
+ postMerge: boolean;
41
+ };
@@ -29,7 +29,16 @@ function block(invocation, opts = {}) {
29
29
  ENDMARK,
30
30
  ].join("\n");
31
31
  }
32
- export function installPostCommitHook(root, invocation, opts = {}) {
32
+ function escapeRe(s) {
33
+ return s.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
34
+ }
35
+ /** Shared idempotent create/append/update-in-place logic for every hunch git
36
+ * hook: write a fresh hook file, replace our own managed block in place if the
37
+ * invocation changed, or append after any pre-existing (non-hunch) hook body
38
+ * without clobbering it. Used by all three hook installers below — the three
39
+ * copies had already drifted (installPreCommitHook was missing the chmodSync
40
+ * on its "updated" path) before this was unified. */
41
+ function installManagedBlock(root, hookName, mark, end, blk) {
33
42
  const dir = hooksDir(root);
34
43
  // `git rev-parse --git-path hooks` returns a path relative to the repo in a
35
44
  // normal checkout, but an ABSOLUTE one inside a linked worktree (the shared
@@ -37,17 +46,15 @@ export function installPostCommitHook(root, invocation, opts = {}) {
37
46
  // a bare startsWith("/") misfired on Windows worktrees → a doubled junk path.
38
47
  const abs = isAbsolute(dir) ? dir : join(root, dir);
39
48
  mkdirSync(abs, { recursive: true });
40
- const hookPath = join(abs, "post-commit");
41
- const blk = block(invocation, opts);
49
+ const hookPath = join(abs, hookName);
42
50
  if (!existsSync(hookPath)) {
43
51
  writeFileSync(hookPath, `#!/bin/sh\n${blk}\n`);
44
52
  chmodSync(hookPath, 0o755);
45
53
  return { path: hookPath, action: "created" };
46
54
  }
47
55
  const cur = readFileSync(hookPath, "utf8");
48
- if (cur.includes(MARK)) {
49
- // replace our managed block (invocation may have changed)
50
- const updated = cur.replace(new RegExp(`${escapeRe(MARK)}[\\s\\S]*?${escapeRe(ENDMARK)}`), blk);
56
+ if (cur.includes(mark)) {
57
+ const updated = cur.replace(new RegExp(`${escapeRe(mark)}[\\s\\S]*?${escapeRe(end)}`), blk);
51
58
  if (updated === cur)
52
59
  return { path: hookPath, action: "unchanged" };
53
60
  writeFileSync(hookPath, updated);
@@ -59,8 +66,8 @@ export function installPostCommitHook(root, invocation, opts = {}) {
59
66
  chmodSync(hookPath, 0o755);
60
67
  return { path: hookPath, action: "appended" };
61
68
  }
62
- function escapeRe(s) {
63
- return s.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
69
+ export function installPostCommitHook(root, invocation, opts = {}) {
70
+ return installManagedBlock(root, "post-commit", MARK, ENDMARK, block(invocation, opts));
64
71
  }
65
72
  const PRE_MARK = "# >>> hunch pre-commit (constraint guard) >>>";
66
73
  const PRE_END = "# <<< hunch pre-commit <<<";
@@ -70,72 +77,91 @@ const PRE_END = "# <<< hunch pre-commit <<<";
70
77
  * blocking invariant (see strictgate.ts), so it's safe on a shared repo.
71
78
  * Preserves any existing pre-commit hook. */
72
79
  export function installPreCommitHook(root, invocation, strict = false) {
73
- const dir = hooksDir(root);
74
- // `git rev-parse --git-path hooks` returns a path relative to the repo in a
75
- // normal checkout, but an ABSOLUTE one inside a linked worktree (the shared
76
- // hooks dir). isAbsolute() handles both POSIX (/…) and Windows (C:\… / C:/…);
77
- // a bare startsWith("/") misfired on Windows worktrees → a doubled junk path.
78
- const abs = isAbsolute(dir) ? dir : join(root, dir);
79
- mkdirSync(abs, { recursive: true });
80
- const hookPath = join(abs, "pre-commit");
81
80
  const cmd = `${invocation} check --staged${strict ? " --strict" : ""}`;
82
81
  const blk = [PRE_MARK, strict ? cmd : `${cmd} || true`, PRE_END].join("\n");
83
- if (!existsSync(hookPath)) {
84
- writeFileSync(hookPath, `#!/bin/sh\n${blk}\n`);
85
- chmodSync(hookPath, 0o755);
86
- return { path: hookPath, action: "created" };
87
- }
88
- const cur = readFileSync(hookPath, "utf8");
89
- if (cur.includes(PRE_MARK)) {
90
- const updated = cur.replace(new RegExp(`${escapeRe(PRE_MARK)}[\\s\\S]*?${escapeRe(PRE_END)}`), blk);
91
- if (updated === cur)
92
- return { path: hookPath, action: "unchanged" };
93
- writeFileSync(hookPath, updated);
94
- return { path: hookPath, action: "updated" };
95
- }
96
- writeFileSync(hookPath, cur.endsWith("\n") ? `${cur}${blk}\n` : `${cur}\n${blk}\n`);
97
- chmodSync(hookPath, 0o755);
98
- return { path: hookPath, action: "appended" };
82
+ return installManagedBlock(root, "pre-commit", PRE_MARK, PRE_END, blk);
99
83
  }
100
- const MERGE_MARK = "# >>> hunch post-merge >>>";
101
- const MERGE_END = "# <<< hunch post-merge <<<";
102
- /** Install a post-merge hook that re-syncs the committed grounding docs when a merge
103
- * brought memory in behind them (fnd_c402046ac7). Two branches that each captured a
104
- * record regenerate the same "N+1" counts line; git merges identical lines silently
105
- * and the doc ends up one behind the store. The hook regenerates the existing docs
106
- * from the PUBLIC store right after a local merge/pull that touched .hunch/, so the
107
- * next commit carries them. Foreground (it rewrites five files), loop-guarded via
108
- * HUNCH_SYNC, and it can never fail the merge. Preserves any existing hook. */
109
- export function installPostMergeHook(root, invocation) {
110
- const dir = hooksDir(root);
111
- const abs = isAbsolute(dir) ? dir : join(root, dir);
112
- mkdirSync(abs, { recursive: true });
113
- const hookPath = join(abs, "post-merge");
114
- const blk = [
115
- MERGE_MARK,
84
+ // Original marker, kept byte-for-byte for backward compat: an existing install's
85
+ // grounding-refresh block must still be found and updated in place by its own
86
+ // exact marker text (fnd_c402046ac7).
87
+ const GROUNDING_MERGE_MARK = "# >>> hunch post-merge >>>";
88
+ const GROUNDING_MERGE_END = "# <<< hunch post-merge <<<";
89
+ // Distinct marker for the (newer) repair-provenance half, so the two blocks
90
+ // never collide inside the same post-merge hook file and each can be
91
+ // independently created/updated/removed without touching the other.
92
+ const REPAIR_MERGE_MARK = "# >>> hunch post-merge (repair-provenance) >>>";
93
+ const REPAIR_MERGE_END = "# <<< hunch post-merge (repair-provenance) <<<";
94
+ function groundingMergeBlock(invocation) {
95
+ return [
96
+ GROUNDING_MERGE_MARK,
116
97
  'if [ -z "$HUNCH_SYNC" ]; then',
117
98
  " if ! git diff --quiet ORIG_HEAD HEAD -- .hunch 2>/dev/null; then",
118
99
  ` ( HUNCH_SYNC=1 ${invocation} grounding --refresh 2>/dev/null || true )`,
119
100
  " fi",
120
101
  "fi",
121
- MERGE_END,
102
+ GROUNDING_MERGE_END,
122
103
  ].join("\n");
123
- if (!existsSync(hookPath)) {
124
- writeFileSync(hookPath, `#!/bin/sh\n${blk}\n`);
125
- chmodSync(hookPath, 0o755);
126
- return { path: hookPath, action: "created" };
127
- }
128
- const cur = readFileSync(hookPath, "utf8");
129
- if (cur.includes(MERGE_MARK)) {
130
- const updated = cur.replace(new RegExp(`${escapeRe(MERGE_MARK)}[\\s\\S]*?${escapeRe(MERGE_END)}`), blk);
131
- if (updated === cur)
132
- return { path: hookPath, action: "unchanged" };
133
- writeFileSync(hookPath, updated);
134
- chmodSync(hookPath, 0o755);
135
- return { path: hookPath, action: "updated" };
136
- }
137
- writeFileSync(hookPath, cur.endsWith("\n") ? `${cur}${blk}\n` : `${cur}\n${blk}\n`);
138
- chmodSync(hookPath, 0o755);
139
- return { path: hookPath, action: "appended" };
104
+ }
105
+ function repairProvenanceMergeBlock(invocation) {
106
+ return [
107
+ REPAIR_MERGE_MARK,
108
+ 'if [ -z "$HUNCH_MERGE_SYNC" ]; then',
109
+ " export HUNCH_MERGE_SYNC=1",
110
+ // No --apply: this only detects a squash-merge orphaning a decision's commit
111
+ // and queues the match (.hunch/pending-commit-repairs.json, local-only) for a
112
+ // human to confirm via `hunch repair-provenance --apply` — the match signal
113
+ // (file-set overlap, not git's own rename detection) isn't strong enough to
114
+ // trust an unattended, backgrounded write into shared team memory.
115
+ ` ( ${invocation} repair-provenance --from-hook --quiet >/dev/null 2>&1 || true ) &`,
116
+ "fi",
117
+ REPAIR_MERGE_END,
118
+ ].join("\n");
119
+ }
120
+ /** How significant a combined install result is, for picking one HookInstall
121
+ * action out of two independent sub-installs into the same file — "created"
122
+ * (the file itself is new) outranks "appended"/"updated" (an existing file
123
+ * changed), which outrank "unchanged". */
124
+ const ACTION_RANK = { created: 3, appended: 2, updated: 2, unchanged: 1 };
125
+ /** Install a post-merge hook carrying TWO independently-managed blocks:
126
+ * re-sync the committed grounding docs when a merge brought memory in behind
127
+ * them (fnd_c402046ac7, HUNCH_SYNC-guarded, foreground — it rewrites five
128
+ * files and can never fail the merge), and opportunistically DETECT a
129
+ * decision's commit provenance going orphaned right after a squash-merged
130
+ * branch lands locally (including a fast-forward from `git pull`) — while
131
+ * the original commits are still fully intact and matchable — queuing the
132
+ * match for a human to confirm via `hunch repair-provenance --apply`
133
+ * (HUNCH_MERGE_SYNC-guarded, backgrounded; own env var since this hook makes
134
+ * no commit of its own and so can't reuse HUNCH_SYNC's re-trigger guard).
135
+ * Each block is keyed by its own marker pair (installManagedBlock), so
136
+ * re-running updates only its own block, preserves the other untouched, and
137
+ * a repo carrying only one half (an older install, or a hand-edited hook)
138
+ * gets the other appended rather than clobbered. */
139
+ export function installPostMergeHook(root, invocation) {
140
+ const grounding = installManagedBlock(root, "post-merge", GROUNDING_MERGE_MARK, GROUNDING_MERGE_END, groundingMergeBlock(invocation));
141
+ const repair = installManagedBlock(root, "post-merge", REPAIR_MERGE_MARK, REPAIR_MERGE_END, repairProvenanceMergeBlock(invocation));
142
+ return ACTION_RANK[repair.action] >= ACTION_RANK[grounding.action] ? repair : grounding;
143
+ }
144
+ /** Read-only diagnostic (used by `hunch doctor`): which of the three managed
145
+ * hooks are currently present. Never writes anything — a hook counts as
146
+ * installed if its managed marker is present, regardless of whether the
147
+ * invocation inside it happens to be stale. postMerge requires BOTH halves
148
+ * (grounding-refresh and repair-provenance) present — a repo carrying only
149
+ * one is a partial install, same as `installPostMergeHook` self-healing it. */
150
+ export function hookStatus(root) {
151
+ const dir = hooksDir(root);
152
+ const abs = isAbsolute(dir) ? dir : join(root, dir);
153
+ const has = (name, mark) => {
154
+ try {
155
+ return readFileSync(join(abs, name), "utf8").includes(mark);
156
+ }
157
+ catch {
158
+ return false;
159
+ }
160
+ };
161
+ return {
162
+ postCommit: has("post-commit", MARK),
163
+ preCommit: has("pre-commit", PRE_MARK),
164
+ postMerge: has("post-merge", GROUNDING_MERGE_MARK) && has("post-merge", REPAIR_MERGE_MARK),
165
+ };
140
166
  }
141
167
  //# sourceMappingURL=hooks.js.map
@@ -0,0 +1,39 @@
1
+ /**
2
+ * MADR export (the MADR bridge, export half — roadmap's "compiler" projection).
3
+ *
4
+ * Renders the PUBLIC decision graph as a regenerated MADR 3.x corpus: one
5
+ * NNNN-slug.md per decision, superseded links derived from superseded_by /
6
+ * valid_to, rejected alternatives under Considered Options. The projection is a
7
+ * DISPOSABLE build artifact — the graph stays the source of truth, numbers are
8
+ * assigned per export (date order), and every emitted file carries a generated
9
+ * marker. The writer only ever overwrites files bearing that marker: a
10
+ * hand-written ADR corpus in the target directory is untouchable (refused with
11
+ * a warning), so `hunch import-adr` + `hunch export-adr` can share a repo
12
+ * without the export eating its own import source.
13
+ *
14
+ * Privacy boundary: rendering takes an explicit PUBLIC-only decision list —
15
+ * callers must pass store.json.loadAll("decisions"), never the overlay union.
16
+ */
17
+ import type { Decision } from "../core/types.js";
18
+ export declare const MADR_EXPORT_MARKER = "<!-- hunch:generated madr-export \u2014 regenerated by `hunch export-adr`; edits will be overwritten -->";
19
+ export interface MadrFile {
20
+ /** file name only, NNNN-slug.md */
21
+ name: string;
22
+ text: string;
23
+ decisionId: string;
24
+ }
25
+ export interface MadrExportResult {
26
+ files: MadrFile[];
27
+ /** the one-line Backstage catalog-info annotation for this corpus */
28
+ backstageAnnotation: string;
29
+ }
30
+ /** Render one decision as a MADR 3.x document. Deterministic: content depends
31
+ * only on the decision record and the corpus's name map. */
32
+ export declare function renderMadr(d: Decision, nameOf: Map<string, string>): string;
33
+ /** Project a PUBLIC decision list into a MADR corpus. Numbering is date order
34
+ * (valid_from, then date, then id for determinism) and assigned per export —
35
+ * the projection is disposable, links inside it are internally consistent. */
36
+ export declare function exportMadrCorpus(decisions: readonly Decision[], dirForAnnotation: string): MadrExportResult;
37
+ /** True when an existing file may be overwritten by the export: only our own
38
+ * generated output ever qualifies. */
39
+ export declare function isRegenerableMadr(existingText: string): boolean;