@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.
- package/AGENTS.md +26 -19
- package/LICENSE +3 -2
- package/NOTICE +4 -6
- package/QuickStart.md +46 -35
- package/README.md +56 -56
- package/bin/etiquette +5 -1
- package/bin/etiquette-core +5 -1
- package/docs/ARCHITECTURE.md +17 -20
- package/docs/CONCEPTS.md +5 -5
- package/docs/CORE_PROFILE.md +15 -7
- package/docs/LANE_PROVISIONING.md +2 -7
- package/docs/README.md +14 -13
- package/docs/RELEASE_SURFACE_AUDIT.md +7 -8
- package/docs/SEAT_DISCIPLINE.md +91 -55
- package/docs/SEAT_PROVISIONING.md +19 -16
- package/docs/TEAM_HANDOFF.md +21 -21
- package/docs/WORKTREE_QOL.md +0 -7
- package/docs/contracts/ledger-entry/README.md +11 -11
- package/docs/contracts/ledger-entry/ledger-entry.v0.2.md +2 -2
- package/docs/contracts/ledger-entry/ledger-entry.v0.md +8 -8
- package/lib/etiquette-core.js +313 -0
- package/lib/etiquette.js +1151 -0
- package/package.json +11 -10
- package/templates/etiquette-vanilla-v0/README.md +3 -2
- package/templates/etiquette-vanilla-v0/source/control-seat/README.md +6 -0
- package/templates/etiquette-vanilla-v0/source/control-seat/bin/access-assurance-check +55 -0
- package/templates/etiquette-vanilla-v0/source/control-seat/bin/seat-doctor +5 -0
- package/templates/etiquette-vanilla-v0/source/control-seat/docs/work/access/ACCESS-ASSURANCE.md +39 -0
- package/templates/etiquette-vanilla-v0/source/control-seat/docs/work/access/workspace-secure-profile.v0.json +53 -0
- package/templates/etiquette-vanilla-v0/validate-vanilla.sh +5 -0
- package/templates/hosted-receiver/README.md +41 -0
- package/templates/hosted-receiver/w1-github-webhook-receiver.mjs +129 -0
- package/templates/seat-packs-v0/README.md +2 -3
- package/templates/seat-packs-v0/source/claude-code-seat/README.md +3 -3
- package/templates/seat-packs-v0/source/codex-seat/README.md +3 -3
- package/templates/seat-packs-v0/source/gemini-seat/README.md +3 -3
- package/templates/seat-packs-v0/source/ollama-seat/README.md +3 -3
- package/templates/seat-packs-v0/source/openrouter-seat/README.md +3 -3
- package/docs/CONCEPT_STATUS.md +0 -66
- package/packages/control/src/authority/lease.ts +0 -261
- package/packages/control/src/authority/node-delegation.ts +0 -257
- package/packages/control/src/authority/rig-conductor.ts +0 -632
- package/packages/control/src/cli/argv.ts +0 -155
- package/packages/control/src/cli/commands/console.ts +0 -200
- package/packages/control/src/cli/commands/dispatch-core.ts +0 -89
- package/packages/control/src/cli/commands/dispatch.ts +0 -279
- package/packages/control/src/cli/commands/harness.ts +0 -89
- package/packages/control/src/cli/commands/hook.ts +0 -50
- package/packages/control/src/cli/commands/ledger.ts +0 -91
- package/packages/control/src/cli/commands/local-workflow.ts +0 -4690
- package/packages/control/src/cli/commands/memory.ts +0 -445
- package/packages/control/src/cli/commands/release.ts +0 -108
- package/packages/control/src/cli/commands/rubric.ts +0 -103
- package/packages/control/src/cli/commands/seat.ts +0 -179
- package/packages/control/src/cli/commands/session.ts +0 -127
- package/packages/control/src/cli/commands/supervision.ts +0 -246
- package/packages/control/src/cli/commands/sync.ts +0 -119
- package/packages/control/src/cli/commands/workflow.ts +0 -86
- package/packages/control/src/cli/commands/workspace.ts +0 -50
- package/packages/control/src/cli/core-usage.ts +0 -34
- package/packages/control/src/cli/prompt.ts +0 -67
- package/packages/control/src/cli/supervision-deps.ts +0 -44
- package/packages/control/src/cli/usage.ts +0 -241
- package/packages/control/src/cli.ts +0 -207
- package/packages/control/src/core-cli.ts +0 -50
- package/packages/control/src/dispatch/decision.ts +0 -202
- package/packages/control/src/dispatch/projection.ts +0 -293
- package/packages/control/src/dispatch/record.ts +0 -153
- package/packages/control/src/engagement/project.ts +0 -170
- package/packages/control/src/fs.ts +0 -19
- package/packages/control/src/harness/pruning.ts +0 -406
- package/packages/control/src/hooks/dispatcher.ts +0 -117
- package/packages/control/src/hooks/outbox.ts +0 -86
- package/packages/control/src/hooks/sanitize.ts +0 -6
- package/packages/control/src/hooks/types.ts +0 -34
- package/packages/control/src/index.ts +0 -384
- package/packages/control/src/ledger/entry.ts +0 -303
- package/packages/control/src/ledger/indexer.ts +0 -542
- package/packages/control/src/memory/context.ts +0 -149
- package/packages/control/src/memory/drain-import.ts +0 -207
- package/packages/control/src/memory/indexer.ts +0 -284
- package/packages/control/src/memory/query.ts +0 -75
- package/packages/control/src/memory/sanitize.ts +0 -50
- package/packages/control/src/memory/sharded-drain-import.ts +0 -212
- package/packages/control/src/memory/status.ts +0 -211
- package/packages/control/src/memory/store-lifecycle.ts +0 -509
- package/packages/control/src/memory/store.ts +0 -284
- package/packages/control/src/memory/types.ts +0 -146
- package/packages/control/src/parity/surfaces.ts +0 -748
- package/packages/control/src/project.ts +0 -141
- package/packages/control/src/projection/local-ledger-view.ts +0 -373
- package/packages/control/src/projection/return-enforcement.ts +0 -48
- package/packages/control/src/projection/timeline-preview.ts +0 -539
- package/packages/control/src/projection/timeline.ts +0 -708
- package/packages/control/src/release/readiness.ts +0 -842
- package/packages/control/src/rubric/loader.ts +0 -326
- package/packages/control/src/rubric/promotion.ts +0 -54
- package/packages/control/src/rubric/runner.ts +0 -159
- package/packages/control/src/rubric/types.ts +0 -158
- package/packages/control/src/seat/owner-card.ts +0 -388
- package/packages/control/src/seat/readiness.ts +0 -834
- package/packages/control/src/session/runbook.ts +0 -431
- package/packages/control/src/shared/sanitize.ts +0 -49
- package/packages/control/src/supervision/action-classes.ts +0 -192
- package/packages/control/src/supervision/command-apply.ts +0 -378
- package/packages/control/src/supervision/errors.ts +0 -14
- package/packages/control/src/supervision/event-replay.ts +0 -155
- package/packages/control/src/supervision/events.ts +0 -109
- package/packages/control/src/supervision/index.ts +0 -16
- package/packages/control/src/supervision/manifest.ts +0 -127
- package/packages/control/src/supervision/paths.ts +0 -49
- package/packages/control/src/supervision/projection-adapter.ts +0 -274
- package/packages/control/src/supervision/projection.ts +0 -75
- package/packages/control/src/supervision/rebuild.ts +0 -99
- package/packages/control/src/supervision/session-open.ts +0 -131
- package/packages/control/src/supervision/session-read.ts +0 -99
- package/packages/control/src/supervision/sqlite-impl.ts +0 -71
- package/packages/control/src/supervision/sqlite.ts +0 -121
- package/packages/control/src/supervision/store-rows.ts +0 -371
- package/packages/control/src/supervision/turn-close.ts +0 -154
- package/packages/control/src/supervision/turn-open.ts +0 -284
- package/packages/control/src/sync/event-log-merge-driver.ts +0 -263
- package/packages/control/src/sync/join-plan.ts +0 -375
- package/packages/control/src/sync/outbox.ts +0 -492
- package/packages/control/src/workflow/evaluator.ts +0 -140
- package/packages/control/src/workflow/loader.ts +0 -200
- package/packages/control/src/workflow/types.ts +0 -90
- package/packages/control/src/workspace/authority.ts +0 -499
- package/packages/protocol/src/guards.ts +0 -119
- package/packages/protocol/src/huddle-board.ts +0 -198
- package/packages/protocol/src/huddle.ts +0 -295
- package/packages/protocol/src/incident.ts +0 -251
- package/packages/protocol/src/index.ts +0 -8
- package/packages/protocol/src/interfaces.ts +0 -107
- package/packages/protocol/src/packet-profile.ts +0 -195
- package/packages/protocol/src/state.ts +0 -81
- package/packages/protocol/src/types.ts +0 -434
- package/release/lineage.v0.json +0 -15
- package/scripts/release-candidate-verify.sh +0 -175
- package/scripts/release-checksum.sh +0 -25
- package/scripts/release-pack-canary.sh +0 -97
- package/scripts/release-scan.sh +0 -249
- package/scripts/release-sign.sh +0 -34
|
@@ -1,509 +0,0 @@
|
|
|
1
|
-
import { createHash } from 'node:crypto';
|
|
2
|
-
import { mkdir, readdir, readFile, writeFile } from 'node:fs/promises';
|
|
3
|
-
import { dirname, join, posix, resolve, sep } from 'node:path';
|
|
4
|
-
import { parse as parseYaml, stringify as stringifyYaml } from 'yaml';
|
|
5
|
-
import {
|
|
6
|
-
WORKSPACE_MEMORY_ACCESS,
|
|
7
|
-
WORKSPACE_MEMORY_MOUNT_MODES,
|
|
8
|
-
WORKSPACE_MEMORY_SCOPES,
|
|
9
|
-
WORKSPACE_MEMORY_WRITE_POLICIES,
|
|
10
|
-
type WorkspaceMemoryAccess,
|
|
11
|
-
type WorkspaceMemoryMountMode,
|
|
12
|
-
type WorkspaceMemoryScope,
|
|
13
|
-
type WorkspaceMemoryStore,
|
|
14
|
-
type WorkspaceMemoryWritePolicy,
|
|
15
|
-
} from '../workspace/authority';
|
|
16
|
-
import { assertSharedMemoryScopeId, assertSharedMemoryText, detectMemoryRiskFlags } from './sanitize';
|
|
17
|
-
import { EVIDENCE_REF_TYPES, type EvidenceRef, type EvidenceRefType } from './types';
|
|
18
|
-
|
|
19
|
-
type JsonRecord = Record<string, unknown>;
|
|
20
|
-
|
|
21
|
-
export interface MemoryStoreManifest extends WorkspaceMemoryStore {
|
|
22
|
-
schema: 'memory-store.v0';
|
|
23
|
-
created_at: string;
|
|
24
|
-
storage_layout: {
|
|
25
|
-
manifest_ref: 'store.yaml';
|
|
26
|
-
candidate_files_dir: 'candidates/files';
|
|
27
|
-
candidate_entries_dir: 'candidates/entries';
|
|
28
|
-
rollups_dir: 'rollups';
|
|
29
|
-
};
|
|
30
|
-
authority_boundary: {
|
|
31
|
-
store_can_authorize_work: false;
|
|
32
|
-
agent_can_write_canonical_memory: false;
|
|
33
|
-
shared_write_back_requires_promotion: true;
|
|
34
|
-
};
|
|
35
|
-
}
|
|
36
|
-
|
|
37
|
-
export interface MemoryStoreEntry {
|
|
38
|
-
schema: 'memory-store-entry.v0';
|
|
39
|
-
entry_id: string;
|
|
40
|
-
store_id: string;
|
|
41
|
-
path: string;
|
|
42
|
-
status: 'candidate';
|
|
43
|
-
created_at: string;
|
|
44
|
-
content_ref: string;
|
|
45
|
-
evidence_refs: EvidenceRef[];
|
|
46
|
-
risk_flags: string[];
|
|
47
|
-
authority_boundary: {
|
|
48
|
-
entry_can_authorize_work: false;
|
|
49
|
-
entry_can_replace_canonical_memory: false;
|
|
50
|
-
agent_wrote_canonical_memory: false;
|
|
51
|
-
};
|
|
52
|
-
}
|
|
53
|
-
|
|
54
|
-
export interface MemoryConsolidationRollup {
|
|
55
|
-
schema: 'memory-consolidation-rollup.v0';
|
|
56
|
-
rollup_id: string;
|
|
57
|
-
created_at: string;
|
|
58
|
-
input_stores: string[];
|
|
59
|
-
output_store: string;
|
|
60
|
-
candidate_count: number;
|
|
61
|
-
entry_refs: string[];
|
|
62
|
-
summary: string;
|
|
63
|
-
output_policy: 'candidate_only' | 'curator_promoted';
|
|
64
|
-
status: 'candidate';
|
|
65
|
-
authority_boundary: {
|
|
66
|
-
rollup_can_authorize_work: false;
|
|
67
|
-
mutates_input_store: false;
|
|
68
|
-
promotes_without_curator: false;
|
|
69
|
-
};
|
|
70
|
-
}
|
|
71
|
-
|
|
72
|
-
export interface MemoryStoreSummary {
|
|
73
|
-
store: MemoryStoreManifest;
|
|
74
|
-
counts: {
|
|
75
|
-
candidate_entries: number;
|
|
76
|
-
rollups: number;
|
|
77
|
-
};
|
|
78
|
-
}
|
|
79
|
-
|
|
80
|
-
const LAYOUT = {
|
|
81
|
-
manifest_ref: 'store.yaml',
|
|
82
|
-
candidate_files_dir: 'candidates/files',
|
|
83
|
-
candidate_entries_dir: 'candidates/entries',
|
|
84
|
-
rollups_dir: 'rollups',
|
|
85
|
-
} as const;
|
|
86
|
-
|
|
87
|
-
export function defaultMemoryStoresRoot(projectRoot: string): string {
|
|
88
|
-
return join(projectRoot, '.etiquette', 'memory', 'stores');
|
|
89
|
-
}
|
|
90
|
-
|
|
91
|
-
function isoNow(at?: string): string {
|
|
92
|
-
const value = at ?? new Date().toISOString();
|
|
93
|
-
if (!Number.isFinite(Date.parse(value))) throw new Error('--at must be an ISO-8601 timestamp');
|
|
94
|
-
return new Date(value).toISOString();
|
|
95
|
-
}
|
|
96
|
-
|
|
97
|
-
function stableHash(value: unknown): string {
|
|
98
|
-
return createHash('sha256').update(JSON.stringify(value)).digest('hex').slice(0, 16);
|
|
99
|
-
}
|
|
100
|
-
|
|
101
|
-
function asRecord(value: unknown, name: string): JsonRecord {
|
|
102
|
-
if (typeof value !== 'object' || value === null || Array.isArray(value)) {
|
|
103
|
-
throw new Error(`${name} must be an object`);
|
|
104
|
-
}
|
|
105
|
-
return value as JsonRecord;
|
|
106
|
-
}
|
|
107
|
-
|
|
108
|
-
function asString(record: JsonRecord, key: string): string {
|
|
109
|
-
const value = record[key];
|
|
110
|
-
if (typeof value !== 'string' || value.trim().length === 0) {
|
|
111
|
-
throw new Error(`${key} must be a non-empty string`);
|
|
112
|
-
}
|
|
113
|
-
return value.trim();
|
|
114
|
-
}
|
|
115
|
-
|
|
116
|
-
function asBoolean(record: JsonRecord, key: string): boolean {
|
|
117
|
-
const value = record[key];
|
|
118
|
-
if (typeof value !== 'boolean') throw new Error(`${key} must be a boolean`);
|
|
119
|
-
return value;
|
|
120
|
-
}
|
|
121
|
-
|
|
122
|
-
function asEnum<T extends readonly string[]>(values: T, raw: unknown, key: string): T[number] {
|
|
123
|
-
if (typeof raw !== 'string' || !(values as readonly string[]).includes(raw)) {
|
|
124
|
-
throw new Error(`${key} must be one of: ${values.join(', ')}`);
|
|
125
|
-
}
|
|
126
|
-
return raw as T[number];
|
|
127
|
-
}
|
|
128
|
-
|
|
129
|
-
function storeDir(root: string, storeId: string): string {
|
|
130
|
-
return join(root, storeId);
|
|
131
|
-
}
|
|
132
|
-
|
|
133
|
-
function assertStoreId(storeId: string): string {
|
|
134
|
-
assertSharedMemoryScopeId(storeId, 'store_id');
|
|
135
|
-
return storeId;
|
|
136
|
-
}
|
|
137
|
-
|
|
138
|
-
function assertMemoryPath(raw: string): string {
|
|
139
|
-
const value = raw.trim();
|
|
140
|
-
if (!value) throw new Error('--path is required');
|
|
141
|
-
if (value.startsWith('/') || value.startsWith('\\') || /^[A-Za-z]:[\\/]/.test(value)) {
|
|
142
|
-
throw new Error('--path must be repo-relative, not an absolute local path');
|
|
143
|
-
}
|
|
144
|
-
if (value.includes('\\')) throw new Error('--path must use POSIX / separators');
|
|
145
|
-
const normalized = posix.normalize(value);
|
|
146
|
-
if (normalized === '.' || normalized.startsWith('../') || normalized === '..') {
|
|
147
|
-
throw new Error('--path must stay inside the memory store');
|
|
148
|
-
}
|
|
149
|
-
assertSharedMemoryText(normalized, 'path');
|
|
150
|
-
return normalized;
|
|
151
|
-
}
|
|
152
|
-
|
|
153
|
-
function assertStoreRelativeRef(raw: string, field: string): string {
|
|
154
|
-
const value = raw.trim();
|
|
155
|
-
if (!value) throw new Error(`${field} is required`);
|
|
156
|
-
if (value.startsWith('/') || value.startsWith('\\') || /^[A-Za-z]:[\\/]/.test(value)) {
|
|
157
|
-
throw new Error(`${field} must be store-relative, not an absolute local path`);
|
|
158
|
-
}
|
|
159
|
-
if (value.includes('\\')) throw new Error(`${field} must use POSIX / separators`);
|
|
160
|
-
const normalized = posix.normalize(value);
|
|
161
|
-
if (normalized === '.' || normalized.startsWith('../') || normalized === '..') {
|
|
162
|
-
throw new Error(`${field} must stay inside the memory store`);
|
|
163
|
-
}
|
|
164
|
-
assertSharedMemoryText(normalized, field);
|
|
165
|
-
return normalized.replace(/^\.\/+/, '');
|
|
166
|
-
}
|
|
167
|
-
|
|
168
|
-
function relativeRef(...parts: string[]): string {
|
|
169
|
-
return parts.join('/').replaceAll(sep, '/');
|
|
170
|
-
}
|
|
171
|
-
|
|
172
|
-
async function readStructured(path: string): Promise<unknown> {
|
|
173
|
-
const raw = await readFile(path, 'utf-8');
|
|
174
|
-
return parseYaml(raw) as unknown;
|
|
175
|
-
}
|
|
176
|
-
|
|
177
|
-
async function writeYaml(path: string, value: unknown): Promise<void> {
|
|
178
|
-
await mkdir(dirname(path), { recursive: true });
|
|
179
|
-
await writeFile(path, stringifyYaml(value));
|
|
180
|
-
}
|
|
181
|
-
|
|
182
|
-
function validateStoreShape(store: WorkspaceMemoryStore, prefix = 'memory store'): void {
|
|
183
|
-
assertStoreId(store.store_id);
|
|
184
|
-
if (store.access === 'read_only' && store.write_policy !== 'none') {
|
|
185
|
-
throw new Error(`${prefix} read_only stores must use write_policy: none`);
|
|
186
|
-
}
|
|
187
|
-
if (store.access === 'read_write' && store.write_policy === 'none') {
|
|
188
|
-
throw new Error(`${prefix} read_write stores require candidate_only or curator_promoted write_policy`);
|
|
189
|
-
}
|
|
190
|
-
if (store.scope === 'org' && store.access === 'read_write' && store.write_policy !== 'curator_promoted') {
|
|
191
|
-
throw new Error(`${prefix} org shared read_write stores require write_policy: curator_promoted`);
|
|
192
|
-
}
|
|
193
|
-
if (store.scope === 'workspace' && store.access === 'read_write' && store.write_policy === 'candidate_only') {
|
|
194
|
-
throw new Error(`${prefix} workspace shared read_write stores require curator_promoted writes`);
|
|
195
|
-
}
|
|
196
|
-
if (store.access === 'read_write' && store.evidence_required !== true) {
|
|
197
|
-
throw new Error(`${prefix} read_write stores must require evidence`);
|
|
198
|
-
}
|
|
199
|
-
}
|
|
200
|
-
|
|
201
|
-
function parseMemoryStoreManifest(value: unknown): MemoryStoreManifest {
|
|
202
|
-
const record = asRecord(value, 'memory_store');
|
|
203
|
-
if (asEnum(['memory-store.v0'] as const, record.schema, 'schema') !== 'memory-store.v0') {
|
|
204
|
-
throw new Error('schema must be memory-store.v0');
|
|
205
|
-
}
|
|
206
|
-
const store: MemoryStoreManifest = {
|
|
207
|
-
schema: 'memory-store.v0',
|
|
208
|
-
store_id: asString(record, 'store_id'),
|
|
209
|
-
scope: asEnum(WORKSPACE_MEMORY_SCOPES, record.scope, 'scope') as WorkspaceMemoryScope,
|
|
210
|
-
access: asEnum(WORKSPACE_MEMORY_ACCESS, record.access, 'access') as WorkspaceMemoryAccess,
|
|
211
|
-
mount_mode: asEnum(WORKSPACE_MEMORY_MOUNT_MODES, record.mount_mode, 'mount_mode') as WorkspaceMemoryMountMode,
|
|
212
|
-
write_policy: asEnum(WORKSPACE_MEMORY_WRITE_POLICIES, record.write_policy, 'write_policy') as WorkspaceMemoryWritePolicy,
|
|
213
|
-
evidence_required: asBoolean(record, 'evidence_required'),
|
|
214
|
-
created_at: asString(record, 'created_at'),
|
|
215
|
-
storage_layout: LAYOUT,
|
|
216
|
-
authority_boundary: {
|
|
217
|
-
store_can_authorize_work: false,
|
|
218
|
-
agent_can_write_canonical_memory: false,
|
|
219
|
-
shared_write_back_requires_promotion: true,
|
|
220
|
-
},
|
|
221
|
-
};
|
|
222
|
-
validateStoreShape(store);
|
|
223
|
-
return store;
|
|
224
|
-
}
|
|
225
|
-
|
|
226
|
-
function parseMemoryStoreEntry(value: unknown): MemoryStoreEntry {
|
|
227
|
-
const record = asRecord(value, 'memory_store_entry');
|
|
228
|
-
if (asEnum(['memory-store-entry.v0'] as const, record.schema, 'schema') !== 'memory-store-entry.v0') {
|
|
229
|
-
throw new Error('schema must be memory-store-entry.v0');
|
|
230
|
-
}
|
|
231
|
-
const refs = record.evidence_refs;
|
|
232
|
-
if (!Array.isArray(refs)) throw new Error('evidence_refs must be an array');
|
|
233
|
-
return {
|
|
234
|
-
schema: 'memory-store-entry.v0',
|
|
235
|
-
entry_id: asString(record, 'entry_id'),
|
|
236
|
-
store_id: asString(record, 'store_id'),
|
|
237
|
-
path: asString(record, 'path'),
|
|
238
|
-
status: 'candidate',
|
|
239
|
-
created_at: asString(record, 'created_at'),
|
|
240
|
-
content_ref: asString(record, 'content_ref'),
|
|
241
|
-
evidence_refs: refs.map((item, index) => {
|
|
242
|
-
const ref = asRecord(item, `evidence_refs[${index}]`);
|
|
243
|
-
return {
|
|
244
|
-
type: asEnum(EVIDENCE_REF_TYPES, ref.type, `evidence_refs[${index}].type`) as EvidenceRefType,
|
|
245
|
-
ref: asString(ref, 'ref'),
|
|
246
|
-
};
|
|
247
|
-
}),
|
|
248
|
-
risk_flags: Array.isArray(record.risk_flags) ? record.risk_flags.map(String) : [],
|
|
249
|
-
authority_boundary: {
|
|
250
|
-
entry_can_authorize_work: false,
|
|
251
|
-
entry_can_replace_canonical_memory: false,
|
|
252
|
-
agent_wrote_canonical_memory: false,
|
|
253
|
-
},
|
|
254
|
-
};
|
|
255
|
-
}
|
|
256
|
-
|
|
257
|
-
export function parseEvidenceRefs(raw: string): EvidenceRef[] {
|
|
258
|
-
const refs = raw.split(',').map((item) => item.trim()).filter(Boolean).map((item) => {
|
|
259
|
-
const colon = item.indexOf(':');
|
|
260
|
-
if (colon <= 0 || colon === item.length - 1) {
|
|
261
|
-
throw new Error('--evidence entries must use type:ref, for example receipt:abc');
|
|
262
|
-
}
|
|
263
|
-
const type = item.slice(0, colon);
|
|
264
|
-
const ref = item.slice(colon + 1);
|
|
265
|
-
if (!(EVIDENCE_REF_TYPES as readonly string[]).includes(type)) {
|
|
266
|
-
throw new Error(`--evidence type must be one of: ${EVIDENCE_REF_TYPES.join(', ')}`);
|
|
267
|
-
}
|
|
268
|
-
assertSharedMemoryText(ref, 'evidence.ref');
|
|
269
|
-
return { type: type as EvidenceRefType, ref };
|
|
270
|
-
});
|
|
271
|
-
if (refs.length === 0) throw new Error('--evidence must include at least one type:ref');
|
|
272
|
-
return refs;
|
|
273
|
-
}
|
|
274
|
-
|
|
275
|
-
export async function initMemoryStore(options: {
|
|
276
|
-
projectRoot: string;
|
|
277
|
-
storesRoot?: string;
|
|
278
|
-
storeId: string;
|
|
279
|
-
scope: WorkspaceMemoryScope;
|
|
280
|
-
access: WorkspaceMemoryAccess;
|
|
281
|
-
mountMode?: WorkspaceMemoryMountMode;
|
|
282
|
-
writePolicy?: WorkspaceMemoryWritePolicy;
|
|
283
|
-
evidenceRequired?: boolean;
|
|
284
|
-
at?: string;
|
|
285
|
-
}): Promise<MemoryStoreManifest> {
|
|
286
|
-
const manifest: MemoryStoreManifest = {
|
|
287
|
-
schema: 'memory-store.v0',
|
|
288
|
-
store_id: assertStoreId(options.storeId),
|
|
289
|
-
scope: options.scope,
|
|
290
|
-
access: options.access,
|
|
291
|
-
mount_mode: options.mountMode ?? 'filesystem_like',
|
|
292
|
-
write_policy: options.writePolicy ?? (options.access === 'read_only' ? 'none' : 'candidate_only'),
|
|
293
|
-
evidence_required: options.evidenceRequired ?? options.access === 'read_write',
|
|
294
|
-
created_at: isoNow(options.at),
|
|
295
|
-
storage_layout: LAYOUT,
|
|
296
|
-
authority_boundary: {
|
|
297
|
-
store_can_authorize_work: false,
|
|
298
|
-
agent_can_write_canonical_memory: false,
|
|
299
|
-
shared_write_back_requires_promotion: true,
|
|
300
|
-
},
|
|
301
|
-
};
|
|
302
|
-
validateStoreShape(manifest);
|
|
303
|
-
const root = resolve(options.storesRoot ?? defaultMemoryStoresRoot(options.projectRoot));
|
|
304
|
-
const dir = storeDir(root, manifest.store_id);
|
|
305
|
-
await mkdir(join(dir, LAYOUT.candidate_files_dir), { recursive: true });
|
|
306
|
-
await mkdir(join(dir, LAYOUT.candidate_entries_dir), { recursive: true });
|
|
307
|
-
await mkdir(join(dir, LAYOUT.rollups_dir), { recursive: true });
|
|
308
|
-
await writeYaml(join(dir, LAYOUT.manifest_ref), manifest);
|
|
309
|
-
return manifest;
|
|
310
|
-
}
|
|
311
|
-
|
|
312
|
-
export async function loadMemoryStoreManifest(options: {
|
|
313
|
-
projectRoot: string;
|
|
314
|
-
storesRoot?: string;
|
|
315
|
-
storeId: string;
|
|
316
|
-
}): Promise<MemoryStoreManifest> {
|
|
317
|
-
const root = resolve(options.storesRoot ?? defaultMemoryStoresRoot(options.projectRoot));
|
|
318
|
-
return parseMemoryStoreManifest(await readStructured(join(storeDir(root, assertStoreId(options.storeId)), LAYOUT.manifest_ref)));
|
|
319
|
-
}
|
|
320
|
-
|
|
321
|
-
async function countYamlFiles(dir: string): Promise<number> {
|
|
322
|
-
try {
|
|
323
|
-
const items = await readdir(dir, { withFileTypes: true });
|
|
324
|
-
return items.filter((item) => item.isFile() && /\.ya?ml$/i.test(item.name)).length;
|
|
325
|
-
} catch (error) {
|
|
326
|
-
if ((error as NodeJS.ErrnoException).code === 'ENOENT') return 0;
|
|
327
|
-
throw error;
|
|
328
|
-
}
|
|
329
|
-
}
|
|
330
|
-
|
|
331
|
-
async function summarizeStore(projectRoot: string, storesRoot: string | undefined, store: MemoryStoreManifest): Promise<MemoryStoreSummary> {
|
|
332
|
-
const root = resolve(storesRoot ?? defaultMemoryStoresRoot(projectRoot));
|
|
333
|
-
const dir = storeDir(root, store.store_id);
|
|
334
|
-
return {
|
|
335
|
-
store,
|
|
336
|
-
counts: {
|
|
337
|
-
candidate_entries: await countYamlFiles(join(dir, LAYOUT.candidate_entries_dir)),
|
|
338
|
-
rollups: await countYamlFiles(join(dir, LAYOUT.rollups_dir)),
|
|
339
|
-
},
|
|
340
|
-
};
|
|
341
|
-
}
|
|
342
|
-
|
|
343
|
-
export async function showMemoryStore(options: {
|
|
344
|
-
projectRoot: string;
|
|
345
|
-
storesRoot?: string;
|
|
346
|
-
storeId: string;
|
|
347
|
-
}): Promise<MemoryStoreSummary> {
|
|
348
|
-
const store = await loadMemoryStoreManifest(options);
|
|
349
|
-
return summarizeStore(options.projectRoot, options.storesRoot, store);
|
|
350
|
-
}
|
|
351
|
-
|
|
352
|
-
export async function listMemoryStores(options: {
|
|
353
|
-
projectRoot: string;
|
|
354
|
-
storesRoot?: string;
|
|
355
|
-
}): Promise<MemoryStoreSummary[]> {
|
|
356
|
-
const root = resolve(options.storesRoot ?? defaultMemoryStoresRoot(options.projectRoot));
|
|
357
|
-
let items;
|
|
358
|
-
try {
|
|
359
|
-
items = await readdir(root, { withFileTypes: true });
|
|
360
|
-
} catch (error) {
|
|
361
|
-
if ((error as NodeJS.ErrnoException).code === 'ENOENT') return [];
|
|
362
|
-
throw error;
|
|
363
|
-
}
|
|
364
|
-
const summaries: MemoryStoreSummary[] = [];
|
|
365
|
-
for (const item of items) {
|
|
366
|
-
if (!item.isDirectory()) continue;
|
|
367
|
-
const store = await loadMemoryStoreManifest({ ...options, storeId: item.name });
|
|
368
|
-
summaries.push(await summarizeStore(options.projectRoot, options.storesRoot, store));
|
|
369
|
-
}
|
|
370
|
-
return summaries.sort((a, b) => a.store.store_id.localeCompare(b.store.store_id));
|
|
371
|
-
}
|
|
372
|
-
|
|
373
|
-
export async function addMemoryStoreCandidate(options: {
|
|
374
|
-
projectRoot: string;
|
|
375
|
-
storesRoot?: string;
|
|
376
|
-
storeId: string;
|
|
377
|
-
path: string;
|
|
378
|
-
content: string;
|
|
379
|
-
evidenceRefs: EvidenceRef[];
|
|
380
|
-
at?: string;
|
|
381
|
-
}): Promise<MemoryStoreEntry> {
|
|
382
|
-
const store = await loadMemoryStoreManifest(options);
|
|
383
|
-
if (store.access === 'read_only' || store.write_policy === 'none') {
|
|
384
|
-
throw new Error(`memory store ${store.store_id} is read-only and cannot accept candidates`);
|
|
385
|
-
}
|
|
386
|
-
if (store.evidence_required && options.evidenceRefs.length === 0) {
|
|
387
|
-
throw new Error(`memory store ${store.store_id} requires evidence refs`);
|
|
388
|
-
}
|
|
389
|
-
const path = assertMemoryPath(options.path);
|
|
390
|
-
assertSharedMemoryText(options.content, 'content');
|
|
391
|
-
const createdAt = isoNow(options.at);
|
|
392
|
-
const entryId = `mem-entry:${stableHash({
|
|
393
|
-
store_id: store.store_id,
|
|
394
|
-
path,
|
|
395
|
-
content: options.content,
|
|
396
|
-
evidence_refs: options.evidenceRefs,
|
|
397
|
-
})}`;
|
|
398
|
-
const root = resolve(options.storesRoot ?? defaultMemoryStoresRoot(options.projectRoot));
|
|
399
|
-
const dir = storeDir(root, store.store_id);
|
|
400
|
-
const contentRef = relativeRef(LAYOUT.candidate_files_dir, path);
|
|
401
|
-
const entry: MemoryStoreEntry = {
|
|
402
|
-
schema: 'memory-store-entry.v0',
|
|
403
|
-
entry_id: entryId,
|
|
404
|
-
store_id: store.store_id,
|
|
405
|
-
path,
|
|
406
|
-
status: 'candidate',
|
|
407
|
-
created_at: createdAt,
|
|
408
|
-
content_ref: contentRef,
|
|
409
|
-
evidence_refs: options.evidenceRefs,
|
|
410
|
-
risk_flags: detectMemoryRiskFlags(options.content),
|
|
411
|
-
authority_boundary: {
|
|
412
|
-
entry_can_authorize_work: false,
|
|
413
|
-
entry_can_replace_canonical_memory: false,
|
|
414
|
-
agent_wrote_canonical_memory: false,
|
|
415
|
-
},
|
|
416
|
-
};
|
|
417
|
-
await mkdir(dirname(join(dir, contentRef)), { recursive: true });
|
|
418
|
-
await writeFile(join(dir, contentRef), options.content);
|
|
419
|
-
await writeYaml(join(dir, LAYOUT.candidate_entries_dir, `${entryId.replace(':', '-')}.yaml`), entry);
|
|
420
|
-
return entry;
|
|
421
|
-
}
|
|
422
|
-
|
|
423
|
-
async function loadCandidateEntries(projectRoot: string, storesRoot: string | undefined, storeId: string): Promise<MemoryStoreEntry[]> {
|
|
424
|
-
const root = resolve(storesRoot ?? defaultMemoryStoresRoot(projectRoot));
|
|
425
|
-
const dir = join(storeDir(root, assertStoreId(storeId)), LAYOUT.candidate_entries_dir);
|
|
426
|
-
let items;
|
|
427
|
-
try {
|
|
428
|
-
items = await readdir(dir, { withFileTypes: true });
|
|
429
|
-
} catch (error) {
|
|
430
|
-
if ((error as NodeJS.ErrnoException).code === 'ENOENT') return [];
|
|
431
|
-
throw error;
|
|
432
|
-
}
|
|
433
|
-
const entries: MemoryStoreEntry[] = [];
|
|
434
|
-
for (const item of items) {
|
|
435
|
-
if (!item.isFile() || !/\.ya?ml$/i.test(item.name)) continue;
|
|
436
|
-
entries.push(parseMemoryStoreEntry(await readStructured(join(dir, item.name))));
|
|
437
|
-
}
|
|
438
|
-
return entries.sort((a, b) => a.entry_id.localeCompare(b.entry_id));
|
|
439
|
-
}
|
|
440
|
-
|
|
441
|
-
export async function listMemoryStoreCandidateEntries(options: {
|
|
442
|
-
projectRoot: string;
|
|
443
|
-
storesRoot?: string;
|
|
444
|
-
storeId: string;
|
|
445
|
-
}): Promise<MemoryStoreEntry[]> {
|
|
446
|
-
return loadCandidateEntries(options.projectRoot, options.storesRoot, options.storeId);
|
|
447
|
-
}
|
|
448
|
-
|
|
449
|
-
export async function readMemoryStoreCandidateContent(options: {
|
|
450
|
-
projectRoot: string;
|
|
451
|
-
storesRoot?: string;
|
|
452
|
-
storeId: string;
|
|
453
|
-
entry: MemoryStoreEntry;
|
|
454
|
-
}): Promise<string> {
|
|
455
|
-
if (options.entry.store_id !== options.storeId) {
|
|
456
|
-
throw new Error(`entry ${options.entry.entry_id} belongs to store ${options.entry.store_id}, not ${options.storeId}`);
|
|
457
|
-
}
|
|
458
|
-
const root = resolve(options.storesRoot ?? defaultMemoryStoresRoot(options.projectRoot));
|
|
459
|
-
const contentRef = assertStoreRelativeRef(options.entry.content_ref, 'content_ref');
|
|
460
|
-
return readFile(join(storeDir(root, assertStoreId(options.storeId)), contentRef), 'utf-8');
|
|
461
|
-
}
|
|
462
|
-
|
|
463
|
-
export async function consolidateMemoryStores(options: {
|
|
464
|
-
projectRoot: string;
|
|
465
|
-
storesRoot?: string;
|
|
466
|
-
inputStores: string[];
|
|
467
|
-
outputStore: string;
|
|
468
|
-
at?: string;
|
|
469
|
-
}): Promise<MemoryConsolidationRollup> {
|
|
470
|
-
if (options.inputStores.length === 0) throw new Error('--input must include at least one store id');
|
|
471
|
-
const output = await loadMemoryStoreManifest({ ...options, storeId: options.outputStore });
|
|
472
|
-
if (output.access !== 'read_write' || output.write_policy === 'none') {
|
|
473
|
-
throw new Error(`consolidation output store ${output.store_id} must be read_write`);
|
|
474
|
-
}
|
|
475
|
-
const entries = (await Promise.all(options.inputStores.map((storeId) => loadCandidateEntries(
|
|
476
|
-
options.projectRoot,
|
|
477
|
-
options.storesRoot,
|
|
478
|
-
storeId,
|
|
479
|
-
)))).flat();
|
|
480
|
-
const createdAt = isoNow(options.at);
|
|
481
|
-
const rollupId = `mem-rollup:${stableHash({
|
|
482
|
-
input_stores: options.inputStores,
|
|
483
|
-
output_store: output.store_id,
|
|
484
|
-
entries: entries.map((entry) => entry.entry_id),
|
|
485
|
-
at: createdAt,
|
|
486
|
-
})}`;
|
|
487
|
-
const rollup: MemoryConsolidationRollup = {
|
|
488
|
-
schema: 'memory-consolidation-rollup.v0',
|
|
489
|
-
rollup_id: rollupId,
|
|
490
|
-
created_at: createdAt,
|
|
491
|
-
input_stores: options.inputStores.map(assertStoreId),
|
|
492
|
-
output_store: output.store_id,
|
|
493
|
-
candidate_count: entries.length,
|
|
494
|
-
entry_refs: entries.map((entry) => `${entry.store_id}:${entry.entry_id}`),
|
|
495
|
-
summary: entries.length === 0
|
|
496
|
-
? 'No candidate memory entries were available for consolidation.'
|
|
497
|
-
: `Consolidated ${entries.length} candidate memory entr${entries.length === 1 ? 'y' : 'ies'} from ${options.inputStores.join(', ')}.`,
|
|
498
|
-
output_policy: output.write_policy,
|
|
499
|
-
status: 'candidate',
|
|
500
|
-
authority_boundary: {
|
|
501
|
-
rollup_can_authorize_work: false,
|
|
502
|
-
mutates_input_store: false,
|
|
503
|
-
promotes_without_curator: false,
|
|
504
|
-
},
|
|
505
|
-
};
|
|
506
|
-
const root = resolve(options.storesRoot ?? defaultMemoryStoresRoot(options.projectRoot));
|
|
507
|
-
await writeYaml(join(storeDir(root, output.store_id), LAYOUT.rollups_dir, `${rollupId.replace(':', '-')}.yaml`), rollup);
|
|
508
|
-
return rollup;
|
|
509
|
-
}
|