@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,47 @@
1
+ /** Decision logic for the strict agent hook (`hunch hook`): does editing a file
2
+ * hit a BLOCKING invariant — directly (its scope matches) or via blast radius
3
+ * (a guarded dependency)? Extracted from the CLI so it is unit-testable against a
4
+ * real HunchStore, and so the model-facing refusal text lives in one audited
5
+ * place. Mirrors the `hunch check` direct/near logic, blocking-severity only. */
6
+ import type { HunchStore } from "../store/hunchStore.js";
7
+ export interface BlockingHit {
8
+ /** Refusal text fed back to the model as the deny reason. Deliberately states
9
+ * ONLY the invariant — never how to disable the guard, so an autonomous agent
10
+ * cannot be coached into lowering enforcement to get its edit through. */
11
+ reason: string;
12
+ /** Structured provenance for the catch-log (core/events.ts). `reason` alone can't
13
+ * be aggregated; these carry which rule earned its keep. A constraint block sets
14
+ * `constraint`; a veto block sets `decision`. IDS ONLY — never rule TEXT: the hook
15
+ * writes to the PUBLIC repo's events.log but can enforce a PRIVATE overlay rule, so
16
+ * the log must stay leak-safe (an opaque id resolves to text only where that record
17
+ * is visible). No subject/object/assert here — a plain block has no conformance
18
+ * predicate to report (dec_6253f7e6d6). */
19
+ event: {
20
+ kind: "constraint" | "veto";
21
+ constraint?: string;
22
+ decision?: string;
23
+ };
24
+ }
25
+ /** Return a BlockingHit if editing `file` (repo-relative) hits a blocking
26
+ * invariant directly or through its blast radius, else null. `proposedAddedLines`
27
+ * are the lines the edit would ADD: a CONTENT-MATCHED invariant (one carrying a
28
+ * `match` regex) denies ONLY when those lines actually trip it — so it stays quiet
29
+ * on edits that don't break the rule, instead of blocking every edit in scope
30
+ * (dec_e0a36efbf5). Scope-only invariants keep the blunt scope-touch behavior. */
31
+ export declare function blockingInScope(store: HunchStore, file: string, proposedAddedLines?: string[]): BlockingHit | null;
32
+ /** Return a BlockingHit if the proposed added lines for `file` re-introduce an
33
+ * approach an in-force decision deliberately REJECTED (a blocking veto), else null.
34
+ * The deny text states ONLY the decision + receipt (what was rejected, what was
35
+ * chosen) — never how to supersede or disable the guard, so an autonomous agent
36
+ * cannot be coached into reversing a decision to land its edit (dec_a466655539). */
37
+ /** Flatten the proposed-edit text from a PreToolUse `tool_input` across all three
38
+ * edit tools — Edit (`new_string`), Write (`content`), MultiEdit (`edits[].new_string`)
39
+ * — into candidate added lines for the Veto Guard. Empty input → []. */
40
+ export declare function proposedEditLines(toolInput: {
41
+ new_string?: string;
42
+ content?: string;
43
+ edits?: Array<{
44
+ new_string?: string;
45
+ }>;
46
+ } | undefined): string[];
47
+ export declare function vetoInScope(store: HunchStore, file: string, proposedAddedLines: string[]): BlockingHit | null;
@@ -0,0 +1,36 @@
1
+ export declare function shortHash(input: string, len?: number): string;
2
+ /** Full sha1 (used for signature_hash etc.). */
3
+ export declare function sha1(input: string): string;
4
+ /** Symbol id from file + name + kind — deterministic across re-indexes. */
5
+ export declare function symbolId(file: string, name: string, kind: string): string;
6
+ /** Component id from a stable name. */
7
+ export declare function componentId(name: string): string;
8
+ /** Edge id from its endpoints + type — deterministic, dedupes naturally. */
9
+ export declare function edgeId(from: string, to: string, type: string): string;
10
+ /** Stable Engineering Landscape resource identity. The kind remains visible so
11
+ * fragments stay useful without a lookup table; the natural key is normalized
12
+ * only where spelling cannot carry meaning (outer whitespace, path separators,
13
+ * and a trailing slash). Kind-specific discovery may apply stricter canonical
14
+ * rules before calling this helper. */
15
+ export declare function resourceId(kind: string, naturalKey: string): string;
16
+ /** Resource relationships ride the existing edge graph and therefore share its
17
+ * endpoint/type identity rule. Keeping one helper prevents a parallel graph from
18
+ * minting incompatible relationship ids. */
19
+ export declare function resourceRelationshipId(from: string, to: string, type: string): string;
20
+ /** Decision id. Seed with the CANONICAL full commit sha (the auto-sync and MCP
21
+ * commit paths both do this, so a recorded decision upgrades the auto-draft for
22
+ * the same commit), or with "manual:<title>" for an ad-hoc MCP decision. */
23
+ export declare function decisionId(seed: string): string;
24
+ /** Bug id seeded by symptom/test so the same failure doesn't spawn duplicates. */
25
+ export declare function bugId(seed: string): string;
26
+ /** Runbook id seeded by its task; trim + lowercase so re-capturing the same task
27
+ * is idempotent rather than minting a duplicate. */
28
+ export declare function runbookId(seed: string): string;
29
+ /** Constraint id seeded by its statement. Trim + lowercase so trivial
30
+ * whitespace/case variants of the same rule collapse to one id (idempotent
31
+ * re-capture), instead of minting a duplicate constraint. */
32
+ export declare function constraintId(statement: string): string;
33
+ /** Finding id seeded by its title (trim + lowercase, same idiom as constraints):
34
+ * re-recording the same observation UPDATES it (e.g. a triage change) instead of
35
+ * minting a duplicate. A genuinely new observation deserves a new title. */
36
+ export declare function findingId(title: string): string;
@@ -0,0 +1,30 @@
1
+ import type { Decision } from "./types.js";
2
+ export type ImportedAdrDisposition = "approve" | "decline";
3
+ export interface ImportedAdrReview {
4
+ disposition: ImportedAdrDisposition;
5
+ sourceHash: string;
6
+ reviewHash: string;
7
+ reviewer: string | null;
8
+ reviewedAt: string | null;
9
+ }
10
+ export declare function isImportedAdrDecision(decision: Decision): boolean;
11
+ export declare function importedAdrSourceHash(decision: Decision): string | null;
12
+ /** Hash the complete mapped meaning, not just the ADR source bytes. This prevents
13
+ * a later importer/parser change from carrying old authority onto new semantics
14
+ * even when the Markdown file itself did not change. */
15
+ export declare function importedAdrReviewHash(decision: Decision): string;
16
+ export declare function importedAdrReview(decision: Decision): ImportedAdrReview | null;
17
+ export declare function isPendingImportedAdrReview(decision: Decision): boolean;
18
+ export declare function pendingImportedAdrReviews(decisions: readonly Decision[]): Decision[];
19
+ /** Apply one explicit answer to one exact live imported ADR. Never changes the
20
+ * ADR lifecycle or content: decline means "reviewed, keep advisory", not delete. */
21
+ export declare function applyImportedAdrReview(decision: Decision, input: {
22
+ disposition: ImportedAdrDisposition;
23
+ expectedSourceHash: string;
24
+ expectedReviewHash: string;
25
+ reviewer: string;
26
+ reviewedAt?: string;
27
+ }): Decision;
28
+ /** Carry a review across idempotent re-import only when the source bytes match.
29
+ * A changed hash deliberately returns the clean new import, reopening review. */
30
+ export declare function carryImportedAdrReview(previous: Decision | null | undefined, next: Decision): Decision;
@@ -0,0 +1,23 @@
1
+ /**
2
+ * Write `data` to `file` via a temp file + rename, so an interrupted write can't
3
+ * leave the target truncated (the symbols/edges index is the worst to half-write).
4
+ *
5
+ * Durability (issue #34): the temp file is fsync'd BEFORE the rename, and the
6
+ * parent directory best-effort after it. A process kill was always safe (page
7
+ * cache preserves ordering), but on power loss / OS crash the rename's metadata
8
+ * could reach disk before the temp file's data blocks — leaving the target
9
+ * present but truncated or garbage, the exact state the atomic-write invariant
10
+ * (con_902759b3dc) exists to prevent.
11
+ *
12
+ * Windows caveat: renameSync can't REPLACE a file another process holds open (even
13
+ * for read) — it throws EPERM/EBUSY/EACCES, exactly when the MCP server is reading
14
+ * while a CLI writes. Retry that atomic replacement with bounded backoff. If the
15
+ * contention persists, fail with the old target untouched; never trade availability
16
+ * for a direct write that an interruption could truncate. Failed writes clean up the
17
+ * temporary file.
18
+ */
19
+ export declare function writeFileAtomic(file: string, data: string): void;
20
+ /** Atomically create a complete file only when no target exists. A same-dir
21
+ * hard link publishes the fully written temp inode with create-if-absent
22
+ * semantics, so concurrent lifecycle writers can never be overwritten. */
23
+ export declare function writeFileAtomicIfAbsent(file: string, data: string): boolean;
@@ -0,0 +1,4 @@
1
+ /** Parse JSON with the comments and trailing commas accepted by VS Code JSONC.
2
+ * String-aware scanning keeps comment-looking text and commas inside strings
3
+ * untouched. Callers still validate the returned shape for their own contract. */
4
+ export declare function parseJsonc(raw: string): unknown;
@@ -0,0 +1,66 @@
1
+ import { type Edge, type Resource } from "./types.js";
2
+ import { type LandscapeDiscoveryResult } from "../extractors/landscapeDiscovery.js";
3
+ export declare const LANDSCAPE_REVIEW_SCHEMA_VERSION: "hunch.landscape-review/1";
4
+ export declare const LANDSCAPE_ADOPTION_RECEIPT_SCHEMA_VERSION: "hunch.landscape-adoption-receipt/1";
5
+ export declare const MAX_LANDSCAPE_REFRESH_REVISIONS = 16;
6
+ export interface LandscapeReview {
7
+ schema: typeof LANDSCAPE_REVIEW_SCHEMA_VERSION;
8
+ authority: "human_confirmed";
9
+ reviewId: string;
10
+ reviewer: string;
11
+ reviewedAt: string;
12
+ discoveryHash: string;
13
+ sourceRevision: string;
14
+ repositoryRootIdentity: string;
15
+ selectedCandidateHashes: string[];
16
+ acknowledgedIssueCodes: string[];
17
+ }
18
+ export interface LandscapeAdoptionReceipt {
19
+ schema: typeof LANDSCAPE_ADOPTION_RECEIPT_SCHEMA_VERSION;
20
+ authority: "human_confirmed";
21
+ receiptId: string;
22
+ review: LandscapeReview;
23
+ acceptedResourceIds: string[];
24
+ acceptedRelationshipIds: string[];
25
+ writtenResourceIds: string[];
26
+ writtenRelationshipIds: string[];
27
+ reusedResourceIds: string[];
28
+ reusedRelationshipIds: string[];
29
+ }
30
+ export interface LandscapeAdoptionPlan {
31
+ receipt: LandscapeAdoptionReceipt;
32
+ resourcesToWrite: Resource[];
33
+ relationshipsToWrite: Edge[];
34
+ /** Existing, byte-proven adoption records replaced by this reviewed revision. */
35
+ refreshedResourceIds: string[];
36
+ refreshedRelationshipIds: string[];
37
+ }
38
+ export interface PlanLandscapeAdoptionInput {
39
+ discovery: LandscapeDiscoveryResult;
40
+ expectedDiscoveryHash: string;
41
+ reviewer: string;
42
+ reviewedAt?: string;
43
+ /** `all` remains an explicit operator choice; otherwise every hash is named. */
44
+ candidateHashes: "all" | string[];
45
+ acknowledgeIssues?: boolean;
46
+ existingResources?: Resource[];
47
+ existingRelationships?: Edge[];
48
+ /**
49
+ * Explicitly allow a newer exact revision to replace records from a prior
50
+ * reviewed adoption. Every replacement still requires a matching entry in
51
+ * `previousDiscoveries` so the old accepted bytes can be reconstructed and
52
+ * verified; metadata alone never grants replacement authority.
53
+ */
54
+ refreshReviewed?: boolean;
55
+ previousDiscoveries?: LandscapeDiscoveryResult[];
56
+ }
57
+ /** Refuse a result that was changed after exact-revision discovery. */
58
+ export declare function assertLandscapeDiscoveryIntegrity(discovery: LandscapeDiscoveryResult): void;
59
+ /**
60
+ * Convert an exact candidate discovery into a prevalidated write plan.
61
+ *
62
+ * Planning has no side effects. The caller persists only `resourcesToWrite` and
63
+ * `relationshipsToWrite` through the ordinary Hunch capture boundary after this
64
+ * function has proved every selection, endpoint and existing-record conflict.
65
+ */
66
+ export declare function planLandscapeAdoption(input: PlanLandscapeAdoptionInput): LandscapeAdoptionPlan;
@@ -0,0 +1,76 @@
1
+ import { type Edge, type Resource } from "./types.js";
2
+ export declare const LANDSCAPE_FRAGMENT_SCHEMA_VERSION: "hunch.landscape-fragment/1";
3
+ export type LandscapeSelectionReason = "exact-target" | "task-match" | "orientation-root" | "graph-neighbor";
4
+ export interface SelectedLandscapeResource {
5
+ record: Resource;
6
+ selectionReason: LandscapeSelectionReason;
7
+ selectionRank: number;
8
+ }
9
+ export interface SelectedLandscapeRelationship {
10
+ record: Edge;
11
+ selectionReason: "graph-connection";
12
+ selectionRank: number;
13
+ }
14
+ export interface LandscapeSelectionOmission {
15
+ kind: "resources" | "relationships";
16
+ recordId: string;
17
+ reason: "landscape-cap";
18
+ detail: string;
19
+ }
20
+ export interface ReviewedLandscapeSelection {
21
+ schema: typeof LANDSCAPE_FRAGMENT_SCHEMA_VERSION;
22
+ authority: "human_confirmed";
23
+ target: string;
24
+ resources: SelectedLandscapeResource[];
25
+ relationships: SelectedLandscapeRelationship[];
26
+ omitted: LandscapeSelectionOmission[];
27
+ }
28
+ export interface DeliveredLandscapeResource extends SelectedLandscapeResource {
29
+ rank: number;
30
+ deliveryReason: "ranked";
31
+ required: false;
32
+ blocking: false;
33
+ provenanceStatus: "current";
34
+ tokenCost: number;
35
+ }
36
+ export interface DeliveredLandscapeRelationship extends SelectedLandscapeRelationship {
37
+ rank: number;
38
+ deliveryReason: "ranked";
39
+ required: false;
40
+ blocking: false;
41
+ provenanceStatus: "current";
42
+ tokenCost: number;
43
+ }
44
+ export interface LandscapeFragmentOmission {
45
+ kind: "resources" | "relationships";
46
+ recordId: string;
47
+ reason: "budget" | "stale-provenance" | "endpoint-not-delivered" | "landscape-cap";
48
+ detail: string;
49
+ }
50
+ export interface LandscapeDeliveryFragment {
51
+ schema: typeof LANDSCAPE_FRAGMENT_SCHEMA_VERSION;
52
+ authority: "human_confirmed";
53
+ target: string;
54
+ resources: DeliveredLandscapeResource[];
55
+ relationships: DeliveredLandscapeRelationship[];
56
+ omitted: LandscapeFragmentOmission[];
57
+ reviewIds: string[];
58
+ discoveryHashes: string[];
59
+ sourceRevisions: string[];
60
+ fragmentHash: string;
61
+ }
62
+ export declare function landscapeFragmentHash(value: unknown): string;
63
+ /**
64
+ * Select a small orientation fragment from durable graph records.
65
+ *
66
+ * Candidate/unreviewed/stale records are excluded before ranking, so neither a
67
+ * good lexical match nor a graph edge can accidentally upgrade their authority.
68
+ */
69
+ export declare function selectReviewedLandscape(resources: readonly Resource[], relationships: readonly Edge[], target: string, maxItems?: number): ReviewedLandscapeSelection;
70
+ export declare function createLandscapeDeliveryFragment(input: {
71
+ selection: ReviewedLandscapeSelection;
72
+ resources: DeliveredLandscapeResource[];
73
+ relationships: DeliveredLandscapeRelationship[];
74
+ omitted: LandscapeFragmentOmission[];
75
+ }): LandscapeDeliveryFragment;
76
+ export declare function assertLandscapeDeliveryFragment(value: LandscapeDeliveryFragment): void;
@@ -0,0 +1,37 @@
1
+ /**
2
+ * The memory-move timeline — the data behind `hunch log` and the VS Code "Hunch
3
+ * Source Control" view. Every commit that touched `.hunch/` is one MOVE (a capture,
4
+ * adoption, supersession, or prune), classified deterministically from git's own
5
+ * name-status. Pure parsing — no LLM, no judgment — so the panel shows exactly what
6
+ * git recorded and each move maps back to a real, revertable commit.
7
+ *
8
+ * The parser is split from the git shell-out (extractors/git.gitMemoryLog) so it is
9
+ * unit-testable with canned `git log` output.
10
+ */
11
+ export type MemoryMoveKind = "capture" | "adopt" | "supersede" | "prune" | "repair" | "edit";
12
+ /** One commit that changed the memory graph, ready to render as a timeline entry. */
13
+ export interface MemoryMove {
14
+ sha: string;
15
+ shortSha: string;
16
+ /** committer date, ISO 8601. */
17
+ date: string;
18
+ subject: string;
19
+ kind: MemoryMoveKind;
20
+ /** decision ids (dec_*) this move touched — for the click-through popup. */
21
+ decisionIds: string[];
22
+ /** other record ids touched (bugs/constraints/components/policies). */
23
+ otherIds: string[];
24
+ added: number;
25
+ modified: number;
26
+ deleted: number;
27
+ /** the `.hunch/` files this commit changed. */
28
+ files: string[];
29
+ }
30
+ /** The record-header separator we ask `git log --format` to emit, so header lines
31
+ * are unambiguous against the name-status lines that follow each commit. */
32
+ export declare const MEMLOG_HEADER = "@@@";
33
+ /** The `--format` string that pairs with {@link parseMemoryLog}. */
34
+ export declare const MEMLOG_FORMAT = "@@@%H\t%h\t%cI\t%s";
35
+ /** Parse `git log <MEMLOG_FORMAT> --name-status -- .hunch/` output into classified
36
+ * moves, newest first (git's order). */
37
+ export declare function parseMemoryLog(raw: string): MemoryMove[];
@@ -0,0 +1,28 @@
1
+ import type { EntityKind } from "./types.js";
2
+ import type { HunchPaths } from "./paths.js";
3
+ /** The schema generation this build writes and reads. Bump on any breaking change. */
4
+ export declare const SCHEMA_VERSION = 3;
5
+ /** A repo whose `.hunch/` predates manifests is treated as v1. Migrations are
6
+ * numbered from 2 (each `version` is the number it PRODUCES), so a baseline repo
7
+ * runs every migration with version >= 2 — never author a no-op version:1 one. */
8
+ export declare const BASELINE_VERSION = 1;
9
+ export interface Migration {
10
+ /** The version this migration PRODUCES (i.e. it upgrades version-1 → `version`). */
11
+ version: number;
12
+ description: string;
13
+ /** Transform one raw record (untrusted JSON) of `kind` to the next shape. */
14
+ up(kind: EntityKind, raw: Record<string, unknown>): Record<string, unknown>;
15
+ }
16
+ /** Ordered, ascending by `version`. Empty at v1 (baseline); future versions append. */
17
+ export declare const MIGRATIONS: Migration[];
18
+ export interface Manifest {
19
+ schema_version: number;
20
+ }
21
+ /** Read `.hunch/manifest.json`. A missing/corrupt manifest is treated as the
22
+ * BASELINE version (a pre-manifest `.hunch/`), so future builds still migrate it. */
23
+ export declare function readManifest(paths: HunchPaths): Manifest;
24
+ /** Write `.hunch/manifest.json` at `version` (default: the current SCHEMA_VERSION). */
25
+ export declare function writeManifest(paths: HunchPaths, version?: number): void;
26
+ /** Apply every migration in (fromVersion, toVersion] to a single raw record. Skips
27
+ * non-object input untouched (the loader's Zod pass will reject it). */
28
+ export declare function migrateRaw(kind: EntityKind, raw: unknown, fromVersion: number, migrations?: Migration[], toVersion?: number): unknown;
@@ -0,0 +1,89 @@
1
+ import { z } from "zod";
2
+ import { type Finding } from "./types.js";
3
+ export declare const USEFULNESS_OBSERVATION_SCHEMA_VERSION: "hunch.usefulness-observation/1";
4
+ export declare const USEFULNESS_SIGNALS: readonly ["used", "prevented", "near_miss", "contradicted", "stale", "unused", "unknown"];
5
+ export declare const UsefulnessObservationSchema: z.ZodObject<{
6
+ schema: z.ZodLiteral<"hunch.usefulness-observation/1">;
7
+ observationId: z.ZodString;
8
+ episode: z.ZodObject<{
9
+ provider: z.ZodString;
10
+ schemaVersion: z.ZodString;
11
+ episodeId: z.ZodString;
12
+ episodeHash: z.ZodString;
13
+ terminalAt: z.ZodString;
14
+ result: z.ZodEnum<{
15
+ fail: "fail";
16
+ pass: "pass";
17
+ uncertain: "uncertain";
18
+ abandoned: "abandoned";
19
+ rolled_back: "rolled_back";
20
+ }>;
21
+ }, z.core.$strict>;
22
+ delivery: z.ZodObject<{
23
+ receiptRef: z.ZodString;
24
+ receiptHash: z.ZodString;
25
+ graphRevision: z.ZodString;
26
+ sourceRevision: z.ZodString;
27
+ sourceContentHash: z.ZodString;
28
+ }, z.core.$strict>;
29
+ record: z.ZodObject<{
30
+ recordId: z.ZodString;
31
+ recordKind: z.ZodString;
32
+ recordRevision: z.ZodString;
33
+ contentHash: z.ZodString;
34
+ }, z.core.$strict>;
35
+ change: z.ZodOptional<z.ZodObject<{
36
+ schema: z.ZodLiteral<"hunch.change-identity/1">;
37
+ algorithm: z.ZodLiteral<"git-raw-tree-delta-sha256/1">;
38
+ change_id: z.ZodString;
39
+ base_revision: z.ZodString;
40
+ head_revision: z.ZodString;
41
+ base_tree: z.ZodString;
42
+ head_tree: z.ZodString;
43
+ delta_hash: z.ZodString;
44
+ patch_id: z.ZodNullable<z.ZodString>;
45
+ file_count: z.ZodNumber;
46
+ paths_hash: z.ZodString;
47
+ content_hash: z.ZodString;
48
+ }, z.core.$strict>>;
49
+ signal: z.ZodEnum<{
50
+ unknown: "unknown";
51
+ stale: "stale";
52
+ used: "used";
53
+ prevented: "prevented";
54
+ near_miss: "near_miss";
55
+ contradicted: "contradicted";
56
+ unused: "unused";
57
+ }>;
58
+ evidence: z.ZodArray<z.ZodObject<{
59
+ kind: z.ZodEnum<{
60
+ observation: "observation";
61
+ artifact: "artifact";
62
+ receipt: "receipt";
63
+ event: "event";
64
+ verification: "verification";
65
+ }>;
66
+ ref: z.ZodString;
67
+ hash: z.ZodString;
68
+ }, z.core.$strict>>;
69
+ observedAt: z.ZodString;
70
+ retainUntil: z.ZodString;
71
+ privacy: z.ZodObject<{
72
+ payloadMode: z.ZodLiteral<"references_hashes_only">;
73
+ rawTranscriptIncluded: z.ZodLiteral<false>;
74
+ rawProviderOutputIncluded: z.ZodLiteral<false>;
75
+ }, z.core.$strict>;
76
+ authority: z.ZodObject<{
77
+ behavioralEffect: z.ZodLiteral<"none">;
78
+ mayChangeRanking: z.ZodLiteral<false>;
79
+ mayPromoteKnowledge: z.ZodLiteral<false>;
80
+ mayGrantAuthority: z.ZodLiteral<false>;
81
+ }, z.core.$strict>;
82
+ contentHash: z.ZodString;
83
+ }, z.core.$strict>;
84
+ export type UsefulnessObservation = z.infer<typeof UsefulnessObservationSchema>;
85
+ export type CreateUsefulnessObservationInput = Omit<UsefulnessObservation, "schema" | "observationId" | "authority" | "contentHash">;
86
+ export declare function createUsefulnessObservation(input: CreateUsefulnessObservationInput): UsefulnessObservation;
87
+ export declare function assertUsefulnessObservation(value: unknown): asserts value is UsefulnessObservation;
88
+ /** Contradiction and staleness become open advisory review work, never truth. */
89
+ export declare function usefulnessObservationFinding(value: unknown): Finding | null;
@@ -0,0 +1,24 @@
1
+ /** Validate the materialized overlay without following links. Git's metadata is
2
+ * deliberately skipped, but its own directory must still be contained under the
3
+ * canonical overlay root. Every remotely controlled entry must be an ordinary
4
+ * file or real directory whose canonical path stays inside that root. */
5
+ export declare function safeOverlayTree(root: string): boolean;
6
+ /** Validate `git ls-tree -r -t -z <exact-oid>` output before checkout. A Git
7
+ * remote can encode symlinks (120000) and gitlinks (160000); accepting only
8
+ * ordinary blobs and trees makes the fetched object graph safe to materialize.
9
+ * The explicit Hunch topology rules keep canonical record directories and
10
+ * capability/config paths from changing shape on a later pull. */
11
+ export declare function safeOverlayGitTreeListing(listing: string): boolean;
12
+ /** A dedicated Hunch overlay needs exactly one attribute capability: selecting
13
+ * the locally installed `merge=hunch` JSON merge driver, plus Hunch's exact
14
+ * `.hunch/manifest.json merge=text` override (the manifest has no record id and
15
+ * must use Git's built-in text merge), and the five exact generated-doc rules
16
+ * installed with the grounding merge driver. Reject every other token/pattern pair,
17
+ * including byte-transforming built-ins such as `ident` and
18
+ * `working-tree-encoding`, rather than maintaining a command-key blacklist.
19
+ * Blank lines and comments remain harmless. */
20
+ export declare function hunchAttributesAreSafe(content: string): boolean;
21
+ /** Validate every committed .gitattributes blob in an already-safe ls-tree
22
+ * listing. Blob loading is injected so clone and later-pull seams share one
23
+ * parser without either trusting worktree bytes before materialization. */
24
+ export declare function hunchTreeAttributesAreSafe(listing: string, readBlob: (oid: string) => string | null): boolean;
@@ -129,7 +129,8 @@ export function safeOverlayGitTreeListing(listing) {
129
129
  /** A dedicated Hunch overlay needs exactly one attribute capability: selecting
130
130
  * the locally installed `merge=hunch` JSON merge driver, plus Hunch's exact
131
131
  * `.hunch/manifest.json merge=text` override (the manifest has no record id and
132
- * must use Git's built-in text merge). Reject every other token/pattern pair,
132
+ * must use Git's built-in text merge), and the five exact generated-doc rules
133
+ * installed with the grounding merge driver. Reject every other token/pattern pair,
133
134
  * including byte-transforming built-ins such as `ident` and
134
135
  * `working-tree-encoding`, rather than maintaining a command-key blacklist.
135
136
  * Blank lines and comments remain harmless. */
@@ -144,6 +145,11 @@ export function hunchAttributesAreSafe(content) {
144
145
  const attributes = fields.slice(1);
145
146
  if (attributes.every((attribute) => attribute === "merge=hunch"))
146
147
  continue;
148
+ // These exact document paths are the only grounding-driver routes installed
149
+ // by Hunch. Never accept a wildcard or allow it to target JSON memory.
150
+ if (["CLAUDE.md", "AGENTS.md", ".github/copilot-instructions.md", ".cursor/rules/hunch.mdc", ".windsurf/rules/hunch.md"].includes(fields[0])
151
+ && attributes.every((attribute) => attribute === "merge=hunch-grounding"))
152
+ continue;
147
153
  if (fields[0] === ".hunch/manifest.json"
148
154
  && attributes.every((attribute) => attribute === "merge=text"))
149
155
  continue;
@@ -0,0 +1,31 @@
1
+ export declare const HUNCH_DIR = ".hunch";
2
+ /** Canonicalize a free-form path/target to repo-relative POSIX form. Hunch stores
3
+ * every path with forward slashes (git emits "/" on all OSes), so any user- or
4
+ * agent-supplied target must be normalized before comparison — otherwise a
5
+ * Windows caller passing `src\auth\session.ts` never matches the stored
6
+ * `src/auth/session.ts`. Safe on symbol names too: they contain no backslashes. */
7
+ export declare function toPosixTarget(target: string): string;
8
+ export interface HunchPaths {
9
+ /** Repo root (where .hunch/ lives). */
10
+ root: string;
11
+ hunch: string;
12
+ sqlite: string;
13
+ /** `.hunch/manifest.json` — records the on-disk schema version. */
14
+ manifest: string;
15
+ /** `.hunch/config.json` — user runtime config (firmness, etc.). */
16
+ config: string;
17
+ dir(kind: string): string;
18
+ }
19
+ export declare function hunchPaths(root: string): HunchPaths;
20
+ /** Build paths for a hunch-layout directory given DIRECTLY — i.e. `hunchDir` IS
21
+ * the dir holding the kind subdirs (decisions/, bugs/, …). Used for an external
22
+ * PRIVATE overlay store (HUNCH_PRIVATE_DIR), which lives in a separate repo the
23
+ * user controls rather than under the current repo's `.hunch/`. */
24
+ export declare function hunchPathsForDir(hunchDir: string): HunchPaths;
25
+ /** Walk up from `start` to the nearest dir containing a .hunch/ dir OR a .git
26
+ * (repo boundary), else `start`. Lets `hunch` run from subdirs. A `.git`
27
+ * WITHOUT `.hunch` stops the walk: an ancestor `.hunch` above the repo
28
+ * boundary belongs to some other scope (e.g. a stray ~/.hunch) and must never
29
+ * hijack a fresh repo — init would scaffold, index, and scan OUTSIDE the repo. */
30
+ export declare function isDir(path: string): boolean;
31
+ export declare function findRoot(start?: string): string;