@etiquekit/etq 1.0.12 → 1.0.13

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 (149) hide show
  1. package/AGENTS.md +19 -26
  2. package/LICENSE +2 -3
  3. package/NOTICE +6 -4
  4. package/QuickStart.md +35 -47
  5. package/README.md +54 -56
  6. package/bin/etiquette +1 -5
  7. package/bin/etiquette-core +1 -5
  8. package/docs/ARCHITECTURE.md +5 -5
  9. package/docs/CODEX_CLIENT_COMPATIBILITY.md +1 -1
  10. package/docs/CONCEPTS.md +7 -23
  11. package/docs/CONCEPT_STATUS.md +66 -0
  12. package/docs/CORE_PROFILE.md +3 -5
  13. package/docs/LANE_PROVISIONING.md +7 -2
  14. package/docs/README.md +4 -4
  15. package/docs/RELEASE_SURFACE_AUDIT.md +7 -6
  16. package/docs/SEAT_DISCIPLINE.md +55 -91
  17. package/docs/SEAT_PROVISIONING.md +19 -22
  18. package/docs/TEAM_HANDOFF.md +21 -21
  19. package/docs/WORKTREE_QOL.md +7 -6
  20. package/docs/contracts/ledger-entry/README.md +11 -11
  21. package/docs/contracts/ledger-entry/ledger-entry.v0.2.md +3 -3
  22. package/docs/contracts/ledger-entry/ledger-entry.v0.md +8 -8
  23. package/package.json +11 -11
  24. package/packages/control/src/authority/lease.ts +261 -0
  25. package/packages/control/src/authority/node-delegation.ts +257 -0
  26. package/packages/control/src/authority/rig-conductor.ts +632 -0
  27. package/packages/control/src/cli/argv.ts +155 -0
  28. package/packages/control/src/cli/commands/console.ts +200 -0
  29. package/packages/control/src/cli/commands/dispatch-core.ts +89 -0
  30. package/packages/control/src/cli/commands/dispatch.ts +279 -0
  31. package/packages/control/src/cli/commands/harness.ts +89 -0
  32. package/packages/control/src/cli/commands/hook.ts +50 -0
  33. package/packages/control/src/cli/commands/ledger.ts +91 -0
  34. package/packages/control/src/cli/commands/local-workflow.ts +4686 -0
  35. package/packages/control/src/cli/commands/memory.ts +445 -0
  36. package/packages/control/src/cli/commands/release.ts +108 -0
  37. package/packages/control/src/cli/commands/rubric.ts +103 -0
  38. package/packages/control/src/cli/commands/seat.ts +179 -0
  39. package/packages/control/src/cli/commands/session.ts +127 -0
  40. package/packages/control/src/cli/commands/supervision.ts +246 -0
  41. package/packages/control/src/cli/commands/sync.ts +119 -0
  42. package/packages/control/src/cli/commands/workflow.ts +86 -0
  43. package/packages/control/src/cli/commands/workspace.ts +50 -0
  44. package/packages/control/src/cli/core-usage.ts +34 -0
  45. package/packages/control/src/cli/prompt.ts +67 -0
  46. package/packages/control/src/cli/supervision-deps.ts +44 -0
  47. package/packages/control/src/cli/usage.ts +241 -0
  48. package/packages/control/src/cli.ts +207 -0
  49. package/packages/control/src/core-cli.ts +50 -0
  50. package/packages/control/src/dispatch/decision.ts +202 -0
  51. package/packages/control/src/dispatch/projection.ts +293 -0
  52. package/packages/control/src/dispatch/record.ts +153 -0
  53. package/packages/control/src/engagement/project.ts +170 -0
  54. package/packages/control/src/fs.ts +19 -0
  55. package/packages/control/src/harness/pruning.ts +406 -0
  56. package/packages/control/src/hooks/dispatcher.ts +117 -0
  57. package/packages/control/src/hooks/outbox.ts +86 -0
  58. package/packages/control/src/hooks/sanitize.ts +6 -0
  59. package/packages/control/src/hooks/types.ts +34 -0
  60. package/packages/control/src/index.ts +384 -0
  61. package/packages/control/src/ledger/entry.ts +303 -0
  62. package/packages/control/src/ledger/indexer.ts +542 -0
  63. package/packages/control/src/memory/context.ts +149 -0
  64. package/packages/control/src/memory/drain-import.ts +207 -0
  65. package/packages/control/src/memory/indexer.ts +284 -0
  66. package/packages/control/src/memory/query.ts +75 -0
  67. package/packages/control/src/memory/sanitize.ts +50 -0
  68. package/packages/control/src/memory/sharded-drain-import.ts +212 -0
  69. package/packages/control/src/memory/status.ts +211 -0
  70. package/packages/control/src/memory/store-lifecycle.ts +509 -0
  71. package/packages/control/src/memory/store.ts +284 -0
  72. package/packages/control/src/memory/types.ts +146 -0
  73. package/packages/control/src/parity/surfaces.ts +748 -0
  74. package/packages/control/src/project.ts +141 -0
  75. package/packages/control/src/projection/local-ledger-view.ts +373 -0
  76. package/packages/control/src/projection/return-enforcement.ts +48 -0
  77. package/packages/control/src/projection/timeline-preview.ts +539 -0
  78. package/packages/control/src/projection/timeline.ts +708 -0
  79. package/packages/control/src/release/readiness.ts +831 -0
  80. package/packages/control/src/rubric/loader.ts +326 -0
  81. package/packages/control/src/rubric/promotion.ts +54 -0
  82. package/packages/control/src/rubric/runner.ts +159 -0
  83. package/packages/control/src/rubric/types.ts +158 -0
  84. package/packages/control/src/seat/owner-card.ts +388 -0
  85. package/packages/control/src/seat/readiness.ts +834 -0
  86. package/packages/control/src/session/runbook.ts +431 -0
  87. package/packages/control/src/shared/sanitize.ts +49 -0
  88. package/packages/control/src/supervision/action-classes.ts +192 -0
  89. package/packages/control/src/supervision/command-apply.ts +378 -0
  90. package/packages/control/src/supervision/errors.ts +14 -0
  91. package/packages/control/src/supervision/event-replay.ts +155 -0
  92. package/packages/control/src/supervision/events.ts +109 -0
  93. package/packages/control/src/supervision/index.ts +16 -0
  94. package/packages/control/src/supervision/manifest.ts +127 -0
  95. package/packages/control/src/supervision/paths.ts +49 -0
  96. package/packages/control/src/supervision/projection-adapter.ts +274 -0
  97. package/packages/control/src/supervision/projection.ts +75 -0
  98. package/packages/control/src/supervision/rebuild.ts +99 -0
  99. package/packages/control/src/supervision/session-open.ts +131 -0
  100. package/packages/control/src/supervision/session-read.ts +99 -0
  101. package/packages/control/src/supervision/sqlite-impl.ts +71 -0
  102. package/packages/control/src/supervision/sqlite.ts +121 -0
  103. package/packages/control/src/supervision/store-rows.ts +371 -0
  104. package/packages/control/src/supervision/turn-close.ts +154 -0
  105. package/packages/control/src/supervision/turn-open.ts +284 -0
  106. package/packages/control/src/sync/event-log-merge-driver.ts +263 -0
  107. package/packages/control/src/sync/join-plan.ts +375 -0
  108. package/packages/control/src/sync/outbox.ts +492 -0
  109. package/packages/control/src/workflow/evaluator.ts +140 -0
  110. package/packages/control/src/workflow/loader.ts +200 -0
  111. package/packages/control/src/workflow/types.ts +90 -0
  112. package/packages/control/src/workspace/authority.ts +499 -0
  113. package/packages/protocol/src/guards.ts +119 -0
  114. package/packages/protocol/src/huddle-board.ts +198 -0
  115. package/packages/protocol/src/huddle.ts +295 -0
  116. package/packages/protocol/src/incident.ts +251 -0
  117. package/packages/protocol/src/index.ts +8 -0
  118. package/packages/protocol/src/interfaces.ts +107 -0
  119. package/packages/protocol/src/packet-profile.ts +195 -0
  120. package/packages/protocol/src/state.ts +81 -0
  121. package/packages/protocol/src/types.ts +434 -0
  122. package/release/lineage.v0.json +14 -0
  123. package/scripts/release-candidate-verify.sh +175 -0
  124. package/scripts/release-checksum.sh +25 -0
  125. package/scripts/release-pack-canary.sh +97 -0
  126. package/scripts/release-scan.sh +249 -0
  127. package/scripts/release-sign.sh +34 -0
  128. package/templates/etiquette-vanilla-v0/README.md +2 -3
  129. package/templates/etiquette-vanilla-v0/source/control-seat/README.md +0 -6
  130. package/templates/etiquette-vanilla-v0/source/control-seat/bin/seat-doctor +0 -5
  131. package/templates/etiquette-vanilla-v0/validate-vanilla.sh +0 -5
  132. package/templates/seat-packs-v0/README.md +3 -2
  133. package/templates/seat-packs-v0/source/claude-code-seat/README.md +3 -29
  134. package/templates/seat-packs-v0/source/codex-seat/README.md +3 -17
  135. package/templates/seat-packs-v0/source/gemini-seat/README.md +3 -23
  136. package/templates/seat-packs-v0/source/ollama-seat/README.md +3 -3
  137. package/templates/seat-packs-v0/source/openrouter-seat/README.md +3 -3
  138. package/docs/AGENT_SPAWNING.md +0 -78
  139. package/docs/INTEGRATION_UI.md +0 -215
  140. package/docs/OPERATOR_PLAYBOOK.md +0 -77
  141. package/docs/TAG_ROUTE.md +0 -95
  142. package/lib/etiquette-core.js +0 -315
  143. package/lib/etiquette.js +0 -1160
  144. package/templates/DISPATCH_PREMISES.md +0 -39
  145. package/templates/etiquette-vanilla-v0/source/control-seat/bin/access-assurance-check +0 -55
  146. package/templates/etiquette-vanilla-v0/source/control-seat/docs/work/access/ACCESS-ASSURANCE.md +0 -39
  147. package/templates/etiquette-vanilla-v0/source/control-seat/docs/work/access/workspace-secure-profile.v0.json +0 -53
  148. package/templates/hosted-receiver/README.md +0 -41
  149. package/templates/hosted-receiver/w1-github-webhook-receiver.mjs +0 -129
@@ -0,0 +1,445 @@
1
+ import { resolve } from 'node:path';
2
+ import { optionalString, parseInt32, parseList, requireString } from '../argv';
3
+ import { buildMemoryContext } from '../../memory/context';
4
+ import { drainImportMemoryCandidates } from '../../memory/drain-import';
5
+ import { shardedDrainImportMemoryCandidates } from '../../memory/sharded-drain-import';
6
+ import { defaultMemoryExportDir, indexMemory } from '../../memory/indexer';
7
+ import { preflightMemory, rejectedMemory, searchMemory, showMemory, staleMemory } from '../../memory/query';
8
+ import { memoryCapabilityStatus, type MemoryCapabilityStatus } from '../../memory/status';
9
+ import {
10
+ addMemoryStoreCandidate,
11
+ consolidateMemoryStores,
12
+ initMemoryStore,
13
+ listMemoryStores,
14
+ parseEvidenceRefs,
15
+ showMemoryStore,
16
+ type MemoryStoreSummary,
17
+ } from '../../memory/store-lifecycle';
18
+ import {
19
+ WORKSPACE_MEMORY_ACCESS,
20
+ WORKSPACE_MEMORY_MOUNT_MODES,
21
+ WORKSPACE_MEMORY_SCOPES,
22
+ WORKSPACE_MEMORY_WRITE_POLICIES,
23
+ type WorkspaceMemoryAccess,
24
+ type WorkspaceMemoryMountMode,
25
+ type WorkspaceMemoryScope,
26
+ type WorkspaceMemoryWritePolicy,
27
+ } from '../../workspace/authority';
28
+
29
+ const MEMORY_VERBS = new Set([
30
+ 'index',
31
+ 'search',
32
+ 'show',
33
+ 'preflight',
34
+ 'context',
35
+ 'status',
36
+ 'stale',
37
+ 'rejected',
38
+ 'store',
39
+ 'consolidate',
40
+ 'drain-import',
41
+ 'sharded-drain-import',
42
+ ]);
43
+
44
+ export function isMemoryCommand(command: string): boolean {
45
+ return command === 'memory';
46
+ }
47
+
48
+ function asLimit(raw: string | boolean | undefined, fallback: number): number {
49
+ if (raw === undefined) return fallback;
50
+ const parsed = parseInt32(raw, 'limit');
51
+ if (parsed < 1 || parsed > 50) {
52
+ throw new Error('--limit must be between 1 and 50');
53
+ }
54
+ return parsed;
55
+ }
56
+
57
+ function renderCompact(items: {
58
+ capsule_id: string;
59
+ title: string;
60
+ category: string;
61
+ confidence: string;
62
+ stale_status: string;
63
+ evidence_count: number;
64
+ }[]): string {
65
+ if (items.length === 0) return 'memory: no matching capsules\n';
66
+ return `${items.map((item) => [
67
+ `capsule: ${item.capsule_id}`,
68
+ `title: ${item.title}`,
69
+ `category: ${item.category}`,
70
+ `confidence: ${item.confidence}`,
71
+ `stale_status: ${item.stale_status}`,
72
+ `evidence_count: ${item.evidence_count}`,
73
+ ].join('\n')).join('\n---\n')}\n`;
74
+ }
75
+
76
+ function renderRejected(items: {
77
+ candidate_id: string;
78
+ title: string;
79
+ category: string;
80
+ risk_flags: string[];
81
+ rejection_reasons: string[];
82
+ source_refs: { type: string; ref: string }[];
83
+ }[]): string {
84
+ if (items.length === 0) return 'memory: no rejected candidates\n';
85
+ return `${items.map((item) => [
86
+ `candidate: ${item.candidate_id}`,
87
+ `title: ${item.title}`,
88
+ `category: ${item.category}`,
89
+ `risk_flags: ${item.risk_flags.join(', ') || 'none'}`,
90
+ `rejection_reasons: ${item.rejection_reasons.join(', ') || 'none'}`,
91
+ `source_refs: ${item.source_refs.map((ref) => `${ref.type}:${ref.ref}`).join(', ')}`,
92
+ ].join('\n')).join('\n---\n')}\n`;
93
+ }
94
+
95
+ function renderContext(context: ReturnType<typeof buildMemoryContext>): string {
96
+ const scope = [
97
+ context.scope.org_id ? `org_id=${context.scope.org_id}` : undefined,
98
+ context.scope.workspace_id ? `workspace_id=${context.scope.workspace_id}` : undefined,
99
+ context.scope.lane_ref ? `lane_ref=${context.scope.lane_ref}` : undefined,
100
+ context.scope.task_ref ? `task_ref=${context.scope.task_ref}` : undefined,
101
+ ].filter(Boolean).join(' ');
102
+ const items = context.items.length === 0
103
+ ? [' capsules: none']
104
+ : context.items.flatMap((item) => [
105
+ ` - ${item.capsule_id}`,
106
+ ` title: ${item.title}`,
107
+ ` category: ${item.category}`,
108
+ ` confidence: ${item.confidence}`,
109
+ ` stale_status: ${item.stale_status}`,
110
+ ` evidence_refs: ${item.evidence_refs.map((ref) => `${ref.type}:${ref.ref}`).join(', ')}`,
111
+ ]);
112
+ return [
113
+ 'memory_context:',
114
+ ` schema: ${context.schema}`,
115
+ ` query: ${context.query}`,
116
+ ` scope: ${scope || 'workspace_local'}`,
117
+ ' boundary: summaries-only; advisory memory; cannot authorize, close, replace receipts, or mutate ledger',
118
+ ...items,
119
+ ].join('\n') + '\n';
120
+ }
121
+
122
+ function enumValue<T extends readonly string[]>(values: T, raw: string, key: string): T[number] {
123
+ if (!(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 optionalBoolean(flags: Record<string, string | boolean>, key: string): boolean | undefined {
130
+ const value = flags[key];
131
+ if (value === undefined) return undefined;
132
+ if (value === true) return true;
133
+ if (value === 'true') return true;
134
+ if (value === 'false') return false;
135
+ throw new Error(`--${key} must be true or false`);
136
+ }
137
+
138
+ function renderStoreSummary(summary: MemoryStoreSummary): string {
139
+ return [
140
+ `store: ${summary.store.store_id}`,
141
+ `scope: ${summary.store.scope}`,
142
+ `access: ${summary.store.access}`,
143
+ `mount_mode: ${summary.store.mount_mode}`,
144
+ `write_policy: ${summary.store.write_policy}`,
145
+ `evidence_required: ${String(summary.store.evidence_required)}`,
146
+ `candidate_entries: ${summary.counts.candidate_entries}`,
147
+ `rollups: ${summary.counts.rollups}`,
148
+ `agent_can_write_canonical_memory: ${String(summary.store.authority_boundary.agent_can_write_canonical_memory)}`,
149
+ ].join('\n');
150
+ }
151
+
152
+ function renderStoreList(summaries: MemoryStoreSummary[]): string {
153
+ if (summaries.length === 0) return 'memory_stores: none\n';
154
+ return `${summaries.map(renderStoreSummary).join('\n---\n')}\n`;
155
+ }
156
+
157
+ function renderMemoryCapabilityStatus(status: MemoryCapabilityStatus): string {
158
+ return [
159
+ 'memory_capability_status:',
160
+ ` schema: ${status.schema}`,
161
+ ` module_status: ${status.module_status}`,
162
+ ` full_feature_parity_claimed: ${String(status.parity_posture.full_feature_parity_claimed)}`,
163
+ ` managed_runtime_filesystem_memory: ${status.parity_posture.managed_runtime_filesystem_memory}`,
164
+ ` mem0_style_semantic_memory: ${status.parity_posture.mem0_style_semantic_memory}`,
165
+ ` approved_file_count: ${status.evidence_sources.approved_file_count}`,
166
+ ` recall_capsules: ${status.recall_index.stats.recall_capsules}`,
167
+ ` memory_candidates: ${status.recall_index.stats.candidates.total}`,
168
+ ` rejected_candidates: ${status.recall_index.stats.candidates.rejected}`,
169
+ ` search_adapter: ${status.recall_index.search_adapter}`,
170
+ ` vector_adapter: ${status.recall_index.vector_adapter}`,
171
+ ` store_count: ${status.store_lifecycle.store_count}`,
172
+ ` store_candidate_entries: ${status.store_lifecycle.candidate_entries}`,
173
+ ` rollups: ${status.store_lifecycle.rollups}`,
174
+ ` drain_import_receipts: ${status.remote_writeback.drain_import_receipts}`,
175
+ ` memory_can_authorize_work: ${String(status.authority_boundary.memory_can_authorize_work)}`,
176
+ ` memory_can_mutate_ledger: ${String(status.authority_boundary.memory_can_mutate_ledger)}`,
177
+ ` memory_is_execution_truth: ${String(status.authority_boundary.memory_is_execution_truth)}`,
178
+ ` marination_is_same_primitive: ${String(status.consolidation.marination_is_same_primitive)}`,
179
+ ` missing_for_full_parity: ${status.missing_for_full_parity.join(', ')}`,
180
+ ].join('\n') + '\n';
181
+ }
182
+
183
+ function parseShardSpecs(raw: string | boolean | undefined): Array<{ shardId: string; sourceProjectRoot: string }> {
184
+ const specs = parseList(raw);
185
+ if (specs.length === 0) throw new Error('--shard must include at least one id=path entry');
186
+ return specs.map((spec) => {
187
+ const index = spec.indexOf('=');
188
+ if (index <= 0 || index === spec.length - 1) {
189
+ throw new Error('--shard entries must use id=path');
190
+ }
191
+ const sourceProjectRoot = spec.slice(index + 1).trim();
192
+ if (sourceProjectRoot.length === 0) throw new Error('--shard entries must use id=path');
193
+ return {
194
+ shardId: spec.slice(0, index).trim(),
195
+ sourceProjectRoot: resolve(sourceProjectRoot),
196
+ };
197
+ });
198
+ }
199
+
200
+ export async function dispatchMemory(
201
+ positionals: string[],
202
+ flags: Record<string, string | boolean>,
203
+ ): Promise<void> {
204
+ const verb = positionals[1];
205
+ if (!verb || !MEMORY_VERBS.has(verb)) {
206
+ throw new Error(`memory verb must be one of: ${[...MEMORY_VERBS].join(', ')}`);
207
+ }
208
+ const projectRoot = resolve(optionalString(flags, 'project') ?? process.cwd());
209
+ const dbPath = optionalString(flags, 'db');
210
+ const json = Boolean(flags.json);
211
+
212
+ if (verb === 'index') {
213
+ const requestedExport = flags.export;
214
+ const exportDir = requestedExport === true
215
+ ? defaultMemoryExportDir(projectRoot, optionalString(flags, 'workspace-id'))
216
+ : optionalString(flags, 'export');
217
+ const localPrivate = Boolean(flags['local-private']);
218
+ if (localPrivate && !json) {
219
+ console.error([
220
+ 'WARNING: --local-private bypasses only the private-context promotion guard.',
221
+ 'Local-private capsules stay local and must not be shared, exported, or used as authority.',
222
+ ].join('\n'));
223
+ }
224
+ const result = await indexMemory({
225
+ projectRoot,
226
+ dbPath,
227
+ exportDir,
228
+ localPrivate,
229
+ orgId: optionalString(flags, 'org-id'),
230
+ workspaceId: optionalString(flags, 'workspace-id'),
231
+ confirmSharedExport: Boolean(flags['confirm-shared-export']),
232
+ });
233
+ console.log(json ? JSON.stringify(result, null, 2) : [
234
+ 'memory_index:',
235
+ ` candidates: ${result.candidates}`,
236
+ ` promoted: ${result.promoted}`,
237
+ ` rejected: ${result.rejected}`,
238
+ ].join('\n'));
239
+ return;
240
+ }
241
+
242
+ if (verb === 'status') {
243
+ const storesRoot = optionalString(flags, 'root') ? resolve(requireString(flags, 'root')) : undefined;
244
+ const status = await memoryCapabilityStatus({ projectRoot, dbPath, storesRoot });
245
+ console.log(json ? JSON.stringify(status, null, 2) : renderMemoryCapabilityStatus(status));
246
+ return;
247
+ }
248
+
249
+ if (verb === 'store') {
250
+ const subverb = positionals[2];
251
+ if (!subverb || !['init', 'list', 'show', 'add'].includes(subverb)) {
252
+ throw new Error('memory store verb must be one of: init, list, show, add');
253
+ }
254
+ const storesRoot = optionalString(flags, 'root') ? resolve(requireString(flags, 'root')) : undefined;
255
+ if (subverb === 'init') {
256
+ const access = enumValue(
257
+ WORKSPACE_MEMORY_ACCESS,
258
+ requireString(flags, 'access'),
259
+ 'access',
260
+ ) as WorkspaceMemoryAccess;
261
+ const result = await initMemoryStore({
262
+ projectRoot,
263
+ storesRoot,
264
+ storeId: requireString(flags, 'store'),
265
+ scope: enumValue(WORKSPACE_MEMORY_SCOPES, requireString(flags, 'scope'), 'scope') as WorkspaceMemoryScope,
266
+ access,
267
+ mountMode: optionalString(flags, 'mount-mode')
268
+ ? enumValue(WORKSPACE_MEMORY_MOUNT_MODES, requireString(flags, 'mount-mode'), 'mount-mode') as WorkspaceMemoryMountMode
269
+ : undefined,
270
+ writePolicy: optionalString(flags, 'write-policy')
271
+ ? enumValue(WORKSPACE_MEMORY_WRITE_POLICIES, requireString(flags, 'write-policy'), 'write-policy') as WorkspaceMemoryWritePolicy
272
+ : undefined,
273
+ evidenceRequired: optionalBoolean(flags, 'evidence-required'),
274
+ at: optionalString(flags, 'at'),
275
+ });
276
+ console.log(json ? JSON.stringify(result, null, 2) : [
277
+ 'memory_store_init:',
278
+ ` store_id: ${result.store_id}`,
279
+ ` scope: ${result.scope}`,
280
+ ` access: ${result.access}`,
281
+ ` write_policy: ${result.write_policy}`,
282
+ ` agent_can_write_canonical_memory: ${String(result.authority_boundary.agent_can_write_canonical_memory)}`,
283
+ ].join('\n'));
284
+ return;
285
+ }
286
+ if (subverb === 'list') {
287
+ const summaries = await listMemoryStores({ projectRoot, storesRoot });
288
+ console.log(json ? JSON.stringify(summaries, null, 2) : renderStoreList(summaries));
289
+ return;
290
+ }
291
+ if (subverb === 'show') {
292
+ const summary = await showMemoryStore({ projectRoot, storesRoot, storeId: requireString(flags, 'store') });
293
+ console.log(json ? JSON.stringify(summary, null, 2) : `${renderStoreSummary(summary)}\n`);
294
+ return;
295
+ }
296
+ const entry = await addMemoryStoreCandidate({
297
+ projectRoot,
298
+ storesRoot,
299
+ storeId: requireString(flags, 'store'),
300
+ path: requireString(flags, 'path'),
301
+ content: requireString(flags, 'content'),
302
+ evidenceRefs: parseEvidenceRefs(requireString(flags, 'evidence')),
303
+ at: optionalString(flags, 'at'),
304
+ });
305
+ console.log(json ? JSON.stringify(entry, null, 2) : [
306
+ 'memory_store_candidate:',
307
+ ` entry_id: ${entry.entry_id}`,
308
+ ` store_id: ${entry.store_id}`,
309
+ ` path: ${entry.path}`,
310
+ ` content_ref: ${entry.content_ref}`,
311
+ ` evidence_refs: ${entry.evidence_refs.map((ref) => `${ref.type}:${ref.ref}`).join(', ')}`,
312
+ ` agent_wrote_canonical_memory: ${String(entry.authority_boundary.agent_wrote_canonical_memory)}`,
313
+ ].join('\n'));
314
+ return;
315
+ }
316
+
317
+ if (verb === 'consolidate') {
318
+ const storesRoot = optionalString(flags, 'root') ? resolve(requireString(flags, 'root')) : undefined;
319
+ const rollup = await consolidateMemoryStores({
320
+ projectRoot,
321
+ storesRoot,
322
+ inputStores: parseList(flags.input),
323
+ outputStore: requireString(flags, 'out'),
324
+ at: optionalString(flags, 'at'),
325
+ });
326
+ console.log(json ? JSON.stringify(rollup, null, 2) : [
327
+ 'memory_consolidation_rollup:',
328
+ ` rollup_id: ${rollup.rollup_id}`,
329
+ ` input_stores: ${rollup.input_stores.join(', ')}`,
330
+ ` output_store: ${rollup.output_store}`,
331
+ ` candidate_count: ${rollup.candidate_count}`,
332
+ ` output_policy: ${rollup.output_policy}`,
333
+ ` mutates_input_store: ${String(rollup.authority_boundary.mutates_input_store)}`,
334
+ ` promotes_without_curator: ${String(rollup.authority_boundary.promotes_without_curator)}`,
335
+ ].join('\n'));
336
+ return;
337
+ }
338
+
339
+ if (verb === 'drain-import') {
340
+ const receipt = await drainImportMemoryCandidates({
341
+ sourceProjectRoot: resolve(requireString(flags, 'from-project')),
342
+ targetProjectRoot: projectRoot,
343
+ sourceStoresRoot: optionalString(flags, 'from-root') ? resolve(requireString(flags, 'from-root')) : undefined,
344
+ targetStoresRoot: optionalString(flags, 'to-root') ? resolve(requireString(flags, 'to-root')) : undefined,
345
+ sourceStoreId: requireString(flags, 'from-store'),
346
+ targetStoreId: requireString(flags, 'to-store'),
347
+ sourceWorkspaceRef: optionalString(flags, 'source-ref'),
348
+ targetWorkspaceRef: optionalString(flags, 'target-ref'),
349
+ pathPrefix: optionalString(flags, 'path-prefix'),
350
+ receiptPath: optionalString(flags, 'receipt') ? resolve(requireString(flags, 'receipt')) : undefined,
351
+ at: optionalString(flags, 'at'),
352
+ });
353
+ console.log(json ? JSON.stringify(receipt, null, 2) : [
354
+ 'memory_drain_import:',
355
+ ` receipt_id: ${receipt.receipt_id}`,
356
+ ` source: ${receipt.source.workspace_ref}/${receipt.source.store_id}`,
357
+ ` target: ${receipt.target.workspace_ref}/${receipt.target.store_id}`,
358
+ ` imported_count: ${receipt.target.imported_count}`,
359
+ ` drain_can_write_parent_ledger: ${String(receipt.authority_boundary.drain_can_write_parent_ledger)}`,
360
+ ` imported_entries_are_candidates: ${String(receipt.authority_boundary.imported_entries_are_candidates)}`,
361
+ ` source_store_mutated: ${String(receipt.authority_boundary.source_store_mutated)}`,
362
+ ].join('\n'));
363
+ return;
364
+ }
365
+
366
+ if (verb === 'sharded-drain-import') {
367
+ const receipt = await shardedDrainImportMemoryCandidates({
368
+ targetProjectRoot: projectRoot,
369
+ sourceStoreId: requireString(flags, 'from-store'),
370
+ targetStoreId: requireString(flags, 'to-store'),
371
+ shards: parseShardSpecs(flags.shard),
372
+ sourceStoresRoot: optionalString(flags, 'from-root') ? resolve(requireString(flags, 'from-root')) : undefined,
373
+ targetStoresRoot: optionalString(flags, 'to-root') ? resolve(requireString(flags, 'to-root')) : undefined,
374
+ targetWorkspaceRef: optionalString(flags, 'target-ref'),
375
+ receiptDir: optionalString(flags, 'receipt-dir'),
376
+ receiptPath: optionalString(flags, 'receipt') ? resolve(requireString(flags, 'receipt')) : undefined,
377
+ at: optionalString(flags, 'at'),
378
+ });
379
+ console.log(json ? JSON.stringify(receipt, null, 2) : [
380
+ 'memory_sharded_drain_import:',
381
+ ` receipt_id: ${receipt.receipt_id}`,
382
+ ` drain_topology: ${receipt.topology.drain_topology}`,
383
+ ` shard_key: ${receipt.topology.shard_key}`,
384
+ ` shard_count: ${receipt.topology.shard_count}`,
385
+ ` imported_count: ${receipt.totals.imported_count}`,
386
+ ` promotion_gate_required: ${String(receipt.authority_boundary.promotion_gate_required)}`,
387
+ ` sharded_drain_can_write_parent_ledger: ${String(receipt.authority_boundary.sharded_drain_can_write_parent_ledger)}`,
388
+ ` candidate_import_is_canonical_truth: ${String(receipt.authority_boundary.candidate_import_is_canonical_truth)}`,
389
+ ].join('\n'));
390
+ return;
391
+ }
392
+
393
+ if (verb === 'search') {
394
+ const query = positionals.slice(2).join(' ').trim();
395
+ if (!query) throw new Error('<query> is required');
396
+ const items = searchMemory({ projectRoot, dbPath, query, limit: asLimit(flags.limit, 10) });
397
+ console.log(json ? JSON.stringify(items, null, 2) : renderCompact(items));
398
+ return;
399
+ }
400
+
401
+ if (verb === 'show') {
402
+ const capsuleId = positionals[2];
403
+ if (!capsuleId) throw new Error('<capsule_id> is required');
404
+ const capsule = showMemory({ projectRoot, dbPath, capsuleId });
405
+ if (!capsule) throw new Error(`unknown capsule: ${capsuleId}`);
406
+ console.log(JSON.stringify(capsule, null, 2));
407
+ return;
408
+ }
409
+
410
+ if (verb === 'preflight') {
411
+ const topic = positionals.slice(2).join(' ').trim();
412
+ if (!topic) throw new Error('<lane-or-task> is required');
413
+ const items = preflightMemory({ projectRoot, dbPath, topic, limit: asLimit(flags.limit, 5) });
414
+ console.log(json ? JSON.stringify(items, null, 2) : renderCompact(items));
415
+ return;
416
+ }
417
+
418
+ if (verb === 'context') {
419
+ const positionalQuery = positionals.slice(2).join(' ').trim();
420
+ const query = optionalString(flags, 'query') ?? (positionalQuery || undefined);
421
+ const context = buildMemoryContext({
422
+ projectRoot,
423
+ dbPath,
424
+ query,
425
+ laneRef: optionalString(flags, 'lane'),
426
+ taskRef: optionalString(flags, 'task'),
427
+ orgId: optionalString(flags, 'org-id'),
428
+ workspaceId: optionalString(flags, 'workspace-id'),
429
+ limit: asLimit(flags.limit, 5),
430
+ });
431
+ console.log(json ? JSON.stringify(context, null, 2) : renderContext(context));
432
+ return;
433
+ }
434
+
435
+ if (verb === 'stale') {
436
+ const items = staleMemory({ projectRoot, dbPath, limit: asLimit(flags.limit, 10) });
437
+ console.log(json ? JSON.stringify(items, null, 2) : renderCompact(items));
438
+ return;
439
+ }
440
+
441
+ if (verb === 'rejected') {
442
+ const items = rejectedMemory({ projectRoot, dbPath, limit: asLimit(flags.limit, 10) });
443
+ console.log(json ? JSON.stringify(items, null, 2) : renderRejected(items));
444
+ }
445
+ }
@@ -0,0 +1,108 @@
1
+ import { resolve } from 'node:path';
2
+ import { optionalString } from '../argv';
3
+ import {
4
+ buildReleaseGaReadinessReport,
5
+ buildReleaseReadinessReport,
6
+ listReleaseDistributionChannels,
7
+ type ReleaseDistributionChannel,
8
+ type ReleaseGaReadinessReport,
9
+ type ReleaseReadinessReport,
10
+ } from '../../release/readiness';
11
+
12
+ const RELEASE_VERBS = new Set(['readiness', 'ga-readiness', 'channels']);
13
+
14
+ export function isReleaseCommand(command: string): boolean {
15
+ return command === 'release';
16
+ }
17
+
18
+ function renderReadiness(report: ReleaseReadinessReport): string {
19
+ const lines = [
20
+ 'release_readiness:',
21
+ ` schema: ${report.schema}`,
22
+ ` verdict: ${report.verdict}`,
23
+ ` package: ${report.package.name ?? 'unknown'}@${report.package.version ?? 'unknown'}`,
24
+ ` private: ${String(report.package.private)}`,
25
+ ` codex_recommended_minimum: ${report.client_compatibility.codex_recommended_minimum}`,
26
+ ` core_release_requires_codex: ${String(report.client_compatibility.core_release_requires_codex)}`,
27
+ ` blockers: ${report.blockers.join(', ') || 'none'}`,
28
+ ` warnings: ${report.warnings.join(', ') || 'none'}`,
29
+ ` command_can_publish: ${String(report.authority_boundary.command_can_publish)}`,
30
+ ` command_can_flip_private: ${String(report.authority_boundary.command_can_flip_private)}`,
31
+ ` command_can_create_distribution_channel: ${String(report.authority_boundary.command_can_create_distribution_channel)}`,
32
+ ];
33
+ for (const item of report.checks) {
34
+ lines.push(` - ${item.id}: ${item.status} (${item.summary})`);
35
+ if (item.next_action) lines.push(` next: ${item.next_action}`);
36
+ }
37
+ return `${lines.join('\n')}\n`;
38
+ }
39
+
40
+ function renderGaReadiness(report: ReleaseGaReadinessReport): string {
41
+ const lines = [
42
+ 'release_ga_readiness:',
43
+ ` schema: ${report.schema}`,
44
+ ` verdict: ${report.verdict}`,
45
+ ` channel: ${report.channel ?? 'missing'}`,
46
+ ` tag: ${report.tag ?? 'unknown'}`,
47
+ ` channels: ${report.channels.map((item) => `${item.id}(${item.kind})`).join(', ') || 'none'}`,
48
+ ` package_readiness: ${report.package_readiness}`,
49
+ ` blockers: ${report.blockers.join(', ') || 'none'}`,
50
+ ` warnings: ${report.warnings.join(', ') || 'none'}`,
51
+ ` command_can_publish: ${String(report.authority_boundary.command_can_publish)}`,
52
+ ` command_can_sign: ${String(report.authority_boundary.command_can_sign)}`,
53
+ ` command_can_tag: ${String(report.authority_boundary.command_can_tag)}`,
54
+ ` command_can_create_distribution_channel: ${String(report.authority_boundary.command_can_create_distribution_channel)}`,
55
+ ];
56
+ for (const item of report.checks) {
57
+ lines.push(` - ${item.id}: ${item.status} (${item.summary})`);
58
+ if (item.next_action) lines.push(` next: ${item.next_action}`);
59
+ }
60
+ return `${lines.join('\n')}\n`;
61
+ }
62
+
63
+ function renderChannels(channels: ReleaseDistributionChannel[]): string {
64
+ const lines = ['release_channels:'];
65
+ for (const item of channels) {
66
+ lines.push(` - ${item.id}: ${item.kind}`);
67
+ lines.push(` label: ${item.label}`);
68
+ lines.push(` private_repo_compatible: ${String(item.private_repo_compatible)}`);
69
+ lines.push(` artifact: ${item.artifact}`);
70
+ }
71
+ return `${lines.join('\n')}\n`;
72
+ }
73
+
74
+ export async function dispatchRelease(
75
+ positionals: string[],
76
+ flags: Record<string, string | boolean>,
77
+ ): Promise<void> {
78
+ const verb = positionals[1];
79
+ if (!verb || !RELEASE_VERBS.has(verb)) {
80
+ throw new Error(`release verb must be one of: ${[...RELEASE_VERBS].join(', ')}`);
81
+ }
82
+
83
+ const projectRoot = resolve(optionalString(flags, 'project') ?? process.cwd());
84
+ if (verb === 'channels') {
85
+ const channels = listReleaseDistributionChannels();
86
+ console.log(Boolean(flags.json) ? JSON.stringify({ schema: 'release-channels.v0', channels }, null, 2) : renderChannels(channels));
87
+ return;
88
+ }
89
+
90
+ if (verb === 'readiness') {
91
+ const report = await buildReleaseReadinessReport(projectRoot);
92
+ console.log(Boolean(flags.json) ? JSON.stringify(report, null, 2) : renderReadiness(report));
93
+ if (flags['fail-on-blocked'] && report.verdict === 'blocked') {
94
+ process.exit(1);
95
+ }
96
+ return;
97
+ }
98
+
99
+ const report = await buildReleaseGaReadinessReport(projectRoot, {
100
+ channel: optionalString(flags, 'channel'),
101
+ tagFreeConfirmed: Boolean(flags['tag-free-confirmed']),
102
+ verifyTagRemote: optionalString(flags, 'verify-tag-remote'),
103
+ });
104
+ console.log(Boolean(flags.json) ? JSON.stringify(report, null, 2) : renderGaReadiness(report));
105
+ if (flags['fail-on-blocked'] && report.verdict === 'blocked') {
106
+ process.exit(1);
107
+ }
108
+ }
@@ -0,0 +1,103 @@
1
+ import { resolve } from 'node:path';
2
+ import { optionalString, requireString } from '../argv';
3
+ import {
4
+ loadFixtureSet,
5
+ loadPromotionPolicy,
6
+ loadRubric,
7
+ loadSandboxReceipt,
8
+ } from '../../rubric/loader';
9
+ import { runRubricSandbox } from '../../rubric/runner';
10
+ import { evaluatePromotionReadiness } from '../../rubric/promotion';
11
+ import { RUN_ENVIRONMENTS, type RunEnvironment } from '../../rubric/types';
12
+
13
+ const RUBRIC_VERBS = new Set(['validate', 'sandbox', 'promote-check']);
14
+
15
+ export function isRubricCommand(command: string): boolean {
16
+ return command === 'rubric';
17
+ }
18
+
19
+ function parseRunEnvironment(raw: string | boolean | undefined): RunEnvironment {
20
+ if (raw === undefined) return 'local';
21
+ if (typeof raw !== 'string' || !(RUN_ENVIRONMENTS as readonly string[]).includes(raw)) {
22
+ throw new Error(`--environment must be one of: ${RUN_ENVIRONMENTS.join(', ')}`);
23
+ }
24
+ return raw as RunEnvironment;
25
+ }
26
+
27
+ function renderValidate(rubric: Awaited<ReturnType<typeof loadRubric>>): string {
28
+ return [
29
+ 'rubric_validate:',
30
+ ` rubric_id: ${rubric.rubric_id}`,
31
+ ` rubric_version: ${rubric.rubric_version}`,
32
+ ` kind: ${rubric.kind}`,
33
+ ` status: ${rubric.status}`,
34
+ ].join('\n');
35
+ }
36
+
37
+ function renderSandbox(receipt: Awaited<ReturnType<typeof runRubricSandbox>>): string {
38
+ return [
39
+ 'rubric_sandbox:',
40
+ ` receipt_id: ${receipt.receipt_id}`,
41
+ ` rubric_id: ${receipt.rubric_id}`,
42
+ ` total_fixtures: ${receipt.summary.total_fixtures}`,
43
+ ` known_bad_rejected: ${receipt.summary.known_bad_rejected}`,
44
+ ` known_good_accepted: ${receipt.summary.known_good_accepted}`,
45
+ ` unexpected_outcomes: ${receipt.summary.unexpected_outcomes}`,
46
+ ].join('\n');
47
+ }
48
+
49
+ function renderPromotion(result: Awaited<ReturnType<typeof evaluatePromotionReadiness>>): string {
50
+ return [
51
+ 'rubric_promotion_check:',
52
+ ` rubric_id: ${result.rubric_id}`,
53
+ ` recommendation: ${result.recommendation}`,
54
+ ` missing: ${result.missing.join(', ') || 'none'}`,
55
+ ` can_activate_rubric: ${String(result.authority_boundary.can_activate_rubric)}`,
56
+ ].join('\n');
57
+ }
58
+
59
+ export async function dispatchRubric(
60
+ positionals: string[],
61
+ flags: Record<string, string | boolean>,
62
+ ): Promise<void> {
63
+ const verb = positionals[1];
64
+ if (!verb || !RUBRIC_VERBS.has(verb)) {
65
+ throw new Error(`rubric verb must be one of: ${[...RUBRIC_VERBS].join(', ')}`);
66
+ }
67
+ const json = Boolean(flags.json);
68
+
69
+ if (verb === 'validate') {
70
+ const rubric = await loadRubric(resolve(requireString(flags, 'rubric')));
71
+ console.log(json ? JSON.stringify(rubric, null, 2) : renderValidate(rubric));
72
+ return;
73
+ }
74
+
75
+ if (verb === 'sandbox') {
76
+ const rubric = await loadRubric(resolve(requireString(flags, 'rubric')));
77
+ const fixtureSet = await loadFixtureSet(resolve(requireString(flags, 'fixtures')));
78
+ const receipt = await runRubricSandbox({
79
+ rubric,
80
+ fixtureSet,
81
+ runBy: requireString(flags, 'run-by'),
82
+ runEnvironment: parseRunEnvironment(flags.environment),
83
+ runAt: optionalString(flags, 'at'),
84
+ receiptPath: optionalString(flags, 'receipt') ? resolve(requireString(flags, 'receipt')) : undefined,
85
+ });
86
+ console.log(json ? JSON.stringify(receipt, null, 2) : renderSandbox(receipt));
87
+ return;
88
+ }
89
+
90
+ if (verb === 'promote-check') {
91
+ const rubric = await loadRubric(resolve(requireString(flags, 'rubric')));
92
+ const receipt = await loadSandboxReceipt(resolve(requireString(flags, 'receipt')));
93
+ const policyPath = optionalString(flags, 'policy');
94
+ const policy = policyPath ? await loadPromotionPolicy(resolve(policyPath)) : undefined;
95
+ const result = evaluatePromotionReadiness({
96
+ rubric,
97
+ receipt,
98
+ policy,
99
+ grantRef: optionalString(flags, 'grant-ref'),
100
+ });
101
+ console.log(json ? JSON.stringify(result, null, 2) : renderPromotion(result));
102
+ }
103
+ }