@etiquekit/etq 1.0.12 → 1.0.14
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 +19 -26
- package/LICENSE +2 -3
- package/NOTICE +6 -4
- package/QuickStart.md +35 -47
- package/README.md +54 -54
- package/bin/etiquette +1 -5
- package/bin/etiquette-core +1 -5
- package/docs/ARCHITECTURE.md +5 -5
- package/docs/CODEX_CLIENT_COMPATIBILITY.md +1 -1
- package/docs/CONCEPTS.md +7 -23
- package/docs/CONCEPT_STATUS.md +66 -0
- package/docs/CORE_PROFILE.md +3 -5
- package/docs/LANE_PROVISIONING.md +7 -2
- package/docs/README.md +4 -4
- package/docs/RELEASE_SURFACE_AUDIT.md +8 -7
- package/docs/SEAT_DISCIPLINE.md +55 -91
- package/docs/SEAT_PROVISIONING.md +19 -22
- package/docs/TEAM_HANDOFF.md +21 -21
- package/docs/WORKTREE_QOL.md +7 -6
- package/docs/contracts/ledger-entry/README.md +11 -11
- package/docs/contracts/ledger-entry/ledger-entry.v0.2.md +3 -3
- package/docs/contracts/ledger-entry/ledger-entry.v0.md +8 -8
- package/package.json +9 -9
- package/packages/control/src/authority/lease.ts +261 -0
- package/packages/control/src/authority/node-delegation.ts +257 -0
- package/packages/control/src/authority/rig-conductor.ts +632 -0
- package/packages/control/src/cli/argv.ts +155 -0
- package/packages/control/src/cli/commands/console.ts +200 -0
- package/packages/control/src/cli/commands/dispatch-core.ts +89 -0
- package/packages/control/src/cli/commands/dispatch.ts +279 -0
- package/packages/control/src/cli/commands/harness.ts +89 -0
- package/packages/control/src/cli/commands/hook.ts +50 -0
- package/packages/control/src/cli/commands/ledger.ts +91 -0
- package/packages/control/src/cli/commands/local-workflow.ts +4690 -0
- package/packages/control/src/cli/commands/memory.ts +445 -0
- package/packages/control/src/cli/commands/release.ts +108 -0
- package/packages/control/src/cli/commands/rubric.ts +103 -0
- package/packages/control/src/cli/commands/seat.ts +179 -0
- package/packages/control/src/cli/commands/session.ts +127 -0
- package/packages/control/src/cli/commands/supervision.ts +246 -0
- package/packages/control/src/cli/commands/sync.ts +119 -0
- package/packages/control/src/cli/commands/workflow.ts +86 -0
- package/packages/control/src/cli/commands/workspace.ts +50 -0
- package/packages/control/src/cli/core-usage.ts +34 -0
- package/packages/control/src/cli/prompt.ts +67 -0
- package/packages/control/src/cli/supervision-deps.ts +44 -0
- package/packages/control/src/cli/usage.ts +241 -0
- package/packages/control/src/cli.ts +207 -0
- package/packages/control/src/core-cli.ts +50 -0
- package/packages/control/src/dispatch/decision.ts +202 -0
- package/packages/control/src/dispatch/projection.ts +293 -0
- package/packages/control/src/dispatch/record.ts +153 -0
- package/packages/control/src/engagement/project.ts +170 -0
- package/packages/control/src/fs.ts +19 -0
- package/packages/control/src/harness/pruning.ts +406 -0
- package/packages/control/src/hooks/dispatcher.ts +117 -0
- package/packages/control/src/hooks/outbox.ts +86 -0
- package/packages/control/src/hooks/sanitize.ts +6 -0
- package/packages/control/src/hooks/types.ts +34 -0
- package/packages/control/src/index.ts +384 -0
- package/packages/control/src/ledger/entry.ts +303 -0
- package/packages/control/src/ledger/indexer.ts +542 -0
- package/packages/control/src/memory/context.ts +149 -0
- package/packages/control/src/memory/drain-import.ts +207 -0
- package/packages/control/src/memory/indexer.ts +284 -0
- package/packages/control/src/memory/query.ts +75 -0
- package/packages/control/src/memory/sanitize.ts +50 -0
- package/packages/control/src/memory/sharded-drain-import.ts +212 -0
- package/packages/control/src/memory/status.ts +211 -0
- package/packages/control/src/memory/store-lifecycle.ts +509 -0
- package/packages/control/src/memory/store.ts +284 -0
- package/packages/control/src/memory/types.ts +146 -0
- package/packages/control/src/parity/surfaces.ts +748 -0
- package/packages/control/src/project.ts +141 -0
- package/packages/control/src/projection/local-ledger-view.ts +373 -0
- package/packages/control/src/projection/return-enforcement.ts +48 -0
- package/packages/control/src/projection/timeline-preview.ts +539 -0
- package/packages/control/src/projection/timeline.ts +708 -0
- package/packages/control/src/release/readiness.ts +842 -0
- package/packages/control/src/rubric/loader.ts +326 -0
- package/packages/control/src/rubric/promotion.ts +54 -0
- package/packages/control/src/rubric/runner.ts +159 -0
- package/packages/control/src/rubric/types.ts +158 -0
- package/packages/control/src/seat/owner-card.ts +388 -0
- package/packages/control/src/seat/readiness.ts +834 -0
- package/packages/control/src/session/runbook.ts +431 -0
- package/packages/control/src/shared/sanitize.ts +49 -0
- package/packages/control/src/supervision/action-classes.ts +192 -0
- package/packages/control/src/supervision/command-apply.ts +378 -0
- package/packages/control/src/supervision/errors.ts +14 -0
- package/packages/control/src/supervision/event-replay.ts +155 -0
- package/packages/control/src/supervision/events.ts +109 -0
- package/packages/control/src/supervision/index.ts +16 -0
- package/packages/control/src/supervision/manifest.ts +127 -0
- package/packages/control/src/supervision/paths.ts +49 -0
- package/packages/control/src/supervision/projection-adapter.ts +274 -0
- package/packages/control/src/supervision/projection.ts +75 -0
- package/packages/control/src/supervision/rebuild.ts +99 -0
- package/packages/control/src/supervision/session-open.ts +131 -0
- package/packages/control/src/supervision/session-read.ts +99 -0
- package/packages/control/src/supervision/sqlite-impl.ts +71 -0
- package/packages/control/src/supervision/sqlite.ts +121 -0
- package/packages/control/src/supervision/store-rows.ts +371 -0
- package/packages/control/src/supervision/turn-close.ts +154 -0
- package/packages/control/src/supervision/turn-open.ts +284 -0
- package/packages/control/src/sync/event-log-merge-driver.ts +263 -0
- package/packages/control/src/sync/join-plan.ts +375 -0
- package/packages/control/src/sync/outbox.ts +492 -0
- package/packages/control/src/workflow/evaluator.ts +140 -0
- package/packages/control/src/workflow/loader.ts +200 -0
- package/packages/control/src/workflow/types.ts +90 -0
- package/packages/control/src/workspace/authority.ts +499 -0
- package/packages/protocol/src/guards.ts +119 -0
- package/packages/protocol/src/huddle-board.ts +198 -0
- package/packages/protocol/src/huddle.ts +295 -0
- package/packages/protocol/src/incident.ts +251 -0
- package/packages/protocol/src/index.ts +8 -0
- package/packages/protocol/src/interfaces.ts +107 -0
- package/packages/protocol/src/packet-profile.ts +195 -0
- package/packages/protocol/src/state.ts +81 -0
- package/packages/protocol/src/types.ts +434 -0
- package/release/lineage.v0.json +15 -0
- package/scripts/release-candidate-verify.sh +175 -0
- package/scripts/release-checksum.sh +25 -0
- package/scripts/release-pack-canary.sh +97 -0
- package/scripts/release-scan.sh +249 -0
- package/scripts/release-sign.sh +34 -0
- package/templates/etiquette-vanilla-v0/README.md +2 -3
- package/templates/etiquette-vanilla-v0/source/control-seat/README.md +0 -6
- package/templates/etiquette-vanilla-v0/source/control-seat/bin/seat-doctor +0 -5
- package/templates/etiquette-vanilla-v0/validate-vanilla.sh +0 -5
- package/templates/seat-packs-v0/README.md +3 -2
- package/templates/seat-packs-v0/source/claude-code-seat/README.md +3 -29
- package/templates/seat-packs-v0/source/codex-seat/README.md +3 -17
- package/templates/seat-packs-v0/source/gemini-seat/README.md +3 -23
- 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/AGENT_SPAWNING.md +0 -78
- package/docs/INTEGRATION_UI.md +0 -215
- package/docs/OPERATOR_PLAYBOOK.md +0 -77
- package/docs/TAG_ROUTE.md +0 -95
- package/lib/etiquette-core.js +0 -315
- package/lib/etiquette.js +0 -1160
- package/templates/DISPATCH_PREMISES.md +0 -39
- package/templates/etiquette-vanilla-v0/source/control-seat/bin/access-assurance-check +0 -55
- package/templates/etiquette-vanilla-v0/source/control-seat/docs/work/access/ACCESS-ASSURANCE.md +0 -39
- package/templates/etiquette-vanilla-v0/source/control-seat/docs/work/access/workspace-secure-profile.v0.json +0 -53
- package/templates/hosted-receiver/README.md +0 -41
- 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
|
+
}
|