@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,125 @@
1
+ import type { HunchPaths } from "../core/paths.js";
2
+ import { type EntityKind, type EntityFor } from "../core/types.js";
3
+ /** Curated entities are intentionally small, human-reviewable records. Symbols
4
+ * and edges are dense indexes, so they get a much larger but still finite cap. */
5
+ export declare const MAX_JSON_RECORD_BYTES: number;
6
+ export declare const MAX_JSON_INDEX_BYTES: number;
7
+ export declare const MAX_JSON_MANIFEST_BYTES: number;
8
+ export declare const MAX_JSON_DIRECTORY_ENTRIES_PER_KIND = 100000;
9
+ export declare class JsonStore {
10
+ private readonly paths;
11
+ private _warnedForward;
12
+ /** Memoized validated records per kind. loadAll() is on every read-path method
13
+ * (why/checkConstraints/fragility/assembleContext…), each of which previously
14
+ * re-read the directory and re-ran Zod over every record. Cache keyed by the
15
+ * kind dir's mtime: every write goes through writeFileAtomic (temp + rename) or
16
+ * rmSync, both of which bump the dir mtime — so an OUT-OF-BAND write by another
17
+ * process self-invalidates on the next read. In-process writes also invalidate
18
+ * explicitly (exact, independent of mtime granularity). */
19
+ private cache;
20
+ private readonly lexicalRoot;
21
+ private readonly canonicalRoot;
22
+ private readonly lexicalHunch;
23
+ constructor(paths: HunchPaths);
24
+ private lstatOrMissing;
25
+ private expectedCanonical;
26
+ /** Require an ordinary, canonically-contained directory. Creation is one
27
+ * component at a time after its parent has been validated; recursive mkdir
28
+ * would otherwise traverse a malicious pre-existing symlink. */
29
+ private safeDirectory;
30
+ private safeHunchDirectory;
31
+ private assertKind;
32
+ private safeKindDirectory;
33
+ private maxBytes;
34
+ private assertSafeRecordId;
35
+ private assertFileBelongsTo;
36
+ private validateExistingFile;
37
+ /** Read through the exact descriptor whose type, identity, containment, and
38
+ * finite size were checked. Returning null means the file is absent. */
39
+ private readContainedFile;
40
+ private writeContainedFile;
41
+ private removeContainedFile;
42
+ private jsonFileNames;
43
+ /** Drop memoized loadAll results. Writes through this store invalidate the
44
+ * affected kind automatically; call this for OUT-OF-BAND changes to .hunch/
45
+ * JSON (e.g. the long-lived MCP server reacting to a file-watch, or after an
46
+ * external `hunch migrate`). */
47
+ clearCache(): void;
48
+ /** Cheap revision marker for the JSON source tree. Hunch writes records with
49
+ * temp-file + rename, so every supported add/update/delete bumps the containing
50
+ * kind directory's metadata. Include the manifest separately because a schema
51
+ * migration can change how otherwise-identical record bytes are interpreted.
52
+ * This lets long-lived readers notice another process without hashing or
53
+ * reparsing the complete graph on every request. */
54
+ changeStamp(): string;
55
+ private invalidate;
56
+ /** Create .hunch/<kind>/ directories. Stamp the manifest at the CURRENT version
57
+ * only when scaffolding a FRESH .hunch/ (so `hunch index`/`sync` on a brand-new
58
+ * repo records the version too, not just `init`). A pre-existing .hunch/ without
59
+ * a manifest is LEGACY — left unstamped so it defaults to the baseline and
60
+ * `hunch migrate` upgrades it. */
61
+ ensureDirs(): void;
62
+ /** The on-disk schema version (from the manifest), read FRESH each call so a
63
+ * long-lived process (the MCP server) reflects an out-of-band `hunch migrate`. */
64
+ schemaVersion(): number;
65
+ /** Migrate one raw record UP to the current schema before validation, so a
66
+ * schema bump never makes the loader silently skip (drop) old records. The
67
+ * on-disk `version` is read ONCE per load (not per record) by the caller. */
68
+ private migrate;
69
+ private fileFor;
70
+ /** Load every record of a kind, validated against its schema. Memoized — the
71
+ * returned array is shared and MUST be treated read-only (every caller already
72
+ * derives via filter/map/sort, which copy). Invalidated on write. */
73
+ loadAll<K extends EntityKind>(kind: K): EntityFor[K][];
74
+ /** Uncached disk read + validate. Invalid records are skipped with a warning
75
+ * rather than crashing the whole load. */
76
+ private readAllFromDisk;
77
+ /** Cross-process mutex for single-file index read-modify-write (issue #35).
78
+ * The long-lived MCP server and CLI hooks write the same `.hunch/` concurrently;
79
+ * two unsynchronized RMWs over index.json each read the same base array and the
80
+ * second rename silently erases the first's record. `mkdirSync` is the atomic
81
+ * acquire (EEXIST = held). A stale lock (killed process) is taken over by age;
82
+ * against a live contender we wait briefly and then proceed WITH a warning —
83
+ * never worse than the historical lockless behavior, and capture paths must not
84
+ * start throwing on lock contention. */
85
+ private withSingleFileLock;
86
+ /** Write a single record (validated) to its JSON file / into the index array. */
87
+ put<K extends EntityKind>(kind: K, record: EntityFor[K]): EntityFor[K];
88
+ /** Bulk replace all records of a kind (used by the extractor for symbols/edges). */
89
+ replaceAll<K extends EntityKind>(kind: K, records: EntityFor[K][]): void;
90
+ /** Read a single-file index as a raw array (no validation). Missing/empty → [].
91
+ * A non-empty file that fails to parse THROWS — we must never silently treat a
92
+ * corrupt index as empty and then rewrite it, which would flatten every existing
93
+ * record. (`hunch index` rebuilds from scratch via replaceAll to recover.) */
94
+ private readRawArray;
95
+ get<K extends EntityKind>(kind: K, id: string): EntityFor[K] | undefined;
96
+ /** Direct authoritative state lookup. No directory enumeration or stale cache;
97
+ * migration precedes validation and corrupt files fail closed. */
98
+ getDirect<K extends "derived" | "receipts" | "commitments">(kind: K, id: string): EntityFor[K] | undefined;
99
+ /** On-disk record count, independent of validation: per-record kinds count
100
+ * every non-tombstone .json file (a 0-byte merge tombstone is an intentional
101
+ * absence), single-file kinds count raw array entries (a corrupt index file
102
+ * throws readRawArray's own actionable refusal). Lets a caller about to
103
+ * DELETE the kind — `hunch private --migrate` — prove the validating loader
104
+ * dropped nothing first, instead of silently destroying the records loadAll
105
+ * skipped (issue #29, the same never-silently-drop contract as
106
+ * con_947c578b2c). */
107
+ rawRecordCount(kind: EntityKind): number;
108
+ /** Remove a record (used by the curate/reject flow). Returns true if removed.
109
+ * For single-file kinds we operate on the RAW JSON array (not the validating
110
+ * loader) so deleting one record can't silently drop schema-invalid siblings. */
111
+ delete<K extends EntityKind>(kind: K, id: string): boolean;
112
+ /** Remove EVERY record of a kind from disk (the kind dir's JSON files), keeping
113
+ * the dir itself so the layout/manifest survive. Used by `hunch private --migrate`
114
+ * to empty the PUBLIC store after its records have been moved into the private
115
+ * overlay. Returns the number of files removed. Invalidates the memoized load. */
116
+ dropAll(kind: EntityKind): number;
117
+ /** Persist a schema migration: rewrite every LOADABLE record in its current shape.
118
+ * A record that still fails validation after migration is kept untouched (never
119
+ * deleted) and counted as `skipped`, so migration can't lose data. The caller
120
+ * bumps the manifest afterward. */
121
+ persistMigration(): {
122
+ migrated: number;
123
+ skipped: number;
124
+ };
125
+ }
@@ -0,0 +1,39 @@
1
+ /**
2
+ * Structured three-way merge of `.hunch/` JSON for TEAM workflows.
3
+ *
4
+ * Concurrent branches both writing the Hunch conflict on `git merge`: the
5
+ * single-file symbols/edges index is rewritten wholesale by `hunch index`, and two
6
+ * people can touch the same decision/bug/constraint. A registered git merge driver
7
+ * (`hunch merge-driver`, wired up by `hunch init`) calls mergeHunchJson per
8
+ * conflicted file so records merge BY ID instead of leaving conflict markers.
9
+ *
10
+ * Scope: git only invokes a content merge driver when the SAME path differs on both
11
+ * sides — so this resolves (a) the symbols/edges index ARRAY and (b) edits to the
12
+ * same id in the same per-record file. Per-record ADD/DELETE across branches are
13
+ * distinct files and git handles them at the tree level (the driver isn't called).
14
+ *
15
+ * Resolution for a record changed on BOTH sides: human-confirmed beats auto, then
16
+ * higher provenance.confidence, then the more recently verified, then a deterministic
17
+ * content tiebreak (so both developers' merges converge on the same result). Records
18
+ * are pure data here — no filesystem access; the CLI reads/writes the files.
19
+ */
20
+ type Rec = Record<string, unknown>;
21
+ export interface MergeResult {
22
+ text: string;
23
+ /** true → could not structurally merge (corrupt JSON / id-less records); the
24
+ * caller should fall back to git's normal conflict handling. */
25
+ conflict: boolean;
26
+ }
27
+ /** Merge three versions of one `.hunch` JSON file (an index array OR a single
28
+ * record object). Returns the merged text, or conflict=true to fall back. */
29
+ export declare function mergeHunchJson(baseText: string, oursText: string, theirsText: string): MergeResult;
30
+ /** Three-way merge of record arrays keyed by `id`. Additions on either side are
31
+ * kept; a record changed on one side only takes that side; a delete is honored
32
+ * only if the other side left the record unchanged (a modification beats a delete);
33
+ * a both-sides change is resolved by `pickWinner`. */
34
+ export declare function mergeRecordsById(base: Rec[], ours: Rec[], theirs: Rec[]): Rec[];
35
+ /** Both sides changed the same record: pick the one to keep. */
36
+ export declare function pickWinner(ours: Rec, theirs: Rec): Rec;
37
+ /** Stable JSON with recursively SORTED keys, so equality/compare ignore key order. */
38
+ export declare function canon(v: unknown): string;
39
+ export {};
@@ -0,0 +1,24 @@
1
+ /**
2
+ * Move a repo's PUBLIC engineering memory into its PRIVATE overlay, for
3
+ * `hunch private --migrate` (turn a public-by-default repo into a code-only public
4
+ * repo whose memory lives in a separate private store).
5
+ *
6
+ * The merge is a UNION BY ID: every public record is absorbed into the private
7
+ * store, and a record that already exists on both sides (e.g. a decision present
8
+ * in both) is resolved by the same precedence the merge driver uses (human-confirmed
9
+ * › higher confidence › more recent › deterministic tiebreak). Private-only records
10
+ * are preserved untouched. This NEVER deletes from the public store — the CLI empties
11
+ * it separately (JsonStore.dropAll) only after this returns, so an interrupted move
12
+ * can't lose records.
13
+ */
14
+ import { type EntityKind } from "../core/types.js";
15
+ import type { JsonStore } from "./jsonStore.js";
16
+ export interface MoveResult {
17
+ /** Per-kind count of public records absorbed into the overlay. */
18
+ moved: Partial<Record<EntityKind, number>>;
19
+ /** Total public records absorbed. */
20
+ total: number;
21
+ }
22
+ /** Union every public record into `priv` (by id). Returns what was absorbed.
23
+ * Idempotent: re-running with the same input rewrites identical content. */
24
+ export declare function movePublicMemoryToPrivate(pub: JsonStore, priv: JsonStore): MoveResult;
@@ -0,0 +1,56 @@
1
+ import { type Scope, type StateFacet, type ChangeEvent } from "../core/stateContract.js";
2
+ import { type Ledger } from "./changeLedger.js";
3
+ import type { HunchStore } from "./hunchStore.js";
4
+ export declare const REPLAY_SCHEMA_VERSION: "nuryel.replay/1";
5
+ /** The facets the contract is the ONLY writer of; a record here without a ledger event is an orphan. */
6
+ export declare const STATE_ONLY_FACETS: readonly ["receipts", "commitments", "derived", "entities", "relationships"];
7
+ export type ReplayDivergenceKind = "missing-record" | "hash-drift" | "orphan-record" | "idempotency-drift" | "legacy-drift";
8
+ export interface ReplayDivergence {
9
+ kind: ReplayDivergenceKind;
10
+ facet: StateFacet;
11
+ record_id: string;
12
+ /** The seq of the ledger event the record was checked against (0 when none). */
13
+ seq: number;
14
+ expected_hash: string | null;
15
+ actual_hash: string | null;
16
+ detail: string;
17
+ }
18
+ /** One line per record the fold produced: the state the ledger implies. Sorted by id, so the
19
+ * snapshot hash is order-independent and two clones of one ledger agree on it. */
20
+ export interface ReplaySnapshotEntry {
21
+ facet: StateFacet;
22
+ record_id: string;
23
+ record_hash: string;
24
+ /** The last change the ledger recorded for it. */
25
+ change: ChangeEvent["change"];
26
+ seq: number;
27
+ }
28
+ export interface ReplayReport {
29
+ schema: typeof REPLAY_SCHEMA_VERSION;
30
+ scope: Scope;
31
+ ledger: {
32
+ head_seq: number;
33
+ floor_seq: number;
34
+ events: number;
35
+ idempotency_entries: number;
36
+ };
37
+ /** Hash of the state the ledger implies (the fold) and of the records on file for the same ids —
38
+ * over the facets the contract owns (legacy facets are advisory and excluded). */
39
+ replay_hash: string;
40
+ stored_hash: string;
41
+ records: {
42
+ named_by_ledger: number;
43
+ verified: number;
44
+ verified_by_idempotency: number;
45
+ unverifiable: number;
46
+ legacy_checked: number;
47
+ };
48
+ divergences: ReplayDivergence[];
49
+ /** True when the ledger and the files agree on every record the check can decide. */
50
+ ok: boolean;
51
+ }
52
+ /** Fold a ledger into the state it implies: the last event per record, in seq order. */
53
+ export declare function foldLedger(ledger: Ledger): Map<string, ReplaySnapshotEntry>;
54
+ /** Verify that a partition's stored records are exactly what its ledger implies. Read-only. */
55
+ export declare function verifyReplay(store: HunchStore, scope: Scope): ReplayReport;
56
+ export declare function formatReplayReport(r: ReplayReport): string;
@@ -0,0 +1,17 @@
1
+ /** Canonical content hash of the exact title+body that fed both FTS and the
2
+ * embedding for a doc. Stored in `embeddings.doc_hash` so reindex can tell, with
3
+ * NO model loaded, whether a stored vector is stale (its source text changed).
4
+ * The NUL separator keeps the title/body boundary unambiguous. Reuses the shared
5
+ * sha1-truncate idiom from core/ids so the hashing scheme lives in one place. */
6
+ export declare function embedHash(title: string, body: string): string;
7
+ export declare const SCHEMA_SQL = "\nPRAGMA journal_mode = WAL;\nPRAGMA foreign_keys = OFF;\n\nCREATE TABLE IF NOT EXISTS components (\n id TEXT PRIMARY KEY,\n kind TEXT, name TEXT, responsibility TEXT,\n paths TEXT, status TEXT, owners TEXT,\n fragility REAL,\n prov_source TEXT, prov_confidence REAL, prov_evidence TEXT,\n created_at TEXT, updated_at TEXT\n);\n\nCREATE TABLE IF NOT EXISTS resources (\n id TEXT PRIMARY KEY,\n schema TEXT, kind TEXT, name TEXT, scope TEXT, locator TEXT,\n lifecycle TEXT, criticality TEXT, contract_version TEXT,\n currentness TEXT, metadata TEXT,\n prov_source TEXT, prov_confidence REAL, prov_evidence TEXT,\n created_at TEXT, updated_at TEXT\n);\nCREATE INDEX IF NOT EXISTS idx_resources_kind ON resources(kind);\nCREATE INDEX IF NOT EXISTS idx_resources_lifecycle ON resources(lifecycle);\n\nCREATE TABLE IF NOT EXISTS edges (\n id TEXT PRIMARY KEY,\n \"from\" TEXT, \"to\" TEXT, type TEXT, reason TEXT, strength REAL,\n prov_source TEXT, prov_confidence REAL, prov_evidence TEXT\n);\nCREATE INDEX IF NOT EXISTS idx_edges_from ON edges(\"from\");\nCREATE INDEX IF NOT EXISTS idx_edges_to ON edges(\"to\");\nCREATE INDEX IF NOT EXISTS idx_edges_type ON edges(type);\n\n-- A rebuildable projection over the subset of the existing edge graph carrying\n-- the resource-relationship contract. JSON edges remain the one authority.\nCREATE TABLE IF NOT EXISTS resource_relationships (\n id TEXT PRIMARY KEY,\n schema TEXT, \"from\" TEXT, \"to\" TEXT, type TEXT, reason TEXT, strength REAL,\n currentness TEXT, environment TEXT, criticality TEXT, contract_version TEXT,\n metadata TEXT, prov_source TEXT, prov_confidence REAL, prov_evidence TEXT\n);\nCREATE INDEX IF NOT EXISTS idx_resource_relationships_from ON resource_relationships(\"from\");\nCREATE INDEX IF NOT EXISTS idx_resource_relationships_to ON resource_relationships(\"to\");\nCREATE INDEX IF NOT EXISTS idx_resource_relationships_type ON resource_relationships(type);\n\nCREATE TABLE IF NOT EXISTS symbols (\n id TEXT PRIMARY KEY,\n file TEXT, name TEXT, kind TEXT, signature_hash TEXT,\n calls TEXT, called_by TEXT,\n loc INTEGER, churn_90d INTEGER, bug_count INTEGER, fan_in INTEGER, fan_out INTEGER,\n last_changed TEXT\n);\nCREATE INDEX IF NOT EXISTS idx_symbols_file ON symbols(file);\nCREATE INDEX IF NOT EXISTS idx_symbols_name ON symbols(name);\n\nCREATE TABLE IF NOT EXISTS decisions (\n id TEXT PRIMARY KEY,\n title TEXT, status TEXT, context TEXT, decision TEXT,\n consequences TEXT, alternatives_rejected TEXT,\n related_components TEXT, related_files TEXT,\n supersedes TEXT, caused_by_bug TEXT, \"commit\" TEXT,\n prov_source TEXT, prov_confidence REAL, prov_evidence TEXT,\n date TEXT\n);\n\nCREATE TABLE IF NOT EXISTS bugs (\n id TEXT PRIMARY KEY,\n title TEXT, symptom TEXT, root_cause TEXT, severity TEXT, status TEXT,\n affected_files TEXT, affected_symbols TEXT, lineage TEXT,\n prov_source TEXT, prov_confidence REAL, prov_evidence TEXT\n);\n\nCREATE TABLE IF NOT EXISTS constraints (\n id TEXT PRIMARY KEY,\n type TEXT, statement TEXT, scope TEXT, severity TEXT, enforcement TEXT,\n rationale TEXT, source_decision TEXT, violations TEXT,\n prov_source TEXT, prov_confidence REAL, prov_evidence TEXT\n);\n\n-- Local semantic-search vectors (opt-in; written by `hunch embed`). One row per\n-- (ref, model); vec is a Float32 BLOB. DELIBERATELY NOT in RESET_SQL: reindex()\n-- runs RESET on nearly every path (MCP startup, every query/context), so resetting\n-- embeddings here would wipe them constantly and make the feature a no-op. Staleness\n-- is tracked by doc_hash and reconciled by pruneStaleEmbeddings() instead. Recall is\n-- exact brute-force cosine in JS (graphs are small); sqlite-vec only past ~100k rows.\nCREATE TABLE IF NOT EXISTS embeddings (\n ref TEXT, kind TEXT, model TEXT, dim INTEGER, doc_hash TEXT, vec BLOB,\n PRIMARY KEY (ref, model)\n);\n";
8
+ /** Preferred search table when the host SQLite build includes FTS5. Kept
9
+ * separate from SCHEMA_SQL so a missing optional SQLite module cannot prevent
10
+ * deterministic graph/constraint operations from opening the derived index. */
11
+ export declare const FTS_SEARCH_SCHEMA_SQL = "\nCREATE VIRTUAL TABLE IF NOT EXISTS search USING fts5(\n ref UNINDEXED, -- entity id\n kind UNINDEXED, -- components | resources | edges | symbols | decisions | bugs | constraints | runbooks | findings\n -- | receipts | commitments | derived | entities | relationships (nuryel.state/1; title = subject key)\n title,\n body,\n tokenize = 'porter unicode61'\n);\n";
12
+ /** Portable keyword-search fallback. HunchStore detects that MATCH/bm25 are
13
+ * unavailable and performs a bounded LIKE scan over the same four columns. */
14
+ export declare const PLAIN_SEARCH_SCHEMA_SQL = "\nCREATE TABLE IF NOT EXISTS search (\n ref TEXT,\n kind TEXT,\n title TEXT,\n body TEXT\n);\nCREATE INDEX IF NOT EXISTS idx_search_ref ON search(ref);\nCREATE INDEX IF NOT EXISTS idx_search_kind ON search(kind);\n";
15
+ /** Drop derived data (used before a full reindex). NOTE: embeddings is omitted on
16
+ * purpose — see the embeddings table comment above. */
17
+ export declare const RESET_SQL = "\nDELETE FROM components; DELETE FROM resources; DELETE FROM edges; DELETE FROM resource_relationships; DELETE FROM symbols;\nDELETE FROM decisions; DELETE FROM bugs; DELETE FROM constraints;\nDELETE FROM search;\n";
@@ -0,0 +1,145 @@
1
+ import { z } from "zod";
2
+ import type { HunchStore } from "./hunchStore.js";
3
+ import { readLedger } from "./changeLedger.js";
4
+ import { type DeliveryEnvelope } from "../core/delivery.js";
5
+ import { STATE_CONTRACT_VERSION, type Scope, type ReadResponse, type WriteResult, type RecordsResponse } from "../core/stateContract.js";
6
+ /** A typed refusal. `code` is stable for bindings; `conflict` names the incumbent when one exists. */
7
+ export declare class StateRefusal extends Error {
8
+ readonly code: "outside-grants" | "unsupported" | "malformed" | "identity" | "conflict" | "no-partition-home" | "idempotency";
9
+ readonly conflict: {
10
+ incumbent_id: string;
11
+ reason: string;
12
+ } | null;
13
+ constructor(code: "outside-grants" | "unsupported" | "malformed" | "identity" | "conflict" | "no-partition-home" | "idempotency", message: string, conflict?: {
14
+ incumbent_id: string;
15
+ reason: string;
16
+ } | null);
17
+ }
18
+ /** The partition this store IS. A served partition declares itself in `.hunch/partition.json`
19
+ * (`{ kind, id }`, committed with the store); a plain checkout is the repository partition
20
+ * named after its directory, sanitized to the contract's token grammar — stable per clone,
21
+ * discoverable through `capabilities`, and the scope every legacy record defaults to. */
22
+ export declare function partitionOf(store: HunchStore): Scope;
23
+ /** @deprecated name kept for callers written before served partitions; same value as partitionOf. */
24
+ export declare const repositoryScope: typeof partitionOf;
25
+ export declare const SubscribeResponseSchema: z.ZodObject<{
26
+ schema: z.ZodLiteral<"nuryel.state.subscribe/1">;
27
+ scope: z.ZodObject<{
28
+ kind: z.ZodEnum<{
29
+ repository: "repository";
30
+ organization: "organization";
31
+ team: "team";
32
+ user: "user";
33
+ }>;
34
+ id: z.ZodString;
35
+ }, z.core.$strict>;
36
+ head_seq: z.ZodNumber;
37
+ events: z.ZodArray<z.ZodObject<{
38
+ schema: z.ZodLiteral<"nuryel.state.subscribe/1">;
39
+ seq: z.ZodNumber;
40
+ at: z.ZodString;
41
+ scope: z.ZodObject<{
42
+ kind: z.ZodEnum<{
43
+ repository: "repository";
44
+ organization: "organization";
45
+ team: "team";
46
+ user: "user";
47
+ }>;
48
+ id: z.ZodString;
49
+ }, z.core.$strict>;
50
+ facet: z.ZodEnum<{
51
+ decisions: "decisions";
52
+ constraints: "constraints";
53
+ bugs: "bugs";
54
+ findings: "findings";
55
+ receipts: "receipts";
56
+ commitments: "commitments";
57
+ derived: "derived";
58
+ entities: "entities";
59
+ relationships: "relationships";
60
+ }>;
61
+ record_id: z.ZodString;
62
+ record_hash: z.ZodString;
63
+ change: z.ZodEnum<{
64
+ updated: "updated";
65
+ retired: "retired";
66
+ superseded: "superseded";
67
+ created: "created";
68
+ invalidated: "invalidated";
69
+ }>;
70
+ subject: z.ZodOptional<z.ZodString>;
71
+ invalidates: z.ZodDefault<z.ZodArray<z.ZodString>>;
72
+ cause: z.ZodOptional<z.ZodUnion<readonly [z.ZodObject<{
73
+ kind: z.ZodLiteral<"receipt">;
74
+ receipt_id: z.ZodString;
75
+ }, z.core.$strict>, z.ZodObject<{
76
+ kind: z.ZodLiteral<"external">;
77
+ ref: z.ZodObject<{
78
+ system: z.ZodString;
79
+ object_type: z.ZodString;
80
+ object_key: z.ZodString;
81
+ version: z.ZodOptional<z.ZodString>;
82
+ content_hash: z.ZodOptional<z.ZodString>;
83
+ observed_at: z.ZodString;
84
+ locator: z.ZodOptional<z.ZodString>;
85
+ }, z.core.$strict>;
86
+ }, z.core.$strict>, z.ZodObject<{
87
+ kind: z.ZodLiteral<"write">;
88
+ principal: z.ZodString;
89
+ }, z.core.$strict>]>>;
90
+ }, z.core.$strict>>;
91
+ filtered: z.ZodBoolean;
92
+ floor_seq: z.ZodDefault<z.ZodNumber>;
93
+ resync: z.ZodDefault<z.ZodBoolean>;
94
+ }, z.core.$strict>;
95
+ export type SubscribeResponse = z.infer<typeof SubscribeResponseSchema>;
96
+ export declare function capabilities(store: HunchStore): {
97
+ protocol: typeof STATE_CONTRACT_VERSION;
98
+ capabilities: string[];
99
+ repository: Scope;
100
+ partitions: Scope["kind"][];
101
+ };
102
+ /** Where a scope's records and ledger live in this store (exported for the replay check, which
103
+ * must read the SAME home the write verb wrote — never a second routing rule). */
104
+ export declare function stateHomeFor(store: HunchStore, scope: Scope): {
105
+ home: "public" | "private";
106
+ hunchDir: string;
107
+ isPrivate: boolean;
108
+ };
109
+ /** read — the system-of-record answer for a subject, under the delivery envelope's receipt.
110
+ * Grants are the first predicate on every candidate; a matching record in a scope the
111
+ * principal lacks is NAMED in denied_scopes and never described. */
112
+ export declare function readState(store: HunchStore, input: unknown): {
113
+ response: ReadResponse;
114
+ envelope: DeliveryEnvelope;
115
+ };
116
+ /** Union read — one state_of_record across several partitions, each read by `readState` against
117
+ * its own store. Pure: no store, no grants decided here (every input already passed its own
118
+ * grant check). The primary's receipt, scope and envelope lead; refs concatenate (each already
119
+ * carries its partition), `depends_on` concatenates, `invalidated_by` is a sorted union, `records`
120
+ * merge by id (first writer wins — ids are identity, two copies are the same record),
121
+ * `denied_scopes` is the union of every partition's denied plus `extraDenied` (requested-but-
122
+ * ungranted scopes the host refused to open), `scopes` names the partitions read and `receipts`
123
+ * carries one delivery receipt per partition. Reusable by any host (HTTP today; MCP or CLI
124
+ * fronting several roots later). */
125
+ export declare function mergeReadResponses(primary: ReadResponse, others: readonly ReadResponse[], extraDenied?: readonly Scope[]): ReadResponse;
126
+ export interface WriteOptions {
127
+ /** Internal batch owner rebuilds once in finally while holding the write lock. */
128
+ deferReindex?: boolean;
129
+ /** Internal cache scoped to one uninterrupted partition write lock. Never retained. */
130
+ ledgerCache?: {
131
+ ledger?: ReturnType<typeof readLedger>;
132
+ };
133
+ /** Durability step after the record is on disk (auto-commit / push). Absent = "local". */
134
+ flush?: (isPrivate: boolean, message: string) => "pushed" | "committed" | null;
135
+ now?: () => Date;
136
+ }
137
+ /** write — provenance + idempotency in, durability out. A replay returns the original;
138
+ * a conflict names the incumbent; nothing is ever silently overwritten or duplicated. */
139
+ export declare function writeState(store: HunchStore, input: unknown, opts?: WriteOptions): WriteResult;
140
+ /** subscribe — the scope's ordered change stream after a cursor. Unfiltered, the events are
141
+ * contiguous and assertChangeSequence holds; filtered, `head_seq` is still the cursor. */
142
+ export declare function subscribeState(store: HunchStore, input: unknown): SubscribeResponse;
143
+ /** records — fetch by id, grants first. Every id is accounted for: found, denied (its scope is
144
+ * outside the grants — named, never described) or missing. */
145
+ export declare function recordsState(store: HunchStore, input: unknown): RecordsResponse;
@@ -0,0 +1,15 @@
1
+ import type { HunchStore } from "./hunchStore.js";
2
+ import { type CaptureBatchResult, type WriteResult } from "../core/stateContract.js";
3
+ import { type WriteOptions } from "./stateBinding.js";
4
+ interface CaptureOptions extends WriteOptions {
5
+ sourceHashes?: Map<string, string>;
6
+ }
7
+ /** The caller selects relevant atomic claims; this deterministic boundary checks evidence
8
+ * fidelity and deduplication. It does not pretend to prove semantic entailment or relevance.
9
+ * Both bindings hold the partition write lock over lookup AND write. */
10
+ export declare function captureState(store: HunchStore, input: unknown, opts?: CaptureOptions): WriteResult;
11
+ /** Bounded partial-success batch. The caller holds the same partition lock as writeState.
12
+ * Every result has its input index; a refused claim never hides a later valid new detail.
13
+ * A duplicate-only batch performs no record, ledger, index or Git writes. */
14
+ export declare function captureBatchState(store: HunchStore, input: unknown, opts?: WriteOptions): CaptureBatchResult;
15
+ export {};
@@ -0,0 +1,22 @@
1
+ import { z } from "zod";
2
+ declare const adapterSchema: z.ZodObject<{
3
+ name: z.ZodString;
4
+ command: z.ZodString;
5
+ args: z.ZodArray<z.ZodString>;
6
+ protocol: z.ZodEnum<{
7
+ stdin: "stdin";
8
+ acp: "acp";
9
+ }>;
10
+ probe_args: z.ZodDefault<z.ZodArray<z.ZodString>>;
11
+ timeout_ms: z.ZodDefault<z.ZodNumber>;
12
+ }, z.core.$strict>;
13
+ export type AgentCliAdapter = z.infer<typeof adapterSchema>;
14
+ export interface AgentCliWorker {
15
+ name: string;
16
+ draftProse?(prompt: string): Promise<string>;
17
+ }
18
+ export declare function readAgentCliConfig(file: string): AgentCliAdapter[];
19
+ /** ACP (Kimi and other agents) or plain stdin → JSON stdout for any user-configured CLI. */
20
+ export declare function runAgentCli(adapterInput: AgentCliAdapter, prompt: string): Promise<string>;
21
+ export declare function discoverAgentClis(configured?: AgentCliAdapter[], initiator?: string): AgentCliWorker[];
22
+ export {};
@@ -0,0 +1,13 @@
1
+ export interface AgentInitiator {
2
+ provider: string | null;
3
+ source: "explicit" | "environment" | "client" | "unknown" | "ambiguous";
4
+ }
5
+ export declare function normalizeInitiator(name: string): string;
6
+ export declare function detectInitiator(env?: NodeJS.ProcessEnv): AgentInitiator;
7
+ export declare function currentInitiator(env?: NodeJS.ProcessEnv): AgentInitiator;
8
+ export declare function withInitiator<T>(initiator: AgentInitiator, work: () => T): T;
9
+ /** Bind the MCP client, not the process that happened to start the server. Unknown clients stay unknown. */
10
+ export declare function initiatorFromClient(name: string | undefined): AgentInitiator;
11
+ /** Freeze the operation's origin before spawning Git hooks or other deferred children. */
12
+ export declare function initiatorChildEnv(env?: NodeJS.ProcessEnv): NodeJS.ProcessEnv;
13
+ export declare function assertInitiatorProvider(provider: string): void;