@fractalsynapse/dsh-chapters 0.0.0-stage → 0.2.21
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/LICENSE +14 -0
- package/README.md +76 -2
- package/cordis.patch.yml +363 -0
- package/docs/architecture.md +692 -0
- package/docs/autorun-guide.md +248 -0
- package/docs/backlog.md +2022 -0
- package/docs/contract.md +938 -0
- package/docs/development.md +990 -0
- package/docs/host-compaction-seam.md +225 -0
- package/docs/host-protected-head-ask.md +80 -0
- package/docs/knowledge-repo.md +1332 -0
- package/docs/plans-carriage.md +571 -0
- package/docs/project-state.md +642 -0
- package/docs/provider.md +98 -0
- package/docs/superpowers/plans/2026-10-17-chapters-save.md +198 -0
- package/docs/superpowers/plans/2026-11-02-chapter-removal.md +151 -0
- package/docs/superpowers/specs/2026-11-02-chapter-removal-design.md +115 -0
- package/docs/verify.md +561 -0
- package/lib/archive.d.ts +161 -0
- package/lib/archive.js +229 -0
- package/lib/archive.js.map +1 -0
- package/lib/arrival.d.ts +23 -0
- package/lib/arrival.js +125 -0
- package/lib/arrival.js.map +1 -0
- package/lib/artifact-query.d.ts +62 -0
- package/lib/artifact-query.js +187 -0
- package/lib/artifact-query.js.map +1 -0
- package/lib/attach.d.ts +46 -0
- package/lib/attach.js +120 -0
- package/lib/attach.js.map +1 -0
- package/lib/auto-save.d.ts +145 -0
- package/lib/auto-save.js +334 -0
- package/lib/auto-save.js.map +1 -0
- package/lib/aux-message.d.ts +17 -0
- package/lib/aux-message.js +20 -0
- package/lib/aux-message.js.map +1 -0
- package/lib/client.js +1419 -0
- package/lib/client.js.map +1 -0
- package/lib/codegraph.d.ts +76 -0
- package/lib/codegraph.js +190 -0
- package/lib/codegraph.js.map +1 -0
- package/lib/commands.d.ts +52 -0
- package/lib/commands.js +946 -0
- package/lib/commands.js.map +1 -0
- package/lib/compose.d.ts +30 -0
- package/lib/compose.js +185 -0
- package/lib/compose.js.map +1 -0
- package/lib/continue-core.d.ts +245 -0
- package/lib/continue-core.js +405 -0
- package/lib/continue-core.js.map +1 -0
- package/lib/diag.d.ts +55 -0
- package/lib/diag.js +186 -0
- package/lib/diag.js.map +1 -0
- package/lib/engine-core.d.ts +301 -0
- package/lib/engine-core.js +531 -0
- package/lib/engine-core.js.map +1 -0
- package/lib/engine-note.d.ts +35 -0
- package/lib/engine-note.js +32 -0
- package/lib/engine-note.js.map +1 -0
- package/lib/engine.d.ts +329 -0
- package/lib/engine.js +1179 -0
- package/lib/engine.js.map +1 -0
- package/lib/enrich-queue.d.ts +66 -0
- package/lib/enrich-queue.js +126 -0
- package/lib/enrich-queue.js.map +1 -0
- package/lib/enrich-store.d.ts +31 -0
- package/lib/enrich-store.js +103 -0
- package/lib/enrich-store.js.map +1 -0
- package/lib/enrich-wire.d.ts +163 -0
- package/lib/enrich-wire.js +353 -0
- package/lib/enrich-wire.js.map +1 -0
- package/lib/enrich.d.ts +102 -0
- package/lib/enrich.js +267 -0
- package/lib/enrich.js.map +1 -0
- package/lib/gitops.d.ts +107 -0
- package/lib/gitops.js +539 -0
- package/lib/gitops.js.map +1 -0
- package/lib/index.d.ts +241 -0
- package/lib/index.js +1162 -0
- package/lib/index.js.map +1 -0
- package/lib/indexing.d.ts +98 -0
- package/lib/indexing.js +223 -0
- package/lib/indexing.js.map +1 -0
- package/lib/injections.d.ts +90 -0
- package/lib/injections.js +165 -0
- package/lib/injections.js.map +1 -0
- package/lib/last-resort.d.ts +24 -0
- package/lib/last-resort.js +27 -0
- package/lib/last-resort.js.map +1 -0
- package/lib/log-shape.d.ts +38 -0
- package/lib/log-shape.js +93 -0
- package/lib/log-shape.js.map +1 -0
- package/lib/models-command.d.ts +74 -0
- package/lib/models-command.js +100 -0
- package/lib/models-command.js.map +1 -0
- package/lib/notice.d.ts +74 -0
- package/lib/notice.js +92 -0
- package/lib/notice.js.map +1 -0
- package/lib/plans.d.ts +154 -0
- package/lib/plans.js +315 -0
- package/lib/plans.js.map +1 -0
- package/lib/preset.d.ts +186 -0
- package/lib/preset.js +212 -0
- package/lib/preset.js.map +1 -0
- package/lib/prompt-keys.d.ts +7 -0
- package/lib/prompt-keys.js +8 -0
- package/lib/prompt-keys.js.map +1 -0
- package/lib/prompt-loop.d.ts +73 -0
- package/lib/prompt-loop.js +147 -0
- package/lib/prompt-loop.js.map +1 -0
- package/lib/prompt-push.d.ts +47 -0
- package/lib/prompt-push.js +115 -0
- package/lib/prompt-push.js.map +1 -0
- package/lib/prompt-runner.d.ts +48 -0
- package/lib/prompt-runner.js +90 -0
- package/lib/prompt-runner.js.map +1 -0
- package/lib/prompt-state.d.ts +142 -0
- package/lib/prompt-state.js +101 -0
- package/lib/prompt-state.js.map +1 -0
- package/lib/prompt.d.ts +270 -0
- package/lib/prompt.js +408 -0
- package/lib/prompt.js.map +1 -0
- package/lib/provider.d.ts +44 -0
- package/lib/provider.js +66 -0
- package/lib/provider.js.map +1 -0
- package/lib/redact.d.ts +24 -0
- package/lib/redact.js +85 -0
- package/lib/redact.js.map +1 -0
- package/lib/registry.d.ts +187 -0
- package/lib/registry.js +235 -0
- package/lib/registry.js.map +1 -0
- package/lib/removal.d.ts +159 -0
- package/lib/removal.js +556 -0
- package/lib/removal.js.map +1 -0
- package/lib/render.d.ts +97 -0
- package/lib/render.js +530 -0
- package/lib/render.js.map +1 -0
- package/lib/repair-run.d.ts +76 -0
- package/lib/repair-run.js +506 -0
- package/lib/repair-run.js.map +1 -0
- package/lib/repair.d.ts +208 -0
- package/lib/repair.js +357 -0
- package/lib/repair.js.map +1 -0
- package/lib/repo.d.ts +78 -0
- package/lib/repo.js +184 -0
- package/lib/repo.js.map +1 -0
- package/lib/rules-commands.d.ts +46 -0
- package/lib/rules-commands.js +379 -0
- package/lib/rules-commands.js.map +1 -0
- package/lib/rules.d.ts +267 -0
- package/lib/rules.js +478 -0
- package/lib/rules.js.map +1 -0
- package/lib/save-core.d.ts +72 -0
- package/lib/save-core.js +220 -0
- package/lib/save-core.js.map +1 -0
- package/lib/search.d.ts +112 -0
- package/lib/search.js +282 -0
- package/lib/search.js.map +1 -0
- package/lib/session-rules.d.ts +222 -0
- package/lib/session-rules.js +454 -0
- package/lib/session-rules.js.map +1 -0
- package/lib/signature.d.ts +45 -0
- package/lib/signature.js +199 -0
- package/lib/signature.js.map +1 -0
- package/lib/store.d.ts +156 -0
- package/lib/store.js +368 -0
- package/lib/store.js.map +1 -0
- package/lib/sync.d.ts +220 -0
- package/lib/sync.js +810 -0
- package/lib/sync.js.map +1 -0
- package/lib/tools.d.ts +113 -0
- package/lib/tools.js +1090 -0
- package/lib/tools.js.map +1 -0
- package/lib/treedx/client.d.ts +57 -0
- package/lib/treedx/client.js +82 -0
- package/lib/treedx/client.js.map +1 -0
- package/lib/treedx/provider.d.ts +39 -0
- package/lib/treedx/provider.js +479 -0
- package/lib/treedx/provider.js.map +1 -0
- package/lib/treedx/state.d.ts +27 -0
- package/lib/treedx/state.js +65 -0
- package/lib/treedx/state.js.map +1 -0
- package/lib/types.d.ts +210 -0
- package/lib/types.js +31 -0
- package/lib/types.js.map +1 -0
- package/lib/version.d.ts +9 -0
- package/lib/version.js +10 -0
- package/lib/version.js.map +1 -0
- package/lib/vocabulary.d.ts +41 -0
- package/lib/vocabulary.js +118 -0
- package/lib/vocabulary.js.map +1 -0
- package/lib/warnings.d.ts +10 -0
- package/lib/warnings.js +18 -0
- package/lib/warnings.js.map +1 -0
- package/package.json +151 -3
package/lib/store.js
ADDED
|
@@ -0,0 +1,368 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The storage-domain adapter: zod schemas, the durable spec, and the ports
|
|
3
|
+
* that connect the pure registry (registry.ts) and pure archive writer
|
|
4
|
+
* (archive.ts) to host persistence and the filesystem. This is glue next to
|
|
5
|
+
* `src/index.ts` — thin by policy; every decision lives in the pure layer.
|
|
6
|
+
*
|
|
7
|
+
* Keying: one record per SESSION id (`sessions` table), because chapters are
|
|
8
|
+
* keyed by creating session, not a linear chain id — siblings branching from
|
|
9
|
+
* one ancestor cannot collide, and the flat TOC is built by walking links
|
|
10
|
+
* (registry.ancestorPath), never by a directory scan.
|
|
11
|
+
*
|
|
12
|
+
* Concurrency, honestly: read-modify-write per key. Same-session archive
|
|
13
|
+
* flows are serialized by their own reserve-before-write ordering, and a
|
|
14
|
+
* session has exactly one write handle (the kernel refuses a second, measured
|
|
15
|
+
* r3). Cross-process same-key writes are last-writer-wins on the JSON
|
|
16
|
+
* backend — acceptable for MVP, worth revisiting before multi-host chains.
|
|
17
|
+
*/
|
|
18
|
+
import { z } from 'zod';
|
|
19
|
+
import { ruleRecordSchema } from "./rules.js";
|
|
20
|
+
import { defineDomain, domainTable } from '@deepseek-ai/dsh-storage-domain';
|
|
21
|
+
import { mkdir, readFile, writeFile } from 'node:fs/promises';
|
|
22
|
+
import { dirname, isAbsolute } from 'node:path';
|
|
23
|
+
import { appendChapters, freshSession, releaseReservation, reserve, reservePlan as reservePlanNumber } from "./registry.js";
|
|
24
|
+
// ---------------------------------------------------------------- schemas + spec
|
|
25
|
+
const chapterRecordSchema = z.object({
|
|
26
|
+
number: z.number().int().positive(),
|
|
27
|
+
path: z.string().min(1),
|
|
28
|
+
title: z.string(),
|
|
29
|
+
summary: z.string(),
|
|
30
|
+
startSeq: z.number().int().nonnegative(),
|
|
31
|
+
endSeq: z.number().int().nonnegative(),
|
|
32
|
+
topics: z.array(z.string()).default([]),
|
|
33
|
+
messages: z.number().int().nonnegative().optional(),
|
|
34
|
+
shadowedSeqs: z.array(z.number().int().nonnegative()).optional(),
|
|
35
|
+
sha256: z.string().min(16),
|
|
36
|
+
/** P2 §2.4 provenance chains, newest-first per field (absent = everything
|
|
37
|
+
* still deterministic; enrichment only ever grows these). */
|
|
38
|
+
generated: z.record(z.string(), z.array(z.object({
|
|
39
|
+
by: z.string(), model: z.string().optional(), at: z.string().optional(),
|
|
40
|
+
}))).optional(),
|
|
41
|
+
estimatedTokens: z.number().int().nonnegative(),
|
|
42
|
+
artifacts: z.array(z.object({
|
|
43
|
+
path: z.string().min(1),
|
|
44
|
+
sha256: z.string().min(16),
|
|
45
|
+
bytes: z.number().int().nonnegative(),
|
|
46
|
+
})),
|
|
47
|
+
// Plans this chapter carries (docs/plans-carriage.md § 2). Optional: a stored row
|
|
48
|
+
// from before the feature simply has no field, which reads as "no plans here".
|
|
49
|
+
plans: z.array(z.object({
|
|
50
|
+
seq: z.number().int().nonnegative(),
|
|
51
|
+
status: z.enum(['approved', 'rejected', 'dismissed', 'pending', 'superseded', 'failed']),
|
|
52
|
+
title: z.string(),
|
|
53
|
+
sha256: z.string().min(16),
|
|
54
|
+
artifactPath: z.string().min(1),
|
|
55
|
+
bytes: z.number().int().nonnegative(),
|
|
56
|
+
estTokens: z.number().int().nonnegative(),
|
|
57
|
+
resultSeq: z.number().int().nonnegative().nullable(),
|
|
58
|
+
})).optional(),
|
|
59
|
+
planMode: z.enum(['active', 'inactive', 'unknown']).optional(),
|
|
60
|
+
// Where each plan's own `kind: plan` document landed.
|
|
61
|
+
planDocs: z.array(z.object({
|
|
62
|
+
seq: z.number().int().nonnegative(),
|
|
63
|
+
path: z.string().min(1),
|
|
64
|
+
sha256: z.string().min(16),
|
|
65
|
+
})).optional(),
|
|
66
|
+
// Tombstone status (#60 supersede, #255 operator removal) and its bookkeeping. Optional so rows
|
|
67
|
+
// written before these fields parse unchanged — and WITHOUT them here, zod STRIPS them on parse:
|
|
68
|
+
// the removed-stub round-trip test in registry.test.ts is the guard that the watermark-carrying
|
|
69
|
+
// fields survive every schema seam (repair's `repairs:` provenance rode the same hole).
|
|
70
|
+
status: z.enum(['superseded', 'removed']).optional(),
|
|
71
|
+
supersededBy: z.number().int().positive().optional(),
|
|
72
|
+
removedAt: z.string().min(10).optional(),
|
|
73
|
+
repairs: z.array(z.object({ class: z.string(), over: z.number().int(), at: z.number().int() })).optional(),
|
|
74
|
+
});
|
|
75
|
+
const collectionSchema = z.object({
|
|
76
|
+
seqs: z.array(z.number().int().nonnegative()).min(1),
|
|
77
|
+
paths: z.array(z.string()),
|
|
78
|
+
commands: z.array(z.string()),
|
|
79
|
+
terms: z.array(z.string()),
|
|
80
|
+
size: z.number().int().nonnegative(),
|
|
81
|
+
by: z.literal('deterministic'),
|
|
82
|
+
});
|
|
83
|
+
const sessionStateSchema = z.object({
|
|
84
|
+
rev: z.number().int().nonnegative().default(0),
|
|
85
|
+
parentSession: z.string().nullable(),
|
|
86
|
+
rootSession: z.string().min(1),
|
|
87
|
+
nextChapterNumber: z.number().int().positive(),
|
|
88
|
+
// Plan-document numbering (plans-carriage § 11). Optional with a default so a
|
|
89
|
+
// stored row from before the feature parses unchanged.
|
|
90
|
+
nextPlanNumber: z.number().int().positive().default(1),
|
|
91
|
+
chapters: z.array(chapterRecordSchema),
|
|
92
|
+
reservations: z.record(z.string(), z.array(z.number().int().positive())).default({}),
|
|
93
|
+
// #113: the Suggested Next Step bar's durable facts. Optional-with-default (the `nextPlanNumber`
|
|
94
|
+
// precedent) so a stored row from before the feature parses unchanged and no chapter bytes move.
|
|
95
|
+
promptSuggestion: z.object({
|
|
96
|
+
status: z.enum(['off', 'analyzing', 'ready', 'failed']),
|
|
97
|
+
text: z.string(),
|
|
98
|
+
turnSeq: z.number().int().nullable(),
|
|
99
|
+
error: z.string(),
|
|
100
|
+
at: z.string(),
|
|
101
|
+
intent: z.enum(['worker-step', 'goal-edit']).default('worker-step'),
|
|
102
|
+
goalRevision: z.number().int().nullable().default(null),
|
|
103
|
+
}).default({ status: 'off', text: '', turnSeq: null, error: '', at: '', intent: 'worker-step', goalRevision: null }),
|
|
104
|
+
plans: z.record(z.string(), z.object({
|
|
105
|
+
number: z.number().int().positive(),
|
|
106
|
+
path: z.string().min(1),
|
|
107
|
+
title: z.string(),
|
|
108
|
+
summary: z.string(),
|
|
109
|
+
chapters: z.array(z.object({
|
|
110
|
+
number: z.number().int().positive(),
|
|
111
|
+
path: z.string().min(1),
|
|
112
|
+
title: z.string(),
|
|
113
|
+
summary: z.string(),
|
|
114
|
+
startSeq: z.number().int().nonnegative(),
|
|
115
|
+
endSeq: z.number().int().nonnegative(),
|
|
116
|
+
})).optional(),
|
|
117
|
+
})).default({}),
|
|
118
|
+
finalized: z.record(z.string(), z.array(z.number().int().positive())).default({}),
|
|
119
|
+
collections: z.array(collectionSchema).default([]),
|
|
120
|
+
});
|
|
121
|
+
/** Parse (and backfill defaults for) one raw registry record — the seam
|
|
122
|
+
* migrations and tests use; the domain open path uses the same schema. */
|
|
123
|
+
export const parseSessionState = (raw) => sessionStateSchema.parse(raw);
|
|
124
|
+
const projectRecordSchema = z.object({
|
|
125
|
+
projectKey: z.string().min(1),
|
|
126
|
+
slug: z.string(),
|
|
127
|
+
remote: z.string().min(1),
|
|
128
|
+
harnessId: z.string().min(1),
|
|
129
|
+
linkedAt: z.string(),
|
|
130
|
+
cwd: z.string(),
|
|
131
|
+
/** Transport kind (§15 amendment); absent on stored rows = 'git'. */
|
|
132
|
+
kind: z.enum(['git', 'treedx']).default('git'),
|
|
133
|
+
/** TreeDX: server-assigned repository id, resolved at link time. */
|
|
134
|
+
repoId: z.string().optional(),
|
|
135
|
+
});
|
|
136
|
+
/** Parse (and default-fill) one stored project row — the migration seam tests
|
|
137
|
+
* and any future provider-kind backfill use; same shape as parseSessionState. */
|
|
138
|
+
export const parseProjectRecord = (raw) => projectRecordSchema.parse(raw);
|
|
139
|
+
/** Durable declaration of the dsh_chapters registry domain. */ export const chapterDomainSpec = defineDomain({
|
|
140
|
+
name: 'dsh_chapters',
|
|
141
|
+
// v1: + settings table (enrichment model overrides, P2). Existing v0 media
|
|
142
|
+
// load under compatibleVersions; absent tables materialize empty.
|
|
143
|
+
// v2: + rules table (P3 §7.1 records; status rides per-machine curation facts,
|
|
144
|
+
// never this table).
|
|
145
|
+
// v3: + projects.kind / projects.repoId (§15 amendment: transport is
|
|
146
|
+
// pluggable). Stored v2 rows parse with kind defaulting to 'git' — old data
|
|
147
|
+
// keeps working with zero migration, and the git path is untouched.
|
|
148
|
+
version: 3,
|
|
149
|
+
compatibleVersions: [0, 1, 2],
|
|
150
|
+
tables: {
|
|
151
|
+
sessions: domainTable(sessionStateSchema),
|
|
152
|
+
projects: domainTable(projectRecordSchema),
|
|
153
|
+
settings: domainTable(z.object({ value: z.string() })),
|
|
154
|
+
rules: domainTable(ruleRecordSchema),
|
|
155
|
+
},
|
|
156
|
+
});
|
|
157
|
+
export function makeDomainStore(domain) {
|
|
158
|
+
const table = domain.table('sessions');
|
|
159
|
+
const projects = domain.table('projects');
|
|
160
|
+
const settings = domain.table('settings');
|
|
161
|
+
const rules = domain.table('rules');
|
|
162
|
+
// One serialized section per session (#59). The row is a single whole value and five doors plus the
|
|
163
|
+
// turn/end signature listener write it; the wide sites hold a read across a model call or hundreds
|
|
164
|
+
// of filesystem writes, and an unguarded whole-row put then silently ERASES whatever another door
|
|
165
|
+
// committed in between (measured in the field: a compaction's manifest, reservation, `finalized`
|
|
166
|
+
// entry and chapter record all vanished while its 960 KB file stayed on disk, and the chapter number
|
|
167
|
+
// was re-spent). Serialization plus a rev check turns that into a refusal with numbers.
|
|
168
|
+
const sections = new Map();
|
|
169
|
+
const inside = new Set();
|
|
170
|
+
const runExclusive = async (sessionId, job) => {
|
|
171
|
+
if (inside.has(sessionId)) {
|
|
172
|
+
// Nested sections would deadlock (the inner waits on the outer's gate). Refusing is honest and
|
|
173
|
+
// cheap; a caller that needs to compose two mutations does it inside ONE fn.
|
|
174
|
+
throw new Error(`mutate: re-entered the registry section for ${sessionId} — compose the mutations in one fn`);
|
|
175
|
+
}
|
|
176
|
+
const prev = sections.get(sessionId) ?? Promise.resolve();
|
|
177
|
+
let release;
|
|
178
|
+
const gate = new Promise((r) => { release = r; });
|
|
179
|
+
// Chain: this session's next waiter must also wait for OUR completion, not just our predecessor.
|
|
180
|
+
const mine = prev.then(() => gate);
|
|
181
|
+
sections.set(sessionId, mine);
|
|
182
|
+
await prev.catch(() => undefined);
|
|
183
|
+
inside.add(sessionId);
|
|
184
|
+
try {
|
|
185
|
+
return await job();
|
|
186
|
+
}
|
|
187
|
+
finally {
|
|
188
|
+
inside.delete(sessionId);
|
|
189
|
+
release();
|
|
190
|
+
if (sections.get(sessionId) === mine)
|
|
191
|
+
sections.delete(sessionId);
|
|
192
|
+
}
|
|
193
|
+
};
|
|
194
|
+
const readRow = (sessionId) => table.get(sessionId) ?? freshSession(sessionId);
|
|
195
|
+
/** Bump the write counter and store. Only ever called from INSIDE a section. */
|
|
196
|
+
const writeInside = async (sessionId, state, currentRev) => {
|
|
197
|
+
await table.put(sessionId, { ...state, rev: currentRev + 1 });
|
|
198
|
+
};
|
|
199
|
+
return {
|
|
200
|
+
async get(sessionId) {
|
|
201
|
+
return readRow(sessionId);
|
|
202
|
+
},
|
|
203
|
+
async put(sessionId, state, expectedRev) {
|
|
204
|
+
return runExclusive(sessionId, async () => {
|
|
205
|
+
const current = readRow(sessionId);
|
|
206
|
+
if (expectedRev !== undefined && (current.rev ?? 0) !== expectedRev) {
|
|
207
|
+
throw new Error(`registry put refused: session ${sessionId} moved under this write (expected rev ${expectedRev}, `
|
|
208
|
+
+ `found ${current.rev ?? 0}). Another door committed since the read — re-read and re-apply, `
|
|
209
|
+
+ 'do not clobber it (store.mutate).');
|
|
210
|
+
}
|
|
211
|
+
await writeInside(sessionId, state, current.rev ?? 0);
|
|
212
|
+
});
|
|
213
|
+
},
|
|
214
|
+
async mutate(sessionId, fn) {
|
|
215
|
+
return runExclusive(sessionId, async () => {
|
|
216
|
+
const before = readRow(sessionId);
|
|
217
|
+
const draft = structuredClone(before);
|
|
218
|
+
const next = await fn(draft);
|
|
219
|
+
// Returning the very object handed in means "no change" — the same idiom commitChapters used
|
|
220
|
+
// (`if (next !== state)`) so an idempotent retry costs no write and no rev bump.
|
|
221
|
+
if (next === draft)
|
|
222
|
+
return before;
|
|
223
|
+
await writeInside(sessionId, next, before.rev ?? 0);
|
|
224
|
+
return next;
|
|
225
|
+
});
|
|
226
|
+
},
|
|
227
|
+
projects() {
|
|
228
|
+
return projects.entries();
|
|
229
|
+
},
|
|
230
|
+
sessions() {
|
|
231
|
+
return table.entries();
|
|
232
|
+
},
|
|
233
|
+
getSetting(key) {
|
|
234
|
+
return settings.get(key)?.value;
|
|
235
|
+
},
|
|
236
|
+
async putSetting(key, value) {
|
|
237
|
+
await settings.put(key, { value });
|
|
238
|
+
},
|
|
239
|
+
async deleteSetting(key) {
|
|
240
|
+
const del = settings.delete;
|
|
241
|
+
if (typeof del !== 'function') {
|
|
242
|
+
throw new Error('settings table lacks delete — cannot restore the config default (host too old)');
|
|
243
|
+
}
|
|
244
|
+
await del.call(settings, key);
|
|
245
|
+
},
|
|
246
|
+
rules() {
|
|
247
|
+
return rules.entries();
|
|
248
|
+
},
|
|
249
|
+
getRule(id) {
|
|
250
|
+
return rules.get(id);
|
|
251
|
+
},
|
|
252
|
+
async putRule(id, value) {
|
|
253
|
+
await rules.put(id, value);
|
|
254
|
+
},
|
|
255
|
+
};
|
|
256
|
+
}
|
|
257
|
+
/**
|
|
258
|
+
* The NumberAllocator archive.ts asks for, bound to one author session.
|
|
259
|
+
* Reserve-before-write per docs/architecture.md § Atomicity; retries reuse
|
|
260
|
+
* numbers through registry.reserve's attempt-keyed idempotence.
|
|
261
|
+
*/
|
|
262
|
+
export function makeAllocator(store, sessionId) {
|
|
263
|
+
return {
|
|
264
|
+
// Allocation is the operation that MUST NOT interleave: two doors that both read
|
|
265
|
+
// `nextChapterNumber: 15` write two different `015-*.md` files into one tree, which is the exact
|
|
266
|
+
// damage #59 measured. `mutate` makes read-allocate-write one section, so the number a door is
|
|
267
|
+
// handed is already durable by the time the call returns.
|
|
268
|
+
async reserve(attemptId, count) {
|
|
269
|
+
let numbers = [];
|
|
270
|
+
await store.mutate(sessionId, (state) => {
|
|
271
|
+
const next = reserve(state, attemptId, count);
|
|
272
|
+
numbers = next.numbers;
|
|
273
|
+
return next.state;
|
|
274
|
+
});
|
|
275
|
+
return numbers;
|
|
276
|
+
},
|
|
277
|
+
async reservePlan(attemptId, count) {
|
|
278
|
+
let numbers = [];
|
|
279
|
+
await store.mutate(sessionId, (state) => {
|
|
280
|
+
const next = reservePlanNumber(state, attemptId, count);
|
|
281
|
+
numbers = next.numbers;
|
|
282
|
+
return next.state;
|
|
283
|
+
});
|
|
284
|
+
return numbers;
|
|
285
|
+
},
|
|
286
|
+
async release(attemptId) {
|
|
287
|
+
await store.mutate(sessionId, (state) => releaseReservation(state, attemptId));
|
|
288
|
+
},
|
|
289
|
+
};
|
|
290
|
+
}
|
|
291
|
+
/** The commit-point write: verified chapter records into the session state. */
|
|
292
|
+
export async function commitChapters(store, sessionId, records) {
|
|
293
|
+
// One section, fresh read inside it (#59): the old get→append→put held the snapshot across an
|
|
294
|
+
// await, which is how another door's committed state got erased.
|
|
295
|
+
await store.mutate(sessionId, (state) => appendChapters(state, records));
|
|
296
|
+
}
|
|
297
|
+
export async function acquireChapterStore(storageDomain) {
|
|
298
|
+
try {
|
|
299
|
+
const domain = await storageDomain.open(chapterDomainSpec);
|
|
300
|
+
return { store: makeDomainStore(domain), domain, owner: true };
|
|
301
|
+
}
|
|
302
|
+
catch (error) {
|
|
303
|
+
if (!/already[- ]open/i.test(String(error?.message ?? error)))
|
|
304
|
+
throw error;
|
|
305
|
+
const existing = storageDomain.get?.(chapterDomainSpec.name);
|
|
306
|
+
if (existing === undefined)
|
|
307
|
+
throw error;
|
|
308
|
+
return { store: makeDomainStore(existing), domain: existing, owner: false };
|
|
309
|
+
}
|
|
310
|
+
}
|
|
311
|
+
// ---------------------------------------------------------------- filesystem port
|
|
312
|
+
const join = (...parts) => parts.filter(Boolean).join('/').replace(/\/{2,}/g, '/');
|
|
313
|
+
/**
|
|
314
|
+
* ArchiveFs over node:fs, anchored at one base dir (the session workspace).
|
|
315
|
+
* Plugins are trusted same-process code — the sandbox governs TOOLS, not
|
|
316
|
+
* this path — so node:fs is legitimate here; `ctx.fs` stays untouched.
|
|
317
|
+
* `read` maps ENOENT to undefined; anything else propagates: a permission
|
|
318
|
+
* error must not look like a missing file.
|
|
319
|
+
*/
|
|
320
|
+
/** Resolve a configured store root against the session cwd. An ABSOLUTE root
|
|
321
|
+
* wins verbatim — the hosted profiles (#33) pin `/workspace/.dsh-chapters` so
|
|
322
|
+
* archives land on a persisted volume no matter which subdirectory a session
|
|
323
|
+
* runs in; relative roots keep the historical per-workspace meaning. */
|
|
324
|
+
export function resolveStoreRoot(cwd, storeRoot) {
|
|
325
|
+
return isAbsolute(storeRoot) ? storeRoot : join(cwd, storeRoot);
|
|
326
|
+
}
|
|
327
|
+
export function makeArchiveFs(baseDir) {
|
|
328
|
+
// absolute paths pass through (resolveStoreRoot may hand writeArchive an
|
|
329
|
+
// absolute storeRoot); relative ones stay workspace-rooted as always.
|
|
330
|
+
const abs = (p) => (isAbsolute(p) ? p : join(baseDir, p));
|
|
331
|
+
return {
|
|
332
|
+
async write(p, content) {
|
|
333
|
+
const target = abs(p);
|
|
334
|
+
await mkdir(dirname(target), { recursive: true });
|
|
335
|
+
await writeFile(target, content, 'utf8');
|
|
336
|
+
},
|
|
337
|
+
async read(p) {
|
|
338
|
+
try {
|
|
339
|
+
return await readFile(abs(p), 'utf8');
|
|
340
|
+
}
|
|
341
|
+
catch (error) {
|
|
342
|
+
if (error.code === 'ENOENT')
|
|
343
|
+
return undefined;
|
|
344
|
+
throw error;
|
|
345
|
+
}
|
|
346
|
+
},
|
|
347
|
+
async exists(p) {
|
|
348
|
+
return (await this.read(p)) !== undefined;
|
|
349
|
+
},
|
|
350
|
+
};
|
|
351
|
+
}
|
|
352
|
+
/**
|
|
353
|
+
* "Add <store-root>/ to .gitignore on first write and say so" — silently
|
|
354
|
+
* polluting a user's tree is its own small bug (docs/architecture.md §
|
|
355
|
+
* Storage). Returns a human line for the tool result's warnings.
|
|
356
|
+
*/
|
|
357
|
+
export async function ensureStoreGitignore(fs, storeRoot) {
|
|
358
|
+
const current = await fs.read('.gitignore');
|
|
359
|
+
const entry = `${storeRoot.replace(/\/+$/, '')}/`;
|
|
360
|
+
if (current !== undefined && current.split('\n').some((line) => line.trim() === entry)) {
|
|
361
|
+
return `${entry} already in .gitignore`;
|
|
362
|
+
}
|
|
363
|
+
const updated = (current ?? '') + (current !== undefined && !current.endsWith('\n') ? '\n' : '') +
|
|
364
|
+
`\n# dsh-chapters archive (verbatim chapter store; see README)\n${entry}\n`;
|
|
365
|
+
await fs.write('.gitignore', updated);
|
|
366
|
+
return `added ${entry} to .gitignore — say so: the plugin wrote into the repo root`;
|
|
367
|
+
}
|
|
368
|
+
//# sourceMappingURL=store.js.map
|
package/lib/store.js.map
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"store.js","sourceRoot":"","sources":["../src/store.ts"],"names":[],"mappings":"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"}
|
package/lib/sync.d.ts
ADDED
|
@@ -0,0 +1,220 @@
|
|
|
1
|
+
import { type ProviderKind, type SyncProvider } from './provider.ts';
|
|
2
|
+
import { type VocabOpts } from './vocabulary.ts';
|
|
3
|
+
/** A linked knowledge project (record §2.1, §8): one record per linked
|
|
4
|
+
* workspace cwd; deepest-cwd match resolves the active project. */
|
|
5
|
+
export interface ProjectRecord {
|
|
6
|
+
projectKey: string;
|
|
7
|
+
slug: string;
|
|
8
|
+
remote: string;
|
|
9
|
+
harnessId: string;
|
|
10
|
+
linkedAt: string;
|
|
11
|
+
/** Workspace cwd this record was linked from (deepest-prefix match, record §8). */
|
|
12
|
+
cwd: string;
|
|
13
|
+
/** Transport kind (§15 amendment); absent = 'git' (pre-provider records). */
|
|
14
|
+
kind?: ProviderKind;
|
|
15
|
+
/** TreeDX: the server-assigned repository id (resolved at link time). */
|
|
16
|
+
repoId?: string | undefined;
|
|
17
|
+
}
|
|
18
|
+
export interface SyncResult {
|
|
19
|
+
ok: boolean;
|
|
20
|
+
/** What actually happened, for the status command and tests. */
|
|
21
|
+
steps: string[];
|
|
22
|
+
detail: string;
|
|
23
|
+
/** 'synced' only when the remote accepted the push; 'local-only' when the
|
|
24
|
+
* mirror is current but the remote did not take it (record §5.3). */
|
|
25
|
+
mode: 'synced' | 'local-only';
|
|
26
|
+
}
|
|
27
|
+
/**
|
|
28
|
+
* Per-project credentials — stored under the DSH HOME, never the workspace
|
|
29
|
+
* (user directive 2026-09-18: the project tree is the agent's reading room;
|
|
30
|
+
* the plugin is the only accessor). 0600 file per projectKey. Note honestly:
|
|
31
|
+
* same-uid files are a hygiene boundary, not a cryptographic one — the real
|
|
32
|
+
* guarantee is that the plugin never echoes the token anywhere an agent sees.
|
|
33
|
+
*/
|
|
34
|
+
export declare const tokenPath: (projectKey: string) => string;
|
|
35
|
+
export declare function readToken(cwd: string, storeRoot: string, projectKey: string): string | undefined;
|
|
36
|
+
export declare function writeToken(projectKey: string, token: string): void;
|
|
37
|
+
export interface SyncOpts {
|
|
38
|
+
/** Workspace root — the store and the mirror both live under it. */
|
|
39
|
+
cwd: string;
|
|
40
|
+
/** Store root relative to cwd (`.dsh-chapters`). */
|
|
41
|
+
storeRoot: string;
|
|
42
|
+
/** Mirror directory relative to cwd (`.dsh-knowledge`). */
|
|
43
|
+
cloneDir: string;
|
|
44
|
+
project: ProjectRecord;
|
|
45
|
+
/** Per-harness token for this project (record §2.1); undefined for file remotes. */
|
|
46
|
+
token?: string;
|
|
47
|
+
/** Collection files to (re)publish: one JSONL file per session's signatures. */
|
|
48
|
+
collections?: {
|
|
49
|
+
sessionId: string;
|
|
50
|
+
lines: string[];
|
|
51
|
+
}[];
|
|
52
|
+
/** P2 §6.3 vocabulary pass over the mirror's chapters (undefined = off). */
|
|
53
|
+
vocab?: VocabPassOpts;
|
|
54
|
+
/** Force a run even when the lock is held (tests). Default false. */
|
|
55
|
+
force?: boolean;
|
|
56
|
+
/** Transport provider (tests inject a fake; default resolves from the
|
|
57
|
+
* project record's kind via src/provider.ts — 'git' when absent). */
|
|
58
|
+
provider?: SyncProvider;
|
|
59
|
+
/** #255: names the operator removed from THIS project's store. Rehydration skips them;
|
|
60
|
+
* the count lands in the steps. Absent ⇒ guard inert (the pre-#255 behavior). */
|
|
61
|
+
removed?: RemovedGuard;
|
|
62
|
+
}
|
|
63
|
+
/**
|
|
64
|
+
* Deepest-cwd match (record §8): the active knowledge project for a
|
|
65
|
+
* workspace path. Shared by the notice line, the scheduler, and the commands.
|
|
66
|
+
*/
|
|
67
|
+
export declare function projectForCwd(records: Iterable<[string, ProjectRecord]>, cwd: string): ProjectRecord | undefined;
|
|
68
|
+
/** The mirror directory, relative to the workspace (record §3.1). */
|
|
69
|
+
export declare const DEFAULT_CLONE_DIR = ".dsh-knowledge";
|
|
70
|
+
/**
|
|
71
|
+
* File lock around store transactions and clone ops (§5.2): the host-plane
|
|
72
|
+
* plugin and the realm engine may be different module instances in the same
|
|
73
|
+
* process, so an in-process mutex is not safe — a file is the honest lock.
|
|
74
|
+
* Stale locks (>10 min) are stolen; live ones make runSync skip, not block.
|
|
75
|
+
*/
|
|
76
|
+
export declare function syncLockPath(storeDir: string): string;
|
|
77
|
+
/**
|
|
78
|
+
* Map workspace-store files to repo paths (record §2.2):
|
|
79
|
+
* <storeRoot>/<rootSession>/chapters/x.md → chapters/<projectKey>/<rootSession>/x.md
|
|
80
|
+
* <storeRoot>/<rootSession>/artifacts/ab/ab….txt → artifacts/<projectKey>/ab/ab….txt
|
|
81
|
+
* Artifacts are content-addressed and shared across sessions, so their
|
|
82
|
+
* per-session prefix is dropped; chapters keep it.
|
|
83
|
+
*/
|
|
84
|
+
export declare function planStoreToRepo(storeDir: string, projectKey: string): {
|
|
85
|
+
abs: string;
|
|
86
|
+
rel: string;
|
|
87
|
+
}[];
|
|
88
|
+
export declare const statusFilePath: (cwd: string, storeRoot: string) => string;
|
|
89
|
+
/**
|
|
90
|
+
* Build the Layer-2 index inside the clone (record §10.2): entries from every
|
|
91
|
+
* chapter/rule file (frontmatter), curation facts from every harness's
|
|
92
|
+
* curation.jsonl, the manifest from the clone's index/manifest.json. Returns
|
|
93
|
+
* true when shards or the manifest changed (the commit step then ships them).
|
|
94
|
+
* Deterministic: the index is a pure function of the clone's corpus, so any
|
|
95
|
+
* machine can rebuild it identically (the recovery path, record §10.2).
|
|
96
|
+
*/
|
|
97
|
+
export declare function buildIndexInClone(cloneDir: string): boolean;
|
|
98
|
+
export interface VocabPassOpts extends VocabOpts {
|
|
99
|
+
/** write applied aliases? false (shadow) = report only — the default */
|
|
100
|
+
apply: boolean;
|
|
101
|
+
}
|
|
102
|
+
/**
|
|
103
|
+
* One sync pass (record §5): lock → ensure clone (offline init fallback) →
|
|
104
|
+
* publish → commit → pull → push. Failure semantics §5.3: EVERY remote-path
|
|
105
|
+
* failure (clone, pull, push) degrades to local-only mode — the mirror stays
|
|
106
|
+
* current for search and the status file says so with detail; only a local
|
|
107
|
+
* commit failure or lock loss returns without a current mirror. A diverged
|
|
108
|
+
* pull rebuilds the mirror from the remote (safe: the mirror is transport,
|
|
109
|
+
* the store is truth, §3.1). ok=true means the remote accepted the push.
|
|
110
|
+
*/
|
|
111
|
+
/**
|
|
112
|
+
* Store rehydration (0.1.5 — treeseed workspace-wipe incident, 2026-10-08):
|
|
113
|
+
* the pool is not only a downstream copy. When a workspace store loses files
|
|
114
|
+
* (accidental rm of dot-dirs, cleanup, fresh checkout) the registry rows and
|
|
115
|
+
* continuation TOCs still cite the store paths — `read` dangles silently.
|
|
116
|
+
* The freshly cloned/pulled mirror is usually the ONLY surviving copy of
|
|
117
|
+
* those exact bytes, so every sync restores before it publishes:
|
|
118
|
+
* mirror chapters/<projectKey>/<session>/<file>.md → <storeRoot>/<session>/chapters/<file>
|
|
119
|
+
* mirror artifacts/<projectKey>/<path> → <storeRoot>/artifacts/<path>
|
|
120
|
+
* Only ABSENT files are filled (the store stays ground truth for anything it
|
|
121
|
+
* still holds — rehydration fills gaps, it never arbitrates drift); other
|
|
122
|
+
* projects' prefixes are out of scope; counts land in the sync steps so the
|
|
123
|
+
* recovery is never silent.
|
|
124
|
+
*
|
|
125
|
+
* #255: an operator REMOVAL deletes the store file with intent. Without a guard, the next pass
|
|
126
|
+
* would copy the still-present mirror file straight back over the operator's decision — the
|
|
127
|
+
* deletion resurrecting itself between unlink and push. `removed` names those files (session#file
|
|
128
|
+
* for chapters and plan docs, blob basename for artifacts) and rehydration skips them, counting
|
|
129
|
+
* every skip so the pass says what it refused.
|
|
130
|
+
*/
|
|
131
|
+
export interface RemovedGuard {
|
|
132
|
+
/** `<sessionDir>#<file>` — the mirror walk's identity for a chapter or plan document. */
|
|
133
|
+
chapterKeys: ReadonlySet<string>;
|
|
134
|
+
/** Artifact basenames (`<sha>.txt`) — content-addressed, unique by construction. */
|
|
135
|
+
blobKeys: ReadonlySet<string>;
|
|
136
|
+
}
|
|
137
|
+
export declare function rehydrateStoreFromMirror(cloneDir: string, storeDir: string, projectKey: string, removed?: RemovedGuard): {
|
|
138
|
+
restored: number;
|
|
139
|
+
skippedRemoved: number;
|
|
140
|
+
};
|
|
141
|
+
export declare function runSync(opts: SyncOpts): Promise<SyncResult>;
|
|
142
|
+
/**
|
|
143
|
+
* Pull-only pass for the pre-fork refresh point (record §5.1): ensure the
|
|
144
|
+
* mirror exists (clone, offline-init fallback) and ff-pull it. Never throws,
|
|
145
|
+
* never pushes, never commits — a read-side refresh.
|
|
146
|
+
*/
|
|
147
|
+
export declare function runPull(opts: Omit<SyncOpts, 'force'>): Promise<{
|
|
148
|
+
ok: boolean;
|
|
149
|
+
detail: string;
|
|
150
|
+
}>;
|
|
151
|
+
/**
|
|
152
|
+
* A workspace-scoped collections reader for the scheduler (§5): only session
|
|
153
|
+
* trees whose store directory lives in THIS workspace publish. Shared by the
|
|
154
|
+
* host plugin and the realm engine (the two schedulers are the same code over
|
|
155
|
+
* different module copies — the file lock keeps them honest).
|
|
156
|
+
*/
|
|
157
|
+
export declare function makeCollectionsReader(sessions: () => IterableIterator<[string, import('./registry.ts').SessionState]>, storeRoot: string): (cwd: string) => {
|
|
158
|
+
sessionId: string;
|
|
159
|
+
lines: string[];
|
|
160
|
+
}[];
|
|
161
|
+
/**
|
|
162
|
+
* The debounced scheduler (§5.2: "consecutive archive events within the
|
|
163
|
+
* configured window coalesce into one push"). Pure wiring — the plane that
|
|
164
|
+
* owns it (host plugin or realm engine) injects how to find the project, the
|
|
165
|
+
* token, and the collection files. Never throws; a failed pass lands in the
|
|
166
|
+
* status file like any other.
|
|
167
|
+
*/
|
|
168
|
+
export interface SyncSchedulerDeps {
|
|
169
|
+
storeRoot: string;
|
|
170
|
+
cloneDir?: string;
|
|
171
|
+
debounceMs: number;
|
|
172
|
+
resolveProject(cwd: string): ProjectRecord | undefined;
|
|
173
|
+
tokenFor(cwd: string, projectKey: string): string | undefined;
|
|
174
|
+
/** Collection JSONL lines per session, read from the registry (may be empty). */
|
|
175
|
+
collectionsFor(cwd: string): {
|
|
176
|
+
sessionId: string;
|
|
177
|
+
lines: string[];
|
|
178
|
+
}[];
|
|
179
|
+
/** #255: the removed-stub guard for this cwd's project, read from the registry rows.
|
|
180
|
+
* The planes that own a store handle supply it; a scheduler without one simply never
|
|
181
|
+
* guards, and the pass says so nowhere it cannot know. */
|
|
182
|
+
removedFor?(cwd: string): RemovedGuard | undefined;
|
|
183
|
+
/** P2 §6.3 vocabulary pass options for every scheduled pass (undefined = off). */
|
|
184
|
+
vocab?: VocabPassOpts;
|
|
185
|
+
/** Injectable timer (tests). Defaults to setTimeout/clearTimeout. */
|
|
186
|
+
setTimer?: (fn: () => void, ms: number) => {
|
|
187
|
+
cancel(): void;
|
|
188
|
+
};
|
|
189
|
+
provider?: SyncProvider;
|
|
190
|
+
/** P2: fired after a REAL sync pass completes (success or degraded) — the
|
|
191
|
+
* enrichment queue's natural idle point. Not fired for 'no project
|
|
192
|
+
* linked' early-outs (nothing was published, nothing to enrich). */
|
|
193
|
+
onSyncDone?: (cwd: string, ok: boolean) => void;
|
|
194
|
+
}
|
|
195
|
+
export interface SyncScheduler {
|
|
196
|
+
/** Debounced push after an archive event (compaction/fork). */
|
|
197
|
+
schedule(cwd: string, why: string): void;
|
|
198
|
+
/** Immediate pass (a link, an explicit sync). An explicit project wins
|
|
199
|
+
* over the cwd lookup — a re-link must sync the NEW record, not whichever
|
|
200
|
+
* record the cwd tie-break happens to pick. */
|
|
201
|
+
run(cwd: string, why: string, project?: ProjectRecord): Promise<SyncResult>;
|
|
202
|
+
/** Pre-fork refresh (§5.1): pull-only, bounded, never blocks long. */
|
|
203
|
+
pullFor(cwd: string): Promise<{
|
|
204
|
+
ok: boolean;
|
|
205
|
+
detail: string;
|
|
206
|
+
}>;
|
|
207
|
+
hasPending(cwd: string): boolean;
|
|
208
|
+
/** Await any in-flight/scheduled pass (tests + shutdown). */
|
|
209
|
+
drain(): Promise<void>;
|
|
210
|
+
}
|
|
211
|
+
export declare function createSyncScheduler(deps: SyncSchedulerDeps): SyncScheduler;
|
|
212
|
+
/** Read the last sync status (for /chapters-status). Null when never run. */
|
|
213
|
+
export declare function readSyncStatus(cwd: string, storeRoot: string): {
|
|
214
|
+
at: string;
|
|
215
|
+
projectKey: string;
|
|
216
|
+
lastOk: boolean;
|
|
217
|
+
mode?: string;
|
|
218
|
+
detail: string;
|
|
219
|
+
steps: string[];
|
|
220
|
+
} | null;
|