@etiquekit/etq 1.0.14 → 1.0.15

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 (143) hide show
  1. package/AGENTS.md +26 -19
  2. package/LICENSE +3 -2
  3. package/NOTICE +4 -6
  4. package/QuickStart.md +46 -35
  5. package/README.md +56 -56
  6. package/bin/etiquette +5 -1
  7. package/bin/etiquette-core +5 -1
  8. package/docs/ARCHITECTURE.md +17 -20
  9. package/docs/CONCEPTS.md +5 -5
  10. package/docs/CORE_PROFILE.md +15 -7
  11. package/docs/LANE_PROVISIONING.md +2 -7
  12. package/docs/README.md +14 -13
  13. package/docs/RELEASE_SURFACE_AUDIT.md +7 -8
  14. package/docs/SEAT_DISCIPLINE.md +91 -55
  15. package/docs/SEAT_PROVISIONING.md +19 -16
  16. package/docs/TEAM_HANDOFF.md +21 -21
  17. package/docs/WORKTREE_QOL.md +0 -7
  18. package/docs/contracts/ledger-entry/README.md +11 -11
  19. package/docs/contracts/ledger-entry/ledger-entry.v0.2.md +2 -2
  20. package/docs/contracts/ledger-entry/ledger-entry.v0.md +8 -8
  21. package/lib/etiquette-core.js +313 -0
  22. package/lib/etiquette.js +1151 -0
  23. package/package.json +11 -10
  24. package/templates/etiquette-vanilla-v0/README.md +3 -2
  25. package/templates/etiquette-vanilla-v0/source/control-seat/README.md +6 -0
  26. package/templates/etiquette-vanilla-v0/source/control-seat/bin/access-assurance-check +55 -0
  27. package/templates/etiquette-vanilla-v0/source/control-seat/bin/seat-doctor +5 -0
  28. package/templates/etiquette-vanilla-v0/source/control-seat/docs/work/access/ACCESS-ASSURANCE.md +39 -0
  29. package/templates/etiquette-vanilla-v0/source/control-seat/docs/work/access/workspace-secure-profile.v0.json +53 -0
  30. package/templates/etiquette-vanilla-v0/validate-vanilla.sh +5 -0
  31. package/templates/hosted-receiver/README.md +41 -0
  32. package/templates/hosted-receiver/w1-github-webhook-receiver.mjs +129 -0
  33. package/templates/seat-packs-v0/README.md +2 -3
  34. package/templates/seat-packs-v0/source/claude-code-seat/README.md +3 -3
  35. package/templates/seat-packs-v0/source/codex-seat/README.md +3 -3
  36. package/templates/seat-packs-v0/source/gemini-seat/README.md +3 -3
  37. package/templates/seat-packs-v0/source/ollama-seat/README.md +3 -3
  38. package/templates/seat-packs-v0/source/openrouter-seat/README.md +3 -3
  39. package/docs/CONCEPT_STATUS.md +0 -66
  40. package/packages/control/src/authority/lease.ts +0 -261
  41. package/packages/control/src/authority/node-delegation.ts +0 -257
  42. package/packages/control/src/authority/rig-conductor.ts +0 -632
  43. package/packages/control/src/cli/argv.ts +0 -155
  44. package/packages/control/src/cli/commands/console.ts +0 -200
  45. package/packages/control/src/cli/commands/dispatch-core.ts +0 -89
  46. package/packages/control/src/cli/commands/dispatch.ts +0 -279
  47. package/packages/control/src/cli/commands/harness.ts +0 -89
  48. package/packages/control/src/cli/commands/hook.ts +0 -50
  49. package/packages/control/src/cli/commands/ledger.ts +0 -91
  50. package/packages/control/src/cli/commands/local-workflow.ts +0 -4690
  51. package/packages/control/src/cli/commands/memory.ts +0 -445
  52. package/packages/control/src/cli/commands/release.ts +0 -108
  53. package/packages/control/src/cli/commands/rubric.ts +0 -103
  54. package/packages/control/src/cli/commands/seat.ts +0 -179
  55. package/packages/control/src/cli/commands/session.ts +0 -127
  56. package/packages/control/src/cli/commands/supervision.ts +0 -246
  57. package/packages/control/src/cli/commands/sync.ts +0 -119
  58. package/packages/control/src/cli/commands/workflow.ts +0 -86
  59. package/packages/control/src/cli/commands/workspace.ts +0 -50
  60. package/packages/control/src/cli/core-usage.ts +0 -34
  61. package/packages/control/src/cli/prompt.ts +0 -67
  62. package/packages/control/src/cli/supervision-deps.ts +0 -44
  63. package/packages/control/src/cli/usage.ts +0 -241
  64. package/packages/control/src/cli.ts +0 -207
  65. package/packages/control/src/core-cli.ts +0 -50
  66. package/packages/control/src/dispatch/decision.ts +0 -202
  67. package/packages/control/src/dispatch/projection.ts +0 -293
  68. package/packages/control/src/dispatch/record.ts +0 -153
  69. package/packages/control/src/engagement/project.ts +0 -170
  70. package/packages/control/src/fs.ts +0 -19
  71. package/packages/control/src/harness/pruning.ts +0 -406
  72. package/packages/control/src/hooks/dispatcher.ts +0 -117
  73. package/packages/control/src/hooks/outbox.ts +0 -86
  74. package/packages/control/src/hooks/sanitize.ts +0 -6
  75. package/packages/control/src/hooks/types.ts +0 -34
  76. package/packages/control/src/index.ts +0 -384
  77. package/packages/control/src/ledger/entry.ts +0 -303
  78. package/packages/control/src/ledger/indexer.ts +0 -542
  79. package/packages/control/src/memory/context.ts +0 -149
  80. package/packages/control/src/memory/drain-import.ts +0 -207
  81. package/packages/control/src/memory/indexer.ts +0 -284
  82. package/packages/control/src/memory/query.ts +0 -75
  83. package/packages/control/src/memory/sanitize.ts +0 -50
  84. package/packages/control/src/memory/sharded-drain-import.ts +0 -212
  85. package/packages/control/src/memory/status.ts +0 -211
  86. package/packages/control/src/memory/store-lifecycle.ts +0 -509
  87. package/packages/control/src/memory/store.ts +0 -284
  88. package/packages/control/src/memory/types.ts +0 -146
  89. package/packages/control/src/parity/surfaces.ts +0 -748
  90. package/packages/control/src/project.ts +0 -141
  91. package/packages/control/src/projection/local-ledger-view.ts +0 -373
  92. package/packages/control/src/projection/return-enforcement.ts +0 -48
  93. package/packages/control/src/projection/timeline-preview.ts +0 -539
  94. package/packages/control/src/projection/timeline.ts +0 -708
  95. package/packages/control/src/release/readiness.ts +0 -842
  96. package/packages/control/src/rubric/loader.ts +0 -326
  97. package/packages/control/src/rubric/promotion.ts +0 -54
  98. package/packages/control/src/rubric/runner.ts +0 -159
  99. package/packages/control/src/rubric/types.ts +0 -158
  100. package/packages/control/src/seat/owner-card.ts +0 -388
  101. package/packages/control/src/seat/readiness.ts +0 -834
  102. package/packages/control/src/session/runbook.ts +0 -431
  103. package/packages/control/src/shared/sanitize.ts +0 -49
  104. package/packages/control/src/supervision/action-classes.ts +0 -192
  105. package/packages/control/src/supervision/command-apply.ts +0 -378
  106. package/packages/control/src/supervision/errors.ts +0 -14
  107. package/packages/control/src/supervision/event-replay.ts +0 -155
  108. package/packages/control/src/supervision/events.ts +0 -109
  109. package/packages/control/src/supervision/index.ts +0 -16
  110. package/packages/control/src/supervision/manifest.ts +0 -127
  111. package/packages/control/src/supervision/paths.ts +0 -49
  112. package/packages/control/src/supervision/projection-adapter.ts +0 -274
  113. package/packages/control/src/supervision/projection.ts +0 -75
  114. package/packages/control/src/supervision/rebuild.ts +0 -99
  115. package/packages/control/src/supervision/session-open.ts +0 -131
  116. package/packages/control/src/supervision/session-read.ts +0 -99
  117. package/packages/control/src/supervision/sqlite-impl.ts +0 -71
  118. package/packages/control/src/supervision/sqlite.ts +0 -121
  119. package/packages/control/src/supervision/store-rows.ts +0 -371
  120. package/packages/control/src/supervision/turn-close.ts +0 -154
  121. package/packages/control/src/supervision/turn-open.ts +0 -284
  122. package/packages/control/src/sync/event-log-merge-driver.ts +0 -263
  123. package/packages/control/src/sync/join-plan.ts +0 -375
  124. package/packages/control/src/sync/outbox.ts +0 -492
  125. package/packages/control/src/workflow/evaluator.ts +0 -140
  126. package/packages/control/src/workflow/loader.ts +0 -200
  127. package/packages/control/src/workflow/types.ts +0 -90
  128. package/packages/control/src/workspace/authority.ts +0 -499
  129. package/packages/protocol/src/guards.ts +0 -119
  130. package/packages/protocol/src/huddle-board.ts +0 -198
  131. package/packages/protocol/src/huddle.ts +0 -295
  132. package/packages/protocol/src/incident.ts +0 -251
  133. package/packages/protocol/src/index.ts +0 -8
  134. package/packages/protocol/src/interfaces.ts +0 -107
  135. package/packages/protocol/src/packet-profile.ts +0 -195
  136. package/packages/protocol/src/state.ts +0 -81
  137. package/packages/protocol/src/types.ts +0 -434
  138. package/release/lineage.v0.json +0 -15
  139. package/scripts/release-candidate-verify.sh +0 -175
  140. package/scripts/release-checksum.sh +0 -25
  141. package/scripts/release-pack-canary.sh +0 -97
  142. package/scripts/release-scan.sh +0 -249
  143. package/scripts/release-sign.sh +0 -34
@@ -1,542 +0,0 @@
1
- import { Database } from 'bun:sqlite';
2
- import { existsSync, mkdirSync, rmSync, statSync } from 'node:fs';
3
- import { readFile, readdir } from 'node:fs/promises';
4
- import { dirname, extname, join, relative, resolve } from 'node:path';
5
- import {
6
- LEDGER_STATUS_EFFECTS,
7
- assertLedgerTopicHints,
8
- type LedgerAuthorityClass,
9
- type LedgerEntryRefs,
10
- type LedgerEntryType,
11
- type LedgerExportClass,
12
- type LedgerStatusEffect,
13
- } from './entry';
14
-
15
- const DOCUMENT_SCAN_DIRS = [
16
- 'docs/work/ledger',
17
- 'docs/work/receipts',
18
- 'docs/work/topic-snapshots',
19
- 'docs/work/ledger-digests',
20
- 'docs/work/consumer-registry',
21
- 'docs/work/bus-feed',
22
- ];
23
-
24
- const TEXT_EXTENSIONS = new Set(['.md', '.yaml', '.yml', '.json', '.jsonl', '.ndjson', '.txt']);
25
-
26
- export interface ParsedLedgerLine {
27
- entry_id: string;
28
- schema: 'ledger-entry.v0' | 'ledger-entry.v0.1' | 'ledger-entry.v0.2';
29
- created_at: string;
30
- entry_type: LedgerEntryType | string;
31
- spine_eligible: boolean;
32
- authority_class: LedgerAuthorityClass | string;
33
- grants_execution: boolean;
34
- export_class: LedgerExportClass | string;
35
- refs: LedgerEntryRefs;
36
- topic_hints: string[];
37
- status_effect: LedgerStatusEffect;
38
- source_path: string;
39
- line_no: number;
40
- extra: Record<string, string>;
41
- }
42
-
43
- export interface LedgerIndexResult {
44
- dbPath: string;
45
- ledger_entries: number;
46
- refs: number;
47
- topics: number;
48
- documents: number;
49
- }
50
-
51
- export interface CompactLedgerSearchResult {
52
- entry_id: string;
53
- title: string;
54
- type: string;
55
- topic_hints: string[];
56
- status_effect: LedgerStatusEffect;
57
- refs: string[];
58
- confidence: 'metadata';
59
- is_canonical: false;
60
- regenerable: true;
61
- can_authorize: false;
62
- }
63
-
64
- interface LedgerEntryRow {
65
- entry_id: string;
66
- entry_type: string;
67
- topic_hints_json: string;
68
- status_effect: LedgerStatusEffect;
69
- refs_json: string;
70
- source_path?: string;
71
- line_no?: number;
72
- }
73
-
74
- export interface LedgerIndexEntryDetail extends CompactLedgerSearchResult {
75
- source_path: string;
76
- line_no: number;
77
- }
78
-
79
- export function defaultLedgerIndexDbPath(projectRoot: string): string {
80
- return join(projectRoot, '.etiquette', 'state', 'ledger', 'index.db');
81
- }
82
-
83
- export function defaultPrivateVerbatimDbPath(projectRoot: string): string {
84
- return join(projectRoot, '.etiquette', 'state', 'ledger', 'private-verbatim.db');
85
- }
86
-
87
- export async function ledgerIndexNeedsRebuild(options: {
88
- projectRoot: string;
89
- dbPath?: string;
90
- }): Promise<boolean> {
91
- const projectRoot = resolve(options.projectRoot);
92
- const dbPath = options.dbPath ?? defaultLedgerIndexDbPath(projectRoot);
93
- if (!existsSync(dbPath)) return true;
94
- const dbMtime = statSync(dbPath).mtimeMs;
95
- const batches = await Promise.all(DOCUMENT_SCAN_DIRS.map((dir) => collectDocuments(projectRoot, dir)));
96
- for (const relPath of new Set(batches.flat())) {
97
- if (statSync(join(projectRoot, relPath)).mtimeMs > dbMtime) return true;
98
- }
99
- return false;
100
- }
101
-
102
- function compactWhitespace(value: string): string {
103
- return value.replace(/\s+/g, ' ').trim();
104
- }
105
-
106
- function stripMarkdownHeading(value: string): string {
107
- return compactWhitespace(value.replace(/^#+\s*/, ''));
108
- }
109
-
110
- function titleFromContent(content: string, fallback: string): string {
111
- const heading = content.split('\n').find((line) => line.startsWith('# '));
112
- return heading ? stripMarkdownHeading(heading) : fallback;
113
- }
114
-
115
- function parseKeyValueTokens(line: string): { created_at: string; fields: Record<string, string> } | null {
116
- const match = /^-\s+(\S+)\s+(.*)$/.exec(line.trim());
117
- if (!match) return null;
118
- const [, createdAt, rest] = match;
119
- const fields: Record<string, string> = {};
120
- for (const token of rest.split(/\s+/)) {
121
- const equalsAt = token.indexOf('=');
122
- if (equalsAt <= 0) continue;
123
- fields[token.slice(0, equalsAt)] = token.slice(equalsAt + 1);
124
- }
125
- return { created_at: createdAt, fields };
126
- }
127
-
128
- function parseBool(value: string | undefined): boolean {
129
- return value === 'true';
130
- }
131
-
132
- function splitList(value: string | undefined): string[] {
133
- if (!value || value === 'none') return [];
134
- return value.split(',').map((part) => part.trim()).filter(Boolean);
135
- }
136
-
137
- function parseRefs(value: string | undefined): LedgerEntryRefs {
138
- const refs: LedgerEntryRefs = {};
139
- for (const item of splitList(value)) {
140
- const colonAt = item.indexOf(':');
141
- if (colonAt <= 0) continue;
142
- const key = item.slice(0, colonAt) as keyof LedgerEntryRefs;
143
- const ref = item.slice(colonAt + 1);
144
- if (!['lanes', 'commits', 'prs', 'bus', 'artifacts', 'receipts'].includes(key)) continue;
145
- refs[key] = [...(refs[key] ?? []), ref];
146
- }
147
- return refs;
148
- }
149
-
150
- function refPairs(refs: LedgerEntryRefs): { type: string; ref: string }[] {
151
- const pairs: { type: string; ref: string }[] = [];
152
- for (const key of ['lanes', 'commits', 'prs', 'bus', 'artifacts', 'receipts'] as const) {
153
- for (const ref of refs[key] ?? []) {
154
- pairs.push({ type: key, ref });
155
- }
156
- }
157
- return pairs;
158
- }
159
-
160
- function statusEffect(value: string | undefined): LedgerStatusEffect {
161
- if (!value) return 'none';
162
- if (LEDGER_STATUS_EFFECTS.includes(value as LedgerStatusEffect)) {
163
- return value as LedgerStatusEffect;
164
- }
165
- throw new Error(`unsupported ledger status_effect: ${value}`);
166
- }
167
-
168
- export function parseLedgerEntryLine(line: string, sourcePath = 'docs/work/ledger/LEDGER.md', lineNo = 0): ParsedLedgerLine | null {
169
- const parsed = parseKeyValueTokens(line);
170
- if (!parsed) return null;
171
- const { created_at: createdAt, fields } = parsed;
172
- if (fields.schema !== 'ledger-entry.v0' && fields.schema !== 'ledger-entry.v0.1' && fields.schema !== 'ledger-entry.v0.2') return null;
173
- const entryId = fields.entry_id;
174
- if (!entryId) return null;
175
- const topicHints = splitList(fields.topic_hints);
176
- assertLedgerTopicHints(topicHints);
177
- const effect = statusEffect(fields.status_effect);
178
- const known = new Set([
179
- 'schema',
180
- 'entry_id',
181
- 'entry_type',
182
- 'spine_eligible',
183
- 'authority_class',
184
- 'grants_execution',
185
- 'export_class',
186
- 'refs',
187
- 'topic_hints',
188
- 'status_effect',
189
- ]);
190
- const extra = Object.fromEntries(Object.entries(fields).filter(([key]) => !known.has(key)));
191
- return {
192
- entry_id: entryId,
193
- schema: fields.schema,
194
- created_at: createdAt,
195
- entry_type: fields.entry_type ?? 'event',
196
- spine_eligible: parseBool(fields.spine_eligible),
197
- authority_class: fields.authority_class ?? 'advisory',
198
- grants_execution: parseBool(fields.grants_execution),
199
- export_class: fields.export_class ?? 'local_private',
200
- refs: parseRefs(fields.refs),
201
- topic_hints: topicHints,
202
- status_effect: effect,
203
- source_path: sourcePath,
204
- line_no: lineNo,
205
- extra,
206
- };
207
- }
208
-
209
- function resetDbFiles(dbPath: string): void {
210
- for (const path of [dbPath, `${dbPath}-wal`, `${dbPath}-shm`]) {
211
- if (existsSync(path)) rmSync(path, { force: true });
212
- }
213
- }
214
-
215
- function applyLedgerIndexSchema(db: Database): void {
216
- db.exec(`
217
- PRAGMA journal_mode = DELETE;
218
- PRAGMA busy_timeout = 5000;
219
- CREATE TABLE IF NOT EXISTS ledger_entries (
220
- entry_id TEXT PRIMARY KEY,
221
- schema TEXT NOT NULL,
222
- created_at TEXT NOT NULL,
223
- entry_type TEXT NOT NULL,
224
- spine_eligible INTEGER NOT NULL,
225
- authority_class TEXT NOT NULL,
226
- grants_execution INTEGER NOT NULL,
227
- export_class TEXT NOT NULL,
228
- summary TEXT NOT NULL,
229
- topic_hints_json TEXT NOT NULL,
230
- status_effect TEXT NOT NULL,
231
- refs_json TEXT NOT NULL,
232
- source_path TEXT NOT NULL,
233
- line_no INTEGER NOT NULL,
234
- extra_json TEXT NOT NULL,
235
- is_canonical INTEGER NOT NULL DEFAULT 0,
236
- regenerable INTEGER NOT NULL DEFAULT 1,
237
- can_authorize INTEGER NOT NULL DEFAULT 0,
238
- indexed_at TEXT NOT NULL
239
- );
240
- CREATE TABLE IF NOT EXISTS refs (
241
- entry_id TEXT NOT NULL,
242
- ref_type TEXT NOT NULL,
243
- ref TEXT NOT NULL,
244
- FOREIGN KEY(entry_id) REFERENCES ledger_entries(entry_id)
245
- );
246
- CREATE TABLE IF NOT EXISTS topics (
247
- entry_id TEXT NOT NULL,
248
- topic TEXT NOT NULL,
249
- source TEXT NOT NULL,
250
- FOREIGN KEY(entry_id) REFERENCES ledger_entries(entry_id)
251
- );
252
- CREATE TABLE IF NOT EXISTS documents (
253
- document_id TEXT PRIMARY KEY,
254
- doc_type TEXT NOT NULL,
255
- path TEXT NOT NULL,
256
- title TEXT NOT NULL,
257
- indexed_at TEXT NOT NULL
258
- );
259
- CREATE VIRTUAL TABLE IF NOT EXISTS fts_chunks
260
- USING fts5(chunk_id UNINDEXED, source_type UNINDEXED, source_id UNINDEXED, title, body, ref UNINDEXED);
261
- `);
262
- }
263
-
264
- function documentType(relPath: string): string {
265
- if (relPath.includes('/topic-snapshots/')) return 'topic_snapshot';
266
- if (relPath.includes('/ledger-digests/')) return 'ledger_digest';
267
- if (relPath.includes('/consumer-registry/')) return 'consumer_registry';
268
- if (relPath.includes('/bus-feed/')) return 'bus_feed';
269
- if (relPath.includes('/receipts/')) return 'receipt';
270
- if (relPath.includes('/ledger/')) return 'ledger';
271
- return 'document';
272
- }
273
-
274
- async function collectDocuments(root: string, relDir: string): Promise<string[]> {
275
- const absDir = join(root, relDir);
276
- if (!existsSync(absDir)) return [];
277
- const out: string[] = [];
278
- async function walk(dir: string): Promise<void> {
279
- const entries = await readdir(dir, { withFileTypes: true });
280
- for (const entry of entries) {
281
- const abs = join(dir, entry.name);
282
- if (entry.isDirectory()) {
283
- if (entry.name === 'node_modules' || entry.name === '.git' || entry.name === '_worktrees') continue;
284
- await walk(abs);
285
- continue;
286
- }
287
- if (!entry.isFile() || !TEXT_EXTENSIONS.has(extname(entry.name))) continue;
288
- out.push(relative(root, abs));
289
- }
290
- }
291
- await walk(absDir);
292
- return out.sort();
293
- }
294
-
295
- async function insertDocuments(db: Database, projectRoot: string, indexedAt: string): Promise<number> {
296
- const batches = await Promise.all(DOCUMENT_SCAN_DIRS.map((dir) => collectDocuments(projectRoot, dir)));
297
- const docs = [...new Set(batches.flat())].sort();
298
- const insertDoc = db.query(`
299
- INSERT OR REPLACE INTO documents (document_id, doc_type, path, title, indexed_at)
300
- VALUES (?, ?, ?, ?, ?)
301
- `);
302
- const insertFts = db.query(`
303
- INSERT INTO fts_chunks (chunk_id, source_type, source_id, title, body, ref)
304
- VALUES (?, ?, ?, ?, ?, ?)
305
- `);
306
- for (const relPath of docs) {
307
- const content = await readFile(join(projectRoot, relPath), 'utf-8');
308
- const title = titleFromContent(content, relPath.split('/').at(-1) ?? relPath);
309
- const id = `doc:${relPath}`;
310
- const type = documentType(relPath);
311
- insertDoc.run(id, type, relPath, title, indexedAt);
312
- insertFts.run(`doc:${relPath}`, 'document', id, title, `${type} ${title} ${relPath}`, relPath);
313
- }
314
- return docs.length;
315
- }
316
-
317
- function compactRefsForSearch(refs: LedgerEntryRefs): string {
318
- return refPairs(refs).map((pair) => `${pair.type}:${pair.ref}`).join(' ');
319
- }
320
-
321
- function insertLedgerEntry(db: Database, entry: ParsedLedgerLine, indexedAt: string): { refs: number; topics: number } {
322
- const summary = [
323
- entry.entry_type,
324
- entry.entry_id,
325
- entry.status_effect !== 'none' ? entry.status_effect : '',
326
- entry.topic_hints.join(' '),
327
- compactRefsForSearch(entry.refs),
328
- ].filter(Boolean).join(' ');
329
- db.query(`
330
- INSERT OR REPLACE INTO ledger_entries
331
- (entry_id, schema, created_at, entry_type, spine_eligible, authority_class,
332
- grants_execution, export_class, summary, topic_hints_json, status_effect,
333
- refs_json, source_path, line_no, extra_json, is_canonical, regenerable,
334
- can_authorize, indexed_at)
335
- VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, 0, 1, 0, ?)
336
- `).run(
337
- entry.entry_id,
338
- entry.schema,
339
- entry.created_at,
340
- entry.entry_type,
341
- entry.spine_eligible ? 1 : 0,
342
- entry.authority_class,
343
- entry.grants_execution ? 1 : 0,
344
- entry.export_class,
345
- summary,
346
- JSON.stringify(entry.topic_hints),
347
- entry.status_effect,
348
- JSON.stringify(entry.refs),
349
- entry.source_path,
350
- entry.line_no,
351
- JSON.stringify(entry.extra),
352
- indexedAt,
353
- );
354
- db.query(`
355
- INSERT INTO fts_chunks (chunk_id, source_type, source_id, title, body, ref)
356
- VALUES (?, 'ledger_entry', ?, ?, ?, ?)
357
- `).run(
358
- `ledger:${entry.entry_id}`,
359
- entry.entry_id,
360
- entry.entry_id,
361
- summary,
362
- `${entry.source_path}:${entry.line_no}`,
363
- );
364
- let refs = 0;
365
- for (const pair of refPairs(entry.refs)) {
366
- db.query('INSERT INTO refs (entry_id, ref_type, ref) VALUES (?, ?, ?)').run(entry.entry_id, pair.type, pair.ref);
367
- refs += 1;
368
- }
369
- let topics = 0;
370
- for (const topic of entry.topic_hints) {
371
- db.query('INSERT INTO topics (entry_id, topic, source) VALUES (?, ?, ?)').run(entry.entry_id, topic, 'topic_hints');
372
- topics += 1;
373
- }
374
- return { refs, topics };
375
- }
376
-
377
- export async function rebuildLedgerIndex(options: {
378
- projectRoot: string;
379
- dbPath?: string;
380
- }): Promise<LedgerIndexResult> {
381
- const projectRoot = resolve(options.projectRoot);
382
- const dbPath = options.dbPath ?? defaultLedgerIndexDbPath(projectRoot);
383
- mkdirSync(dirname(dbPath), { recursive: true });
384
- resetDbFiles(dbPath);
385
- const db = new Database(dbPath);
386
- const indexedAt = new Date().toISOString();
387
- try {
388
- applyLedgerIndexSchema(db);
389
- const ledgerPath = join(projectRoot, 'docs', 'work', 'ledger', 'LEDGER.md');
390
- let ledgerEntries = 0;
391
- let refs = 0;
392
- let topics = 0;
393
- if (existsSync(ledgerPath)) {
394
- const relLedgerPath = relative(projectRoot, ledgerPath);
395
- const lines = (await readFile(ledgerPath, 'utf-8')).split('\n');
396
- for (const [index, line] of lines.entries()) {
397
- const entry = parseLedgerEntryLine(line, relLedgerPath, index + 1);
398
- if (!entry) continue;
399
- const counts = insertLedgerEntry(db, entry, indexedAt);
400
- ledgerEntries += 1;
401
- refs += counts.refs;
402
- topics += counts.topics;
403
- }
404
- }
405
- const documents = await insertDocuments(db, projectRoot, indexedAt);
406
- return { dbPath, ledger_entries: ledgerEntries, refs, topics, documents };
407
- } finally {
408
- db.close();
409
- }
410
- }
411
-
412
- function mapSearchRow(row: LedgerEntryRow): CompactLedgerSearchResult {
413
- const refs = refPairs(JSON.parse(row.refs_json) as LedgerEntryRefs).map((pair) => `${pair.type}:${pair.ref}`);
414
- return {
415
- entry_id: row.entry_id,
416
- title: row.entry_id,
417
- type: row.entry_type,
418
- topic_hints: JSON.parse(row.topic_hints_json) as string[],
419
- status_effect: row.status_effect,
420
- refs,
421
- confidence: 'metadata',
422
- is_canonical: false,
423
- regenerable: true,
424
- can_authorize: false,
425
- };
426
- }
427
-
428
- function mapDetailRow(row: Required<LedgerEntryRow>): LedgerIndexEntryDetail {
429
- return {
430
- ...mapSearchRow(row),
431
- source_path: row.source_path,
432
- line_no: row.line_no,
433
- };
434
- }
435
-
436
- function searchTerms(query: string): string[] {
437
- return compactWhitespace(query.toLowerCase()).split(/[^a-z0-9]+/).filter(Boolean).slice(0, 8);
438
- }
439
-
440
- function ftsQuery(terms: string[]): string {
441
- return terms.map((term) => `"${term.replace(/"/g, '""')}"`).join(' ');
442
- }
443
-
444
- function likeSearchRows(db: Database, terms: string[], limit: number): LedgerEntryRow[] {
445
- const like = `%${terms.join('%')}%`;
446
- return db.query<LedgerEntryRow, [string, string, string, number]>(`
447
- SELECT entry_id, entry_type, topic_hints_json, status_effect, refs_json
448
- FROM ledger_entries
449
- WHERE lower(summary) LIKE ?
450
- OR lower(entry_id) LIKE ?
451
- OR lower(topic_hints_json) LIKE ?
452
- ORDER BY created_at DESC, entry_id ASC
453
- LIMIT ?
454
- `).all(like, like, like, limit);
455
- }
456
-
457
- export function searchLedgerIndex(options: {
458
- dbPath: string;
459
- query: string;
460
- limit?: number;
461
- }): CompactLedgerSearchResult[] {
462
- const db = new Database(options.dbPath, { readonly: true });
463
- try {
464
- const terms = searchTerms(options.query);
465
- if (terms.length === 0) return [];
466
- const limit = options.limit ?? 10;
467
- let rows: LedgerEntryRow[];
468
- try {
469
- rows = db.query<LedgerEntryRow, [string, number]>(`
470
- WITH matches AS (
471
- SELECT source_id, bm25(fts_chunks) AS rank
472
- FROM fts_chunks
473
- WHERE fts_chunks MATCH ?
474
- AND source_type = 'ledger_entry'
475
- )
476
- SELECT le.entry_id, le.entry_type, le.topic_hints_json, le.status_effect, le.refs_json
477
- FROM ledger_entries le
478
- JOIN matches ON matches.source_id = le.entry_id
479
- ORDER BY matches.rank ASC, le.created_at DESC, le.entry_id ASC
480
- LIMIT ?
481
- `).all(ftsQuery(terms), limit);
482
- } catch {
483
- rows = likeSearchRows(db, terms, limit);
484
- }
485
- if (rows.length === 0) {
486
- rows = likeSearchRows(db, terms, limit);
487
- }
488
- return rows.map(mapSearchRow);
489
- } finally {
490
- db.close();
491
- }
492
- }
493
-
494
- export function showLedgerIndexEntry(options: {
495
- dbPath: string;
496
- entryIdOrRef: string;
497
- }): LedgerIndexEntryDetail | null {
498
- const db = new Database(options.dbPath, { readonly: true });
499
- try {
500
- const row = db.query<Required<LedgerEntryRow>, [string, string]>(`
501
- SELECT entry_id, entry_type, topic_hints_json, status_effect, refs_json, source_path, line_no
502
- FROM ledger_entries
503
- WHERE entry_id = ?
504
- OR entry_id = (
505
- SELECT entry_id FROM refs WHERE ref = ? ORDER BY entry_id ASC LIMIT 1
506
- )
507
- ORDER BY entry_id ASC
508
- LIMIT 1
509
- `).get(options.entryIdOrRef, options.entryIdOrRef);
510
- return row ? mapDetailRow(row) : null;
511
- } finally {
512
- db.close();
513
- }
514
- }
515
-
516
- export function initializePrivateVerbatimStore(options: {
517
- projectRoot: string;
518
- dbPath?: string;
519
- explicitOptIn?: boolean;
520
- }): string {
521
- if (!options.explicitOptIn) {
522
- throw new Error('private verbatim ledger store requires explicit operator opt-in');
523
- }
524
- const dbPath = options.dbPath ?? defaultPrivateVerbatimDbPath(resolve(options.projectRoot));
525
- mkdirSync(dirname(dbPath), { recursive: true });
526
- const db = new Database(dbPath);
527
- try {
528
- db.exec(`
529
- PRAGMA journal_mode = DELETE;
530
- CREATE TABLE IF NOT EXISTS verbatim_chunks (
531
- chunk_id TEXT PRIMARY KEY,
532
- source_ref TEXT NOT NULL,
533
- body TEXT NOT NULL,
534
- export_class TEXT NOT NULL DEFAULT 'local_private',
535
- captured_at TEXT NOT NULL
536
- );
537
- `);
538
- } finally {
539
- db.close();
540
- }
541
- return dbPath;
542
- }
@@ -1,149 +0,0 @@
1
- import { resolve } from 'node:path';
2
- import { assertSharedMemoryScopeId, assertSharedMemoryText } from './sanitize';
3
- import { defaultMemoryDbPath } from './indexer';
4
- import { openMemoryStore } from './store';
5
- import type { EvidenceRef, MemoryCategory, MemoryConfidence, RecallCapsule, StalenessStatus } from './types';
6
-
7
- export interface MemoryContextItem {
8
- capsule_id: string;
9
- title: string;
10
- category: MemoryCategory;
11
- summary: string;
12
- why_it_matters: string;
13
- confidence: MemoryConfidence;
14
- stale_status: StalenessStatus;
15
- evidence_refs: EvidenceRef[];
16
- recommended_action: RecallCapsule['recommended_action'];
17
- }
18
-
19
- export interface MemoryContextPack {
20
- schema: 'memory-context.v0';
21
- generated_at: string;
22
- query: string;
23
- scope: {
24
- org_id?: string;
25
- workspace_id?: string;
26
- lane_ref?: string;
27
- task_ref?: string;
28
- };
29
- items: MemoryContextItem[];
30
- content_boundary: {
31
- summaries_only: true;
32
- raw_ledger_body_included: false;
33
- raw_event_body_included: false;
34
- raw_prompt_included: false;
35
- raw_tool_output_included: false;
36
- source_snippets_included: false;
37
- local_paths_included: false;
38
- };
39
- authority_boundary: {
40
- can_authorize_work: false;
41
- can_close_task: false;
42
- can_replace_receipt: false;
43
- can_mutate_ledger: false;
44
- memory_is_execution_truth: false;
45
- };
46
- }
47
-
48
- export interface BuildMemoryContextOptions {
49
- projectRoot: string;
50
- dbPath?: string;
51
- query?: string;
52
- laneRef?: string;
53
- taskRef?: string;
54
- orgId?: string;
55
- workspaceId?: string;
56
- limit: number;
57
- at?: string;
58
- }
59
-
60
- function compactQuery(options: BuildMemoryContextOptions): string {
61
- if (options.query?.trim()) {
62
- const query = options.query.trim();
63
- assertSharedMemoryText(query, 'memory context query');
64
- return query;
65
- }
66
- const parts = [options.laneRef, options.taskRef]
67
- .map((part) => part?.trim())
68
- .filter((part): part is string => Boolean(part));
69
- if (parts.length === 0) {
70
- throw new Error('memory context requires --lane, --task, or --query');
71
- }
72
- const query = parts.join(' ');
73
- assertSharedMemoryText(query, 'memory context query');
74
- return query;
75
- }
76
-
77
- function validateScope(options: BuildMemoryContextOptions): MemoryContextPack['scope'] {
78
- if (options.orgId) assertSharedMemoryScopeId(options.orgId, 'scope.org_id');
79
- if (options.workspaceId) assertSharedMemoryScopeId(options.workspaceId, 'scope.workspace_id');
80
- if (options.laneRef) assertSharedMemoryText(options.laneRef, 'scope.lane_ref');
81
- if (options.taskRef) assertSharedMemoryText(options.taskRef, 'scope.task_ref');
82
- return {
83
- ...(options.orgId ? { org_id: options.orgId } : {}),
84
- ...(options.workspaceId ? { workspace_id: options.workspaceId } : {}),
85
- ...(options.laneRef ? { lane_ref: options.laneRef } : {}),
86
- ...(options.taskRef ? { task_ref: options.taskRef } : {}),
87
- };
88
- }
89
-
90
- function contextItem(capsule: RecallCapsule): MemoryContextItem {
91
- return {
92
- capsule_id: capsule.capsule_id,
93
- title: capsule.title,
94
- category: capsule.category,
95
- summary: capsule.summary,
96
- why_it_matters: capsule.why_it_matters,
97
- confidence: capsule.confidence,
98
- stale_status: capsule.staleness.status,
99
- evidence_refs: capsule.evidence_refs,
100
- recommended_action: capsule.recommended_action,
101
- };
102
- }
103
-
104
- export function buildMemoryContext(options: BuildMemoryContextOptions): MemoryContextPack {
105
- if (options.limit < 1 || options.limit > 50) {
106
- throw new Error('memory context limit must be between 1 and 50');
107
- }
108
- const projectRoot = resolve(options.projectRoot);
109
- const query = compactQuery(options);
110
- const scope = validateScope(options);
111
- const generatedAt = options.at ?? new Date().toISOString();
112
- if (!Number.isFinite(Date.parse(generatedAt))) {
113
- throw new Error('--at must be an ISO-8601 timestamp');
114
- }
115
-
116
- const store = openMemoryStore(options.dbPath ?? defaultMemoryDbPath(projectRoot));
117
- try {
118
- const hits = store.search(query, options.limit);
119
- const items = hits
120
- .map((hit) => store.show(hit.capsule_id))
121
- .filter((capsule): capsule is RecallCapsule => capsule !== null)
122
- .map(contextItem);
123
- return {
124
- schema: 'memory-context.v0',
125
- generated_at: new Date(generatedAt).toISOString(),
126
- query,
127
- scope,
128
- items,
129
- content_boundary: {
130
- summaries_only: true,
131
- raw_ledger_body_included: false,
132
- raw_event_body_included: false,
133
- raw_prompt_included: false,
134
- raw_tool_output_included: false,
135
- source_snippets_included: false,
136
- local_paths_included: false,
137
- },
138
- authority_boundary: {
139
- can_authorize_work: false,
140
- can_close_task: false,
141
- can_replace_receipt: false,
142
- can_mutate_ledger: false,
143
- memory_is_execution_truth: false,
144
- },
145
- };
146
- } finally {
147
- store.close();
148
- }
149
- }