@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,13 @@
1
+ /** Canonical committed identity for the deterministic static graph.
2
+ *
3
+ * Public Hunch pumping creates clone-local commits containing `.hunch/` JSON and
4
+ * sometimes refreshed grounding docs. Those commits do not change anything the
5
+ * indexer parses, so binding a shared static receipt to their local SHA makes the
6
+ * same graph produce different artifact ids in otherwise identical clones. Walk
7
+ * first-parent until the newest indexed-code or checkout-attribute change instead.
8
+ * Attributes are a boundary because proof replay validates them before materializing
9
+ * exact source bytes. A merge is always a boundary: its resolution can change the
10
+ * effective tree even when a simple diff listing is incomplete. Reverts touch code
11
+ * and therefore remain distinct. */
12
+ export declare function canonicalStaticGraphBaseline(root: string, ref?: string): string;
13
+ export declare function isAncestorOrSame(root: string, ancestor: string, descendant: string): boolean;
@@ -0,0 +1,64 @@
1
+ import type { Decision } from "../core/types.js";
2
+ import type { HunchStore } from "../store/hunchStore.js";
3
+ import { type BootstrapOptions, type BootstrapReport } from "./bootstrap.js";
4
+ import type { PolicyRepository } from "./repository.js";
5
+ import { type PolicyAssertion, type PolicySpec, type StructuralDelta } from "./schema.js";
6
+ export interface StructuralCandidate {
7
+ id: string;
8
+ assertion: PolicyAssertion;
9
+ scope: PolicySpec["scope"];
10
+ basis: "added-call" | "removed-call" | "added-symbol" | "removed-import" | "added-relative-import" | "removed-relative-import";
11
+ reason: string;
12
+ }
13
+ export interface StructuralInspection {
14
+ decision: string;
15
+ commit: string;
16
+ kind: "revert" | "bug_fix" | "decision";
17
+ delta: StructuralDelta;
18
+ candidates: StructuralCandidate[];
19
+ unsupported: string[];
20
+ private: boolean;
21
+ }
22
+ interface Enumerated {
23
+ candidates: StructuralCandidate[];
24
+ unsupported: string[];
25
+ }
26
+ export interface ExternalImportBoundaryCandidate {
27
+ id: string;
28
+ assertion: PolicyAssertion;
29
+ scope: PolicySpec["scope"];
30
+ basis: "correction-forbidden-import";
31
+ reason: string;
32
+ }
33
+ export type ExternalImportBoundaryInspection = {
34
+ candidate: ExternalImportBoundaryCandidate;
35
+ reason: null;
36
+ } | {
37
+ candidate: null;
38
+ reason: string;
39
+ code: "unsupported_dependency" | "unsupported_file" | "missing_anchor" | "baseline_violated";
40
+ };
41
+ /** Bind one exact file/package boundary to the existing static import graph.
42
+ * The file-level fact is anchored to the first stable symbol because the
43
+ * indexer attaches every external import in a file to every symbol in it. */
44
+ export declare function inspectExternalImportBoundary(store: HunchStore, file: string, specifier: string, opts?: {
45
+ publicOnly?: boolean;
46
+ }): ExternalImportBoundaryInspection;
47
+ export declare function structuralKey(policy: Pick<PolicySpec, "assertion" | "scope" | "data_class">): string;
48
+ export declare function directConflict(candidate: PolicySpec, incumbent: PolicySpec): boolean;
49
+ /** Enumerate every supported assertion that the current graph can bind exactly.
50
+ * Ambiguous or missing bindings are reported; they are never guessed through. */
51
+ export declare function enumerateStructuralCandidates(store: HunchStore, delta: StructuralDelta, opts?: {
52
+ publicOnly?: boolean;
53
+ judgment?: Decision;
54
+ retiredDependenciesOnly?: boolean;
55
+ }): Enumerated;
56
+ export declare function inspectStructuralDecision(store: HunchStore, root: string, decisionId: string, opts?: {
57
+ publicOnly?: boolean;
58
+ privateOnly?: boolean;
59
+ }): StructuralInspection;
60
+ /** Phase-2B opt-in bootstrap: only human-confirmed fix/revert or explicit
61
+ * dependency-retirement decisions, exact
62
+ * first-parent deltas, and exactly one bindable candidate may compile. */
63
+ export declare function bootstrapStructuralPolicies(store: HunchStore, root: string, repository: PolicyRepository, opts?: BootstrapOptions): BootstrapReport;
64
+ export {};
@@ -0,0 +1,63 @@
1
+ /**
2
+ * Provider hook dialects → Hunch's one internal event shape.
3
+ *
4
+ * Hook payloads are an integration boundary: every provider is free to rename
5
+ * fields or tools. Keep that variability here so the policy engine receives
6
+ * the same small, fail-open shape regardless of the assistant that emitted it.
7
+ */
8
+ export declare const HOOK_PROVIDERS: readonly ["claude", "vscode", "windsurf", "antigravity", "cursor"];
9
+ export type HookProvider = (typeof HOOK_PROVIDERS)[number];
10
+ export type HunchHookEvent = "PreToolUse" | "PostToolUse" | "PostToolUseFailure" | "UserPromptSubmit" | "SessionStart" | "SubagentStart" | "PreCompact" | "Stop";
11
+ export interface HunchToolInput {
12
+ file_path?: string;
13
+ new_string?: string;
14
+ content?: string;
15
+ edits?: Array<{
16
+ new_string?: string;
17
+ }>;
18
+ command?: string;
19
+ skill?: string;
20
+ }
21
+ /** A provider-neutral observation of the tool result. Output is ephemeral: the
22
+ * pipeline compares it with bounded expectations but never persists it. */
23
+ export interface HunchToolOutcome {
24
+ status: "success" | "failure" | "unknown";
25
+ output: string;
26
+ }
27
+ export interface HunchHookInput {
28
+ hook_event_name: HunchHookEvent;
29
+ session_id?: string;
30
+ /** Native Claude correlation; never synthesize these from prompt text/time. */
31
+ prompt_id?: string;
32
+ cwd?: string;
33
+ agent_id?: string;
34
+ tool_name?: string;
35
+ tool_input?: HunchToolInput;
36
+ tool_outcome?: HunchToolOutcome;
37
+ prompt?: string;
38
+ /** SessionStart origin ("startup" | "resume" | "clear" | "compact") — compaction
39
+ * summarizes away injected grounding, so "compact" resets injection dedup. */
40
+ source?: string;
41
+ /** SubagentStart: the delegated agent's type (e.g. "Explore", "Plan"). */
42
+ agent_type?: string;
43
+ }
44
+ /** Parse a provider name supplied by a hook config. Unknown values intentionally
45
+ * return null so a bad config cannot make an edit fail. */
46
+ export declare function hookProvider(value: unknown): HookProvider | null;
47
+ /** Normalize a hook stdin payload. Unknown/malformed events return null and the
48
+ * CLI exits successfully without output — the Never Block on Hook Failure rule. */
49
+ export declare function normalizeHookEvent(raw: unknown, provider: HookProvider): HunchHookInput | null;
50
+ /** Provider-aware hook output. Context output is intentionally omitted for
51
+ * Windsurf because its documented hook protocol has no agent-context channel;
52
+ * its always-on project rule + MCP server remain the grounding delivery path. */
53
+ export declare function contextHookOutput(provider: HookProvider, event: HunchHookEvent, text: string): object | null;
54
+ /** Strict-deny response in each native dialect. Windsurf uses documented exit
55
+ * code 2; the caller writes this error to stderr and preserves exit success for
56
+ * every accidental/malformed invocation. */
57
+ export declare function denyHookOutput(provider: HookProvider, reason: string): {
58
+ output: object | null;
59
+ exitCode?: number;
60
+ stderr?: string;
61
+ };
62
+ /** Stop-gate output in each native dialect. */
63
+ export declare function stopHookOutput(provider: HookProvider, reason: string): object;
@@ -194,6 +194,7 @@ export function normalizeHookEvent(raw, provider) {
194
194
  return {
195
195
  hook_event_name: event,
196
196
  session_id: stringAt(input, "session_id", "sessionId", "conversation_id", "conversationId"),
197
+ ...(provider === "claude" ? Object.fromEntries(["prompt_id", "cwd", "agent_id"].filter(key => input[key] !== undefined).map(key => [key, typeof input[key] === "string" ? input[key] : ""])) : {}),
197
198
  tool_name: hunchToolName(stringAt(input, "tool_name", "toolName"), toolInput ?? {}),
198
199
  tool_input: toolInput,
199
200
  ...(toolOutcome ? { tool_outcome: toolOutcome } : {}),
@@ -0,0 +1,60 @@
1
+ import type { Constraint } from "./types.js";
2
+ export interface AutomaticReviewEntry {
3
+ candidate_id: string;
4
+ evidence_hash: string;
5
+ file: string;
6
+ status: "ready" | "saved" | "skipped" | "review" | "deferred";
7
+ reason: string;
8
+ code_hash?: string;
9
+ rule_id?: string;
10
+ cached?: boolean;
11
+ retryable?: boolean;
12
+ }
13
+ /** Bounded sequential analysis with a separate skeptical pass. The caller owns I/O and persistence. */
14
+ export declare function automateReviewMemory(options: {
15
+ packet: unknown;
16
+ existing: Constraint[];
17
+ readCurrent: (file: string) => string;
18
+ generate: (prompt: string) => Promise<string>;
19
+ provider: string;
20
+ limit: number;
21
+ now: string;
22
+ progress?: (candidate: string, position: number) => void;
23
+ previous?: AutomaticReviewEntry[];
24
+ providersUsed?: () => string[];
25
+ }): Promise<{
26
+ schema: "hunch.automatic-review-memory/1";
27
+ repository: string;
28
+ provider: string;
29
+ providers_used: string[];
30
+ authority: "advisory";
31
+ applied: boolean;
32
+ analyzed: number;
33
+ entries: AutomaticReviewEntry[];
34
+ rules: {
35
+ id: string;
36
+ type: "security" | "performance" | "correctness" | "architecture" | "compliance";
37
+ statement: string;
38
+ scope: string[];
39
+ severity: "advisory" | "warning" | "blocking";
40
+ enforcement: "ci" | "advisory_v1" | "manual";
41
+ match: string | null;
42
+ forbids: {
43
+ deps: string[];
44
+ symbols: string[];
45
+ patterns: string[];
46
+ } | null;
47
+ rationale: string;
48
+ source_decision: string | null;
49
+ violations: string[];
50
+ status: "active" | "retired";
51
+ valid_to: string | null;
52
+ provenance: {
53
+ source: string;
54
+ confidence: number;
55
+ evidence: string[];
56
+ last_verified?: string | undefined;
57
+ };
58
+ valid_from?: string | undefined;
59
+ }[];
60
+ }>;
@@ -0,0 +1,66 @@
1
+ /**
2
+ * Auto-review planner — the pure decision core behind `hunch auto-review`.
3
+ *
4
+ * It takes the draft set + a per-draft harness RelevanceVerdict (the "delegate to
5
+ * the harness" judgment) and folds in the two DETERMINISTIC signals the store
6
+ * already produces — near-duplicate detection (dupdetect) and grounding/Critic
7
+ * verification (reviewqueue) — into four disjoint buckets:
8
+ *
9
+ * accept — confirm to human_confirmed. GATED: only Critic-verified,
10
+ * well-grounded drafts the harness judged relevant + non-dup.
11
+ * This is the dec_a466655539 line — a machine never confirms an
12
+ * UN-audited draft; the harness verdict can only ADD a veto, not
13
+ * substitute for the Critic's grounding.
14
+ * rejectDuplicate — reject: a near-duplicate of an accepted record (deterministic
15
+ * dupdetect, OR the harness named an existing decision it restates).
16
+ * rejectIrrelevant— reject: the harness judged it not worth keeping, confidently.
17
+ * keep — everything else stays for a human (the safe default).
18
+ *
19
+ * No I/O, no LLM calls here — verdicts are passed in — so it's fully unit-testable
20
+ * and deterministic given its inputs.
21
+ */
22
+ import type { Decision } from "./types.js";
23
+ import type { RelevanceVerdict } from "../synthesis/provider.js";
24
+ export type AutoReviewAction = "accept" | "rejectDuplicate" | "rejectIrrelevant" | "keep";
25
+ export interface AutoReviewEntry {
26
+ d: Decision;
27
+ action: AutoReviewAction;
28
+ /** human-readable justification for the action (shown in the plan). */
29
+ reason: string;
30
+ /** the harness verdict for this draft, when one was obtained. */
31
+ verdict?: RelevanceVerdict;
32
+ /** grounded-ness parsed from synth telemetry, when present. */
33
+ grounded?: number;
34
+ }
35
+ export interface AutoReviewPlan {
36
+ accept: AutoReviewEntry[];
37
+ rejectDuplicate: AutoReviewEntry[];
38
+ rejectIrrelevant: AutoReviewEntry[];
39
+ keep: AutoReviewEntry[];
40
+ }
41
+ export interface AutoReviewConfig {
42
+ /** grounded-ness threshold for the accept gate. */
43
+ minGrounded?: number;
44
+ /** minimum harness confidence to act on an "irrelevant" verdict (lifecycle rejection).
45
+ * Below this the draft is kept for a human — we never reject on a shaky judgment. */
46
+ minRejectConfidence?: number;
47
+ }
48
+ /** Build the plan. `verdicts` maps draft id → harness verdict (absent → the draft
49
+ * was not judged, e.g. no CLI available; the pure plan can still flag a
50
+ * deterministic duplicate or keep it). The CLI refuses `--apply` on an incomplete
51
+ * requested harness batch; explicit `--no-llm` triage intentionally has no batch. */
52
+ export declare function planAutoReview(drafts: Decision[], allDecisions: Decision[], verdicts: Map<string, RelevanceVerdict>, cfg?: AutoReviewConfig): AutoReviewPlan;
53
+ /** Total drafts the plan would mutate (accept + both rejection buckets). */
54
+ export declare function planMutations(plan: AutoReviewPlan): number;
55
+ /** A caller's EXPLICIT accept/delete choice (from the Review Console's per-card
56
+ * override), resolved against the live draft set. Ids that aren't a current draft
57
+ * — already accepted/superseded, or plain unknown — land in `unknown` so an
58
+ * apply-by-id can never mutate a non-draft record by a stale id (the console holds
59
+ * a snapshot; the store may have moved). An id claimed by BOTH lists resolves to
60
+ * accept (the safer, non-destructive verb) and is reported as unknown for delete. */
61
+ export interface ResolvedSelection {
62
+ accept: Decision[];
63
+ delete: Decision[];
64
+ unknown: string[];
65
+ }
66
+ export declare function resolveSelection(drafts: Decision[], acceptIds: string[], deleteIds: string[]): ResolvedSelection;
@@ -0,0 +1,3 @@
1
+ /** Locale-free UTF-16 code-unit ordering for content-addressed artifacts.
2
+ * Never use localeCompare where ordering contributes to a hash or selection. */
3
+ export declare function compareCodeUnits(left: string, right: string): number;
@@ -0,0 +1,19 @@
1
+ /**
2
+ * Capture-session tokens (decision-grounding, DESIGN §5 Stage 1 / §9.3).
3
+ *
4
+ * hunch_capture_decision issues a short-lived token; the commit path consumes it, so a
5
+ * decision written through the capture front door is provably the tail of an interview
6
+ * — the identity-principle guard against a silent, un-interviewed write. In-memory (the
7
+ * MCP server is long-lived); tokens are one-time-use and expire so an abandoned
8
+ * interview can't leak. Absence of a token never BLOCKS a write yet (staged
9
+ * deprecation §9.3) — the caller decides how to treat an un-gated write.
10
+ */
11
+ declare const CAPTURE_TOKEN_TTL_MS: number;
12
+ /** Issue a token stamped `now` (epoch ms). Prunes expired tokens first so the map can't
13
+ * grow unbounded across a long server life. `mint` supplies the random id (injectable
14
+ * for tests); the call site passes crypto.randomUUID. */
15
+ export declare function issueCaptureToken(mint: () => string, now: number): string;
16
+ /** Consume a token iff it is a live, unexpired capture session. One-time use: a second
17
+ * consume of the same token returns false. */
18
+ export declare function consumeCaptureToken(token: string | undefined, now: number): boolean;
19
+ export { CAPTURE_TOKEN_TTL_MS };
@@ -0,0 +1,22 @@
1
+ export declare const CHANGE_IDENTITY_SCHEMA_VERSION: "hunch.change-identity/1";
2
+ export declare const CHANGE_IDENTITY_ALGORITHM: "git-raw-tree-delta-sha256/1";
3
+ export interface ChangeIdentity {
4
+ schema: typeof CHANGE_IDENTITY_SCHEMA_VERSION;
5
+ algorithm: typeof CHANGE_IDENTITY_ALGORITHM;
6
+ change_id: string;
7
+ base_revision: string;
8
+ head_revision: string;
9
+ base_tree: string;
10
+ head_tree: string;
11
+ delta_hash: string;
12
+ /** Git's looser interoperability identifier; never the Hunch authority. */
13
+ patch_id: string | null;
14
+ file_count: number;
15
+ paths_hash: string;
16
+ content_hash: string;
17
+ }
18
+ /** Derive one deterministic receipt from two commit-ish references. */
19
+ export declare function deriveChangeIdentity(root: string, baseRef: string, headRef?: string): ChangeIdentity;
20
+ /** Validate an identity without trusting any caller-supplied seal. */
21
+ export declare function assertChangeIdentity(value: unknown): asserts value is ChangeIdentity;
22
+ export declare function changesAreEquivalent(left: unknown, right: unknown): boolean;
@@ -0,0 +1,14 @@
1
+ import type { HunchStore } from "../store/hunchStore.js";
2
+ import { type ChangeProof } from "./changeProofContract.js";
3
+ export interface DeriveChangeProofOptions {
4
+ /** Exclude a configured private overlay from the artifact. Required before publication. */
5
+ publicOnly?: boolean;
6
+ }
7
+ /**
8
+ * Produce Hunch's standalone semantic proof for one exact committed tree transition.
9
+ *
10
+ * The proof is read-only and timestamp-free. It binds source bytes, parsed graph,
11
+ * current memory records and every explicit gap; it never grants execution, CI,
12
+ * deployment, merge, ranking, promotion or policy authority.
13
+ */
14
+ export declare function deriveChangeProof(root: string, store: HunchStore, baseRef: string, resultRef?: string, options?: DeriveChangeProofOptions): ChangeProof;
@@ -0,0 +1,140 @@
1
+ /** Rendering for `hunch check` — pure, so the terminal output and the CI/PR
2
+ * comment share one source of truth and stay unit-testable. The CLI builds a
3
+ * CheckReport from the store (direct/near/regression + the hardened strict gate),
4
+ * then renders it as text (terminal, unchanged) or markdown (a PR comment posted
5
+ * by the GitHub Action). The exit-code decision lives with the caller. */
6
+ /** The causal chain behind an invariant — the WHY a diff-only reviewer can't see:
7
+ * the decision that motivated the guard, and the bug whose root cause spawned it.
8
+ * Resolved deterministically from the graph (constraint→source_decision→bug). */
9
+ export interface CausalWhy {
10
+ constraint_id: string;
11
+ decision?: {
12
+ id: string;
13
+ title: string;
14
+ decision: string;
15
+ };
16
+ bug?: {
17
+ id: string;
18
+ title: string;
19
+ root_cause: string;
20
+ };
21
+ }
22
+ export interface CheckDirect {
23
+ id: string;
24
+ severity: string;
25
+ statement: string;
26
+ rationale: string;
27
+ files: string[];
28
+ /** Would this invariant FAIL the commit under --strict (direct + high-confidence + non-stale)? */
29
+ strictBlocks: boolean;
30
+ /** If a blocking invariant is downgraded to advisory under strict, why. */
31
+ downgrade?: "stale" | "low-confidence";
32
+ /** The causal citation (the "why this guard exists") — present when the graph links it. */
33
+ why?: CausalWhy;
34
+ }
35
+ export interface CheckNear {
36
+ id: string;
37
+ severity: string;
38
+ statement: string;
39
+ via: string[];
40
+ }
41
+ export interface CheckRegression {
42
+ kind: string;
43
+ name: string;
44
+ decision: string;
45
+ title: string;
46
+ reason: string;
47
+ blocking: boolean;
48
+ }
49
+ /** A diff re-introducing an approach an in-force decision deliberately REJECTED. */
50
+ export interface CheckVeto {
51
+ decision: string;
52
+ title: string;
53
+ alternative: string;
54
+ chosen: string;
55
+ tier: string;
56
+ evidence: string[];
57
+ blocking: boolean;
58
+ }
59
+ /** A diff that ADDS a symbol already defined elsewhere in the graph — likely a
60
+ * re-implementation the local-context-window agent couldn't see. Advisory only. */
61
+ export interface CheckRedundant {
62
+ name: string;
63
+ kind: string;
64
+ existingFile: string;
65
+ }
66
+ export interface CheckReport {
67
+ fileCount: number;
68
+ strict: boolean;
69
+ direct: CheckDirect[];
70
+ near: CheckNear[];
71
+ regressions: CheckRegression[];
72
+ vetoes: CheckVeto[];
73
+ /** Advisory: symbols this diff adds that already exist elsewhere (possible sprawl). Never blocks. */
74
+ redundant: CheckRedundant[];
75
+ /** Count of direct invariants that pass the hardened strict gate. */
76
+ strictBlockers: number;
77
+ /** Count of blocking-linked regressions. */
78
+ regBlocking: number;
79
+ /** Count of vetoes that pass the veto gate (human-confirmed, in-force, non-stale). */
80
+ vetoBlocking: number;
81
+ }
82
+ export declare function reportIsClean(r: CheckReport): boolean;
83
+ /** PR impact (read-only, advisory): the dependency + memory surface of a change.
84
+ * Composes the SAME primitives as CheckReport so impact and gating never disagree. */
85
+ export interface ImpactReport {
86
+ files: string[];
87
+ /** Files whose code (in)directly depends on the changed files — nearest depth wins. */
88
+ blast: Array<{
89
+ file: string;
90
+ via: string;
91
+ depth: number;
92
+ }>;
93
+ report: CheckReport;
94
+ /** In-force decisions concerning the touched files. */
95
+ decisions: Array<{
96
+ id: string;
97
+ title: string;
98
+ status: string;
99
+ }>;
100
+ }
101
+ /** Terminal/markdown-lite rendering of an ImpactReport (hunch impact / hunch_pr_impact). */
102
+ export declare function renderImpact(im: ImpactReport, scope: string): string;
103
+ /** True when --strict should FAIL the commit/PR. */
104
+ export declare function reportFailsStrict(r: CheckReport): boolean;
105
+ /** The deterministic VERDICT for a merge: block (a hard gate fired), warn (touches
106
+ * memory but nothing hard-blocks), or pass (touches no recorded memory at all). */
107
+ export declare function verdict(r: CheckReport): "block" | "warn" | "pass";
108
+ export declare function renderText(r: CheckReport): string;
109
+ export declare function renderMarkdown(r: CheckReport): string;
110
+ /** Conformance/policy/scan findings arrive from the CLI already flattened, so this
111
+ * renderer stays pure and every output channel shares one gate semantics. */
112
+ export interface SarifExtras {
113
+ conformance?: Array<{
114
+ decision: string;
115
+ title: string;
116
+ detail: string;
117
+ why?: string;
118
+ bug?: string;
119
+ }>;
120
+ policies?: Array<{
121
+ id: string;
122
+ result: string;
123
+ explanation: string;
124
+ blocks: boolean;
125
+ receipt: string;
126
+ gateError?: string;
127
+ }>;
128
+ scanIssues?: Array<{
129
+ path: string;
130
+ detail: string;
131
+ code: string;
132
+ }>;
133
+ }
134
+ /** Render the full check verdict as a SARIF 2.1.0 document. Level mapping is
135
+ * severity-truth, independent of the exit code: `error` = would fail --strict
136
+ * (strict-blocking invariants, blocking-linked regressions/vetoes, conformance
137
+ * violations, authorized policy blocks, incomplete scans), `warning` = blocking
138
+ * records the strict gate downgraded plus warning-severity hits, `note` = advisory
139
+ * (near, redundant, non-blocking receipts). */
140
+ export declare function renderSarif(r: CheckReport, version: string, extras?: SarifExtras): string;
@@ -0,0 +1,171 @@
1
+ import type { Decision } from "./types.js";
2
+ import type { CommitCandidate, CommitRepairStatus } from "../extractors/git.js";
3
+ export interface CommitRewrite {
4
+ id: string;
5
+ from: string;
6
+ to: string;
7
+ }
8
+ /** Bind review to the ordered queue and which targets are currently resolvable.
9
+ * Queue order matters: duplicate decision ids use first-match selection. */
10
+ export declare function commitRepairReviewHash(queued: readonly CommitRewrite[], withheld?: ReadonlySet<CommitRewrite>): string;
11
+ /** A tombstone: the exact {id, from, to} rewrite a human explicitly rejected
12
+ * via `--drop`. Keyed on the full triple, not just {id, from} — the human's
13
+ * "no" answers the specific question the commit-repair-pending escalation
14
+ * asked ("apply THIS proposed replacement, from -> to?"), not a blanket
15
+ * refusal of every future replacement for this decision's still-orphaned
16
+ * commit. A later merge proposing a genuinely different `to` for the same
17
+ * {id, from} is a NEW proposal — not a repeat of the one that was rejected —
18
+ * and surfaces normally. */
19
+ export interface DroppedRewrite {
20
+ id: string;
21
+ from: string;
22
+ to: string;
23
+ }
24
+ export interface CommitRepairPlan {
25
+ rewrites: CommitRewrite[];
26
+ records: string[];
27
+ }
28
+ /** Live decisions whose cited commit still exists here but is no longer an
29
+ * ancestor of the current tip. Only "orphaned" qualifies: a commit that
30
+ * doesn't resolve at all ("unresolvable") is out of repair scope, and a git
31
+ * failure ("unknown") must never be mistaken for repair-eligible. */
32
+ export declare function orphanedCommitDecisions(decisions: readonly Decision[], status: (sha: string) => CommitRepairStatus): Decision[];
33
+ /** Match each orphaned decision to exactly one candidate whose changed files are
34
+ * a superset of the decision's related_files (non-empty, no globs). */
35
+ export declare function planCommitRepair(orphaned: readonly Decision[], candidates: readonly CommitCandidate[]): CommitRepairPlan;
36
+ /** Whether a queued rewrite is still worth ASKING about: the decision it
37
+ * names still exists, still cites the commit the entry expects to replace (a
38
+ * decision that already moved on would make repairDecisionCommit's own
39
+ * stale-plan bail refuse it), and hasn't since been superseded/rejected —
40
+ * same exclusion as orphanedCommitDecisions, since repairing dead history's
41
+ * provenance isn't worth asking about. Backs the read-only escalation
42
+ * surface only (commitRepairEscalations) — absence-means-not-askable is
43
+ * safe there because it self-heals the moment visibility returns.
44
+ * repair-provenance itself does NOT use this: it reads the full store and
45
+ * may legitimately list (dry-run) or leave queued (apply) an entry this
46
+ * predicate would call not-live, so the escalation surface and the CLI can
47
+ * disagree about the same entry — see deadRewrites and resolvedRewriteIds
48
+ * for the CLI's own, looser-on-purpose reasoning. */
49
+ export declare function liveRewrites(queued: readonly CommitRewrite[], decisions: readonly Decision[]): CommitRewrite[];
50
+ /** Entries a decision has demonstrably outgrown: the decision IS present in
51
+ * `decisions`, and it either moved on from `from` or has since been
52
+ * superseded/rejected. An id this list can't see is deliberately NOT
53
+ * included — absence is a property of the READER (a branch checkout that
54
+ * predates the decision, an unmounted private overlay), not proof the match
55
+ * is stale. This predicate backs a DESTRUCTIVE prune (repair-provenance
56
+ * deletes what it returns from the queue file); repairqueue.ts's own
57
+ * docstring is why that matters — the queue is the one durable record of a
58
+ * match once ORIG_HEAD moves on and the matched-away commit can be gc'd, so
59
+ * deleting an entry on a merely-absent id would destroy it unrecoverably.
60
+ * Contrast liveRewrites, whose absence-means-not-askable rule is safe on
61
+ * the read-only escalation surface, which self-heals once visibility
62
+ * returns.
63
+ *
64
+ * Caveat: "demonstrably" is only as strong as this run's view of
65
+ * `decisions`, which comes from the same git-tracked, branch-dependent
66
+ * files that motivate treating ABSENCE as inconclusive. A decision whose
67
+ * `commit`/`status` genuinely differ between branches (e.g. a repair
68
+ * already applied and committed on one branch, checked out here from a
69
+ * branch that predates it) could still read as "moved on" when it hasn't,
70
+ * from this branch's perspective. Far narrower than the absence case this
71
+ * function exists to fix, and not addressed here. */
72
+ export declare function deadRewrites(queued: readonly CommitRewrite[], decisions: readonly Decision[]): CommitRewrite[];
73
+ /** Among `toApply` (the queued/matched candidates this run is about to act
74
+ * on), the ids repair-provenance may treat as RESOLVED once it's done: the
75
+ * decision is present in `decisions` this run, whether or not
76
+ * repairDecisionCommit actually changed it (a present decision that already
77
+ * moved on is "resolved" too — repairDecisionCommit's own bail refused it,
78
+ * and there is nothing more this run can do about it). An id whose decision
79
+ * is NOT present is never resolved — same reasoning as deadRewrites:
80
+ * absence is the reader's transient view, not proof the match is settled,
81
+ * so it must stay queued for a run where the decision is visible again. */
82
+ export declare function resolvedRewriteIds(toApply: readonly CommitRewrite[], decisions: readonly Decision[]): Set<string>;
83
+ /** Remove any match whose exact {id, from, to} triple was already tombstoned
84
+ * by an earlier `--drop` — otherwise a later merge that re-derives the
85
+ * identical candidate would re-queue exactly what the human rejected. A
86
+ * match sharing {id, from} but proposing a different `to` is unaffected: it
87
+ * is a different proposal, not the rejected one.
88
+ *
89
+ * Preserves object identity for every surviving entry (a plain filter, never
90
+ * a clone or a rebuild) — repair-provenance's own action relies on this to
91
+ * diff its swept-vs-active queue by reference (`queue.filter(r =>
92
+ * !active.includes(r))`) rather than re-spelling the {id, from, to} key a
93
+ * third time. Do not change this to normalize or reconstruct entries
94
+ * without updating that call site too. */
95
+ export declare function withoutDropped(rewrites: readonly CommitRewrite[], dropped: readonly DroppedRewrite[]): CommitRewrite[];
96
+ /** Append newly-dropped {id, from, to} triples to the tombstone list,
97
+ * deduped — same append-only, dedupe-by-key shape as mergeRewrites. */
98
+ export declare function addDropped(newly: readonly DroppedRewrite[], existing: readonly DroppedRewrite[]): DroppedRewrite[];
99
+ /** Combine a freshly-computed plan's rewrites with anything already queued
100
+ * (src/core/repairqueue.ts) from an earlier detection. A fresh match —
101
+ * computed just now, against the current range — overrides a queued one for
102
+ * the same decision; queued entries no longer reachable are simply not
103
+ * repeated by the fresh scan.
104
+ *
105
+ * Overrides by object identity, targeting exactly the one queued entry
106
+ * firstFor (id) would pick — the same first-match rule --drop applies, and
107
+ * the same identity-not-id targeting the dead-entry prune applies (#53; the
108
+ * prune itself removes every provably-dead entry, not just a first match).
109
+ * A corrupted queue file, a hand edit, or a bug upstream could otherwise
110
+ * carry a SECOND entry sharing that id; overriding by id alone would
111
+ * silently destroy that still-queued sibling too, with no tombstone and no
112
+ * way to recover it, even though it was never the entry that would have
113
+ * been applied. Any such sibling is left queued and untouched.
114
+ *
115
+ * How a survivor resolves: the dead-entry prune (deadRewrites), once
116
+ * --apply has moved the decision past the sibling's `from`; or a --drop
117
+ * whose tombstone matches the sibling's exact {id, from, to} — the same
118
+ * --drop that dropped the fresh entry, when the sibling happens to share
119
+ * its triple (the drop's own re-sweep, withoutDropped, catches it too), a
120
+ * second explicit --drop otherwise. NOT a later fresh detection: by this
121
+ * same first-match rule it always overrides the fresh entry sitting ahead
122
+ * of it, never the sibling. */
123
+ export declare function mergeRewrites(fresh: readonly CommitRewrite[], queued: readonly CommitRewrite[]): CommitRewrite[];
124
+ /** Partition the entries --apply is about to act on by whether their proposed
125
+ * `to` commit actually exists in this repository. `existing` is
126
+ * `commitsExist`'s result over every `to` in `toApply`: `null` means the
127
+ * check itself failed to run (not a git repo, git missing, timeout) and is
128
+ * treated as fail-open, same discipline as drift.ts's own use of
129
+ * commitsExist — nothing is withheld just because the check couldn't run.
130
+ *
131
+ * A corrupted queue file, a hand-edited entry, or a bug upstream could
132
+ * otherwise get a `to` that never resolves to a real commit written
133
+ * straight into a decision's `commit` field and provenance evidence, then
134
+ * auto-committed into shared team memory. `withheld` entries must stay
135
+ * queued (never deleted, never passed to repairDecisionCommit) — the queue
136
+ * file is the one durable record of the match, same reasoning as
137
+ * deadRewrites/resolvedRewriteIds.
138
+ *
139
+ * Both returned arrays preserve object identity (never clones or rebuilds
140
+ * an entry) — the CLI's queue sweep and its "still queued" reporting both
141
+ * key a `Set` off the exact objects in `withheld`, by reference, precisely
142
+ * because a corrupted queue file can carry two entries sharing an id (one
143
+ * resolvable, one not): an id alone can't tell them apart. Do not change
144
+ * this to normalize or reconstruct entries without updating those call
145
+ * sites too — same caveat withoutDropped's own docstring carries. */
146
+ export declare function withheldForUnresolvableTo(toApply: readonly CommitRewrite[], existing: Set<string> | null): {
147
+ applicable: CommitRewrite[];
148
+ withheld: CommitRewrite[];
149
+ };
150
+ /** The one rule for which entry wins when more than one shares a decision id
151
+ * (a corrupted queue file, a hand edit, or a bug upstream): first match, by
152
+ * construction. Every caller goes through here so no two can independently
153
+ * drift on which entry "won" — this file's own duplicate-id queue handling
154
+ * has drifted between call sites three times already (see withoutDropped's,
155
+ * withheldForUnresolvableTo's, and mergeRewrites' own docstrings). */
156
+ export declare function firstFor(entries: readonly CommitRewrite[], id: string): CommitRewrite | undefined;
157
+ /** pickRewrite is firstFor scoped to a plan's own rewrites (planCommitRepair
158
+ * itself never produces same-id duplicates, but a plan carrying them isn't
159
+ * ruled out here — see firstFor). repairDecisionCommit uses this to decide
160
+ * what to WRITE; a caller that needs to know which entry WOULD be picked
161
+ * without writing anything (the CLI's --apply summary and queue sweep)
162
+ * calls this same function, so the two can never independently drift on
163
+ * which entry "won". */
164
+ export declare function pickRewrite(plan: CommitRepairPlan, id: string): CommitRewrite | undefined;
165
+ /** Pure: rewrite `commit` for one decision, and its matching `commit:<sha>`
166
+ * evidence entry too if one is present (replaceExact is a no-op when the
167
+ * evidence array never cited the old sha) — or return the same reference
168
+ * when the plan doesn't touch it. If the record moved on since the plan was
169
+ * built (its commit no longer equals the plan's `from`), bail entirely
170
+ * rather than applying a now-stale match. */
171
+ export declare function repairDecisionCommit(d: Decision, plan: CommitRepairPlan): Decision;