@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.
- package/README.md +3 -6
- package/dist/cli/automaticReviewMemory.d.ts +13 -0
- package/dist/cli/dna.d.ts +2 -0
- package/dist/cli/index.d.ts +16 -0
- package/dist/cli/index.js +602 -43
- package/dist/cli/integrations.d.ts +2 -0
- package/dist/cli/integrations.js +5 -2
- package/dist/cli/invocation.d.ts +35 -0
- package/dist/cli/preflight.d.ts +1 -0
- package/dist/cli/reviewMemory.d.ts +4 -0
- package/dist/cli/reviewMemoryProvider.d.ts +14 -0
- package/dist/cli/serve.d.ts +2 -0
- package/dist/cli/taskReport.d.ts +6 -0
- package/dist/cli/taskReport.js +124 -0
- package/dist/cli/update.d.ts +14 -0
- package/dist/client/state.d.ts +248 -0
- package/dist/client/state.js +2 -0
- package/dist/constitution/adapters.d.ts +29 -0
- package/dist/constitution/behaviorAttestationBinding.d.ts +3 -0
- package/dist/constitution/behaviorEvaluator.d.ts +8 -0
- package/dist/constitution/behaviorProof.d.ts +9 -0
- package/dist/constitution/behaviorWorkspace.d.ts +25 -0
- package/dist/constitution/bootstrap.d.ts +30 -0
- package/dist/constitution/canonical.d.ts +15 -0
- package/dist/constitution/card.d.ts +58 -0
- package/dist/constitution/compiler.d.ts +52 -0
- package/dist/constitution/composition.d.ts +10 -0
- package/dist/constitution/corpus.d.ts +7 -0
- package/dist/constitution/correctionPolicyMaterializer.d.ts +57 -0
- package/dist/constitution/delta.d.ts +4 -0
- package/dist/constitution/disposition.d.ts +20 -0
- package/dist/constitution/evaluator.d.ts +47 -0
- package/dist/constitution/experiment.d.ts +466 -0
- package/dist/constitution/experimentRunner.d.ts +7 -0
- package/dist/constitution/g2.d.ts +209 -0
- package/dist/constitution/g2BehaviorAttestation.d.ts +45 -0
- package/dist/constitution/g2BehaviorCandidates.d.ts +123 -0
- package/dist/constitution/g2BehaviorDependencies.d.ts +83 -0
- package/dist/constitution/g2BehaviorMaterialization.d.ts +77 -0
- package/dist/constitution/g2BehaviorPolicyMaterializer.d.ts +48 -0
- package/dist/constitution/g2CandidateAttestation.d.ts +43 -0
- package/dist/constitution/g2Candidates.d.ts +80 -0
- package/dist/constitution/g2Drills.d.ts +33 -0
- package/dist/constitution/g3.d.ts +311 -0
- package/dist/constitution/g3Conformance.d.ts +33 -0
- package/dist/constitution/lifecycle.d.ts +25 -0
- package/dist/constitution/mutation.d.ts +15 -0
- package/dist/constitution/nodeTestEvidence.d.ts +13 -0
- package/dist/constitution/plan.d.ts +18 -0
- package/dist/constitution/policyRuntime.d.ts +5 -0
- package/dist/constitution/proof.d.ts +11 -0
- package/dist/constitution/repairPolicies.d.ts +34 -0
- package/dist/constitution/replacementFreeGit.d.ts +14 -0
- package/dist/constitution/replay.d.ts +40 -0
- package/dist/constitution/replayCache.d.ts +14 -0
- package/dist/constitution/replayWorker.d.ts +1 -0
- package/dist/constitution/repository.d.ts +129 -0
- package/dist/constitution/safeCheckout.d.ts +9 -0
- package/dist/constitution/schema.d.ts +1763 -0
- package/dist/constitution/scorecard.d.ts +219 -0
- package/dist/constitution/service.d.ts +398 -0
- package/dist/constitution/shadow.d.ts +54 -0
- package/dist/constitution/sourceMutation.d.ts +15 -0
- package/dist/constitution/staticGraphBaseline.d.ts +13 -0
- package/dist/constitution/structural.d.ts +64 -0
- package/dist/core/agenthook.d.ts +63 -0
- package/dist/core/agenthook.js +1 -0
- package/dist/core/automaticReviewMemory.d.ts +60 -0
- package/dist/core/autoreview.d.ts +66 -0
- package/dist/core/canonicalOrder.d.ts +3 -0
- package/dist/core/capturetoken.d.ts +19 -0
- package/dist/core/changeIdentity.d.ts +22 -0
- package/dist/core/changeProof.d.ts +14 -0
- package/dist/core/checkreport.d.ts +140 -0
- package/dist/core/commitrepair.d.ts +171 -0
- package/dist/core/commitrepair.js +238 -0
- package/dist/core/compare.d.ts +21 -0
- package/dist/core/config.d.ts +20 -0
- package/dist/core/conformance.d.ts +35 -0
- package/dist/core/constraintmatch.d.ts +54 -0
- package/dist/core/correction.d.ts +42 -0
- package/dist/core/correctionStage.d.ts +224 -0
- package/dist/core/declarationClusters.d.ts +149 -0
- package/dist/core/delivery.d.ts +110 -0
- package/dist/core/docanchors.d.ts +32 -0
- package/dist/core/docscan.d.ts +26 -0
- package/dist/core/drift.d.ts +13 -0
- package/dist/core/drift.js +31 -1
- package/dist/core/dupdetect.d.ts +43 -0
- package/dist/core/escalations.d.ts +129 -0
- package/dist/core/escalations.js +165 -0
- package/dist/core/events.d.ts +29 -0
- package/dist/core/evidenceMap.d.ts +164 -0
- package/dist/core/externalImports.d.ts +8 -0
- package/dist/core/format.d.ts +13 -0
- package/dist/core/glob.d.ts +9 -0
- package/dist/core/groundingLag.d.ts +84 -0
- package/dist/core/groundingLag.js +16 -2
- package/dist/core/groundingMerge.d.ts +15 -0
- package/dist/core/groundingMerge.js +95 -0
- package/dist/core/hookcache.d.ts +10 -0
- package/dist/core/hookpolicy.d.ts +47 -0
- package/dist/core/ids.d.ts +36 -0
- package/dist/core/importReview.d.ts +30 -0
- package/dist/core/io.d.ts +23 -0
- package/dist/core/jsonc.d.ts +4 -0
- package/dist/core/landscapeAdoption.d.ts +66 -0
- package/dist/core/landscapeDelivery.d.ts +76 -0
- package/dist/core/memorylog.d.ts +37 -0
- package/dist/core/migrate.d.ts +28 -0
- package/dist/core/outcomeExperience.d.ts +89 -0
- package/dist/core/overlaySafety.d.ts +24 -0
- package/dist/core/overlaySafety.js +7 -1
- package/dist/core/paths.d.ts +31 -0
- package/dist/core/pipeline.d.ts +289 -0
- package/dist/core/premises.d.ts +43 -0
- package/dist/core/provenance.d.ts +21 -0
- package/dist/core/publication.d.ts +54 -0
- package/dist/core/refrepair.d.ts +24 -0
- package/dist/core/refrepair.js +1 -1
- package/dist/core/relativeImports.d.ts +11 -0
- package/dist/core/repair.d.ts +44 -0
- package/dist/core/repairqueue.d.ts +40 -0
- package/dist/core/repairqueue.js +133 -0
- package/dist/core/reviewMemory.d.ts +40 -0
- package/dist/core/reviewqueue.d.ts +54 -0
- package/dist/core/safeRepoFile.d.ts +20 -0
- package/dist/core/served.d.ts +57 -0
- package/dist/core/served.js +14 -0
- package/dist/core/stateContract.d.ts +979 -0
- package/dist/core/stateContract.js +87 -4
- package/dist/core/stateDelivery.d.ts +78 -0
- package/dist/core/stateRecords.d.ts +383 -0
- package/dist/core/stateRecords.js +16 -1
- package/dist/core/stats.d.ts +88 -0
- package/dist/core/strictgate.d.ts +33 -0
- package/dist/core/taskReport.d.ts +226 -0
- package/dist/core/taskReport.js +514 -0
- package/dist/core/taskReportCapture.d.ts +8 -0
- package/dist/core/taskReportCapture.js +36 -0
- package/dist/core/taskReportEvidence.d.ts +26 -0
- package/dist/core/taskReportEvidence.js +268 -0
- package/dist/core/taskReportHook.d.ts +20 -0
- package/dist/core/taskReportHook.js +76 -0
- package/dist/core/taskReportPaths.d.ts +3 -0
- package/dist/core/taskReportPaths.js +23 -0
- package/dist/core/taskReportPublic.d.ts +31 -0
- package/dist/core/taskReportPublic.js +45 -0
- package/dist/core/taskReportRender.d.ts +6 -0
- package/dist/core/taskReportRender.js +97 -0
- package/dist/core/topics.d.ts +73 -0
- package/dist/core/types.d.ts +1239 -0
- package/dist/core/version.d.ts +10 -0
- package/dist/eval/guards.d.ts +45 -0
- package/dist/eval/harness.d.ts +58 -0
- package/dist/extractors/adrImport.d.ts +59 -0
- package/dist/extractors/comments.d.ts +7 -0
- package/dist/extractors/correctionSources.d.ts +11 -0
- package/dist/extractors/diff.d.ts +40 -0
- package/dist/extractors/git.d.ts +304 -0
- package/dist/extractors/git.js +208 -20
- package/dist/extractors/helm.d.ts +31 -0
- package/dist/extractors/indexer.d.ts +55 -0
- package/dist/extractors/landscapeDiscovery.d.ts +41 -0
- package/dist/extractors/languages.d.ts +77 -0
- package/dist/extractors/nativeTreeSitter.d.ts +16 -0
- package/dist/extractors/parse.d.ts +54 -0
- package/dist/extractors/php.d.ts +26 -0
- package/dist/extractors/repoSource.d.ts +53 -0
- package/dist/extractors/testreport.d.ts +36 -0
- package/dist/integrations/ciAction.d.ts +8 -0
- package/dist/integrations/claudeConfig.d.ts +44 -0
- package/dist/integrations/claudemd.d.ts +13 -0
- package/dist/integrations/claudemd.js +11 -2
- package/dist/integrations/gitignore.d.ts +16 -0
- package/dist/integrations/gitignore.js +7 -0
- package/dist/integrations/health.d.ts +74 -0
- package/dist/integrations/hooks.d.ts +41 -0
- package/dist/integrations/hooks.js +92 -66
- package/dist/integrations/madrExport.d.ts +39 -0
- package/dist/integrations/madrManifest.d.ts +69 -0
- package/dist/integrations/mergeDriver.d.ts +3 -0
- package/dist/integrations/mergeDriver.js +25 -8
- package/dist/integrations/probe.d.ts +4 -0
- package/dist/integrations/providers.d.ts +103 -0
- package/dist/integrations/scaffold.d.ts +29 -0
- package/dist/integrations/sync.d.ts +30 -0
- package/dist/integrations/sync.js +3 -2
- package/dist/integrations/team.d.ts +70 -0
- package/dist/integrations/worktree.d.ts +8 -0
- package/dist/mcp/roots.d.ts +16 -0
- package/dist/mcp/server.d.ts +36 -0
- package/dist/mcp/server.js +114 -25
- package/dist/mcp/taskReportTools.d.ts +3 -0
- package/dist/mcp/taskReportTools.js +106 -0
- package/dist/serve/app.d.ts +36 -0
- package/dist/serve/app.js +20 -0
- package/dist/serve/config.d.ts +100 -0
- package/dist/serve/writelock.d.ts +22 -0
- package/dist/store/changeLedger.d.ts +132 -0
- package/dist/store/changeLedger.js +40 -9
- package/dist/store/compact.d.ts +33 -0
- package/dist/store/db.d.ts +9 -0
- package/dist/store/embedder.d.ts +32 -0
- package/dist/store/hunchStore.d.ts +624 -0
- package/dist/store/hunchStore.js +13 -5
- package/dist/store/jsonStore.d.ts +125 -0
- package/dist/store/jsonStore.js +15 -0
- package/dist/store/merge.d.ts +39 -0
- package/dist/store/privateMigrate.d.ts +24 -0
- package/dist/store/replay.d.ts +56 -0
- package/dist/store/schema.d.ts +17 -0
- package/dist/store/stateBinding.d.ts +145 -0
- package/dist/store/stateBinding.js +110 -15
- package/dist/store/stateCapture.d.ts +15 -0
- package/dist/store/stateCapture.js +145 -0
- package/dist/synthesis/cliAdapter.d.ts +22 -0
- package/dist/synthesis/initiator.d.ts +13 -0
- package/dist/synthesis/provider.d.ts +307 -0
- package/dist/synthesis/synthesize.d.ts +117 -0
- package/dist/synthesis/tripwires.d.ts +9 -0
- package/dist/taskReports.d.ts +76 -0
- package/dist/taskReports.js +67 -0
- package/dist/wiki/adopt.d.ts +22 -0
- package/dist/wiki/graph.d.ts +87 -0
- package/dist/wiki/wiki.d.ts +242 -0
- package/package.json +6 -5
- package/server.json +2 -2
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
/** Longest a write may hold the lock before another process may steal it: a put plus a
|
|
2
|
+
* git commit and push — seconds, not minutes. */
|
|
3
|
+
export declare const STALE_AFTER_MS = 60000;
|
|
4
|
+
/** Longest a writer waits before giving up and telling the caller. */
|
|
5
|
+
export declare const ACQUIRE_TIMEOUT_MS = 10000;
|
|
6
|
+
export interface LockOwner {
|
|
7
|
+
pid: number;
|
|
8
|
+
host: string;
|
|
9
|
+
nonce: string;
|
|
10
|
+
at: string;
|
|
11
|
+
}
|
|
12
|
+
export declare class WriteLockTimeout extends Error {
|
|
13
|
+
readonly path: string;
|
|
14
|
+
readonly heldBy: LockOwner | undefined;
|
|
15
|
+
readonly waitedMs: number;
|
|
16
|
+
constructor(path: string, heldBy: LockOwner | undefined, waitedMs: number);
|
|
17
|
+
}
|
|
18
|
+
export declare function writeLockPath(hunchDir: string): string;
|
|
19
|
+
/** Hold the partition's lock across `fn` (sync or async); released even if it throws. */
|
|
20
|
+
export declare function withWriteLock<T>(hunchDir: string, fn: () => T | Promise<T>, opts?: {
|
|
21
|
+
timeoutMs?: number;
|
|
22
|
+
}): Promise<T>;
|
|
@@ -0,0 +1,132 @@
|
|
|
1
|
+
import { z } from "zod";
|
|
2
|
+
import { type ChangeEvent, type Scope } from "../core/stateContract.js";
|
|
3
|
+
export declare const LEDGER_SCHEMA_VERSION: "nuryel.ledger/1";
|
|
4
|
+
export declare const CHANGES_DIR = "changes";
|
|
5
|
+
declare const IdempotencyEntrySchema: z.ZodObject<{
|
|
6
|
+
record_id: z.ZodString;
|
|
7
|
+
record_hash: z.ZodString;
|
|
8
|
+
payload_hash: z.ZodOptional<z.ZodString>;
|
|
9
|
+
facet: z.ZodString;
|
|
10
|
+
seq: z.ZodNumber;
|
|
11
|
+
at: z.ZodString;
|
|
12
|
+
}, z.core.$strict>;
|
|
13
|
+
export type IdempotencyEntry = z.infer<typeof IdempotencyEntrySchema>;
|
|
14
|
+
export declare const LedgerSchema: z.ZodObject<{
|
|
15
|
+
schema: z.ZodLiteral<"nuryel.ledger/1">;
|
|
16
|
+
scope: z.ZodObject<{
|
|
17
|
+
kind: z.ZodEnum<{
|
|
18
|
+
repository: "repository";
|
|
19
|
+
organization: "organization";
|
|
20
|
+
team: "team";
|
|
21
|
+
user: "user";
|
|
22
|
+
}>;
|
|
23
|
+
id: z.ZodString;
|
|
24
|
+
}, z.core.$strict>;
|
|
25
|
+
head_seq: z.ZodNumber;
|
|
26
|
+
floor_seq: z.ZodDefault<z.ZodNumber>;
|
|
27
|
+
events: z.ZodArray<z.ZodObject<{
|
|
28
|
+
schema: z.ZodLiteral<"nuryel.state.subscribe/1">;
|
|
29
|
+
seq: z.ZodNumber;
|
|
30
|
+
at: z.ZodString;
|
|
31
|
+
scope: z.ZodObject<{
|
|
32
|
+
kind: z.ZodEnum<{
|
|
33
|
+
repository: "repository";
|
|
34
|
+
organization: "organization";
|
|
35
|
+
team: "team";
|
|
36
|
+
user: "user";
|
|
37
|
+
}>;
|
|
38
|
+
id: z.ZodString;
|
|
39
|
+
}, z.core.$strict>;
|
|
40
|
+
facet: z.ZodEnum<{
|
|
41
|
+
decisions: "decisions";
|
|
42
|
+
constraints: "constraints";
|
|
43
|
+
bugs: "bugs";
|
|
44
|
+
findings: "findings";
|
|
45
|
+
receipts: "receipts";
|
|
46
|
+
commitments: "commitments";
|
|
47
|
+
derived: "derived";
|
|
48
|
+
entities: "entities";
|
|
49
|
+
relationships: "relationships";
|
|
50
|
+
}>;
|
|
51
|
+
record_id: z.ZodString;
|
|
52
|
+
record_hash: z.ZodString;
|
|
53
|
+
change: z.ZodEnum<{
|
|
54
|
+
updated: "updated";
|
|
55
|
+
retired: "retired";
|
|
56
|
+
superseded: "superseded";
|
|
57
|
+
created: "created";
|
|
58
|
+
invalidated: "invalidated";
|
|
59
|
+
}>;
|
|
60
|
+
subject: z.ZodOptional<z.ZodString>;
|
|
61
|
+
invalidates: z.ZodDefault<z.ZodArray<z.ZodString>>;
|
|
62
|
+
cause: z.ZodOptional<z.ZodUnion<readonly [z.ZodObject<{
|
|
63
|
+
kind: z.ZodLiteral<"receipt">;
|
|
64
|
+
receipt_id: z.ZodString;
|
|
65
|
+
}, z.core.$strict>, z.ZodObject<{
|
|
66
|
+
kind: z.ZodLiteral<"external">;
|
|
67
|
+
ref: z.ZodObject<{
|
|
68
|
+
system: z.ZodString;
|
|
69
|
+
object_type: z.ZodString;
|
|
70
|
+
object_key: z.ZodString;
|
|
71
|
+
version: z.ZodOptional<z.ZodString>;
|
|
72
|
+
content_hash: z.ZodOptional<z.ZodString>;
|
|
73
|
+
observed_at: z.ZodString;
|
|
74
|
+
locator: z.ZodOptional<z.ZodString>;
|
|
75
|
+
}, z.core.$strict>;
|
|
76
|
+
}, z.core.$strict>, z.ZodObject<{
|
|
77
|
+
kind: z.ZodLiteral<"write">;
|
|
78
|
+
principal: z.ZodString;
|
|
79
|
+
}, z.core.$strict>]>>;
|
|
80
|
+
}, z.core.$strict>>;
|
|
81
|
+
idempotency: z.ZodDefault<z.ZodRecord<z.ZodString, z.ZodObject<{
|
|
82
|
+
record_id: z.ZodString;
|
|
83
|
+
record_hash: z.ZodString;
|
|
84
|
+
payload_hash: z.ZodOptional<z.ZodString>;
|
|
85
|
+
facet: z.ZodString;
|
|
86
|
+
seq: z.ZodNumber;
|
|
87
|
+
at: z.ZodString;
|
|
88
|
+
}, z.core.$strict>>>;
|
|
89
|
+
}, z.core.$strict>;
|
|
90
|
+
export type Ledger = z.infer<typeof LedgerSchema>;
|
|
91
|
+
/** Scope ids may carry `:` `@` `+` (safe in the contract, not in every file system), so
|
|
92
|
+
* the file name is the sanitized id plus a short hash of the exact id — readable AND
|
|
93
|
+
* collision-free. The scope inside the file is authoritative, the name is a locator. */
|
|
94
|
+
export declare function ledgerFile(hunchDir: string, scope: Scope): string;
|
|
95
|
+
export declare function emptyLedger(scope: Scope): Ledger;
|
|
96
|
+
/** Read the ledger for a scope; a missing file is an empty ledger, a corrupt one is an
|
|
97
|
+
* error (never silently treated as empty — that would restart the sequence). */
|
|
98
|
+
export declare function readLedger(hunchDir: string, scope: Scope): Ledger;
|
|
99
|
+
export declare function writeLedger(hunchDir: string, ledger: Ledger): void;
|
|
100
|
+
export type PendingChange = Omit<ChangeEvent, "schema" | "seq" | "at" | "scope">;
|
|
101
|
+
/** Append events (in order) and remember an idempotency key in ONE atomic write, so a
|
|
102
|
+
* crash between "record written" and "event appended" can be detected by the next
|
|
103
|
+
* writer (record present, ledger silent) rather than producing a half-applied write. */
|
|
104
|
+
export declare function appendChanges(hunchDir: string, scope: Scope, changes: readonly PendingChange[], idempotency: {
|
|
105
|
+
key: string;
|
|
106
|
+
entry: Omit<IdempotencyEntry, "seq" | "at">;
|
|
107
|
+
} | null, at?: string,
|
|
108
|
+
/** Already validated under the same uninterrupted partition lock; batch-local only. */
|
|
109
|
+
current?: Ledger): ChangeEvent[];
|
|
110
|
+
/** The latest seq that touched a record in this scope, or 0 when the ledger never saw it. */
|
|
111
|
+
export declare function latestSeqFor(ledger: Ledger, recordId: string): number;
|
|
112
|
+
/** Keep the newest `keep` events; everything older is dropped and the floor moves up. The
|
|
113
|
+
* idempotency table is kept whole (it is what makes replays exact); the records themselves are
|
|
114
|
+
* untouched. Returns how many events were dropped. */
|
|
115
|
+
export declare function compactLedger(hunchDir: string, scope: Scope, opts?: {
|
|
116
|
+
keep?: number;
|
|
117
|
+
}): {
|
|
118
|
+
dropped: number;
|
|
119
|
+
floor_seq: number;
|
|
120
|
+
head_seq: number;
|
|
121
|
+
};
|
|
122
|
+
/** Three-way merge of one scope's ledger, for the git merge driver: two clones that both
|
|
123
|
+
* appended to the same partition. The union of events is kept (identity = what changed, to
|
|
124
|
+
* which hash, when, by whom), ordered by time then ours-before-theirs, and RE-SEQUENCED from
|
|
125
|
+
* the higher floor; every subscriber's cursor is therefore invalid after a merge and the gap
|
|
126
|
+
* rule makes it resynchronize. Idempotency entries are unioned; a key both sides used for
|
|
127
|
+
* different records is a conflict the caller must surface (ours is kept). */
|
|
128
|
+
export declare function mergeLedgers(base: Ledger | null, ours: Ledger, theirs: Ledger): {
|
|
129
|
+
ledger: Ledger;
|
|
130
|
+
conflicts: string[];
|
|
131
|
+
};
|
|
132
|
+
export {};
|
|
@@ -10,7 +10,7 @@
|
|
|
10
10
|
* here (see docs/nuryel-state-contract.md, "Not decided here").
|
|
11
11
|
*/
|
|
12
12
|
import { existsSync, mkdirSync, readFileSync } from "node:fs";
|
|
13
|
-
import { join } from "node:path";
|
|
13
|
+
import { join, resolve } from "node:path";
|
|
14
14
|
import { createHash } from "node:crypto";
|
|
15
15
|
import { z } from "zod";
|
|
16
16
|
import { writeFileAtomic } from "../core/io.js";
|
|
@@ -39,6 +39,12 @@ export const LedgerSchema = z.object({
|
|
|
39
39
|
events: z.array(ChangeEventSchema),
|
|
40
40
|
idempotency: z.record(z.string(), IdempotencyEntrySchema).default({}),
|
|
41
41
|
}).strict();
|
|
42
|
+
// Process-local acceleration only: compare the actual bytes on EVERY read, never
|
|
43
|
+
// timestamps or a TTL. Separate processes and same-size replacements stay visible.
|
|
44
|
+
// Keep only four small snapshots; oversized ledgers follow the uncached path.
|
|
45
|
+
const validatedSnapshots = new Map();
|
|
46
|
+
const MAX_CACHED_LEDGERS = 4;
|
|
47
|
+
const MAX_CACHED_CHARACTERS = 1024 * 1024;
|
|
42
48
|
/** Scope ids may carry `:` `@` `+` (safe in the contract, not in every file system), so
|
|
43
49
|
* the file name is the sanitized id plus a short hash of the exact id — readable AND
|
|
44
50
|
* collision-free. The scope inside the file is authoritative, the name is a locator. */
|
|
@@ -53,10 +59,21 @@ export function emptyLedger(scope) {
|
|
|
53
59
|
/** Read the ledger for a scope; a missing file is an empty ledger, a corrupt one is an
|
|
54
60
|
* error (never silently treated as empty — that would restart the sequence). */
|
|
55
61
|
export function readLedger(hunchDir, scope) {
|
|
56
|
-
const file = ledgerFile(hunchDir, scope);
|
|
57
|
-
if (!existsSync(file))
|
|
62
|
+
const file = resolve(ledgerFile(hunchDir, scope));
|
|
63
|
+
if (!existsSync(file)) {
|
|
64
|
+
validatedSnapshots.delete(file);
|
|
58
65
|
return emptyLedger(scope);
|
|
59
|
-
|
|
66
|
+
}
|
|
67
|
+
const text = readFileSync(file, "utf8");
|
|
68
|
+
const cached = validatedSnapshots.get(file);
|
|
69
|
+
if (cached?.text === text && cached.scope === scopePath(scope)) {
|
|
70
|
+
validatedSnapshots.delete(file);
|
|
71
|
+
validatedSnapshots.set(file, cached);
|
|
72
|
+
// append/compaction callers mutate their copy. Never expose the cached object.
|
|
73
|
+
return JSON.parse(cached.normalized);
|
|
74
|
+
}
|
|
75
|
+
validatedSnapshots.delete(file);
|
|
76
|
+
const raw = JSON.parse(text);
|
|
60
77
|
const ledger = LedgerSchema.parse(raw);
|
|
61
78
|
if (scopePath(ledger.scope) !== scopePath(scope))
|
|
62
79
|
throw new Error(`ledger ${file} belongs to scope ${scopePath(ledger.scope)}, not ${scopePath(scope)}`);
|
|
@@ -68,18 +85,30 @@ export function readLedger(hunchDir, scope) {
|
|
|
68
85
|
}
|
|
69
86
|
if (ledger.head_seq !== ledger.floor_seq + ledger.events.length)
|
|
70
87
|
throw new Error(`ledger ${file} head_seq ${ledger.head_seq} disagrees with floor ${ledger.floor_seq} + ${ledger.events.length} events`);
|
|
88
|
+
if (text.length <= MAX_CACHED_CHARACTERS) {
|
|
89
|
+
while (validatedSnapshots.size >= MAX_CACHED_LEDGERS)
|
|
90
|
+
validatedSnapshots.delete(validatedSnapshots.keys().next().value);
|
|
91
|
+
validatedSnapshots.set(file, { text, normalized: JSON.stringify(ledger), scope: scopePath(scope) });
|
|
92
|
+
}
|
|
71
93
|
return ledger;
|
|
72
94
|
}
|
|
73
95
|
export function writeLedger(hunchDir, ledger) {
|
|
96
|
+
writeValidatedLedger(hunchDir, LedgerSchema.parse(ledger));
|
|
97
|
+
}
|
|
98
|
+
function writeValidatedLedger(hunchDir, ledger) {
|
|
74
99
|
const file = ledgerFile(hunchDir, ledger.scope);
|
|
75
100
|
mkdirSync(join(hunchDir, CHANGES_DIR), { recursive: true });
|
|
76
|
-
writeFileAtomic(file, JSON.stringify(
|
|
101
|
+
writeFileAtomic(file, JSON.stringify(ledger, null, 2) + "\n");
|
|
77
102
|
}
|
|
78
103
|
/** Append events (in order) and remember an idempotency key in ONE atomic write, so a
|
|
79
104
|
* crash between "record written" and "event appended" can be detected by the next
|
|
80
105
|
* writer (record present, ledger silent) rather than producing a half-applied write. */
|
|
81
|
-
export function appendChanges(hunchDir, scope, changes, idempotency, at = new Date().toISOString()
|
|
82
|
-
|
|
106
|
+
export function appendChanges(hunchDir, scope, changes, idempotency, at = new Date().toISOString(),
|
|
107
|
+
/** Already validated under the same uninterrupted partition lock; batch-local only. */
|
|
108
|
+
current) {
|
|
109
|
+
const ledger = current ?? readLedger(hunchDir, scope);
|
|
110
|
+
if (scopePath(ledger.scope) !== scopePath(scope))
|
|
111
|
+
throw new Error("cached ledger belongs to another scope");
|
|
83
112
|
const appended = [];
|
|
84
113
|
for (const change of changes) {
|
|
85
114
|
const event = ChangeEventSchema.parse({ schema: "nuryel.state.subscribe/1", seq: ledger.head_seq + 1, at, scope, ...change });
|
|
@@ -88,9 +117,11 @@ export function appendChanges(hunchDir, scope, changes, idempotency, at = new Da
|
|
|
88
117
|
appended.push(event);
|
|
89
118
|
}
|
|
90
119
|
if (idempotency) {
|
|
91
|
-
ledger.idempotency[idempotency.key] = { ...idempotency.entry, seq: ledger.head_seq, at };
|
|
120
|
+
ledger.idempotency[idempotency.key] = IdempotencyEntrySchema.parse({ ...idempotency.entry, seq: ledger.head_seq, at });
|
|
92
121
|
}
|
|
93
|
-
|
|
122
|
+
// The initial ledger and each new event/entry are validated; don't re-validate the
|
|
123
|
+
// full history per assertion. Each append still reaches disk atomically before return.
|
|
124
|
+
writeValidatedLedger(hunchDir, ledger);
|
|
94
125
|
return appended;
|
|
95
126
|
}
|
|
96
127
|
/** The latest seq that touched a record in this scope, or 0 when the ledger never saw it. */
|
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Record compaction (bounds unbounded Hunch growth). Auto-captured drafts
|
|
3
|
+
* accumulate forever; `hunch compact` prunes the LOW-VALUE ones — never an
|
|
4
|
+
* accepted/human-confirmed decision, never an open bug, never a constraint
|
|
5
|
+
* (invariants are sacred), and never a record another record still points to.
|
|
6
|
+
*
|
|
7
|
+
* planCompaction is PURE (takes `now`) so it is fully testable; the CLI supplies
|
|
8
|
+
* Date.now(). Reference safety uses a greatest-fixpoint so records being pruned in
|
|
9
|
+
* the same pass don't keep each other (or a cycle of dead drafts) alive.
|
|
10
|
+
*/
|
|
11
|
+
import type { Bug, Constraint, Decision, EntityKind } from "../core/types.js";
|
|
12
|
+
export interface CompactionCandidate {
|
|
13
|
+
kind: EntityKind;
|
|
14
|
+
id: string;
|
|
15
|
+
title: string;
|
|
16
|
+
reason: string;
|
|
17
|
+
}
|
|
18
|
+
export interface CompactionPlan {
|
|
19
|
+
remove: CompactionCandidate[];
|
|
20
|
+
/** how many records were considered (decisions + bugs). */
|
|
21
|
+
considered: number;
|
|
22
|
+
}
|
|
23
|
+
export interface CompactionInput {
|
|
24
|
+
decisions: Decision[];
|
|
25
|
+
bugs: Bug[];
|
|
26
|
+
constraints: Constraint[];
|
|
27
|
+
}
|
|
28
|
+
export interface CompactionOpts {
|
|
29
|
+
now: number;
|
|
30
|
+
maxAgeDays?: number;
|
|
31
|
+
minConfidence?: number;
|
|
32
|
+
}
|
|
33
|
+
export declare function planCompaction(input: CompactionInput, opts: CompactionOpts): CompactionPlan;
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
export type DB = import("node:sqlite").DatabaseSync;
|
|
2
|
+
export declare function openDb(sqlitePath: string): DB;
|
|
3
|
+
/** In-memory db (tests / ephemeral queries). */
|
|
4
|
+
export declare function openMemoryDb(options?: {
|
|
5
|
+
forcePlainSearch?: boolean;
|
|
6
|
+
}): DB;
|
|
7
|
+
/** Run `fn` inside one transaction: BEGIN → fn → COMMIT, ROLLBACK on throw.
|
|
8
|
+
* (node:sqlite has no better-sqlite3-style transaction() helper.) */
|
|
9
|
+
export declare function withTx<T>(db: DB, fn: () => T): T;
|
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
export interface Embedder {
|
|
2
|
+
/** Embed each text → an L2-normalized Float32Array of length `dim`. */
|
|
3
|
+
embed(texts: string[]): Promise<Float32Array[]>;
|
|
4
|
+
readonly dim: number;
|
|
5
|
+
/** Stable id; also the `embeddings.model` key, so vectors from different models
|
|
6
|
+
* never get compared. */
|
|
7
|
+
readonly id: string;
|
|
8
|
+
}
|
|
9
|
+
export declare class TransformersEmbedder implements Embedder {
|
|
10
|
+
readonly dim = 384;
|
|
11
|
+
readonly id = "all-MiniLM-L6-v2";
|
|
12
|
+
private extractor;
|
|
13
|
+
private load;
|
|
14
|
+
embed(texts: string[]): Promise<Float32Array[]>;
|
|
15
|
+
}
|
|
16
|
+
/** Deterministic, dependency-free embedder for tests (no model download).
|
|
17
|
+
* Bag-of-token-hashes → L2-normalized vector: shared tokens ⇒ similar vectors,
|
|
18
|
+
* enough to exercise hybrid ranking without semantics. Intentionally returns
|
|
19
|
+
* SUBARRAY VIEWS into one backing buffer, so any caller that fails to copy
|
|
20
|
+
* tightly before persisting will corrupt data — that keeps the BLOB round-trip
|
|
21
|
+
* honest. */
|
|
22
|
+
export declare class StubEmbedder implements Embedder {
|
|
23
|
+
readonly dim: number;
|
|
24
|
+
readonly id = "stub-v1";
|
|
25
|
+
constructor(dim?: number);
|
|
26
|
+
embed(texts: string[]): Promise<Float32Array[]>;
|
|
27
|
+
}
|
|
28
|
+
/** Choose an embedder, or null to signal "no semantic search → use pure FTS".
|
|
29
|
+
* Never throws. `HUNCH_EMBEDDER=stub` forces the test stub; `=none` forces the
|
|
30
|
+
* null (FTS-only) path; otherwise use the local model iff its optional dep is
|
|
31
|
+
* installed. */
|
|
32
|
+
export declare function selectEmbedder(): Promise<Embedder | null>;
|