daftari 1.20.0 → 1.22.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 (100) hide show
  1. package/CHANGELOG.md +89 -0
  2. package/README.md +4 -1
  3. package/dist/access/rbac.d.ts +1 -0
  4. package/dist/access/rbac.d.ts.map +1 -1
  5. package/dist/access/rbac.js +7 -0
  6. package/dist/access/rbac.js.map +1 -1
  7. package/dist/cli.d.ts.map +1 -1
  8. package/dist/cli.js +10 -0
  9. package/dist/cli.js.map +1 -1
  10. package/dist/consolidate/birth.d.ts +74 -0
  11. package/dist/consolidate/birth.d.ts.map +1 -0
  12. package/dist/consolidate/birth.js +253 -0
  13. package/dist/consolidate/birth.js.map +1 -0
  14. package/dist/consolidate/clocks.d.ts +15 -0
  15. package/dist/consolidate/clocks.d.ts.map +1 -0
  16. package/dist/consolidate/clocks.js +127 -0
  17. package/dist/consolidate/clocks.js.map +1 -0
  18. package/dist/consolidate/constants.d.ts +26 -0
  19. package/dist/consolidate/constants.d.ts.map +1 -0
  20. package/dist/consolidate/constants.js +83 -0
  21. package/dist/consolidate/constants.js.map +1 -0
  22. package/dist/consolidate/decorrelation.d.ts +69 -0
  23. package/dist/consolidate/decorrelation.d.ts.map +1 -0
  24. package/dist/consolidate/decorrelation.js +287 -0
  25. package/dist/consolidate/decorrelation.js.map +1 -0
  26. package/dist/consolidate/derivation-prompt.d.ts +28 -0
  27. package/dist/consolidate/derivation-prompt.d.ts.map +1 -0
  28. package/dist/consolidate/derivation-prompt.js +72 -0
  29. package/dist/consolidate/derivation-prompt.js.map +1 -0
  30. package/dist/consolidate/edge-write.d.ts +10 -0
  31. package/dist/consolidate/edge-write.d.ts.map +1 -0
  32. package/dist/consolidate/edge-write.js +76 -0
  33. package/dist/consolidate/edge-write.js.map +1 -0
  34. package/dist/consolidate/index.d.ts +2 -0
  35. package/dist/consolidate/index.d.ts.map +1 -0
  36. package/dist/consolidate/index.js +477 -0
  37. package/dist/consolidate/index.js.map +1 -0
  38. package/dist/consolidate/priority.d.ts +25 -0
  39. package/dist/consolidate/priority.d.ts.map +1 -0
  40. package/dist/consolidate/priority.js +103 -0
  41. package/dist/consolidate/priority.js.map +1 -0
  42. package/dist/consolidate/revision.d.ts +75 -0
  43. package/dist/consolidate/revision.d.ts.map +1 -0
  44. package/dist/consolidate/revision.js +232 -0
  45. package/dist/consolidate/revision.js.map +1 -0
  46. package/dist/consolidate/state.d.ts +10 -0
  47. package/dist/consolidate/state.d.ts.map +1 -0
  48. package/dist/consolidate/state.js +54 -0
  49. package/dist/consolidate/state.js.map +1 -0
  50. package/dist/curation/edges.d.ts +5 -0
  51. package/dist/curation/edges.d.ts.map +1 -1
  52. package/dist/curation/edges.js +82 -9
  53. package/dist/curation/edges.js.map +1 -1
  54. package/dist/curation/lint.d.ts +2 -0
  55. package/dist/curation/lint.d.ts.map +1 -1
  56. package/dist/curation/lint.js +5 -0
  57. package/dist/curation/lint.js.map +1 -1
  58. package/dist/curation/provenance.d.ts +1 -0
  59. package/dist/curation/provenance.d.ts.map +1 -1
  60. package/dist/curation/provenance.js +1 -0
  61. package/dist/curation/provenance.js.map +1 -1
  62. package/dist/curation/shadow.d.ts +58 -0
  63. package/dist/curation/shadow.d.ts.map +1 -0
  64. package/dist/curation/shadow.js +221 -0
  65. package/dist/curation/shadow.js.map +1 -0
  66. package/dist/eval/llm.d.ts +3 -1
  67. package/dist/eval/llm.d.ts.map +1 -1
  68. package/dist/eval/llm.js +6 -2
  69. package/dist/eval/llm.js.map +1 -1
  70. package/dist/index.js +1 -1
  71. package/dist/storage/index-db.d.ts +1 -0
  72. package/dist/storage/index-db.d.ts.map +1 -1
  73. package/dist/storage/index-db.js +14 -9
  74. package/dist/storage/index-db.js.map +1 -1
  75. package/dist/tools/curation.d.ts +2 -0
  76. package/dist/tools/curation.d.ts.map +1 -1
  77. package/dist/tools/curation.js +5 -1
  78. package/dist/tools/curation.js.map +1 -1
  79. package/dist/tools/edges.d.ts.map +1 -1
  80. package/dist/tools/edges.js +14 -10
  81. package/dist/tools/edges.js.map +1 -1
  82. package/dist/tools/staged-actions.d.ts +1 -0
  83. package/dist/tools/staged-actions.d.ts.map +1 -1
  84. package/dist/tools/staged-actions.js +18 -5
  85. package/dist/tools/staged-actions.js.map +1 -1
  86. package/dist/tools/write.d.ts +1 -0
  87. package/dist/tools/write.d.ts.map +1 -1
  88. package/dist/tools/write.js +93 -7
  89. package/dist/tools/write.js.map +1 -1
  90. package/dist/utils/config.d.ts +2 -0
  91. package/dist/utils/config.d.ts.map +1 -1
  92. package/dist/utils/config.js +17 -1
  93. package/dist/utils/config.js.map +1 -1
  94. package/dist/utils/git.d.ts +1 -0
  95. package/dist/utils/git.d.ts.map +1 -1
  96. package/dist/utils/git.js +19 -0
  97. package/dist/utils/git.js.map +1 -1
  98. package/package.json +1 -1
  99. package/templates/config.yaml +3 -0
  100. package/templates/reviewer-vault/.daftari/config.yaml +3 -0
@@ -0,0 +1,75 @@
1
+ import { type ContestEdgeInput, type DerivesFromEdge, type ObserveEdgeInput } from "../curation/edges.js";
2
+ import type { LlmClient } from "../eval/llm.js";
3
+ import { type Result } from "../frontmatter/types.js";
4
+ import { type ConsolidatePromptTemplate } from "./constants.js";
5
+ export interface RevisionDeps {
6
+ llm: LlmClient;
7
+ loadDoc: (path: string) => Promise<Result<{
8
+ path: string;
9
+ content: string;
10
+ }, Error>>;
11
+ observe: (input: ObserveEdgeInput) => Promise<Result<DerivesFromEdge, Error>>;
12
+ contest: (input: ContestEdgeInput) => Promise<Result<DerivesFromEdge, Error>>;
13
+ recordRevisionTrace: (row: RevisionTraceRow) => Promise<Result<void, Error>>;
14
+ }
15
+ export interface RevisionOpts {
16
+ vaultRoot: string;
17
+ agent: string;
18
+ panelSize: number;
19
+ budgetRemaining: number;
20
+ model: string;
21
+ }
22
+ export interface RevisionVote {
23
+ axis: ConsolidatePromptTemplate;
24
+ verdict: "survives" | "fails";
25
+ reason: string;
26
+ }
27
+ export interface RevisionVoteError {
28
+ axis: ConsolidatePromptTemplate;
29
+ error: string;
30
+ }
31
+ export type RevisionDecision = "survives" | "fails" | "tie" | "no-vote";
32
+ export interface RevisionTraceRow {
33
+ at: string;
34
+ fromPath: string;
35
+ toPath: string;
36
+ edgeStrengthAtStart: number;
37
+ kSurvivedAtStart: number;
38
+ lastRederivedAtStart: string;
39
+ model: string;
40
+ votes: Array<RevisionVote | RevisionVoteError>;
41
+ decision: RevisionDecision;
42
+ observedCount: number;
43
+ contestedCount: number;
44
+ }
45
+ export interface RevisionOutcome {
46
+ fromPath: string;
47
+ toPath: string;
48
+ votes: Array<RevisionVote | RevisionVoteError>;
49
+ decision: RevisionDecision;
50
+ observedCount: number;
51
+ contestedCount: number;
52
+ writeErrors: Array<{
53
+ axis: ConsolidatePromptTemplate;
54
+ error: string;
55
+ }>;
56
+ llmCalls: number;
57
+ inputTokens: number;
58
+ outputTokens: number;
59
+ traceWritten: boolean;
60
+ traceError?: string;
61
+ }
62
+ export declare function parseRevisionVerdict(raw: unknown): Result<{
63
+ verdict: "survives" | "fails";
64
+ reason: string;
65
+ }, Error>;
66
+ export declare function revisionPanel(edge: {
67
+ fromPath: string;
68
+ toPath: string;
69
+ strength: number;
70
+ kSurvived: number;
71
+ lastRederived: string;
72
+ }, deps: RevisionDeps, opts: RevisionOpts): Promise<Result<RevisionOutcome, Error>>;
73
+ export declare function revisionTracePath(vaultRoot: string): string;
74
+ export declare function appendRevisionTrace(vaultRoot: string, row: RevisionTraceRow): Promise<Result<void, Error>>;
75
+ //# sourceMappingURL=revision.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"revision.d.ts","sourceRoot":"","sources":["../../src/consolidate/revision.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,232 @@
1
+ // Revision mode (spec §4.1, brief item 2). Cast a panel of M independent votes
2
+ // on a due edge — each vote uses a distinct prompt template so §11.3's replay
3
+ // guard counts them all as independent in one sitting. Each vote either
4
+ // survives (→ edge_observe, strength accrues) or fails-case-2 (→ edge_contest,
5
+ // revoke + tension). The §11.3 replay guard handles "same (observer, axis)
6
+ // pair within EDGE_REPLAY_GAP_DAYS"; this code uses distinct axes within the
7
+ // panel so the gap never trips.
8
+ //
9
+ // Case-1 vs case-2 simplification (v1): the LLM is asked to produce
10
+ // survives | fails — we treat every "fails" as case-2 (contest). The spec
11
+ // distinguishes case-1 (an endpoint *changed*, so failure isn't a fault of the
12
+ // derivation) but C's event clock already marks such edges due via git-diff;
13
+ // running the panel on them is the same retry path. If shadow data shows we're
14
+ // contesting edges whose endpoints had recent commits, add a case-1 detector
15
+ // (`git log --since=<lastRederived>` on each endpoint) and skip the contest.
16
+ // Tracked for Stage 5 calibration.
17
+ import { appendFileSync, mkdirSync } from "node:fs";
18
+ import { join, posix } from "node:path";
19
+ import { EDGE_AXES, } from "../curation/edges.js";
20
+ import { err, ok } from "../frontmatter/types.js";
21
+ import { CONSOLIDATE_PROMPT_TEMPLATES } from "./constants.js";
22
+ // --- canon -------------------------------------------------------------------
23
+ function canon(p) {
24
+ return posix.normalize(p).replace(/^\.\//, "");
25
+ }
26
+ // --- verdict parsing ---------------------------------------------------------
27
+ const VALID_REVISION_VERDICTS = new Set(["survives", "fails"]);
28
+ export function parseRevisionVerdict(raw) {
29
+ if (typeof raw !== "object" || raw === null || Array.isArray(raw)) {
30
+ return err(new Error("revision verdict: expected object"));
31
+ }
32
+ const obj = raw;
33
+ const v = obj.verdict;
34
+ const r = obj.reason;
35
+ if (typeof v !== "string" || !VALID_REVISION_VERDICTS.has(v)) {
36
+ return err(new Error(`revision verdict: expected 'survives' | 'fails', got ${JSON.stringify(v)}`));
37
+ }
38
+ if (typeof r !== "string" || r.trim().length === 0) {
39
+ return err(new Error("revision verdict: 'reason' is required"));
40
+ }
41
+ return ok({ verdict: v, reason: r });
42
+ }
43
+ // --- prompt construction -----------------------------------------------------
44
+ const VERDICT_SCHEMA = {
45
+ type: "object",
46
+ required: ["verdict", "reason"],
47
+ properties: {
48
+ verdict: { enum: ["survives", "fails"] },
49
+ reason: { type: "string", minLength: 1 },
50
+ },
51
+ };
52
+ const SYSTEM_BASE = "You re-evaluate whether one document's central claim still derives from another. " +
53
+ "You have re-derived this edge before — your job is to do it independently now, " +
54
+ "from the documents alone. Be conservative: only return 'survives' if the dependence is " +
55
+ "clear and load-bearing in the current text. Return 'fails' if the premise no longer " +
56
+ "supports the conclusion as previously claimed.";
57
+ const MAX_DOC_CHARS = 1500;
58
+ function truncate(s) {
59
+ return s.length <= MAX_DOC_CHARS ? s : `${s.slice(0, MAX_DOC_CHARS)}\n…[truncated]`;
60
+ }
61
+ // User body. The leading `[template:NAME]` marker is the post-hoc audit hook —
62
+ // it's how the trace + the decorrelation report (brief item 8) reconstruct
63
+ // which template each vote used without parsing prose. Tests rely on it too.
64
+ function userBody(axis, from, fromContent, to, toContent, edge) {
65
+ const tag = `[template:${axis}]`;
66
+ const meta = `Edge: ${from} derives_from ${to}. k_survived=${edge.kSurvived}, last_rederived=${edge.lastRederived}.`;
67
+ const fromBlock = `DOC FROM (${from}):\n${truncate(fromContent)}`;
68
+ const toBlock = `DOC TO (${to}):\n${truncate(toContent)}`;
69
+ switch (axis) {
70
+ case "forward":
71
+ return `${tag}\n${meta}\n\n${fromBlock}\n\n${toBlock}\n\nDoes DOC FROM still derive its central claim from DOC TO? Return JSON.`;
72
+ case "reverse":
73
+ return `${tag}\n${meta}\n\n${toBlock}\n\n${fromBlock}\n\nIs DOC FROM's central claim still load-bearing on DOC TO? Return JSON.`;
74
+ case "contrast":
75
+ return `${tag}\n${meta}\n\n${fromBlock}\n\n${toBlock}\n\nIf you had to defend or refute the edge "FROM derives_from TO" today, which would you do? Return JSON: 'survives' to defend, 'fails' to refute.`;
76
+ }
77
+ }
78
+ // --- panel -------------------------------------------------------------------
79
+ // Pick the first N distinct prompt templates so M votes get M distinct
80
+ // (observer, axis) pairs. Validated by the constants test: panelSize cannot
81
+ // exceed CONSOLIDATE_PROMPT_TEMPLATES.length. We still defend at runtime in
82
+ // case a caller passes a runaway value.
83
+ function axesForPanel(panelSize) {
84
+ const k = Math.max(1, Math.min(panelSize, CONSOLIDATE_PROMPT_TEMPLATES.length));
85
+ return CONSOLIDATE_PROMPT_TEMPLATES.slice(0, k);
86
+ }
87
+ export async function revisionPanel(edge, deps, opts) {
88
+ const fromPath = canon(edge.fromPath);
89
+ const toPath = canon(edge.toPath);
90
+ const fromRes = await deps.loadDoc(fromPath);
91
+ if (!fromRes.ok)
92
+ return fromRes;
93
+ const toRes = await deps.loadDoc(toPath);
94
+ if (!toRes.ok)
95
+ return toRes;
96
+ const axes = axesForPanel(opts.panelSize);
97
+ const votes = [];
98
+ const writeErrors = [];
99
+ let llmCalls = 0;
100
+ let inputTokens = 0;
101
+ let outputTokens = 0;
102
+ // --- Phase 1: ELICIT all M votes (no writes). Aggregation must see the whole
103
+ // panel before deciding — applying writes per-vote let a single dissent revoke
104
+ // a healthy edge and a later survive re-seed it (order-dependent churn). ---
105
+ const surviving = [];
106
+ let failsCount = 0;
107
+ let firstFailReason = "re-derivation failed";
108
+ for (const axis of axes) {
109
+ if (llmCalls >= opts.budgetRemaining)
110
+ break;
111
+ const r = await deps.llm.completeJson({
112
+ model: opts.model,
113
+ system: SYSTEM_BASE,
114
+ user: userBody(axis, fromPath, fromRes.value.content, toPath, toRes.value.content, edge),
115
+ schema: VERDICT_SCHEMA,
116
+ });
117
+ llmCalls++;
118
+ if (!r.ok) {
119
+ votes.push({ axis, error: r.error.message });
120
+ continue;
121
+ }
122
+ inputTokens += r.value.input_tokens;
123
+ outputTokens += r.value.output_tokens;
124
+ const parsed = parseRevisionVerdict(r.value.parsed);
125
+ if (!parsed.ok) {
126
+ votes.push({ axis, error: parsed.error.message });
127
+ continue;
128
+ }
129
+ votes.push({ axis, verdict: parsed.value.verdict, reason: parsed.value.reason });
130
+ if (parsed.value.verdict === "survives") {
131
+ surviving.push({ axis, reason: parsed.value.reason });
132
+ }
133
+ else {
134
+ if (failsCount === 0)
135
+ firstFailReason = parsed.value.reason;
136
+ failsCount++;
137
+ }
138
+ }
139
+ // --- Phase 2: AGGREGATE, then apply ONE decision (majority-decides). ---
140
+ const survivesCount = surviving.length;
141
+ let decision;
142
+ let observedCount = 0;
143
+ let contestedCount = 0;
144
+ if (survivesCount === 0 && failsCount === 0) {
145
+ decision = "no-vote"; // all errored / budget-starved — surface, write nothing
146
+ }
147
+ else if (failsCount > survivesCount) {
148
+ // Majority fails ⇒ ONE contest (revoke + tension). Satisfies the spec's
149
+ // multi-pass-agreement-for-contests: a lone dissent can no longer revoke.
150
+ decision = "fails";
151
+ const con = await deps.contest({
152
+ fromPath,
153
+ toPath,
154
+ contestedBy: opts.agent,
155
+ reason: `revision panel (${failsCount}/${survivesCount + failsCount} fail): ${firstFailReason}`,
156
+ });
157
+ if (con.ok)
158
+ contestedCount = 1;
159
+ else
160
+ writeErrors.push({ axis: axes[0], error: `contest failed: ${con.error.message}` });
161
+ }
162
+ else if (survivesCount > failsCount) {
163
+ // Majority survives ⇒ accrue strength: each surviving vote observes with a
164
+ // DISTINCT store axis (EDGE_AXES), so the §11.3 replay guard counts them as
165
+ // independent in one sitting (panelSize ≤ EDGE_AXES.length ⇒ no collision).
166
+ decision = "survives";
167
+ for (let i = 0; i < surviving.length; i++) {
168
+ const storeAxis = EDGE_AXES[i % EDGE_AXES.length];
169
+ const obs = await deps.observe({
170
+ fromPath,
171
+ toPath,
172
+ observedBy: opts.agent,
173
+ blind: true,
174
+ axis: storeAxis,
175
+ note: `revision/${surviving[i].axis}: ${surviving[i].reason}`,
176
+ });
177
+ if (obs.ok)
178
+ observedCount++;
179
+ else
180
+ writeErrors.push({
181
+ axis: surviving[i].axis,
182
+ error: `observe failed: ${obs.error.message}`,
183
+ });
184
+ }
185
+ }
186
+ else {
187
+ // Tie (survives === fails, both > 0): no majority ⇒ surface, write nothing.
188
+ decision = "tie";
189
+ }
190
+ const traceRes = await deps.recordRevisionTrace({
191
+ at: new Date().toISOString().replace(/\.\d{3}Z$/, "Z"),
192
+ fromPath,
193
+ toPath,
194
+ edgeStrengthAtStart: edge.strength,
195
+ kSurvivedAtStart: edge.kSurvived,
196
+ lastRederivedAtStart: edge.lastRederived,
197
+ model: opts.model,
198
+ votes,
199
+ decision,
200
+ observedCount,
201
+ contestedCount,
202
+ });
203
+ return ok({
204
+ fromPath,
205
+ toPath,
206
+ votes,
207
+ decision,
208
+ observedCount,
209
+ contestedCount,
210
+ writeErrors,
211
+ llmCalls,
212
+ inputTokens,
213
+ outputTokens,
214
+ traceWritten: traceRes.ok,
215
+ ...(traceRes.ok ? {} : { traceError: traceRes.error.message }),
216
+ });
217
+ }
218
+ // --- the CLI-wired trace recorder --------------------------------------------
219
+ export function revisionTracePath(vaultRoot) {
220
+ return join(vaultRoot, ".daftari", "revision-trace.jsonl");
221
+ }
222
+ export async function appendRevisionTrace(vaultRoot, row) {
223
+ try {
224
+ mkdirSync(join(vaultRoot, ".daftari"), { recursive: true });
225
+ appendFileSync(revisionTracePath(vaultRoot), `${JSON.stringify(row)}\n`);
226
+ return ok(undefined);
227
+ }
228
+ catch (e) {
229
+ return err(new Error(`cannot record revision trace: ${e instanceof Error ? e.message : String(e)}`));
230
+ }
231
+ }
232
+ //# sourceMappingURL=revision.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"revision.js","sourceRoot":"","sources":["../../src/consolidate/revision.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,10 @@
1
+ import { type Result } from "../frontmatter/types.js";
2
+ export interface ConsolidateState {
3
+ lastConsolidationCommit: string | null;
4
+ birthProcessed: Record<string, string>;
5
+ }
6
+ export declare function consolidateStatePath(vaultRoot: string): string;
7
+ export declare function docContentHash(content: string): string;
8
+ export declare function readConsolidateState(vaultRoot: string): ConsolidateState;
9
+ export declare function writeConsolidateState(vaultRoot: string, state: ConsolidateState): Result<void, Error>;
10
+ //# sourceMappingURL=state.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"state.d.ts","sourceRoot":"","sources":["../../src/consolidate/state.ts"],"names":[],"mappings":"AASA,OAAO,EAAW,KAAK,MAAM,EAAE,MAAM,yBAAyB,CAAC;AAE/D,MAAM,WAAW,gBAAgB;IAC/B,uBAAuB,EAAE,MAAM,GAAG,IAAI,CAAC;IAEvC,cAAc,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC;CACxC;AAMD,wBAAgB,oBAAoB,CAAC,SAAS,EAAE,MAAM,GAAG,MAAM,CAE9D;AAED,wBAAgB,cAAc,CAAC,OAAO,EAAE,MAAM,GAAG,MAAM,CAEtD;AAID,wBAAgB,oBAAoB,CAAC,SAAS,EAAE,MAAM,GAAG,gBAAgB,CAYxE;AAKD,wBAAgB,qBAAqB,CACnC,SAAS,EAAE,MAAM,EACjB,KAAK,EAAE,gBAAgB,GACtB,MAAM,CAAC,IAAI,EAAE,KAAK,CAAC,CAarB"}
@@ -0,0 +1,54 @@
1
+ // .daftari/consolidate-state.json — the loop's ephemeral cross-session memory:
2
+ // the last consolidation commit (the event clock's baseline) and the
3
+ // birth-processed doc hashes (so an edited doc re-births, spec §4.0). Git-ignored
4
+ // and rebuildable: absent OR corrupt ⇒ the empty default, and the next session
5
+ // treats HEAD as its baseline (the nil path, §3.1/§7).
6
+ import { createHash } from "node:crypto";
7
+ import { existsSync, mkdirSync, readFileSync, writeFileSync } from "node:fs";
8
+ import { dirname, join } from "node:path";
9
+ import { err, ok } from "../frontmatter/types.js";
10
+ function emptyState() {
11
+ return { lastConsolidationCommit: null, birthProcessed: {} };
12
+ }
13
+ export function consolidateStatePath(vaultRoot) {
14
+ return join(vaultRoot, ".daftari", "consolidate-state.json");
15
+ }
16
+ export function docContentHash(content) {
17
+ return createHash("sha256").update(content).digest("hex").slice(0, 16);
18
+ }
19
+ // Absent OR corrupt ⇒ the empty default. The state is ephemeral and rebuildable
20
+ // (an empty lastCommit just means the next session uses HEAD as its baseline).
21
+ export function readConsolidateState(vaultRoot) {
22
+ const p = consolidateStatePath(vaultRoot);
23
+ if (!existsSync(p))
24
+ return emptyState();
25
+ try {
26
+ const raw = JSON.parse(readFileSync(p, "utf-8"));
27
+ return {
28
+ lastConsolidationCommit: raw.lastConsolidationCommit ?? null,
29
+ birthProcessed: raw.birthProcessed ?? {},
30
+ };
31
+ }
32
+ catch {
33
+ return emptyState();
34
+ }
35
+ }
36
+ // Returns a Result rather than throwing (CLAUDE.md house style): a full disk or
37
+ // a read-only .daftari is a recoverable degrade — the loop's state is ephemeral,
38
+ // so a failed write just means the next session re-baselines, not a crash.
39
+ export function writeConsolidateState(vaultRoot, state) {
40
+ if (typeof vaultRoot !== "string" || vaultRoot.trim().length === 0) {
41
+ return err(new Error("writeConsolidateState requires a non-empty vaultRoot"));
42
+ }
43
+ try {
44
+ const p = consolidateStatePath(vaultRoot);
45
+ mkdirSync(dirname(p), { recursive: true });
46
+ writeFileSync(p, `${JSON.stringify(state, null, 2)}\n`);
47
+ return ok(undefined);
48
+ }
49
+ catch (e) {
50
+ const reason = e instanceof Error ? e.message : String(e);
51
+ return err(new Error(`cannot write consolidate state: ${reason}`));
52
+ }
53
+ }
54
+ //# sourceMappingURL=state.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"state.js","sourceRoot":"","sources":["../../src/consolidate/state.ts"],"names":[],"mappings":"AAAA,+EAA+E;AAC/E,qEAAqE;AACrE,kFAAkF;AAClF,+EAA+E;AAC/E,uDAAuD;AAEvD,OAAO,EAAE,UAAU,EAAE,MAAM,aAAa,CAAC;AACzC,OAAO,EAAE,UAAU,EAAE,SAAS,EAAE,YAAY,EAAE,aAAa,EAAE,MAAM,SAAS,CAAC;AAC7E,OAAO,EAAE,OAAO,EAAE,IAAI,EAAE,MAAM,WAAW,CAAC;AAC1C,OAAO,EAAE,GAAG,EAAE,EAAE,EAAe,MAAM,yBAAyB,CAAC;AAQ/D,SAAS,UAAU;IACjB,OAAO,EAAE,uBAAuB,EAAE,IAAI,EAAE,cAAc,EAAE,EAAE,EAAE,CAAC;AAC/D,CAAC;AAED,MAAM,UAAU,oBAAoB,CAAC,SAAiB;IACpD,OAAO,IAAI,CAAC,SAAS,EAAE,UAAU,EAAE,wBAAwB,CAAC,CAAC;AAC/D,CAAC;AAED,MAAM,UAAU,cAAc,CAAC,OAAe;IAC5C,OAAO,UAAU,CAAC,QAAQ,CAAC,CAAC,MAAM,CAAC,OAAO,CAAC,CAAC,MAAM,CAAC,KAAK,CAAC,CAAC,KAAK,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC;AACzE,CAAC;AAED,gFAAgF;AAChF,+EAA+E;AAC/E,MAAM,UAAU,oBAAoB,CAAC,SAAiB;IACpD,MAAM,CAAC,GAAG,oBAAoB,CAAC,SAAS,CAAC,CAAC;IAC1C,IAAI,CAAC,UAAU,CAAC,CAAC,CAAC;QAAE,OAAO,UAAU,EAAE,CAAC;IACxC,IAAI,CAAC;QACH,MAAM,GAAG,GAAG,IAAI,CAAC,KAAK,CAAC,YAAY,CAAC,CAAC,EAAE,OAAO,CAAC,CAA8B,CAAC;QAC9E,OAAO;YACL,uBAAuB,EAAE,GAAG,CAAC,uBAAuB,IAAI,IAAI;YAC5D,cAAc,EAAE,GAAG,CAAC,cAAc,IAAI,EAAE;SACzC,CAAC;IACJ,CAAC;IAAC,MAAM,CAAC;QACP,OAAO,UAAU,EAAE,CAAC;IACtB,CAAC;AACH,CAAC;AAED,gFAAgF;AAChF,iFAAiF;AACjF,2EAA2E;AAC3E,MAAM,UAAU,qBAAqB,CACnC,SAAiB,EACjB,KAAuB;IAEvB,IAAI,OAAO,SAAS,KAAK,QAAQ,IAAI,SAAS,CAAC,IAAI,EAAE,CAAC,MAAM,KAAK,CAAC,EAAE,CAAC;QACnE,OAAO,GAAG,CAAC,IAAI,KAAK,CAAC,sDAAsD,CAAC,CAAC,CAAC;IAChF,CAAC;IACD,IAAI,CAAC;QACH,MAAM,CAAC,GAAG,oBAAoB,CAAC,SAAS,CAAC,CAAC;QAC1C,SAAS,CAAC,OAAO,CAAC,CAAC,CAAC,EAAE,EAAE,SAAS,EAAE,IAAI,EAAE,CAAC,CAAC;QAC3C,aAAa,CAAC,CAAC,EAAE,GAAG,IAAI,CAAC,SAAS,CAAC,KAAK,EAAE,IAAI,EAAE,CAAC,CAAC,IAAI,CAAC,CAAC;QACxD,OAAO,EAAE,CAAC,SAAS,CAAC,CAAC;IACvB,CAAC;IAAC,OAAO,CAAC,EAAE,CAAC;QACX,MAAM,MAAM,GAAG,CAAC,YAAY,KAAK,CAAC,CAAC,CAAC,CAAC,CAAC,OAAO,CAAC,CAAC,CAAC,MAAM,CAAC,CAAC,CAAC,CAAC;QAC1D,OAAO,GAAG,CAAC,IAAI,KAAK,CAAC,mCAAmC,MAAM,EAAE,CAAC,CAAC,CAAC;IACrE,CAAC;AACH,CAAC"}
@@ -8,6 +8,9 @@ export declare const EDGE_AXES: readonly ["prompt", "input-neighborhood", "model
8
8
  export type EdgeAxis = (typeof EDGE_AXES)[number];
9
9
  export declare const EDGE_STATUSES: readonly ["candidate", "trigger-bearing", "revoked"];
10
10
  export type EdgeStatus = (typeof EDGE_STATUSES)[number];
11
+ export declare const PREMISE_VOTES: readonly ["from", "to", "symmetric"];
12
+ export type PremiseVote = (typeof PREMISE_VOTES)[number];
13
+ export type DirectionVerdict = "directed" | "symmetric";
11
14
  export interface DerivesFromEdge {
12
15
  fromPath: string;
13
16
  toPath: string;
@@ -16,6 +19,7 @@ export interface DerivesFromEdge {
16
19
  firstObserved: string;
17
20
  lastRederived: string;
18
21
  status: EdgeStatus;
22
+ directionVerdict: DirectionVerdict;
19
23
  observations: number;
20
24
  contestedAt: string | null;
21
25
  contestReason: string | null;
@@ -27,6 +31,7 @@ export interface ObserveEdgeInput {
27
31
  blind: boolean;
28
32
  axis?: EdgeAxis;
29
33
  note?: string;
34
+ premiseVote?: PremiseVote;
30
35
  at?: string;
31
36
  }
32
37
  export interface ContestEdgeInput {
@@ -1 +1 @@
1
- {"version":3,"file":"edges.d.ts","sourceRoot":"","sources":["../../src/curation/edges.ts"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"edges.d.ts","sourceRoot":"","sources":["../../src/curation/edges.ts"],"names":[],"mappings":"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"}
@@ -69,6 +69,10 @@ export const EDGE_REPLAY_GAP_DAYS = 1;
69
69
  // Q3: the axes a re-derivation can vary to count as an independent vote.
70
70
  export const EDGE_AXES = ["prompt", "input-neighborhood", "model"];
71
71
  export const EDGE_STATUSES = ["candidate", "trigger-bearing", "revoked"];
72
+ // Which endpoint an observation judged the load-bearing premise. "to" is the
73
+ // normal directed case (birth orients premise on `to`); a "from"/"to" split or
74
+ // an explicit "symmetric" collapses the edge's direction to unconfirmed.
75
+ export const PREMISE_VOTES = ["from", "to", "symmetric"];
72
76
  export function edgesPath(vaultRoot) {
73
77
  return join(vaultRoot, ".daftari", "edges.jsonl");
74
78
  }
@@ -132,6 +136,30 @@ function edgeKey(from, to) {
132
136
  // Paths are vault-relative and never contain a newline; "\n" cannot collide.
133
137
  return `${from}\n${to}`;
134
138
  }
139
+ // The DURABLE key is canonical (sorted), so the two orientations of a pair —
140
+ // `(x,y)` and `(y,x)` — collapse to ONE edge. Direction is then a derived
141
+ // property of the premise votes, not of key order: this is what makes a
142
+ // post-edit orientation flip a from/to *split* (⇒ symmetric) instead of a
143
+ // second contradictory directed twin. Output orientation is re-derived in
144
+ // deriveEdge.
145
+ function canonPair(from, to) {
146
+ return from <= to ? [from, to] : [to, from];
147
+ }
148
+ // Collapse the cycle's premise votes into a direction verdict (review C1):
149
+ // empty (legacy) or unanimous from/to ⇒ directed; a from/to split or any
150
+ // explicit symmetric vote ⇒ symmetric.
151
+ function directionVerdictOf(votes) {
152
+ if (votes.has("symmetric"))
153
+ return "symmetric";
154
+ if (votes.has("from") && votes.has("to"))
155
+ return "symmetric";
156
+ return "directed";
157
+ }
158
+ function validPremiseVote(v) {
159
+ return typeof v === "string" && PREMISE_VOTES.includes(v)
160
+ ? v
161
+ : null;
162
+ }
135
163
  // Collapses the append-only log to one current state per (from, to), applying
136
164
  // the strength-model rules in FILE order (the order events were appended —
137
165
  // the tools only ever append at the current instant, so file order is time
@@ -156,8 +184,22 @@ function collapse(records) {
156
184
  const from = rec.from;
157
185
  const to = rec.to;
158
186
  const at = rec.at;
159
- const key = edgeKey(from, to);
187
+ const [cFrom, cTo] = canonPair(from, to);
188
+ const key = edgeKey(cFrom, cTo);
160
189
  const existing = byKey.get(key);
190
+ // Resolve this record's premise vote (relative to the record's own from/to)
191
+ // to a CANONICAL endpoint vote: "from" = canonical-first is the premise,
192
+ // "to" = canonical-second is the premise. Opposite orientations of the same
193
+ // pair therefore vote on the same canonical axis and a flip becomes a split.
194
+ const rawVote = validPremiseVote(rec.premiseVote);
195
+ let canonVote = null;
196
+ if (rawVote === "symmetric") {
197
+ canonVote = "symmetric";
198
+ }
199
+ else if (rawVote === "from" || rawVote === "to") {
200
+ const premisePath = rawVote === "to" ? to : from;
201
+ canonVote = premisePath === cFrom ? "from" : "to";
202
+ }
161
203
  if (rec.kind === "contest") {
162
204
  // A contest of an edge that was never observed cannot stand alone.
163
205
  if (!existing || existing.revoked)
@@ -183,8 +225,10 @@ function collapse(records) {
183
225
  ? [`${rec.by}\n${rec.axis}`]
184
226
  : [];
185
227
  byKey.set(key, {
186
- fromPath: from,
187
- toPath: to,
228
+ fromPath: cFrom,
229
+ toPath: cTo,
230
+ seedFrom: from,
231
+ seedTo: to,
188
232
  kSurvived: 0,
189
233
  firstObserved: at,
190
234
  lastRederived: at,
@@ -193,10 +237,13 @@ function collapse(records) {
193
237
  contestedAt: null,
194
238
  contestReason: null,
195
239
  votedPairs: new Set(seedPair),
240
+ premiseVotes: new Set(canonVote ? [canonVote] : []),
196
241
  });
197
242
  continue;
198
243
  }
199
244
  existing.observations += 1;
245
+ if (canonVote)
246
+ existing.premiseVotes.add(canonVote);
200
247
  const qualifying = rec.blind === true &&
201
248
  typeof rec.axis === "string" &&
202
249
  EDGE_AXES.includes(rec.axis) &&
@@ -226,14 +273,34 @@ function deriveEdge(state, now) {
226
273
  : strength >= EDGE_TRIGGER_STRENGTH
227
274
  ? "trigger-bearing"
228
275
  : "candidate";
276
+ // Re-derive the OUTPUT orientation (from=dependent, to=premise). The state's
277
+ // fromPath/toPath are canonical; direction lives in the votes:
278
+ // - symmetric ⇒ keep canonical order (direction unconfirmed, won't propagate)
279
+ // - directed with votes ⇒ premise = the voted canonical endpoint
280
+ // - directed without votes (legacy) ⇒ preserve the seed's original orientation
281
+ const verdict = directionVerdictOf(state.premiseVotes);
282
+ let fromPath = state.fromPath;
283
+ let toPath = state.toPath;
284
+ if (verdict === "directed") {
285
+ if (state.premiseVotes.size === 0) {
286
+ fromPath = state.seedFrom;
287
+ toPath = state.seedTo;
288
+ }
289
+ else {
290
+ const premiseIsFrom = state.premiseVotes.has("from"); // unanimous when directed
291
+ fromPath = premiseIsFrom ? state.toPath : state.fromPath; // dependent
292
+ toPath = premiseIsFrom ? state.fromPath : state.toPath; // premise
293
+ }
294
+ }
229
295
  return {
230
- fromPath: state.fromPath,
231
- toPath: state.toPath,
296
+ fromPath,
297
+ toPath,
232
298
  strength,
233
299
  kSurvived: state.kSurvived,
234
300
  firstObserved: state.firstObserved,
235
301
  lastRederived: state.lastRederived,
236
302
  status,
303
+ directionVerdict: verdict,
237
304
  observations: state.observations,
238
305
  contestedAt: state.contestedAt,
239
306
  contestReason: state.contestReason,
@@ -261,6 +328,10 @@ export async function observeEdge(vaultRoot, input) {
261
328
  if (input.at !== undefined && !Number.isFinite(Date.parse(input.at))) {
262
329
  return err(new Error("observeEdge 'at' must be a parseable timestamp"));
263
330
  }
331
+ if (input.premiseVote !== undefined &&
332
+ !PREMISE_VOTES.includes(input.premiseVote)) {
333
+ return err(new Error(`observeEdge 'premiseVote' must be one of: ${PREMISE_VOTES.join(", ")}`));
334
+ }
264
335
  const record = {
265
336
  kind: "observe",
266
337
  from: input.fromPath.trim(),
@@ -270,6 +341,7 @@ export async function observeEdge(vaultRoot, input) {
270
341
  blind: input.blind,
271
342
  axis: input.axis ?? null,
272
343
  ...(input.note ? { note: input.note } : {}),
344
+ ...(input.premiseVote ? { premiseVote: input.premiseVote } : {}),
273
345
  };
274
346
  try {
275
347
  mkdirSync(join(vaultRoot, ".daftari"), { recursive: true });
@@ -279,7 +351,7 @@ export async function observeEdge(vaultRoot, input) {
279
351
  const reason = e instanceof Error ? e.message : String(e);
280
352
  return err(new Error(`cannot record edge observation: ${reason}`));
281
353
  }
282
- const after = collapse(readRawRecords(vaultRoot)).get(edgeKey(record.from, record.to));
354
+ const after = collapse(readRawRecords(vaultRoot)).get(edgeKey(...canonPair(record.from, record.to)));
283
355
  if (!after)
284
356
  return err(new Error("edge not found after write"));
285
357
  return ok(deriveEdge(after, new Date()));
@@ -300,7 +372,7 @@ export async function contestEdge(vaultRoot, input) {
300
372
  }
301
373
  const from = input.fromPath.trim();
302
374
  const to = input.toPath.trim();
303
- const current = collapse(readRawRecords(vaultRoot)).get(edgeKey(from, to));
375
+ const current = collapse(readRawRecords(vaultRoot)).get(edgeKey(...canonPair(from, to)));
304
376
  if (!current) {
305
377
  return err(new Error(`contestEdge: no such edge: ${from} derives_from ${to}`));
306
378
  }
@@ -323,7 +395,7 @@ export async function contestEdge(vaultRoot, input) {
323
395
  const reason = e instanceof Error ? e.message : String(e);
324
396
  return err(new Error(`cannot record edge contest: ${reason}`));
325
397
  }
326
- const after = collapse(readRawRecords(vaultRoot)).get(edgeKey(from, to));
398
+ const after = collapse(readRawRecords(vaultRoot)).get(edgeKey(...canonPair(from, to)));
327
399
  if (!after)
328
400
  return err(new Error("edge not found after write"));
329
401
  return ok(deriveEdge(after, new Date()));
@@ -351,7 +423,7 @@ export async function listEdges(vaultRoot, filter = {}, now = new Date()) {
351
423
  }
352
424
  export async function getEdge(vaultRoot, fromPath, toPath, now = new Date()) {
353
425
  try {
354
- const state = collapse(readRawRecords(vaultRoot)).get(edgeKey(fromPath, toPath));
426
+ const state = collapse(readRawRecords(vaultRoot)).get(edgeKey(...canonPair(fromPath, toPath)));
355
427
  return ok(state ? deriveEdge(state, now) : null);
356
428
  }
357
429
  catch (e) {
@@ -378,6 +450,7 @@ export function rebuildEdgesIndex(db, vaultRoot, now = new Date()) {
378
450
  last_rederived: e.lastRederived,
379
451
  last_age_decay: at,
380
452
  status: e.status,
453
+ direction_verdict: e.directionVerdict,
381
454
  }));
382
455
  const write = db.transaction(() => {
383
456
  clearDerivesFromEdges(db);