@etiquekit/etq 1.0.11 → 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,179 @@
1
+ import { resolve } from 'node:path';
2
+ import { optionalString, parseInt32, parseList, requireString } from '../argv';
3
+ import { buildAgentOwnerCard, type AgentOwnerCard } from '../../seat/owner-card';
4
+ import {
5
+ CAPABILITY_SOURCES,
6
+ MODEL_TIERS,
7
+ OPERATIONAL_STATUSES,
8
+ SEAT_TYPES,
9
+ evaluateSeatReadiness,
10
+ heartbeatSeat,
11
+ readSeatReadiness,
12
+ reportSeatReadiness,
13
+ type CapabilitySource,
14
+ type ModelTier,
15
+ type OperationalStatus,
16
+ type SeatType,
17
+ } from '../../seat/readiness';
18
+
19
+ const SEAT_VERBS = new Set(['readiness', 'heartbeat', 'status', 'owner-card']);
20
+
21
+ export function isSeatCommand(command: string): boolean {
22
+ return command === 'seat';
23
+ }
24
+
25
+ function parseEnum<T extends readonly string[]>(
26
+ values: T,
27
+ raw: string | boolean | undefined,
28
+ key: string,
29
+ fallback?: T[number],
30
+ ): T[number] {
31
+ if (raw === undefined && fallback !== undefined) return fallback;
32
+ if (typeof raw !== 'string' || !(values as readonly string[]).includes(raw)) {
33
+ throw new Error(`--${key} must be one of: ${values.join(', ')}`);
34
+ }
35
+ return raw as T[number];
36
+ }
37
+
38
+ function parseBool(raw: string | boolean | undefined, fallback: boolean): boolean {
39
+ if (raw === undefined) return fallback;
40
+ if (raw === true) return true;
41
+ if (raw === 'true' || raw === '1' || raw === 'yes') return true;
42
+ if (raw === 'false' || raw === '0' || raw === 'no') return false;
43
+ throw new Error('boolean flags must be true/false, 1/0, yes/no, or bare flag');
44
+ }
45
+
46
+ function parseOptionalInt(raw: string | boolean | undefined, key: string): number | undefined {
47
+ if (raw === undefined) return undefined;
48
+ return parseInt32(raw, key);
49
+ }
50
+
51
+ function renderStatus(status: ReturnType<typeof evaluateSeatReadiness>): string {
52
+ return [
53
+ `seat: ${status.seat}`,
54
+ `operational_status: ${status.operational_status}`,
55
+ `execution_surface: ${status.execution_surface}`,
56
+ `model_tier: ${status.model_tier}`,
57
+ `effective_model_tier: ${status.effective_model_tier}`,
58
+ `capability_source: ${status.capability_source}`,
59
+ `capability_trusted: ${String(status.capability_trusted)}`,
60
+ `active_workers: ${status.active_workers}`,
61
+ `max_recommended_workers: ${status.max_recommended_workers}`,
62
+ `supports_parallel: ${String(status.supports_parallel)}`,
63
+ `supported_strategies: ${status.supported_strategies.join(', ') || 'none'}`,
64
+ `unsupported_strategies: ${status.unsupported_strategies.join(', ') || 'none'}`,
65
+ `constraints: ${status.constraints.join(', ') || 'none'}`,
66
+ `last_heartbeat_at: ${status.last_heartbeat_at}`,
67
+ `stale_after: ${status.stale_after}`,
68
+ `is_stale: ${String(status.is_stale)}`,
69
+ `routing_recommendation: ${status.routing_recommendation}`,
70
+ ].join('\n');
71
+ }
72
+
73
+ function renderOwnerCard(card: AgentOwnerCard): string {
74
+ return [
75
+ 'agent_owner_card:',
76
+ ` seat_id: ${card.seat_id}`,
77
+ ` job: ${card.job}`,
78
+ ` owner_principal: ${card.accountability.owner_principal}`,
79
+ ` maintainer: ${card.accountability.maintainer}`,
80
+ ` current_occupant: ${card.current_occupant?.model_id ?? card.current_occupant?.execution_surface ?? 'none'}`,
81
+ ` occupant_readiness_stale: ${String(card.current_occupant?.is_stale ?? true)}`,
82
+ ` review_stale: ${String(card.review.is_stale)}`,
83
+ ` last_reviewed_at: ${card.review.last_reviewed_at}`,
84
+ ` authority_source_ref: ${card.authority.source_ref}`,
85
+ ` allowed_work: ${card.authority.allowed_work.join(', ')}`,
86
+ ` forbidden_work: ${card.authority.forbidden_work.join(', ')}`,
87
+ ` failure_modes: ${card.failure_modes.map((mode) => mode.id).join(', ')}`,
88
+ ` warnings: ${card.warnings.join(', ') || 'none'}`,
89
+ ` read_model_only: ${String(card.authority_boundary.read_model_only)}`,
90
+ ].join('\n');
91
+ }
92
+
93
+ export async function dispatchSeat(
94
+ positionals: string[],
95
+ flags: Record<string, string | boolean>,
96
+ ): Promise<void> {
97
+ const verb = positionals[1];
98
+ if (!verb || !SEAT_VERBS.has(verb)) {
99
+ throw new Error(`seat verb must be one of: ${[...SEAT_VERBS].join(', ')}`);
100
+ }
101
+
102
+ const projectRoot = resolve(optionalString(flags, 'project') ?? process.cwd());
103
+ const statePath = optionalString(flags, 'state');
104
+ const json = Boolean(flags.json);
105
+ const at = optionalString(flags, 'at');
106
+
107
+ if (verb === 'readiness') {
108
+ const record = await reportSeatReadiness({
109
+ projectRoot,
110
+ statePath,
111
+ seat: requireString(flags, 'seat'),
112
+ seatType: parseEnum(SEAT_TYPES, flags['seat-type'], 'seat-type', 'control_seat') as SeatType,
113
+ executionSurface: optionalString(flags, 'execution-surface') ?? 'generic',
114
+ declaredBy: parseEnum(['operator_principal', 'config'] as const, flags['declared-by'], 'declared-by', 'operator_principal'),
115
+ capabilitySource: parseEnum(CAPABILITY_SOURCES, flags['capability-source'], 'capability-source', 'config') as CapabilitySource,
116
+ operationalStatus: parseEnum(OPERATIONAL_STATUSES, flags['operational-status'], 'operational-status', 'operational') as OperationalStatus,
117
+ modelTier: parseEnum(MODEL_TIERS, flags['model-tier'], 'model-tier', 'local_unknown') as ModelTier,
118
+ modelId: optionalString(flags, 'model-id'),
119
+ activeWorkers: parseOptionalInt(flags['active-workers'], 'active-workers') ?? 0,
120
+ maxRecommendedWorkers: parseOptionalInt(flags['max-recommended-workers'], 'max-recommended-workers') ?? 1,
121
+ supportsParallel: parseBool(flags['supports-parallel'], false),
122
+ supportedStrategies: parseList(flags['supported-strategies']).length > 0
123
+ ? parseList(flags['supported-strategies'])
124
+ : ['solo'],
125
+ unsupportedStrategies: parseList(flags['unsupported-strategies']),
126
+ constraints: parseList(flags.constraints),
127
+ at,
128
+ });
129
+ console.log(json ? JSON.stringify(record, null, 2) : [
130
+ 'seat_readiness:',
131
+ ` seat: ${record.seat}`,
132
+ ` operational_status: ${record.operational_status}`,
133
+ ` model_tier: ${record.model_tier}`,
134
+ ` capability_trusted: ${String(record.capability_trusted)}`,
135
+ ` stale_after: ${record.stale_after}`,
136
+ ].join('\n'));
137
+ return;
138
+ }
139
+
140
+ if (verb === 'heartbeat') {
141
+ const record = await heartbeatSeat({
142
+ projectRoot,
143
+ statePath,
144
+ seat: optionalString(flags, 'seat'),
145
+ operationalStatus: flags['operational-status'] === undefined
146
+ ? undefined
147
+ : parseEnum(OPERATIONAL_STATUSES, flags['operational-status'], 'operational-status') as OperationalStatus,
148
+ activeWorkers: parseOptionalInt(flags['active-workers'], 'active-workers'),
149
+ constraints: flags.constraints === undefined ? undefined : parseList(flags.constraints),
150
+ at,
151
+ });
152
+ console.log(json ? JSON.stringify(record, null, 2) : [
153
+ 'seat_heartbeat:',
154
+ ` seat: ${record.seat}`,
155
+ ` operational_status: ${record.operational_status}`,
156
+ ` active_workers: ${record.active_workers}`,
157
+ ` last_heartbeat_at: ${record.last_heartbeat_at}`,
158
+ ` stale_after: ${record.stale_after}`,
159
+ ].join('\n'));
160
+ return;
161
+ }
162
+
163
+ if (verb === 'status') {
164
+ const record = await readSeatReadiness(projectRoot, statePath);
165
+ const status = evaluateSeatReadiness(record, at);
166
+ console.log(json ? JSON.stringify(status, null, 2) : renderStatus(status));
167
+ return;
168
+ }
169
+
170
+ if (verb === 'owner-card') {
171
+ const card = await buildAgentOwnerCard({
172
+ projectRoot,
173
+ statePath,
174
+ manifestPath: resolve(requireString(flags, 'manifest')),
175
+ at,
176
+ });
177
+ console.log(json ? JSON.stringify(card, null, 2) : renderOwnerCard(card));
178
+ }
179
+ }
@@ -0,0 +1,127 @@
1
+ import { resolve } from 'node:path';
2
+ import { optionalString, requireString } from '../argv';
3
+ import {
4
+ appendSessionRunbookNote,
5
+ createSessionRunbook,
6
+ migrateSessionProjectBindings,
7
+ SESSION_NOTE_SECTIONS,
8
+ type MigrateSessionProjectBindingsResult,
9
+ type SessionRunbookNoteResult,
10
+ type SessionRunbookResult,
11
+ type SessionNoteSection,
12
+ } from '../../session/runbook';
13
+
14
+ const SESSION_VERBS = new Set(['create', 'note', 'migrate-project']);
15
+
16
+ export function isSessionCommand(command: string): boolean {
17
+ return command === 'session';
18
+ }
19
+
20
+ function parseCreationMode(raw: string | boolean | undefined): 'explicit' | 'dynamic' {
21
+ if (raw === undefined) return 'explicit';
22
+ if (raw === 'explicit' || raw === 'dynamic') return raw;
23
+ throw new Error('--creation-mode must be explicit or dynamic');
24
+ }
25
+
26
+ function parseSection(raw: string | boolean | undefined): SessionNoteSection {
27
+ const value = raw === undefined ? 'note' : raw;
28
+ if (typeof value === 'string' && (SESSION_NOTE_SECTIONS as readonly string[]).includes(value)) {
29
+ return value as SessionNoteSection;
30
+ }
31
+ throw new Error(`--section must be one of: ${SESSION_NOTE_SECTIONS.join(', ')}`);
32
+ }
33
+
34
+ function renderCreate(result: SessionRunbookResult): string {
35
+ return [
36
+ 'session_runbook:',
37
+ ` session_id: ${result.manifest.session_id}`,
38
+ ` project_id: ${result.manifest.project_id}`,
39
+ ` task_ref: ${result.manifest.task_ref}`,
40
+ ` seat: ${result.manifest.seat}`,
41
+ ` creation_mode: ${result.manifest.creation_mode}`,
42
+ ` runbook_ref: ${result.manifest.runbook_ref}`,
43
+ ` manifest_ref: ${result.manifest.manifest_ref}`,
44
+ ` global_governance_ledger: ${result.manifest.ledger_boundary.global_governance_ledger}`,
45
+ ` can_authorize: ${String(result.manifest.authority_boundary.can_authorize)}`,
46
+ ` can_write_global_ledger: ${String(result.manifest.authority_boundary.can_write_global_ledger)}`,
47
+ ].join('\n');
48
+ }
49
+
50
+ function renderNote(result: SessionRunbookNoteResult): string {
51
+ return [
52
+ 'session_runbook_note:',
53
+ ` session_id: ${result.manifest.session_id}`,
54
+ ` project_id: ${result.manifest.project_id}`,
55
+ ` section: ${result.section}`,
56
+ ` appended_at: ${result.appended_at}`,
57
+ ` note_count: ${result.manifest.note_count}`,
58
+ ` runbook_ref: ${result.manifest.runbook_ref}`,
59
+ ` can_authorize: ${String(result.manifest.authority_boundary.can_authorize)}`,
60
+ ` can_write_global_ledger: ${String(result.manifest.authority_boundary.can_write_global_ledger)}`,
61
+ ].join('\n');
62
+ }
63
+
64
+ function renderMigrate(result: MigrateSessionProjectBindingsResult): string {
65
+ return [
66
+ 'session_project_binding_migration:',
67
+ ` project_id: ${result.project_id}`,
68
+ ` dry_run: ${String(result.dry_run)}`,
69
+ ` migrated: ${result.migrated.length}`,
70
+ ` unchanged: ${result.unchanged.length}`,
71
+ ` can_authorize: ${String(result.authority_boundary.can_authorize)}`,
72
+ result.migrated.length > 0 ? ` migrated_sessions: ${result.migrated.join(', ')}` : null,
73
+ ].filter((line): line is string => line !== null).join('\n');
74
+ }
75
+
76
+ export async function dispatchSession(
77
+ positionals: string[],
78
+ flags: Record<string, string | boolean>,
79
+ ): Promise<void> {
80
+ const verb = positionals[1];
81
+ if (!verb || !SESSION_VERBS.has(verb)) {
82
+ throw new Error(`session verb must be one of: ${[...SESSION_VERBS].join(', ')}`);
83
+ }
84
+
85
+ const projectRoot = resolve(optionalString(flags, 'project') ?? process.cwd());
86
+ const json = Boolean(flags.json);
87
+ const at = optionalString(flags, 'at');
88
+
89
+ if (verb === 'create') {
90
+ const result = await createSessionRunbook({
91
+ projectRoot,
92
+ seat: requireString(flags, 'seat'),
93
+ taskRef: requireString(flags, 'task'),
94
+ sessionId: optionalString(flags, 'session-id'),
95
+ operatorPrincipal: optionalString(flags, 'operator'),
96
+ ownerPrincipal: optionalString(flags, 'owner'),
97
+ targetRef: optionalString(flags, 'target-ref'),
98
+ creationMode: parseCreationMode(flags['creation-mode']),
99
+ at,
100
+ });
101
+ console.log(json ? JSON.stringify(result.manifest, null, 2) : renderCreate(result));
102
+ return;
103
+ }
104
+
105
+ if (verb === 'migrate-project') {
106
+ const result = await migrateSessionProjectBindings({
107
+ projectRoot,
108
+ dryRun: Boolean(flags['dry-run']),
109
+ at,
110
+ });
111
+ console.log(json ? JSON.stringify(result, null, 2) : renderMigrate(result));
112
+ return;
113
+ }
114
+
115
+ const result = await appendSessionRunbookNote({
116
+ projectRoot,
117
+ sessionId: requireString(flags, 'session'),
118
+ section: parseSection(flags.section),
119
+ message: requireString(flags, 'message'),
120
+ at,
121
+ });
122
+ console.log(json ? JSON.stringify({
123
+ ...result.manifest,
124
+ appended_at: result.appended_at,
125
+ section: result.section,
126
+ }, null, 2) : renderNote(result));
127
+ }
@@ -0,0 +1,246 @@
1
+ import { resolve } from 'node:path';
2
+ import {
3
+ ActionClassViolation,
4
+ closeSupervisionTurn,
5
+ issueSupervisionCommand,
6
+ loadSeatManifest,
7
+ openSupervisionSession,
8
+ openSupervisionTurn,
9
+ projectSupervisionSession,
10
+ readSupervisionSession,
11
+ rebuildSupervisionSession,
12
+ SeatManifestError,
13
+ SupervisionRuntimeError,
14
+ } from '../../supervision/index';
15
+ import { supervisionRuntimePaths } from '../../supervision/paths';
16
+ import {
17
+ optionalString,
18
+ parseActionClass,
19
+ parseAllowedCommands,
20
+ parseInt32,
21
+ parseList,
22
+ parseProjectionPolicy,
23
+ parseSupervisionCommandName,
24
+ parseTurnResult,
25
+ requirePositional,
26
+ requireString,
27
+ } from '../argv';
28
+ import { makeSupervisionDeps, resolveProjectionDir } from '../supervision-deps';
29
+ import { usage } from '../usage';
30
+
31
+ /**
32
+ * Supervision CLI dispatcher. Extracted from the former monolithic `cli.ts`.
33
+ * Behavior is byte-preserving: same command names, same positional/flag
34
+ * shapes, same stdout/stderr, same exit codes (2/3/4/5 for rejections).
35
+ */
36
+ export const SUPERVISION_COMMANDS_CLI: ReadonlySet<string> = new Set([
37
+ 'session-open',
38
+ 'session-status',
39
+ 'turn-open',
40
+ 'turn-close',
41
+ 'session-command',
42
+ 'session-project',
43
+ 'session-rebuild',
44
+ 'seat-validate',
45
+ ]);
46
+
47
+ export async function dispatchSupervision(
48
+ command: string,
49
+ positionals: string[],
50
+ flags: Record<string, string | boolean>,
51
+ ): Promise<void> {
52
+ try {
53
+ switch (command) {
54
+ case 'seat-validate': {
55
+ const override = optionalString(flags, 'manifest');
56
+ const manifest = await loadSeatManifest(override);
57
+ if (flags.json === true) {
58
+ console.log(JSON.stringify(manifest, null, 2));
59
+ } else {
60
+ console.log(`seat ${manifest.seatId} (${manifest.runtimeKind}) — manifest OK`);
61
+ console.log(` allowedActionClasses: ${manifest.allowedActionClasses.join(', ')}`);
62
+ console.log(` canWriteProductRepo: ${manifest.canWriteProductRepo}`);
63
+ console.log(` canRunBuilds: ${manifest.canRunBuilds}`);
64
+ console.log(` canAcceptMobileSteering: ${manifest.canAcceptMobileSteering}`);
65
+ }
66
+ return;
67
+ }
68
+
69
+ case 'session-open': {
70
+ const sessionId = requirePositional(positionals, 1, 'session-id');
71
+ const campaignRoot = resolve(requireString(flags, 'campaign-root'));
72
+ const paths = supervisionRuntimePaths(campaignRoot);
73
+ const projectionDir = resolveProjectionDir(flags, campaignRoot, sessionId);
74
+ const deps = makeSupervisionDeps(projectionDir, paths.eventsPath);
75
+ const session = await openSupervisionSession(
76
+ {
77
+ sessionId,
78
+ campaignRoot,
79
+ topic: optionalString(flags, 'topic') ?? sessionId,
80
+ objective: requireString(flags, 'objective'),
81
+ leadSeat: requireString(flags, 'lead'),
82
+ responderSeat: requireString(flags, 'responder'),
83
+ escalationOwner: requireString(flags, 'escalation-owner'),
84
+ timeCapMinutes: parseInt32(flags['time-cap'], 'time-cap'),
85
+ turnCap: parseInt32(flags['turn-cap'], 'turn-cap'),
86
+ sourceRefs: parseList(flags['source-ref']),
87
+ seatManifestOverride: optionalString(flags, 'seat-manifest'),
88
+ },
89
+ deps,
90
+ );
91
+ console.log(`opened session ${session.sessionId} (status=${session.status})`);
92
+ return;
93
+ }
94
+
95
+ case 'session-status': {
96
+ const sessionId = requirePositional(positionals, 1, 'session-id');
97
+ const campaignRoot = resolve(requireString(flags, 'campaign-root'));
98
+ const paths = supervisionRuntimePaths(campaignRoot);
99
+ const projectionDir = resolveProjectionDir(flags, campaignRoot, sessionId);
100
+ const deps = makeSupervisionDeps(projectionDir, paths.eventsPath);
101
+ const projection = await readSupervisionSession(
102
+ { sessionId, campaignRoot },
103
+ deps,
104
+ );
105
+ if (flags.json === true) {
106
+ console.log(JSON.stringify(projection, null, 2));
107
+ } else {
108
+ const s = projection.sessionSnapshot;
109
+ console.log(`session ${s.sessionId} — ${s.status}`);
110
+ console.log(` topic: ${s.topic}`);
111
+ console.log(` lead/responder/escalation: ${s.leadSeat} / ${s.responderSeat} / ${s.escalationOwner}`);
112
+ console.log(` current_turn: ${projection.currentTurn?.turnId ?? '(none)'}`);
113
+ console.log(` pending_commands: ${projection.pendingCommands.length}`);
114
+ }
115
+ return;
116
+ }
117
+
118
+ case 'turn-open': {
119
+ const sessionId = requirePositional(positionals, 1, 'session-id');
120
+ const campaignRoot = resolve(requireString(flags, 'campaign-root'));
121
+ const paths = supervisionRuntimePaths(campaignRoot);
122
+ const projectionDir = resolveProjectionDir(flags, campaignRoot, sessionId);
123
+ const deps = makeSupervisionDeps(projectionDir, paths.eventsPath);
124
+ const turn = await openSupervisionTurn(
125
+ {
126
+ sessionId,
127
+ campaignRoot,
128
+ ownerSeat: requireString(flags, 'owner'),
129
+ allowedActionClass: parseActionClass(flags['action-class'], 'action-class'),
130
+ maxDurationMinutes: parseInt32(flags['max-duration'], 'max-duration'),
131
+ allowedCommands: parseAllowedCommands(flags['allowed-command'], 'allowed-command'),
132
+ repoRoots: parseList(flags['repo-root']),
133
+ ownedPaths: parseList(flags['owned-path']),
134
+ forbiddenPaths: parseList(flags['forbidden-path']),
135
+ verifyCommands: parseList(flags['verify-command']),
136
+ requiresHumanApproval: flags['requires-human-approval'] === true,
137
+ projectionPolicy: parseProjectionPolicy(flags['projection-policy']),
138
+ seatManifestOverride: optionalString(flags, 'seat-manifest'),
139
+ },
140
+ deps,
141
+ );
142
+ console.log(`opened turn ${turn.turnId} (seq=${turn.sequenceNo}, class=${turn.allowedActionClass})`);
143
+ return;
144
+ }
145
+
146
+ case 'turn-close': {
147
+ const sessionId = requirePositional(positionals, 1, 'session-id');
148
+ const campaignRoot = resolve(requireString(flags, 'campaign-root'));
149
+ const paths = supervisionRuntimePaths(campaignRoot);
150
+ const projectionDir = resolveProjectionDir(flags, campaignRoot, sessionId);
151
+ const deps = makeSupervisionDeps(projectionDir, paths.eventsPath);
152
+ const closed = await closeSupervisionTurn(
153
+ {
154
+ sessionId,
155
+ campaignRoot,
156
+ turnId: requireString(flags, 'turn'),
157
+ result: parseTurnResult(flags['result']),
158
+ summary: requireString(flags, 'summary'),
159
+ evidenceRefs: parseList(flags['evidence-ref']),
160
+ },
161
+ deps,
162
+ );
163
+ console.log(`closed turn ${closed.turnId} (result=${closed.result})`);
164
+ return;
165
+ }
166
+
167
+ case 'session-command': {
168
+ const sessionId = requirePositional(positionals, 1, 'session-id');
169
+ const commandName = parseSupervisionCommandName(positionals[2]);
170
+ const campaignRoot = resolve(requireString(flags, 'campaign-root'));
171
+ const paths = supervisionRuntimePaths(campaignRoot);
172
+ const projectionDir = resolveProjectionDir(flags, campaignRoot, sessionId);
173
+ const deps = makeSupervisionDeps(projectionDir, paths.eventsPath);
174
+ const record = await issueSupervisionCommand(
175
+ {
176
+ sessionId,
177
+ campaignRoot,
178
+ command: commandName,
179
+ actor: optionalString(flags, 'actor') ?? 'operator',
180
+ payloadJson: optionalString(flags, 'payload') ?? '{}',
181
+ seatManifestOverride: optionalString(flags, 'seat-manifest'),
182
+ },
183
+ deps,
184
+ );
185
+ if (record.status === 'rejected') {
186
+ console.error(`command ${commandName} rejected: ${record.rejectionReason}`);
187
+ process.exit(3);
188
+ }
189
+ console.log(`command ${commandName} applied (id=${record.commandId})`);
190
+ return;
191
+ }
192
+
193
+ case 'session-project': {
194
+ const sessionId = requirePositional(positionals, 1, 'session-id');
195
+ const campaignRoot = resolve(requireString(flags, 'campaign-root'));
196
+ const paths = supervisionRuntimePaths(campaignRoot);
197
+ const projectionDir = resolveProjectionDir(flags, campaignRoot, sessionId);
198
+ const deps = makeSupervisionDeps(projectionDir, paths.eventsPath);
199
+ const projection = await projectSupervisionSession(
200
+ { sessionId, campaignRoot },
201
+ deps,
202
+ );
203
+ console.log(`projected session ${projection.sessionId} at ${projection.renderedAt}`);
204
+ return;
205
+ }
206
+
207
+ case 'session-rebuild': {
208
+ const sessionId = requirePositional(positionals, 1, 'session-id');
209
+ const campaignRoot = resolve(requireString(flags, 'campaign-root'));
210
+ const paths = supervisionRuntimePaths(campaignRoot);
211
+ const projectionDir = resolveProjectionDir(flags, campaignRoot, sessionId);
212
+ const deps = makeSupervisionDeps(projectionDir, paths.eventsPath);
213
+ const projection = await rebuildSupervisionSession(
214
+ { sessionId, campaignRoot },
215
+ deps,
216
+ );
217
+ console.log(
218
+ `rebuilt session ${projection.sessionId} (current_status=${projection.sessionSnapshot.status})`,
219
+ );
220
+ return;
221
+ }
222
+
223
+ default: {
224
+ console.error(`unknown supervision command: ${command}`);
225
+ console.error(usage());
226
+ process.exit(1);
227
+ }
228
+ }
229
+ } catch (err) {
230
+ if (err instanceof SeatManifestError) {
231
+ console.error(`seat manifest invalid (${err.path}):`);
232
+ for (const issue of err.issues) console.error(` - ${issue}`);
233
+ process.exit(2);
234
+ }
235
+ if (err instanceof ActionClassViolation) {
236
+ console.error(`action class violation (${err.actionClass}):`);
237
+ for (const issue of err.issues) console.error(` - ${issue}`);
238
+ process.exit(4);
239
+ }
240
+ if (err instanceof SupervisionRuntimeError) {
241
+ console.error(`supervision error [${err.code}]: ${err.message}`);
242
+ process.exit(5);
243
+ }
244
+ throw err;
245
+ }
246
+ }
@@ -0,0 +1,119 @@
1
+ import { resolve } from 'node:path';
2
+ import { optionalString, requireString } from '../argv';
3
+ import {
4
+ DEFAULT_EVENT_LOG_MERGE_DRIVER,
5
+ inspectEventLogMergeDriver,
6
+ installEventLogMergeDriver,
7
+ mergeAppendOnlyEventLogs,
8
+ type EventLogMergeDriverInstallResult,
9
+ type EventLogMergeDriverStatus,
10
+ } from '../../sync/event-log-merge-driver';
11
+ import { buildSyncJoinPlan, renderSyncJoinPlan } from '../../sync/join-plan';
12
+ import { stageSyncOutbox, type SyncStageResult } from '../../sync/outbox';
13
+
14
+ const SYNC_VERBS = new Set(['stage', 'join-plan', 'install-git-merge-driver', 'git-driver-status', 'event-log-merge-driver']);
15
+
16
+ export function isSyncCommand(command: string): boolean {
17
+ return command === 'sync';
18
+ }
19
+
20
+ function renderStage(result: SyncStageResult): string {
21
+ return [
22
+ 'sync_stage:',
23
+ ` staged: ${result.staged}`,
24
+ ` rejected: ${result.rejected.length}`,
25
+ ` local_only: ${result.local_only.length}`,
26
+ ` outbox_ref: ${result.outbox_ref}`,
27
+ ` boundary: local outbox only; no network, no execution, no authorization, no grant attachment`,
28
+ ...result.envelopes.map((envelope) => (
29
+ ` - ${envelope.kind} ${envelope.artifact_ref} -> ${envelope.outbox_ref} (${envelope.target_scope})`
30
+ )),
31
+ ...result.rejected.map((issue) => (
32
+ ` rejected: ${issue.kind} ${issue.artifact_ref} - ${issue.reason}`
33
+ )),
34
+ ].join('\n');
35
+ }
36
+
37
+ function renderInstallDriver(result: EventLogMergeDriverInstallResult): string {
38
+ return [
39
+ 'event_log_merge_driver_install:',
40
+ ` driver_name: ${result.driver_name}`,
41
+ ` attributes_ref: ${result.attributes_ref}`,
42
+ ` attributes_rule: ${result.attributes_rule}`,
43
+ ` gitattributes_updated: ${String(result.gitattributes_updated)}`,
44
+ ` git_config_updated: ${String(result.git_config_updated)}`,
45
+ ` git_config_present: ${String(result.git_config_present)}`,
46
+ ` warnings: ${result.warnings.join(', ') || 'none'}`,
47
+ ' boundary: Git config only; no merge, push, grant, close, or ledger mutation',
48
+ ].join('\n');
49
+ }
50
+
51
+ function renderDriverStatus(result: EventLogMergeDriverStatus): string {
52
+ return [
53
+ 'event_log_merge_driver_status:',
54
+ ` driver_name: ${result.driver_name}`,
55
+ ` attributes_configured: ${String(result.attributes_configured)}`,
56
+ ` git_config_present: ${String(result.git_config_present)}`,
57
+ ` git_config_configured: ${String(result.git_config_configured)}`,
58
+ ` ready: ${String(result.ready)}`,
59
+ ` warnings: ${result.warnings.join(', ') || 'none'}`,
60
+ ' boundary: read-only status; no merge, push, grant, close, or ledger mutation',
61
+ ].join('\n');
62
+ }
63
+
64
+ export async function dispatchSync(
65
+ positionals: string[],
66
+ flags: Record<string, string | boolean>,
67
+ ): Promise<void> {
68
+ const verb = positionals[1];
69
+ if (!verb || !SYNC_VERBS.has(verb)) {
70
+ throw new Error(`sync verb must be one of: ${[...SYNC_VERBS].join(', ')}`);
71
+ }
72
+ if (verb === 'join-plan') {
73
+ const result = await buildSyncJoinPlan({
74
+ invitePath: resolve(requireString(flags, 'invite')),
75
+ joinedBy: requireString(flags, 'joined-by'),
76
+ claimRef: requireString(flags, 'claim-ref'),
77
+ at: optionalString(flags, 'at'),
78
+ });
79
+ console.log(flags.json ? JSON.stringify(result, null, 2) : renderSyncJoinPlan(result));
80
+ return;
81
+ }
82
+ if (verb === 'install-git-merge-driver') {
83
+ const result = await installEventLogMergeDriver({
84
+ projectRoot: resolve(optionalString(flags, 'project') ?? process.cwd()),
85
+ driverName: optionalString(flags, 'driver-name') ?? DEFAULT_EVENT_LOG_MERGE_DRIVER,
86
+ configureGit: flags['skip-git-config'] !== true,
87
+ driverCommand: optionalString(flags, 'driver-command'),
88
+ });
89
+ console.log(flags.json ? JSON.stringify(result, null, 2) : renderInstallDriver(result));
90
+ return;
91
+ }
92
+ if (verb === 'git-driver-status') {
93
+ const result = await inspectEventLogMergeDriver({
94
+ projectRoot: resolve(optionalString(flags, 'project') ?? process.cwd()),
95
+ driverName: optionalString(flags, 'driver-name') ?? DEFAULT_EVENT_LOG_MERGE_DRIVER,
96
+ });
97
+ console.log(flags.json ? JSON.stringify(result, null, 2) : renderDriverStatus(result));
98
+ return;
99
+ }
100
+ if (verb === 'event-log-merge-driver') {
101
+ const ancestorPath = positionals[2];
102
+ const currentPath = positionals[3];
103
+ const otherPath = positionals[4];
104
+ if (!ancestorPath || !currentPath || !otherPath) {
105
+ throw new Error('sync event-log-merge-driver requires <ancestor> <current> <other>');
106
+ }
107
+ mergeAppendOnlyEventLogs({ ancestorPath, currentPath, otherPath });
108
+ return;
109
+ }
110
+ const result = await stageSyncOutbox({
111
+ projectRoot: resolve(optionalString(flags, 'project') ?? process.cwd()),
112
+ outboxDir: optionalString(flags, 'outbox'),
113
+ workspaceId: optionalString(flags, 'workspace-id'),
114
+ orgId: optionalString(flags, 'org-id'),
115
+ seatId: optionalString(flags, 'seat-id'),
116
+ at: optionalString(flags, 'at'),
117
+ });
118
+ console.log(flags.json ? JSON.stringify(result, null, 2) : renderStage(result));
119
+ }