@davesheffer/hunch 1.31.1 → 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 (220) hide show
  1. package/README.md +1 -0
  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/constitution/adapters.d.ts +29 -0
  18. package/dist/constitution/behaviorAttestationBinding.d.ts +3 -0
  19. package/dist/constitution/behaviorEvaluator.d.ts +8 -0
  20. package/dist/constitution/behaviorProof.d.ts +9 -0
  21. package/dist/constitution/behaviorWorkspace.d.ts +25 -0
  22. package/dist/constitution/bootstrap.d.ts +30 -0
  23. package/dist/constitution/canonical.d.ts +15 -0
  24. package/dist/constitution/card.d.ts +58 -0
  25. package/dist/constitution/compiler.d.ts +52 -0
  26. package/dist/constitution/composition.d.ts +10 -0
  27. package/dist/constitution/corpus.d.ts +7 -0
  28. package/dist/constitution/correctionPolicyMaterializer.d.ts +57 -0
  29. package/dist/constitution/delta.d.ts +4 -0
  30. package/dist/constitution/disposition.d.ts +20 -0
  31. package/dist/constitution/evaluator.d.ts +47 -0
  32. package/dist/constitution/experiment.d.ts +466 -0
  33. package/dist/constitution/experimentRunner.d.ts +7 -0
  34. package/dist/constitution/g2.d.ts +209 -0
  35. package/dist/constitution/g2BehaviorAttestation.d.ts +45 -0
  36. package/dist/constitution/g2BehaviorCandidates.d.ts +123 -0
  37. package/dist/constitution/g2BehaviorDependencies.d.ts +83 -0
  38. package/dist/constitution/g2BehaviorMaterialization.d.ts +77 -0
  39. package/dist/constitution/g2BehaviorPolicyMaterializer.d.ts +48 -0
  40. package/dist/constitution/g2CandidateAttestation.d.ts +43 -0
  41. package/dist/constitution/g2Candidates.d.ts +80 -0
  42. package/dist/constitution/g2Drills.d.ts +33 -0
  43. package/dist/constitution/g3.d.ts +311 -0
  44. package/dist/constitution/g3Conformance.d.ts +33 -0
  45. package/dist/constitution/lifecycle.d.ts +25 -0
  46. package/dist/constitution/mutation.d.ts +15 -0
  47. package/dist/constitution/nodeTestEvidence.d.ts +13 -0
  48. package/dist/constitution/plan.d.ts +18 -0
  49. package/dist/constitution/policyRuntime.d.ts +5 -0
  50. package/dist/constitution/proof.d.ts +11 -0
  51. package/dist/constitution/repairPolicies.d.ts +34 -0
  52. package/dist/constitution/replacementFreeGit.d.ts +14 -0
  53. package/dist/constitution/replay.d.ts +40 -0
  54. package/dist/constitution/replayCache.d.ts +14 -0
  55. package/dist/constitution/replayWorker.d.ts +1 -0
  56. package/dist/constitution/repository.d.ts +129 -0
  57. package/dist/constitution/safeCheckout.d.ts +9 -0
  58. package/dist/constitution/schema.d.ts +1763 -0
  59. package/dist/constitution/scorecard.d.ts +219 -0
  60. package/dist/constitution/service.d.ts +398 -0
  61. package/dist/constitution/shadow.d.ts +54 -0
  62. package/dist/constitution/sourceMutation.d.ts +15 -0
  63. package/dist/constitution/staticGraphBaseline.d.ts +13 -0
  64. package/dist/constitution/structural.d.ts +64 -0
  65. package/dist/core/agenthook.d.ts +63 -0
  66. package/dist/core/agenthook.js +1 -0
  67. package/dist/core/automaticReviewMemory.d.ts +60 -0
  68. package/dist/core/autoreview.d.ts +66 -0
  69. package/dist/core/canonicalOrder.d.ts +3 -0
  70. package/dist/core/capturetoken.d.ts +19 -0
  71. package/dist/core/changeIdentity.d.ts +22 -0
  72. package/dist/core/changeProof.d.ts +14 -0
  73. package/dist/core/checkreport.d.ts +140 -0
  74. package/dist/core/commitrepair.d.ts +171 -0
  75. package/dist/core/commitrepair.js +238 -0
  76. package/dist/core/compare.d.ts +21 -0
  77. package/dist/core/config.d.ts +20 -0
  78. package/dist/core/conformance.d.ts +35 -0
  79. package/dist/core/constraintmatch.d.ts +54 -0
  80. package/dist/core/correction.d.ts +42 -0
  81. package/dist/core/correctionStage.d.ts +224 -0
  82. package/dist/core/declarationClusters.d.ts +149 -0
  83. package/dist/core/delivery.d.ts +110 -0
  84. package/dist/core/docanchors.d.ts +32 -0
  85. package/dist/core/docscan.d.ts +26 -0
  86. package/dist/core/drift.d.ts +13 -0
  87. package/dist/core/drift.js +31 -1
  88. package/dist/core/dupdetect.d.ts +43 -0
  89. package/dist/core/escalations.d.ts +129 -0
  90. package/dist/core/escalations.js +165 -0
  91. package/dist/core/events.d.ts +29 -0
  92. package/dist/core/evidenceMap.d.ts +164 -0
  93. package/dist/core/externalImports.d.ts +8 -0
  94. package/dist/core/format.d.ts +13 -0
  95. package/dist/core/glob.d.ts +9 -0
  96. package/dist/core/groundingLag.d.ts +84 -0
  97. package/dist/core/groundingLag.js +16 -2
  98. package/dist/core/groundingMerge.d.ts +15 -0
  99. package/dist/core/groundingMerge.js +95 -0
  100. package/dist/core/hookcache.d.ts +10 -0
  101. package/dist/core/hookpolicy.d.ts +47 -0
  102. package/dist/core/ids.d.ts +36 -0
  103. package/dist/core/importReview.d.ts +30 -0
  104. package/dist/core/io.d.ts +23 -0
  105. package/dist/core/jsonc.d.ts +4 -0
  106. package/dist/core/landscapeAdoption.d.ts +66 -0
  107. package/dist/core/landscapeDelivery.d.ts +76 -0
  108. package/dist/core/memorylog.d.ts +37 -0
  109. package/dist/core/migrate.d.ts +28 -0
  110. package/dist/core/outcomeExperience.d.ts +89 -0
  111. package/dist/core/overlaySafety.d.ts +24 -0
  112. package/dist/core/overlaySafety.js +7 -1
  113. package/dist/core/paths.d.ts +31 -0
  114. package/dist/core/pipeline.d.ts +289 -0
  115. package/dist/core/premises.d.ts +43 -0
  116. package/dist/core/provenance.d.ts +21 -0
  117. package/dist/core/publication.d.ts +54 -0
  118. package/dist/core/refrepair.d.ts +24 -0
  119. package/dist/core/refrepair.js +1 -1
  120. package/dist/core/relativeImports.d.ts +11 -0
  121. package/dist/core/repair.d.ts +44 -0
  122. package/dist/core/repairqueue.d.ts +40 -0
  123. package/dist/core/repairqueue.js +133 -0
  124. package/dist/core/reviewMemory.d.ts +40 -0
  125. package/dist/core/reviewqueue.d.ts +54 -0
  126. package/dist/core/safeRepoFile.d.ts +20 -0
  127. package/dist/core/served.d.ts +57 -0
  128. package/dist/core/served.js +14 -0
  129. package/dist/core/stateContract.d.ts +979 -0
  130. package/dist/core/stateDelivery.d.ts +78 -0
  131. package/dist/core/stateRecords.d.ts +383 -0
  132. package/dist/core/stats.d.ts +88 -0
  133. package/dist/core/strictgate.d.ts +33 -0
  134. package/dist/core/taskReport.d.ts +226 -0
  135. package/dist/core/taskReport.js +514 -0
  136. package/dist/core/taskReportCapture.d.ts +8 -0
  137. package/dist/core/taskReportCapture.js +36 -0
  138. package/dist/core/taskReportEvidence.d.ts +26 -0
  139. package/dist/core/taskReportEvidence.js +268 -0
  140. package/dist/core/taskReportHook.d.ts +20 -0
  141. package/dist/core/taskReportHook.js +76 -0
  142. package/dist/core/taskReportPaths.d.ts +3 -0
  143. package/dist/core/taskReportPaths.js +23 -0
  144. package/dist/core/taskReportPublic.d.ts +31 -0
  145. package/dist/core/taskReportPublic.js +45 -0
  146. package/dist/core/taskReportRender.d.ts +6 -0
  147. package/dist/core/taskReportRender.js +97 -0
  148. package/dist/core/topics.d.ts +73 -0
  149. package/dist/core/types.d.ts +1239 -0
  150. package/dist/core/version.d.ts +10 -0
  151. package/dist/eval/guards.d.ts +45 -0
  152. package/dist/eval/harness.d.ts +58 -0
  153. package/dist/extractors/adrImport.d.ts +59 -0
  154. package/dist/extractors/comments.d.ts +7 -0
  155. package/dist/extractors/correctionSources.d.ts +11 -0
  156. package/dist/extractors/diff.d.ts +40 -0
  157. package/dist/extractors/git.d.ts +304 -0
  158. package/dist/extractors/git.js +208 -20
  159. package/dist/extractors/helm.d.ts +31 -0
  160. package/dist/extractors/indexer.d.ts +55 -0
  161. package/dist/extractors/landscapeDiscovery.d.ts +41 -0
  162. package/dist/extractors/languages.d.ts +77 -0
  163. package/dist/extractors/nativeTreeSitter.d.ts +16 -0
  164. package/dist/extractors/parse.d.ts +54 -0
  165. package/dist/extractors/php.d.ts +26 -0
  166. package/dist/extractors/repoSource.d.ts +53 -0
  167. package/dist/extractors/testreport.d.ts +36 -0
  168. package/dist/integrations/ciAction.d.ts +8 -0
  169. package/dist/integrations/claudeConfig.d.ts +44 -0
  170. package/dist/integrations/claudemd.d.ts +13 -0
  171. package/dist/integrations/claudemd.js +11 -2
  172. package/dist/integrations/gitignore.d.ts +16 -0
  173. package/dist/integrations/gitignore.js +7 -0
  174. package/dist/integrations/health.d.ts +74 -0
  175. package/dist/integrations/hooks.d.ts +41 -0
  176. package/dist/integrations/hooks.js +92 -66
  177. package/dist/integrations/madrExport.d.ts +39 -0
  178. package/dist/integrations/madrManifest.d.ts +69 -0
  179. package/dist/integrations/mergeDriver.d.ts +3 -0
  180. package/dist/integrations/mergeDriver.js +25 -8
  181. package/dist/integrations/probe.d.ts +4 -0
  182. package/dist/integrations/providers.d.ts +103 -0
  183. package/dist/integrations/scaffold.d.ts +29 -0
  184. package/dist/integrations/sync.d.ts +30 -0
  185. package/dist/integrations/sync.js +3 -2
  186. package/dist/integrations/team.d.ts +70 -0
  187. package/dist/integrations/worktree.d.ts +8 -0
  188. package/dist/mcp/roots.d.ts +16 -0
  189. package/dist/mcp/server.d.ts +36 -0
  190. package/dist/mcp/server.js +74 -22
  191. package/dist/mcp/taskReportTools.d.ts +3 -0
  192. package/dist/mcp/taskReportTools.js +106 -0
  193. package/dist/serve/app.d.ts +36 -0
  194. package/dist/serve/config.d.ts +100 -0
  195. package/dist/serve/writelock.d.ts +22 -0
  196. package/dist/store/changeLedger.d.ts +132 -0
  197. package/dist/store/compact.d.ts +33 -0
  198. package/dist/store/db.d.ts +9 -0
  199. package/dist/store/embedder.d.ts +32 -0
  200. package/dist/store/hunchStore.d.ts +624 -0
  201. package/dist/store/hunchStore.js +6 -3
  202. package/dist/store/jsonStore.d.ts +125 -0
  203. package/dist/store/merge.d.ts +39 -0
  204. package/dist/store/privateMigrate.d.ts +24 -0
  205. package/dist/store/replay.d.ts +56 -0
  206. package/dist/store/schema.d.ts +17 -0
  207. package/dist/store/stateBinding.d.ts +145 -0
  208. package/dist/store/stateCapture.d.ts +15 -0
  209. package/dist/synthesis/cliAdapter.d.ts +22 -0
  210. package/dist/synthesis/initiator.d.ts +13 -0
  211. package/dist/synthesis/provider.d.ts +307 -0
  212. package/dist/synthesis/synthesize.d.ts +117 -0
  213. package/dist/synthesis/tripwires.d.ts +9 -0
  214. package/dist/taskReports.d.ts +76 -0
  215. package/dist/taskReports.js +67 -0
  216. package/dist/wiki/adopt.d.ts +22 -0
  217. package/dist/wiki/graph.d.ts +87 -0
  218. package/dist/wiki/wiki.d.ts +242 -0
  219. package/package.json +6 -5
  220. package/server.json +2 -2
@@ -0,0 +1,10 @@
1
+ export declare const HUNCH_VERSION: string;
2
+ /** Exact public npm package consumed by generated CI and shared MCP/provider
3
+ * configs. A floating package name would let one committed configuration run
4
+ * different Hunch semantics as npm's latest release changes. */
5
+ export declare const HUNCH_PACKAGE_SPEC: string;
6
+ /** npm alias used by npx launchers. Giving the fetched package a distinct local
7
+ * alias prevents npm exec from treating this repository (which has the same
8
+ * package name) as satisfying the request and then falling through to an older
9
+ * global `hunch` executable. */
10
+ export declare const HUNCH_NPX_PACKAGE_SPEC: string;
@@ -0,0 +1,45 @@
1
+ /**
2
+ * Guard eval — turn "Hunch catches bad changes" into a MEASURED number.
3
+ *
4
+ * Mirrors the retrieval eval (harness.ts) but scores ENFORCEMENT: each case is a change
5
+ * (changed files + optional diff) with an expected verdict, run through the SAME production
6
+ * path the guards use — `store.buildCheckReport(...) → verdict()` — so the number reflects the
7
+ * real gate, not a re-implementation. The ground-truth-able metrics:
8
+ * - CAUGHT: of changes to guarded code, how many the gate surfaced (block OR warn) —
9
+ * i.e. nothing slipped silently through. (Of those, how many HARD-block a
10
+ * merge depends on firmness/freshness, reported separately.)
11
+ * - FALSE-POSITIVE: of UNRELATED changes, how many the gate flagged — lower = safer to enable.
12
+ */
13
+ import type { HunchStore } from "../store/hunchStore.js";
14
+ /** "catch" = should be surfaced (block OR warn); block/warn/pass = an exact expected verdict. */
15
+ export type Expect = "block" | "warn" | "pass" | "catch";
16
+ export interface GuardCase {
17
+ name: string;
18
+ files: string[];
19
+ diff?: string;
20
+ expect: Expect;
21
+ }
22
+ export interface GuardEvalResult {
23
+ total: number;
24
+ shouldSurface: number;
25
+ surfaced: number;
26
+ hardBlocked: number;
27
+ shouldPass: number;
28
+ falsePositives: number;
29
+ accuracy: number;
30
+ perCase: Array<{
31
+ name: string;
32
+ expect: Expect;
33
+ got: Expect;
34
+ ok: boolean;
35
+ }>;
36
+ }
37
+ /** Parse + validate a hand-authored golden set. */
38
+ export declare function loadGuardCases(json: string): GuardCase[];
39
+ /** Score every case through the real guard pipeline. */
40
+ export declare function evalGuards(store: HunchStore, cases: GuardCase[]): GuardEvalResult;
41
+ /** Scaffold a STARTER set from the live graph: every active, vouched blocking constraint →
42
+ * a CATCH case (a file in its scope — a change there must not slip silently past the gate);
43
+ * a few unrelated paths → PASS cases (the precision side — the gate must NOT over-flag). Both
44
+ * sides are true ground truth. Hand-add regressions / near-misses for fuller coverage. */
45
+ export declare function generateGuardCases(store: HunchStore): GuardCase[];
@@ -0,0 +1,58 @@
1
+ /**
2
+ * Deterministic retrieval eval (roadmap #2). Scores `hybridSearch` against a curated
3
+ * golden set of "why" questions by EXACT ref match — Recall@k + MRR — with no LLM in
4
+ * the scoring path, so it runs as a stable, reproducible CI signal. The `graphWeight`
5
+ * knob lets ONE store be scored graph-OFF (0) vs graph-ON to measure the dependency-
6
+ * graph stream's lift — the measurement gate for the graph-RRF decision. Traversal
7
+ * depth is also injectable so the shipped bounded traversal can be compared directly
8
+ * with its 1-hop baseline on the same cases.
9
+ */
10
+ import type { HunchStore } from "../store/hunchStore.js";
11
+ import type { Embedder } from "../store/embedder.js";
12
+ export interface EvalCase {
13
+ query: string;
14
+ expected: string[];
15
+ note?: string;
16
+ }
17
+ export interface CaseResult {
18
+ query: string;
19
+ expected: number;
20
+ found: number;
21
+ recall: number;
22
+ rr: number;
23
+ }
24
+ export interface EvalMetrics {
25
+ n: number;
26
+ k: number;
27
+ recallAtK: number;
28
+ mrr: number;
29
+ hitRate: number;
30
+ perCase: CaseResult[];
31
+ }
32
+ export interface EvalOpts {
33
+ k?: number;
34
+ embedder?: Embedder | null;
35
+ graphWeight?: number;
36
+ graphDepth?: number;
37
+ graphNodeCap?: number;
38
+ graphTokenCap?: number;
39
+ kind?: string;
40
+ }
41
+ /** Score a golden set: Recall@k, MRR, hit-rate. Deterministic when no embedder. */
42
+ export declare function evaluateRetrieval(store: HunchStore, cases: EvalCase[], opts?: EvalOpts): Promise<EvalMetrics>;
43
+ /** Compare graph-OFF vs graph-ON on the same golden set — the #1 lift measurement. */
44
+ export declare function evaluateGraphLift(store: HunchStore, cases: EvalCase[], opts?: EvalOpts): Promise<{
45
+ off: EvalMetrics;
46
+ on: EvalMetrics;
47
+ recallDelta: number;
48
+ mrrDelta: number;
49
+ }>;
50
+ /** Compare the historical 1-hop graph with the shipped bounded traversal. */
51
+ export declare function evaluateTraversalLift(store: HunchStore, cases: EvalCase[], opts?: EvalOpts): Promise<{
52
+ oneHop: EvalMetrics;
53
+ bounded: EvalMetrics;
54
+ recallDelta: number;
55
+ mrrDelta: number;
56
+ }>;
57
+ /** Parse + validate a golden-set JSON string (array of {query, expected[]}). */
58
+ export declare function loadGoldenSet(raw: string): EvalCase[];
@@ -0,0 +1,59 @@
1
+ import type { Decision } from "../core/types.js";
2
+ /** Directories probed (in order) when no explicit dir is given — adr-tools'
3
+ * default (doc/adr), MADR's (docs/decisions), and the common variants. */
4
+ export declare const ADR_DIR_CANDIDATES: readonly ["docs/adr", "docs/decisions", "doc/adr", "adr", "docs/architecture/decisions"];
5
+ /** An ADR file is NNNN-slug.md (adr-tools / MADR convention). Templates and
6
+ * indexes (adr-template.md, README.md, index.md) never match. */
7
+ export declare const ADR_FILE_RE: RegExp;
8
+ /** A structurally valid ADR filename can still name corpus scaffolding rather
9
+ * than a decision. Keep this classification separate from parse failure so the
10
+ * CLI can exclude templates quietly instead of falsely calling their filename
11
+ * malformed. */
12
+ export declare function isAdrCorpusTemplate(relPath: string): boolean;
13
+ /** Decision.date is required even when an ADR has no date and no Git history is
14
+ * available (for example, a copied corpus outside a repository). Use an honest,
15
+ * deterministic sentinel instead of the import clock: re-importing the same
16
+ * bytes must not silently make an old decision look current. */
17
+ export declare const UNDATED_ADR_DATE = "1970-01-01T00:00:00.000Z";
18
+ export interface AdrSource {
19
+ /** repo-relative posix path, e.g. docs/adr/0005-use-postgres.md */
20
+ relPath: string;
21
+ text: string;
22
+ /** ISO date of the source file's introduction commit, when available. */
23
+ sourceDate?: string | null;
24
+ /** Full source introduction commit, when available. */
25
+ sourceRevision?: string | null;
26
+ }
27
+ export interface ParsedAdr {
28
+ relPath: string;
29
+ number: number;
30
+ slug: string;
31
+ title: string;
32
+ /** raw status text as written (e.g. "Superseded by [ADR-7](...)") */
33
+ statusRaw: string;
34
+ status: "proposed" | "accepted" | "rejected" | "superseded";
35
+ date: string | null;
36
+ context: string;
37
+ decision: string;
38
+ consequences: string[];
39
+ consideredOptions: string[];
40
+ chosenOption: string | null;
41
+ sourceHash: string;
42
+ /** ADR numbers this one supersedes / is superseded by (from status text + links) */
43
+ supersedesNumbers: number[];
44
+ supersededByNumbers: number[];
45
+ }
46
+ export interface AdrImportResult {
47
+ decisions: Decision[];
48
+ warnings: string[];
49
+ }
50
+ /** Parse one ADR markdown file (MADR 3.x/4 frontmatter style or Nygard heading
51
+ * style). Returns null when the filename doesn't follow NNNN-slug.md. */
52
+ export declare function parseAdrMarkdown(text: string, relPath: string): ParsedAdr | null;
53
+ export declare function adrDecisionId(relPath: string): string;
54
+ /** Map a parsed corpus to Decision records. Pure: cross-links resolve by ADR
55
+ * number WITHIN the given corpus only; unresolvable references become warnings,
56
+ * never guessed ids. Bi-temporal closure: a superseded ADR's valid_to is its
57
+ * successor's valid_from (falling back to the successor's date, then its own
58
+ * date) so as-of queries see the corpus's real history. */
59
+ export declare function mapAdrCorpus(sources: AdrSource[]): AdrImportResult;
@@ -0,0 +1,7 @@
1
+ export interface InlineIntent {
2
+ kind: "why" | "rule";
3
+ text: string;
4
+ file: string;
5
+ line: number;
6
+ }
7
+ export declare function extractInlineIntent(root: string): InlineIntent[];
@@ -0,0 +1,11 @@
1
+ import type { ContractAxisOwnerSource } from "../core/pipeline.js";
2
+ export interface CorrectionSourceCollection {
3
+ sources: ContractAxisOwnerSource[];
4
+ files_read: number;
5
+ bytes_read: number;
6
+ files_skipped: number;
7
+ }
8
+ /** Safely read a bounded working-tree TypeScript/PHP corpus. Stage-relevant paths
9
+ * are read first so a huge repository cannot crowd the likely correction layer
10
+ * out of the fixed source budget. */
11
+ export declare function collectCorrectionStageSources(root: string, issue: string): CorrectionSourceCollection;
@@ -0,0 +1,40 @@
1
+ /**
2
+ * Structured analysis of a git unified diff (deterministic, no LLM). Turns raw
3
+ * patch text into "what actually changed" — added/removed/changed symbols, new
4
+ * and dropped dependencies, file add/delete/rename — so the synthesis layer can
5
+ * write an INFORMATIVE decision even with no model available.
6
+ *
7
+ * Parsing is hunk-state-aware: file headers ("--- "/"+++ ") are only honored in
8
+ * the pre-hunk region, so a CONTENT line like `+++counter` (source `++counter`)
9
+ * is never mistaken for a header. Symbol classification is PER FILE, so moving a
10
+ * function between files isn't misread as a signature change.
11
+ */
12
+ export interface SymbolChange {
13
+ name: string;
14
+ kind: "function" | "class" | "interface" | "trait" | "enum" | "type" | "const";
15
+ }
16
+ export interface RenamePair {
17
+ from: string;
18
+ to: string;
19
+ }
20
+ export interface DiffAnalysis {
21
+ filesAdded: string[];
22
+ filesDeleted: string[];
23
+ filesModified: string[];
24
+ filesRenamed: RenamePair[];
25
+ addedSymbols: SymbolChange[];
26
+ removedSymbols: SymbolChange[];
27
+ changedSymbols: SymbolChange[];
28
+ addedDeps: string[];
29
+ removedDeps: string[];
30
+ addedLines: number;
31
+ removedLines: number;
32
+ /** Added line bodies (the "+" content, marker stripped) per file — every file,
33
+ * not just substantive ones. The text veto's symbol/pattern tiers match against
34
+ * — call sites, not just declarations, which addedSymbols can't see. Keyed by
35
+ * the same (new-path) key as perFile. */
36
+ addedLinesByFile: Map<string, string[]>;
37
+ }
38
+ export declare function analyzeDiff(diff: string): DiffAnalysis;
39
+ /** A compact human-readable summary of a DiffAnalysis (used in decision text). */
40
+ export declare function summarizeDiff(a: DiffAnalysis): string;
@@ -0,0 +1,304 @@
1
+ export interface CommitMeta {
2
+ sha: string;
3
+ shortSha: string;
4
+ subject: string;
5
+ body: string;
6
+ author: string;
7
+ date: string;
8
+ files: string[];
9
+ }
10
+ export declare function foreignRepoEnv(source: NodeJS.ProcessEnv): NodeJS.ProcessEnv;
11
+ export declare function isGitRepo(cwd: string): boolean;
12
+ /** Canonical worktree root, or null when Git cannot positively identify one.
13
+ * Callers enforcing a privacy boundary must distinguish "different repo" from
14
+ * "malformed/unknown Git state" instead of treating both as safe. */
15
+ export declare function gitWorktreeRoot(cwd: string): string | null;
16
+ /** True only when `cwd` is the repository's actual worktree root. Unlike
17
+ * `isGitRepo`, this does not accept an ancestor repository discovered by Git's
18
+ * upward walk. Private overlays use this stronger boundary so a nested
19
+ * `.hunch-private/.hunch` can never stage or commit into the code repository. */
20
+ export declare function isGitRepoRoot(cwd: string): boolean;
21
+ export declare function gitNullDevice(): string;
22
+ /** Whether two paths resolve to the same repository identity. Comparing only
23
+ * worktree roots is insufficient: linked worktrees have different roots but
24
+ * share one Git common directory and therefore one publishable history. */
25
+ export declare function sameGitRepository(left: string, right: string): boolean;
26
+ /** Canonical repository identity for deterministic, credential-free discovery.
27
+ * The returned value is an internal identity: callers exposing it must hash
28
+ * local `file:` identities so host paths never enter graph records. Network
29
+ * credentials, URL query strings and fragments are deliberately excluded. */
30
+ export declare function canonicalRemoteRepositoryIdentity(raw: string, cwd: string): string;
31
+ /** Resolve a user-supplied Git remote once, before handing it to commands that
32
+ * run from different working directories. Git otherwise gives a relative local
33
+ * URL a different meaning under `git clone` and `git -C <overlay> remote add`,
34
+ * which can turn a successful preflight into a later privacy-boundary escape. */
35
+ export declare function canonicalRemoteUrl(raw: string, cwd: string): string;
36
+ /** Compare two remote spellings in the contexts where Git would interpret
37
+ * them. This is identity comparison, not brittle string equality. */
38
+ export declare function sameRemoteUrl(left: string, leftCwd: string, right: string, rightCwd: string): boolean;
39
+ /** True when two worktrees can publish to the same local Git history OR name
40
+ * the same configured remote repository. Separate clones of one remote are a
41
+ * single publication boundary even though their local common dirs differ. */
42
+ export declare function sameGitPublication(left: string, right: string): boolean;
43
+ /** Preflight a requested overlay URL before clone/attach can mutate a remote. */
44
+ export declare function repositoryUsesRemote(repoRoot: string, remoteUrl: string, remoteCwd?: string): boolean;
45
+ /** The MAIN worktree's root — the stable anchor for an overlay store. A linked worktree
46
+ * can be `git worktree remove`d, so anything anchored inside it (an overlay clone, an
47
+ * absolute pointer target) silently dies for every OTHER worktree; the main checkout
48
+ * can't be removed. Falls back to `root` when the layout isn't the standard `.git` dir
49
+ * (or not a git repo), preserving today's behavior. */
50
+ export declare function mainWorktreeRoot(root: string): string;
51
+ /** Stable, privacy-safe repository label for artifacts that must be reusable
52
+ * across linked worktrees and ordinary clones. Prefer the canonical fetch
53
+ * remote identity: its SHA-256 digest exposes neither a private URL nor a local
54
+ * path, ignores mutable remote aliases, and does not depend on clone depth. If
55
+ * several fetch remotes exist, canonical identity ordering makes the choice
56
+ * deterministic without privileging a name such as `origin`.
57
+ *
58
+ * Remote-less full clones fall back to intrinsic root commits. A remote-less
59
+ * shallow clone cannot prove a clone-independent repository identity without
60
+ * fetching missing history or persisting a shared ID, so it retains the local
61
+ * main-worktree label (which is still stable across linked worktrees). */
62
+ export declare function stableRepositoryName(root: string): string;
63
+ /** Best-effort: stage ONLY the hunch dir, commit, and push the repo it lives in. Shared by
64
+ * the post-commit auto-commit (CLI sync --commit), MCP private writes, and `hunch private
65
+ * --sync`. HUNCH_SYNC=1 stops the created commit from re-triggering the post-commit hook
66
+ * (no recursion). Stages with a pathspec scoped to `hunchDir`, so it never sweeps unrelated
67
+ * working-tree changes. Never throws — a non-repo dir / offline push just no-ops.
68
+ * `push: false` commits WITHOUT merging or pushing — required when hunchDir is the PUBLIC
69
+ * .hunch/ inside the user's code repo: an automatic pull/push there would merge the remote
70
+ * into their working branch and publish their unpushed code commits. The memory commit
71
+ * simply rides the user's next push.
72
+ * Returns what ACTUALLY happened, so callers never report a commit that was skipped:
73
+ * "pushed" (commit created and pushed), "committed" (commit created; push not requested,
74
+ * or the merge/push failed — retry rides the next flush), null (nothing committed: lock
75
+ * held, backstop refusal, nothing staged, or not a repo). */
76
+ export type GitMemoryObservation = {
77
+ kind: "committed";
78
+ commitSha: string;
79
+ } | {
80
+ kind: "published";
81
+ commitSha: string;
82
+ ref: string;
83
+ basis: "push-status" | "remote-ref-confirmed";
84
+ };
85
+ export type GitMemoryObserver = (event: GitMemoryObservation) => void;
86
+ export type HunchCommitOptions = ({
87
+ push: false;
88
+ alsoStage?: string[];
89
+ } | {
90
+ push?: true;
91
+ protectedRepoRoot: string;
92
+ alsoStage?: string[];
93
+ remote?: HunchRemoteContract;
94
+ }) & {
95
+ observe?: GitMemoryObserver;
96
+ };
97
+ /** A team sync never delegates destination or ref selection to ambient Git
98
+ * configuration. The committed team pointer supplies one fetch URL, one push
99
+ * URL, and one canonical branch ref; `verify` re-proves that contract at each
100
+ * network seam while the Hunch lock is held. */
101
+ export interface HunchRemoteContract {
102
+ fetchUrl: string;
103
+ pushUrl: string;
104
+ urlCwd: string;
105
+ ref: string;
106
+ verify: () => boolean;
107
+ }
108
+ export declare function commitAndPushHunch(hunchDir: string, message: string, opts: HunchCommitOptions): "pushed" | "committed" | null;
109
+ /** True when `rel` is tracked with no staged or unstaged changes (untracked counts as
110
+ * dirty, so a doc the user never committed is never swept into a memory commit). */
111
+ export declare function isGitCleanPath(root: string, rel: string): boolean;
112
+ /** The committed (HEAD) content of a tracked file, or null when the path is
113
+ * untracked/absent at HEAD or git is unavailable. Used to decide whether a
114
+ * dirty grounding doc differs from HEAD ONLY inside its generated section
115
+ * (the stranded-grounding heal, fnd_b269d5c422). */
116
+ export declare function headFileContent(root: string, rel: string): string | null;
117
+ /** Best-effort READ-side sync: merge the overlay's remote into the local branch (e.g. on MCP
118
+ * server start or at a long-lived MCP request boundary) so this machine/session sees other
119
+ * machines' memory. Never throws; leaves a clean tree. Serialized with the commit lock so it
120
+ * can't race a concurrent flush. Returns true only when the checked-out memory HEAD moved,
121
+ * allowing callers to avoid rebuilding the derived SQLite index after a no-op pull. */
122
+ export type HunchPullStatus = "updated" | "current" | "busy" | "failed" | "unconfigured";
123
+ export interface PullHunchOptions {
124
+ timeoutMs?: number;
125
+ env?: NodeJS.ProcessEnv;
126
+ remote?: HunchRemoteContract;
127
+ /** Setup-only convergence for an existing standalone memory repository. */
128
+ allowUnrelatedHistories?: boolean;
129
+ }
130
+ export declare function pullHunchStatus(hunchDir: string, opts?: PullHunchOptions): HunchPullStatus;
131
+ export declare function pullHunch(hunchDir: string): boolean;
132
+ /** Explicit retry path for a clean overlay that already has a local memory commit
133
+ * stranded by an earlier offline/rejected push. Unlike commitAndPushHunch this
134
+ * creates no commit: it only converges and publishes existing overlay history. */
135
+ export declare function syncExistingHunch(hunchDir: string, protectedRepoRoot: string, timeoutMs?: number, remote?: HunchRemoteContract): "pushed" | "current" | "failed";
136
+ export declare function headSha(cwd: string): string;
137
+ /** HEAD for a repository that is not the invocation repository. Unlike
138
+ * headSha, this deliberately ignores code-repo GIT_DIR/GIT_INDEX_FILE state
139
+ * inherited from hooks. */
140
+ export declare function isolatedHeadSha(cwd: string): string;
141
+ /** Stop tracking `paths` in git (remove from the INDEX only — keep the working-tree
142
+ * files). Used by `hunch private --migrate` to un-publish the .hunch memory tree
143
+ * without deleting it locally. `--ignore-unmatch` makes an already-untracked path a
144
+ * no-op rather than an error; best-effort (a non-repo dir just no-ops). */
145
+ export declare function gitUntrackCached(cwd: string, paths: string[]): void;
146
+ /** Resolve any commit-ish (short sha / HEAD / branch) to a canonical full sha.
147
+ * Returns the input unchanged if it can't be resolved (e.g. not a git repo). */
148
+ export declare function revParse(ref: string, cwd: string): string;
149
+ /** Path to the hooks dir (honors core.hooksPath / worktrees). */
150
+ export declare function hooksDir(cwd: string): string;
151
+ export declare function gitDir(cwd: string): string;
152
+ /** The SHARED git dir for the repo — identical across ALL linked worktrees (unlike
153
+ * `gitDir`, which is per-worktree). Absolute, so callers can anchor worktree-shared
154
+ * state (the private-overlay pointer) at one stable place. "" when not a git repo. */
155
+ export declare function gitCommonDir(cwd: string): string;
156
+ /** True when `cwd` is inside a LINKED worktree (not the main checkout): its own git
157
+ * dir differs from the shared common dir. Used by `hunch doctor` and setup messaging. */
158
+ export declare function isLinkedWorktree(cwd: string): boolean;
159
+ /** Current branch name (e.g. "main", "feat/x"), or "" in detached HEAD / non-repo.
160
+ * Stamped onto auto-captured decisions so branch-scoped work stays filterable. */
161
+ export declare function currentBranch(cwd: string): string;
162
+ /** Files changed in a single commit. `--root` makes the initial commit (which
163
+ * has no parent) report its files as additions instead of returning nothing. */
164
+ export declare function commitFiles(sha: string, cwd: string): string[];
165
+ /** Raw `git log` over `.hunch/`, paired with parseMemoryLog — the memory-move
166
+ * timeline (each commit that changed the graph). Newest first; empty on any error
167
+ * (no repo / no history), so the caller degrades to an empty timeline. */
168
+ export declare function gitMemoryLog(root: string, limit?: number): string;
169
+ /** The diff of a single commit restricted to `.hunch/` — what one memory move
170
+ * actually changed, for the click-through popup. Empty on error. */
171
+ export declare function memoryMoveDiff(sha: string, root: string): string;
172
+ /** Push the current branch to its remote (the "approve-to-push" step — public
173
+ * memory rides the repo, so this is a plain branch push). Returns true on success;
174
+ * false when there is no upstream / offline / not a repo. */
175
+ export declare function pushCurrentBranch(root: string): boolean;
176
+ /** Revert one validated memory-only move locally (no push). Returns true on
177
+ * success. Unsafe targets are refused before mutation; a conflicting revert is
178
+ * aborted so the working tree is never left half-reverted. */
179
+ export declare function revertMemoryMove(sha: string, root: string): boolean;
180
+ /** Full metadata + changed files for a commit. */
181
+ export declare function commitMeta(sha: string, cwd: string): CommitMeta | null;
182
+ export interface CommitFileChange {
183
+ status: "added" | "modified" | "deleted" | "renamed" | "copied";
184
+ before: string | null;
185
+ after: string | null;
186
+ }
187
+ /** First-parent and exact blob seams for deterministic before/after analysis.
188
+ * They never check out a ref or mutate the active worktree. */
189
+ export declare function firstParent(sha: string, cwd: string): string | null;
190
+ export declare function fileAtRef(ref: string, file: string, cwd: string): string | null;
191
+ /** Name-status records for one commit, rename-aware and NUL-delimited so paths
192
+ * with whitespace cannot corrupt the parser. */
193
+ export declare function commitChanges(sha: string, cwd: string): CommitFileChange[];
194
+ export interface CommitCandidate {
195
+ sha: string;
196
+ files: string[];
197
+ }
198
+ /** Every commit newly reachable in `oldRef..newRef` (capped to the most
199
+ * recent `maxCount`), with the files each one changed (rename-aware, via
200
+ * commitChanges). Used to find the commit a squash-merge produced from a
201
+ * set of now-orphaned source-branch commits. */
202
+ export declare function mergeRangeChanges(oldRef: string, newRef: string, cwd: string, maxCount?: number): CommitCandidate[];
203
+ /** Batched existence check for many commit shas at once — one `git cat-file
204
+ * --batch-check` process instead of one `rev-parse --verify` per sha. Used
205
+ * by drift.ts's default commitResolvable so hunch drift/heal/doctor spawn a
206
+ * single git process regardless of how many decisions carry a commit, not
207
+ * one per decision. Returns the subset of `shas` that resolve to a real
208
+ * commit object, or `null` if the check itself failed to run (not a git
209
+ * repo, git missing, timeout) — distinct from an empty set, which means
210
+ * "checked, and none of them resolve." A caller that collapsed those two
211
+ * would flag every commit as unresolvable on a transient environment error
212
+ * instead of failing open. */
213
+ export declare function commitsExist(shas: readonly string[], cwd: string): Set<string> | null;
214
+ export type CommitRepairStatus = "orphaned" | "current" | "unresolvable" | "unknown";
215
+ /** Classifies `commit` relative to `ref` for squash-merge repair eligibility.
216
+ * "orphaned" (exists in this repo, but is not an ancestor of `ref`) is the
217
+ * ONLY status planCommitRepair should ever act on. "unresolvable" (the
218
+ * commit doesn't exist here at all) is explicitly out of repair scope —
219
+ * drift.ts's commit-unresolvable kind is what flags that case, because
220
+ * there's no reliable signal left to match a replacement against. "unknown"
221
+ * (the git command itself failed to run — timeout, git missing) must never
222
+ * be treated as repair-eligible; collapsing "false" and "error" into one
223
+ * boolean is exactly the bug this type exists to prevent. */
224
+ export declare function commitRepairStatus(commit: string, ref: string, cwd: string): CommitRepairStatus;
225
+ /** The unified diff for a commit, truncated to keep synthesis prompts bounded.
226
+ * Machine-generated noise (see DIFF_NOISE) is excluded so the model spends its
227
+ * budget on code that encodes intent, not on regenerated lockfiles/build output. */
228
+ export declare function commitDiff(sha: string, cwd: string, maxBytes?: number): string;
229
+ /** Number of commits touching a file in the last `days` (churn). */
230
+ export declare function fileChurn(file: string, cwd: string, days?: number): number;
231
+ /** The most recent commit short-sha that touched a file. */
232
+ export declare function lastCommitForFile(file: string, cwd: string): string;
233
+ /** Full SHA of the commit that introduced a path. Unlike lastCommitForFile this
234
+ * remains stable when lifecycle/proof updates later touch the same policy file. */
235
+ export declare function firstCommitForFile(file: string, cwd: string): string;
236
+ /** ISO author-date of the most recent commit touching a file ("" if none). */
237
+ export declare function lastChangeDate(file: string, cwd: string): string;
238
+ /**
239
+ * Newest author date for every changed path selected by a bounded set of repository-relative
240
+ * Hunch scopes. One Git history walk replaces the per-file process loop used by `staleness()`.
241
+ *
242
+ * The return keys are the concrete paths Git observed, not the input scopes: callers can apply
243
+ * Hunch's own exact/glob/directory matcher without treating Git pathspec interpretation as graph
244
+ * authority. A failed or oversized read returns null, so freshness stays unknown rather than
245
+ * partially scoring a record.
246
+ */
247
+ export declare function scopedLastChangeDates(scopes: readonly string[], cwd: string, maxChangedPaths?: number): Map<string, string> | null;
248
+ /** Batched per-file git metrics for indexing: churn (commits touching the file in
249
+ * the last `days`; pass 0 to skip) and the most-recent commit (`commit:<sha>`).
250
+ *
251
+ * Replaces the indexer's O(files) × 2 `git log` spawns — which dominate
252
+ * `hunch index` wall-time on a large repo, especially on Windows where process
253
+ * creation is costly — with ONE `git log` pass each. Only paths present in `want`
254
+ * are returned (every requested path gets an entry, defaulting to 0 / ""). */
255
+ export declare function fileGitMetrics(cwd: string, want: Iterable<string>, days?: number): Map<string, {
256
+ churn: number;
257
+ lastCommit: string;
258
+ }>;
259
+ /** Files staged for commit (for `hunch check` pre-commit enforcement).
260
+ *
261
+ * Every path enumerator here pins `core.quotePath=false` (issue #50): with
262
+ * git's default quotePath, any path holding bytes > 0x7F comes back
263
+ * octal-quoted (`"src/caf\303\251.ts"`), which matches neither the store's
264
+ * POSIX paths nor constraint scope globs — a blocking constraint over such a
265
+ * file graded as a vacuous PASS, and its churn/last-commit metrics read zero. */
266
+ export declare function stagedFiles(cwd: string): string[];
267
+ /** Files changed anywhere in the working tree compared with HEAD: both staged
268
+ * and unstaged tracked files, plus untracked files. This powers the local,
269
+ * pre-commit Change Gate; it never mutates the index or asks an agent/model. */
270
+ export declare function workingFiles(cwd: string): string[];
271
+ /** Does a ref resolve to a commit in this repo? Lets `--base` fail LOUDLY on an
272
+ * unfetched/typo'd ref instead of silently diffing against nothing (a vacuous
273
+ * CI pass), since the diff helpers below swallow git errors to "". */
274
+ export declare function revExists(ref: string, cwd: string): boolean;
275
+ /** Files a PR/branch changes vs `base` (3-dot: changes on HEAD since the merge-base,
276
+ * i.e. exactly the PR's own commits — the CI Constraint Guard's surface). */
277
+ export declare function rangeFiles(base: string, cwd: string, head?: string): string[];
278
+ /** Commit subjects on `head` since `base` (2-dot: commits added by the task),
279
+ * oldest-first, for distilling a runbook's ordered steps (roadmap #5). */
280
+ export declare function rangeSubjects(base: string, cwd: string, head?: string, max?: number): string[];
281
+ /** The PR's unified diff vs `base` (3-dot), for the Regression Guard's structural
282
+ * analysis. Same noise-exclusion + truncation budget as commit/staged diffs. */
283
+ export declare function rangeDiff(base: string, cwd: string, head?: string, maxBytes?: number): string;
284
+ /** Unified diff of the staged changes (for the Regression Guard's structural
285
+ * analysis). Excludes machine-generated noise and truncates at the SAME budget as
286
+ * commitDiff, so the staged and `--commit` guard paths can't diverge on big diffs. */
287
+ export declare function stagedDiff(cwd: string, maxBytes?: number): string;
288
+ /** Unified diff of the complete local working tree vs HEAD. Git's normal diff
289
+ * includes both staged and unstaged tracked edits; untracked text files are
290
+ * appended as synthetic additions so guards can also see their added symbols.
291
+ * Binary/unreadable files remain in workingFiles (scope checks still apply) but
292
+ * intentionally contribute no synthetic content to regression analysis. */
293
+ export declare function workingDiff(cwd: string, maxBytes?: number): string;
294
+ /** Resolve a time-travel ref (commit / tag / branch / HEAD~n) to the ISO author-
295
+ * date of that commit — the instant valid-time windows are filtered against.
296
+ * Undefined if it can't be resolved (not a git repo, or an unknown ref). Single
297
+ * source for the CLI and MCP as-of paths so they can't drift. */
298
+ export declare function asOfDate(ref: string, cwd: string): string | undefined;
299
+ /** Recent commits (newest-first) for backfill. */
300
+ export declare function logSince(spec: string, cwd: string, max?: number): string[];
301
+ /** Commits that look like bug fixes (for backfill bug seeding). */
302
+ export declare function fixCommits(spec: string, cwd: string, max?: number): string[];
303
+ /** All tracked files matching the given extensions. */
304
+ export declare function trackedFiles(cwd: string, exts: string[]): string[];