openplanr 1.13.3 → 1.14.0

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 (151) hide show
  1. package/README.md +46 -4
  2. package/dist/cli/commands/operate.d.ts +3 -0
  3. package/dist/cli/commands/operate.d.ts.map +1 -0
  4. package/dist/cli/commands/operate.js +568 -0
  5. package/dist/cli/commands/operate.js.map +1 -0
  6. package/dist/cli/index.js +42 -2
  7. package/dist/cli/index.js.map +1 -1
  8. package/dist/services/ai-service.d.ts +9 -2
  9. package/dist/services/ai-service.d.ts.map +1 -1
  10. package/dist/services/ai-service.js +14 -6
  11. package/dist/services/ai-service.js.map +1 -1
  12. package/dist/services/operate/advisors.d.ts +219 -0
  13. package/dist/services/operate/advisors.d.ts.map +1 -0
  14. package/dist/services/operate/advisors.js +745 -0
  15. package/dist/services/operate/advisors.js.map +1 -0
  16. package/dist/services/operate/artifact-route-generation.d.ts +111 -0
  17. package/dist/services/operate/artifact-route-generation.d.ts.map +1 -0
  18. package/dist/services/operate/artifact-route-generation.js +459 -0
  19. package/dist/services/operate/artifact-route-generation.js.map +1 -0
  20. package/dist/services/operate/artifacts.d.ts +26 -0
  21. package/dist/services/operate/artifacts.d.ts.map +1 -0
  22. package/dist/services/operate/artifacts.js +92 -0
  23. package/dist/services/operate/artifacts.js.map +1 -0
  24. package/dist/services/operate/canonical.d.ts +18 -0
  25. package/dist/services/operate/canonical.d.ts.map +1 -0
  26. package/dist/services/operate/canonical.js +157 -0
  27. package/dist/services/operate/canonical.js.map +1 -0
  28. package/dist/services/operate/config.d.ts +56 -0
  29. package/dist/services/operate/config.d.ts.map +1 -0
  30. package/dist/services/operate/config.js +644 -0
  31. package/dist/services/operate/config.js.map +1 -0
  32. package/dist/services/operate/consolidation.d.ts +18 -0
  33. package/dist/services/operate/consolidation.d.ts.map +1 -0
  34. package/dist/services/operate/consolidation.js +539 -0
  35. package/dist/services/operate/consolidation.js.map +1 -0
  36. package/dist/services/operate/doctor.d.ts +12 -0
  37. package/dist/services/operate/doctor.d.ts.map +1 -0
  38. package/dist/services/operate/doctor.js +307 -0
  39. package/dist/services/operate/doctor.js.map +1 -0
  40. package/dist/services/operate/engine.d.ts +53 -0
  41. package/dist/services/operate/engine.d.ts.map +1 -0
  42. package/dist/services/operate/engine.js +1097 -0
  43. package/dist/services/operate/engine.js.map +1 -0
  44. package/dist/services/operate/event-store.d.ts +57 -0
  45. package/dist/services/operate/event-store.d.ts.map +1 -0
  46. package/dist/services/operate/event-store.js +232 -0
  47. package/dist/services/operate/event-store.js.map +1 -0
  48. package/dist/services/operate/evidence-cache.d.ts +17 -0
  49. package/dist/services/operate/evidence-cache.d.ts.map +1 -0
  50. package/dist/services/operate/evidence-cache.js +96 -0
  51. package/dist/services/operate/evidence-cache.js.map +1 -0
  52. package/dist/services/operate/evidence-import.d.ts +40 -0
  53. package/dist/services/operate/evidence-import.d.ts.map +1 -0
  54. package/dist/services/operate/evidence-import.js +340 -0
  55. package/dist/services/operate/evidence-import.js.map +1 -0
  56. package/dist/services/operate/evidence-readiness.d.ts +8 -0
  57. package/dist/services/operate/evidence-readiness.d.ts.map +1 -0
  58. package/dist/services/operate/evidence-readiness.js +78 -0
  59. package/dist/services/operate/evidence-readiness.js.map +1 -0
  60. package/dist/services/operate/evidence.d.ts +71 -0
  61. package/dist/services/operate/evidence.d.ts.map +1 -0
  62. package/dist/services/operate/evidence.js +1022 -0
  63. package/dist/services/operate/evidence.js.map +1 -0
  64. package/dist/services/operate/ids.d.ts +7 -0
  65. package/dist/services/operate/ids.d.ts.map +1 -0
  66. package/dist/services/operate/ids.js +23 -0
  67. package/dist/services/operate/ids.js.map +1 -0
  68. package/dist/services/operate/index.d.ts +14 -0
  69. package/dist/services/operate/index.d.ts.map +1 -0
  70. package/dist/services/operate/index.js +881 -0
  71. package/dist/services/operate/index.js.map +1 -0
  72. package/dist/services/operate/journal.d.ts +34 -0
  73. package/dist/services/operate/journal.d.ts.map +1 -0
  74. package/dist/services/operate/journal.js +229 -0
  75. package/dist/services/operate/journal.js.map +1 -0
  76. package/dist/services/operate/legacy-import-service.d.ts +58 -0
  77. package/dist/services/operate/legacy-import-service.d.ts.map +1 -0
  78. package/dist/services/operate/legacy-import-service.js +922 -0
  79. package/dist/services/operate/legacy-import-service.js.map +1 -0
  80. package/dist/services/operate/lifecycle.d.ts +88 -0
  81. package/dist/services/operate/lifecycle.d.ts.map +1 -0
  82. package/dist/services/operate/lifecycle.js +556 -0
  83. package/dist/services/operate/lifecycle.js.map +1 -0
  84. package/dist/services/operate/lock-service.d.ts +37 -0
  85. package/dist/services/operate/lock-service.d.ts.map +1 -0
  86. package/dist/services/operate/lock-service.js +208 -0
  87. package/dist/services/operate/lock-service.js.map +1 -0
  88. package/dist/services/operate/maintenance.d.ts +38 -0
  89. package/dist/services/operate/maintenance.d.ts.map +1 -0
  90. package/dist/services/operate/maintenance.js +699 -0
  91. package/dist/services/operate/maintenance.js.map +1 -0
  92. package/dist/services/operate/outcomes.d.ts +48 -0
  93. package/dist/services/operate/outcomes.d.ts.map +1 -0
  94. package/dist/services/operate/outcomes.js +312 -0
  95. package/dist/services/operate/outcomes.js.map +1 -0
  96. package/dist/services/operate/pipeline-handoff.d.ts +138 -0
  97. package/dist/services/operate/pipeline-handoff.d.ts.map +1 -0
  98. package/dist/services/operate/pipeline-handoff.js +175 -0
  99. package/dist/services/operate/pipeline-handoff.js.map +1 -0
  100. package/dist/services/operate/projection-persistence.d.ts +55 -0
  101. package/dist/services/operate/projection-persistence.d.ts.map +1 -0
  102. package/dist/services/operate/projection-persistence.js +393 -0
  103. package/dist/services/operate/projection-persistence.js.map +1 -0
  104. package/dist/services/operate/projection.d.ts +4 -0
  105. package/dist/services/operate/projection.d.ts.map +1 -0
  106. package/dist/services/operate/projection.js +128 -0
  107. package/dist/services/operate/projection.js.map +1 -0
  108. package/dist/services/operate/protocol.d.ts +60 -0
  109. package/dist/services/operate/protocol.d.ts.map +1 -0
  110. package/dist/services/operate/protocol.js +62 -0
  111. package/dist/services/operate/protocol.js.map +1 -0
  112. package/dist/services/operate/read-only-providers.d.ts +38 -0
  113. package/dist/services/operate/read-only-providers.d.ts.map +1 -0
  114. package/dist/services/operate/read-only-providers.js +286 -0
  115. package/dist/services/operate/read-only-providers.js.map +1 -0
  116. package/dist/services/operate/redaction.d.ts +47 -0
  117. package/dist/services/operate/redaction.d.ts.map +1 -0
  118. package/dist/services/operate/redaction.js +279 -0
  119. package/dist/services/operate/redaction.js.map +1 -0
  120. package/dist/services/operate/reducer.d.ts +10 -0
  121. package/dist/services/operate/reducer.d.ts.map +1 -0
  122. package/dist/services/operate/reducer.js +11 -0
  123. package/dist/services/operate/reducer.js.map +1 -0
  124. package/dist/services/operate/routes.d.ts +49 -0
  125. package/dist/services/operate/routes.d.ts.map +1 -0
  126. package/dist/services/operate/routes.js +996 -0
  127. package/dist/services/operate/routes.js.map +1 -0
  128. package/dist/services/operate/shipment-observer.d.ts +14 -0
  129. package/dist/services/operate/shipment-observer.d.ts.map +1 -0
  130. package/dist/services/operate/shipment-observer.js +321 -0
  131. package/dist/services/operate/shipment-observer.js.map +1 -0
  132. package/dist/services/operate/stalled-item-service.d.ts +18 -0
  133. package/dist/services/operate/stalled-item-service.d.ts.map +1 -0
  134. package/dist/services/operate/stalled-item-service.js +121 -0
  135. package/dist/services/operate/stalled-item-service.js.map +1 -0
  136. package/dist/services/operate/subprocess-env.d.ts +4 -0
  137. package/dist/services/operate/subprocess-env.d.ts.map +1 -0
  138. package/dist/services/operate/subprocess-env.js +45 -0
  139. package/dist/services/operate/subprocess-env.js.map +1 -0
  140. package/dist/services/operate/types.d.ts +729 -0
  141. package/dist/services/operate/types.d.ts.map +1 -0
  142. package/dist/services/operate/types.js +13 -0
  143. package/dist/services/operate/types.js.map +1 -0
  144. package/dist/services/operate/workspace.d.ts +59 -0
  145. package/dist/services/operate/workspace.d.ts.map +1 -0
  146. package/dist/services/operate/workspace.js +316 -0
  147. package/dist/services/operate/workspace.js.map +1 -0
  148. package/dist/services/runtime-manager-service.d.ts.map +1 -1
  149. package/dist/services/runtime-manager-service.js +71 -2
  150. package/dist/services/runtime-manager-service.js.map +1 -1
  151. package/package.json +10 -4
@@ -0,0 +1,881 @@
1
+ import { readFile } from 'node:fs/promises';
2
+ import path from 'node:path';
3
+ import { applyOperatingInitialization, getOperatingProfile, listOperatingProfiles, normalizeCustomOperatingProfile, prepareOperatingInitialization, validateOperatingConfiguration, } from './config.js';
4
+ import { runOperatingCycle } from './engine.js';
5
+ import { OperatingEventStore } from './event-store.js';
6
+ import { parseStrictJson, readImportedEvidenceFile } from './evidence-import.js';
7
+ import { assertCommittedOperatingView } from './journal.js';
8
+ import { applyOperatingMigration, inspectOperatingMigration, rollbackOperatingMigration, } from './legacy-import-service.js';
9
+ import { answerOperatingGap, applyOrRollbackRoute, decideOperatingDecision, governOperatingFinding, readOperatingCollection, readOperatingReview, transitionOperatingCycle, verifyOperatingGap, } from './lifecycle.js';
10
+ import { exportOperatingDiagnostics, operateAdapterLifecycle, operatingCacheAction, operatingIntegrityAction, repairOperatingSecurity, } from './maintenance.js';
11
+ import { renderOperatingBrief } from './projection.js';
12
+ import { loadOperatingProtocol, resolveOperatingPipelineRoot } from './protocol.js';
13
+ import { executeGitHubReadOnly, executeGitReadOnly } from './read-only-providers.js';
14
+ import { OperateError, } from './types.js';
15
+ import { assertOperatingProject, resolveContainedPath, resolveOperatingPaths, resolveOperatingProject, } from './workspace.js';
16
+ export { canonicalDigest, canonicalize, sha256Digest, } from './canonical.js';
17
+ export { OperatingEventStore } from './event-store.js';
18
+ export { acquireOperatingLock, recoverStaleOperatingLock, withOperatingLock, } from './lock-service.js';
19
+ export { assertOperatingProjectionsCurrent, inspectOperatingProjectionDrift, persistOperatingProjections, prepareOperatingProjectionPersistence, renderOperatingProjectionFiles, } from './projection-persistence.js';
20
+ export { assertOperatingArtifact, loadOperatingProtocol, operatingPipelineAvailable, } from './protocol.js';
21
+ function option(request, name, fallback) {
22
+ return request.options[name] ?? fallback;
23
+ }
24
+ function argument(request, name) {
25
+ const value = request.arguments?.[name];
26
+ return typeof value === 'string' ? value : undefined;
27
+ }
28
+ function stringList(value) {
29
+ if (Array.isArray(value))
30
+ return value
31
+ .map(String)
32
+ .map((entry) => entry.trim())
33
+ .filter(Boolean);
34
+ if (typeof value === 'string')
35
+ return value
36
+ .split(',')
37
+ .map((entry) => entry.trim())
38
+ .filter(Boolean);
39
+ return [];
40
+ }
41
+ async function resolvedOperatingRuntime(projectRoot, requested) {
42
+ if (requested !== 'auto')
43
+ return requested;
44
+ return readFile(path.join(resolveOperatingPaths(projectRoot).localRoot, 'preferences.json'), 'utf8')
45
+ .then((raw) => {
46
+ const runtime = JSON.parse(raw).runtime;
47
+ return typeof runtime === 'string' && runtime ? runtime : 'auto';
48
+ })
49
+ .catch(() => 'auto');
50
+ }
51
+ async function usesNativeOperatingAdvisors(projectRoot, requestedRuntime) {
52
+ const runtime = await resolvedOperatingRuntime(projectRoot, requestedRuntime);
53
+ const adapterId = runtime === 'claude' ? 'claude-code' : runtime;
54
+ if (adapterId === 'auto')
55
+ return false;
56
+ const pipelineRoot = resolveOperatingPipelineRoot();
57
+ if (!pipelineRoot)
58
+ return false;
59
+ const registry = await readFile(path.join(pipelineRoot, 'registry', 'adapters.json'), 'utf8')
60
+ .then((raw) => JSON.parse(raw))
61
+ .catch(() => ({ adapters: [] }));
62
+ const adapter = registry.adapters?.find((entry) => entry.id === adapterId);
63
+ return (adapter?.capabilities?.operatingBoard === true &&
64
+ adapter.capabilities.toolIsolation === 'enforced');
65
+ }
66
+ async function readOperatingLocalFile(projectRoot, selector) {
67
+ const canonicalRoot = await resolveOperatingProject(projectRoot).catch(() => projectRoot);
68
+ for (const candidate of [...new Set([projectRoot, canonicalRoot])]) {
69
+ try {
70
+ return await readFile(selector(resolveOperatingPaths(candidate)), 'utf8');
71
+ }
72
+ catch {
73
+ // Try the canonical invocation path before reporting the file as absent.
74
+ }
75
+ }
76
+ throw new OperateError('E_OPERATE_NOT_INITIALIZED', 'Machine-local operating source configuration is unavailable.');
77
+ }
78
+ async function readCustomOperatingProfile(projectRoot, file) {
79
+ const resolved = await resolveContainedPath(projectRoot, file, { mustExist: true });
80
+ try {
81
+ return normalizeCustomOperatingProfile(parseStrictJson(await readFile(resolved, 'utf8')));
82
+ }
83
+ catch (error) {
84
+ if (error instanceof OperateError && error.code === 'E_OPERATE_CONFIG_INVALID') {
85
+ throw error;
86
+ }
87
+ throw new OperateError('E_OPERATE_CONFIG_INVALID', 'Custom profile must be a valid bounded JSON object.');
88
+ }
89
+ }
90
+ function success(action, value = {}) {
91
+ const nextActions = value.nextActions ?? value.next ?? [];
92
+ return {
93
+ schemaVersion: '1.0.0',
94
+ protocolVersion: '1.2.0',
95
+ ok: true,
96
+ action,
97
+ ...value,
98
+ state: value.state ?? null,
99
+ paths: value.paths ?? {},
100
+ counts: value.counts ?? {},
101
+ warnings: value.warnings ?? [],
102
+ nextActions,
103
+ };
104
+ }
105
+ /**
106
+ * Stable process exit classes for automation.
107
+ *
108
+ * 2 — invalid invocation or configuration
109
+ * 3 — required project/component is unavailable
110
+ * 4 — explicit human authority or confirmation is required
111
+ * 5 — committed state, integrity, or concurrency conflict
112
+ * 6 — evidence/provider/advisor execution could not complete safely
113
+ * 1 — unexpected internal failure
114
+ *
115
+ * Keep this exhaustive so adding a public Operate error cannot silently fall
116
+ * through to the internal-error class.
117
+ */
118
+ const OPERATE_EXIT_CODES = {
119
+ E_OPERATE_INTERNAL: 1,
120
+ E_OPERATE_ACTION_UNKNOWN: 2,
121
+ E_OPERATE_ARTIFACT_REJECTED: 2,
122
+ E_OPERATE_CHARTER_INCOMPLETE: 2,
123
+ E_OPERATE_CONFIG_INVALID: 2,
124
+ E_OPERATE_INPUT_TOO_LARGE: 2,
125
+ E_OPERATE_PATH_ESCAPE: 2,
126
+ E_OPERATE_NOT_INITIALIZED: 3,
127
+ E_OPERATE_PROJECT_REQUIRED: 3,
128
+ E_PIPELINE_NOT_INSTALLED: 3,
129
+ E_OPERATE_AUTHORITY_REQUIRED: 4,
130
+ E_OPERATE_ROUTE_CONFIRMATION_REQUIRED: 4,
131
+ E_OPERATE_CHECKPOINT_INVALID: 5,
132
+ E_OPERATE_CYCLE_ACTIVE: 5,
133
+ E_OPERATE_CYCLE_INPUT_CONFLICT: 5,
134
+ E_OPERATE_CYCLE_NOT_DISPOSED: 5,
135
+ E_OPERATE_HEAD_DIVERGED: 5,
136
+ E_OPERATE_MIGRATION_CONFLICT: 5,
137
+ E_OPERATE_OUTCOME_NOT_READY: 5,
138
+ E_OPERATE_PLANNER_CONFLICT: 5,
139
+ E_OPERATE_PROJECTION_DRIFT: 5,
140
+ E_OPERATE_ROUTE_DRIFT: 5,
141
+ E_OPERATE_SECURITY_REPAIR_REQUIRED: 5,
142
+ E_OPERATE_STALE_LOCK_UNSAFE: 5,
143
+ E_OPERATE_STATE_INVALID: 5,
144
+ E_OPERATE_TRANSACTION_INVALID: 5,
145
+ E_OPERATE_ADVISOR_FAILED: 6,
146
+ E_OPERATE_ADVISOR_ISOLATION: 6,
147
+ E_OPERATE_CAP_EXCEEDED: 6,
148
+ E_OPERATE_CRITICAL_CAP: 6,
149
+ E_OPERATE_EVIDENCE_BUDGET: 6,
150
+ E_OPERATE_EVIDENCE_NOT_READY: 6,
151
+ E_OPERATE_EVIDENCE_REJECTED: 6,
152
+ E_OPERATE_PROVIDER_READ_ONLY: 6,
153
+ E_OPERATE_SECRET_DETECTED: 6,
154
+ };
155
+ function operateExitCode(code) {
156
+ return OPERATE_EXIT_CODES[code];
157
+ }
158
+ function failure(action, error) {
159
+ const code = error instanceof OperateError ? error.code : 'E_OPERATE_INTERNAL';
160
+ const nextActions = code === 'E_PIPELINE_NOT_INSTALLED'
161
+ ? ['npm install -g openplanr@latest', 'planr setup --scope user', 'planr operate inspect']
162
+ : code === 'E_OPERATE_NOT_INITIALIZED'
163
+ ? ['planr operate init']
164
+ : code === 'E_OPERATE_PROJECT_REQUIRED'
165
+ ? ['cd /path/to/your/project', 'planr init', 'planr operate inspect']
166
+ : code === 'E_OPERATE_CONFIG_INVALID' || code === 'E_OPERATE_CHARTER_INCOMPLETE'
167
+ ? ['planr operate config validate']
168
+ : code === 'E_OPERATE_ROUTE_CONFIRMATION_REQUIRED'
169
+ ? ['planr operate routes apply <route-id> --preview --json']
170
+ : code === 'E_OPERATE_MIGRATION_CONFLICT'
171
+ ? ['planr operate migrate inspect', 'planr operate migrations list']
172
+ : code === 'E_OPERATE_HEAD_DIVERGED' ||
173
+ code === 'E_OPERATE_CHECKPOINT_INVALID' ||
174
+ code === 'E_OPERATE_PROJECTION_DRIFT'
175
+ ? ['planr operate integrity status', 'planr operate cycles recover <cycle-id>']
176
+ : code === 'E_OPERATE_CYCLE_ACTIVE' || code === 'E_OPERATE_CYCLE_INPUT_CONFLICT'
177
+ ? ['planr operate status', 'planr operate cycles resume <cycle-id>']
178
+ : code === 'E_OPERATE_CYCLE_NOT_DISPOSED'
179
+ ? [
180
+ 'planr operate findings list',
181
+ 'planr operate decisions list',
182
+ 'planr operate cycles close <cycle-id>',
183
+ ]
184
+ : code.startsWith('E_OPERATE_EVIDENCE') ||
185
+ code.startsWith('E_OPERATE_PROVIDER') ||
186
+ code.startsWith('E_OPERATE_ADVISOR')
187
+ ? ['planr operate sources list', 'planr operate run --preview --json']
188
+ : ['planr operate diagnostics export'];
189
+ return {
190
+ schemaVersion: '1.0.0',
191
+ protocolVersion: '1.2.0',
192
+ ok: false,
193
+ action,
194
+ code,
195
+ message: error instanceof OperateError
196
+ ? error.message
197
+ : 'An unexpected internal Operating Board error occurred.',
198
+ state: null,
199
+ paths: {},
200
+ counts: {},
201
+ warnings: [],
202
+ nextActions,
203
+ data: error instanceof OperateError ? error.details : undefined,
204
+ next: nextActions,
205
+ exitCode: operateExitCode(code),
206
+ };
207
+ }
208
+ async function inspect(request) {
209
+ const pipelineRoot = resolveOperatingPipelineRoot();
210
+ const paths = resolveOperatingPaths(request.projectRoot);
211
+ const initialized = await readFile(paths.config, 'utf8').then(() => true, () => false);
212
+ let project = false;
213
+ try {
214
+ await assertOperatingProject(request.projectRoot);
215
+ project = true;
216
+ }
217
+ catch {
218
+ project = false;
219
+ }
220
+ return success(request.action, {
221
+ message: initialized
222
+ ? 'Operating Board is initialized.'
223
+ : 'Operating Board is available; initialization is optional.',
224
+ data: {
225
+ project,
226
+ initialized,
227
+ pipeline: {
228
+ available: Boolean(pipelineRoot),
229
+ root: pipelineRoot ? '(installed)' : null,
230
+ protocolVersion: pipelineRoot ? '1.2.0' : null,
231
+ },
232
+ commitSafeRoot: project ? '.planr/operate' : null,
233
+ machineLocalState: '~/.planr/operate/<project-hash>',
234
+ },
235
+ next: initialized ? ['planr operate status'] : ['planr operate init'],
236
+ });
237
+ }
238
+ function demo(request) {
239
+ const evidence = [
240
+ {
241
+ id: 'EVD-demo-activation',
242
+ source: 'demo-fixture',
243
+ summary: 'The product has no verified activation baseline for the current cycle.',
244
+ observedFrom: '2026-06-01T00:00:00.000Z',
245
+ observedTo: '2026-06-30T23:59:59.000Z',
246
+ sensitivity: 'public',
247
+ },
248
+ {
249
+ id: 'EVD-demo-delivery',
250
+ source: 'demo-fixture',
251
+ summary: 'Delivery capacity is already allocated to three unmeasured feature bets.',
252
+ observedFrom: '2026-06-01T00:00:00.000Z',
253
+ observedTo: '2026-06-30T23:59:59.000Z',
254
+ sensitivity: 'public',
255
+ },
256
+ ];
257
+ const demoState = {
258
+ kind: 'operating-state',
259
+ schemaVersion: '1.0.0',
260
+ protocolVersion: '1.2.0',
261
+ generatedAt: '2026-07-01T00:00:00.000Z',
262
+ eventHead: { sequence: 0, hash: null },
263
+ cycles: [{ id: 'CYCLE-001', state: 'reviewable', health: 'normal' }],
264
+ findings: [
265
+ {
266
+ id: 'FND-001',
267
+ cycleId: 'CYCLE-001',
268
+ status: 'proposed',
269
+ title: 'Measure activation before increasing delivery scope',
270
+ problem: 'The product has no verified activation baseline.',
271
+ proposal: 'Create one bounded activation instrumentation specification.',
272
+ severity: 'high',
273
+ sensitivity: 'public',
274
+ score: 48,
275
+ lane: 'DEV',
276
+ owner: 'product-engineering',
277
+ parked: false,
278
+ evidenceRefs: ['EVD-demo-activation', 'EVD-demo-delivery'],
279
+ },
280
+ ],
281
+ decisions: [
282
+ {
283
+ id: 'DEC-001',
284
+ cycleId: 'CYCLE-001',
285
+ status: 'open',
286
+ question: 'Which customer event defines activation?',
287
+ recommendation: 'Choose one observable event before planning more feature work.',
288
+ evidenceRefs: ['EVD-demo-activation'],
289
+ },
290
+ ],
291
+ dataGaps: [],
292
+ routes: [
293
+ {
294
+ id: 'ACT-001',
295
+ cycleId: 'CYCLE-001',
296
+ state: 'proposed',
297
+ findingIds: ['FND-001'],
298
+ },
299
+ ],
300
+ specLinks: [],
301
+ outcomes: [],
302
+ learnings: [],
303
+ evidenceSources: [{ id: 'demo-fixture', status: 'collected', itemCount: 2 }],
304
+ summary: {
305
+ currentCycleId: 'CYCLE-001',
306
+ currentConstraint: 'The product has no verified activation baseline.',
307
+ quiet: false,
308
+ evidenceFreshness: 'fresh',
309
+ surfacedFindings: 1,
310
+ parkedFindings: 0,
311
+ openDecisions: 1,
312
+ openGaps: 0,
313
+ stalledItems: 0,
314
+ },
315
+ };
316
+ return success(request.action, {
317
+ message: 'Generated a deterministic, credential-free Operating Board demonstration.',
318
+ data: {
319
+ brief: renderOperatingBrief(demoState),
320
+ evidence,
321
+ state: demoState,
322
+ note: 'Demo performs no provider/model calls and writes no project state.',
323
+ },
324
+ next: ['planr operate init'],
325
+ });
326
+ }
327
+ async function initialize(request) {
328
+ await loadOperatingProtocol();
329
+ if (!request.interactive) {
330
+ const missing = ['profile', 'decisionOwner', 'planningEngine'].filter((name) => !Object.hasOwn(request.options, name) ||
331
+ String(request.options[name] ?? '').trim().length === 0);
332
+ if (missing.length > 0) {
333
+ throw new OperateError('E_OPERATE_CONFIG_INVALID', `Non-interactive initialization requires explicit ${missing
334
+ .map((name) => `--${name.replace(/[A-Z]/g, (letter) => `-${letter.toLowerCase()}`)}`)
335
+ .join(', ')}.`, { missing });
336
+ }
337
+ }
338
+ const profile = option(request, 'profile', 'saas');
339
+ let customProfile;
340
+ const profileFile = option(request, 'profileFile', undefined);
341
+ if (profile === 'custom') {
342
+ if (!profileFile) {
343
+ throw new OperateError('E_OPERATE_CONFIG_INVALID', 'The custom profile requires --profile-file inside the project.');
344
+ }
345
+ customProfile = await readCustomOperatingProfile(request.projectRoot, profileFile);
346
+ }
347
+ else if (profileFile) {
348
+ throw new OperateError('E_OPERATE_CONFIG_INVALID', '--profile-file is valid only with --profile custom.');
349
+ }
350
+ const decisionOwner = option(request, 'decisionOwner', '').trim();
351
+ const planningEngine = option(request, 'planningEngine', 'openplanr');
352
+ const rawCharter = option(request, 'charter', {});
353
+ const preview = await prepareOperatingInitialization({
354
+ projectRoot: request.projectRoot,
355
+ profile,
356
+ decisionOwner,
357
+ planningEngine,
358
+ runtime: option(request, 'runtime', 'auto'),
359
+ cadence: option(request, 'cadence', 'manual'),
360
+ timezone: option(request, 'timezone', Intl.DateTimeFormat().resolvedOptions().timeZone),
361
+ sensitivityCeiling: option(request, 'sensitivityCeiling', 'internal'),
362
+ enabledProviders: stringList(request.options.sources).length
363
+ ? stringList(request.options.sources)
364
+ : undefined,
365
+ evidenceFiles: stringList(request.options.evidenceFile),
366
+ charter: rawCharter,
367
+ customProfile,
368
+ componentRoots: stringList(request.options.components),
369
+ });
370
+ const legacyMigration = await inspectOperatingMigration({
371
+ projectRoot: request.projectRoot,
372
+ });
373
+ const previewData = {
374
+ previewDigest: preview.previewDigest,
375
+ changedPaths: preview.changedPaths,
376
+ localPreferencesChanged: preview.preferencesChanged,
377
+ workspaceDigest: preview.workspace.workspaceDigest,
378
+ config: preview.config,
379
+ legacyMigration,
380
+ };
381
+ if (option(request, 'preview', false) || option(request, 'dryRun', false)) {
382
+ return success(request.action, {
383
+ message: 'Operating Board initialization preview is ready; no state was written.',
384
+ preview: previewData,
385
+ next: ['planr operate init --yes'],
386
+ });
387
+ }
388
+ if (!request.interactive && !option(request, 'yes', false)) {
389
+ throw new OperateError('E_OPERATE_ROUTE_CONFIRMATION_REQUIRED', 'Non-interactive initialization requires --yes after reviewing --preview.', { preview: previewData });
390
+ }
391
+ const applied = await applyOperatingInitialization({
392
+ projectRoot: request.projectRoot,
393
+ preview,
394
+ confirmationDigest: preview.previewDigest,
395
+ });
396
+ return success(request.action, {
397
+ message: applied.initialized
398
+ ? 'Operating Board initialized.'
399
+ : 'Operating Board already matches this configuration.',
400
+ data: {
401
+ changedPaths: applied.changedPaths,
402
+ workspaceDigest: preview.workspace.workspaceDigest,
403
+ legacyMigration,
404
+ },
405
+ next: [
406
+ ...(legacyMigration.record
407
+ ? legacyMigration.record.state === 'conflict'
408
+ ? ['planr operate migrate inspect']
409
+ : ['planr operate migrate apply']
410
+ : []),
411
+ 'planr operate run',
412
+ 'planr operate status',
413
+ ],
414
+ });
415
+ }
416
+ async function showConfig(request) {
417
+ const config = await validateOperatingConfiguration(request.projectRoot);
418
+ if (request.action === 'config.edit') {
419
+ return success(request.action, {
420
+ message: 'Operating configuration is managed at .planr/operate/config.json.',
421
+ data: { path: '.planr/operate/config.json', config },
422
+ next: ['Edit .planr/operate/config.json', 'planr operate config validate'],
423
+ });
424
+ }
425
+ return success(request.action, {
426
+ data: config,
427
+ message: request.action === 'config.validate' ? 'Operating configuration is valid.' : undefined,
428
+ });
429
+ }
430
+ async function profiles(request) {
431
+ if (request.action === 'profiles.list') {
432
+ return success(request.action, { data: listOperatingProfiles() });
433
+ }
434
+ if (request.action === 'profiles.show') {
435
+ const profile = argument(request, 'profile');
436
+ if (!profile) {
437
+ throw new OperateError('E_OPERATE_CONFIG_INVALID', 'Profile ID is required.');
438
+ }
439
+ return success(request.action, {
440
+ data: getOperatingProfile(profile),
441
+ });
442
+ }
443
+ const file = argument(request, 'file');
444
+ if (!file)
445
+ throw new OperateError('E_OPERATE_CONFIG_INVALID', 'Profile file is required.');
446
+ const parsed = await readCustomOperatingProfile(request.projectRoot, file);
447
+ const profile = { ...getOperatingProfile('custom'), ...parsed };
448
+ if (!profile.enabledRoles?.length || !profile.enabledProviders?.length) {
449
+ throw new OperateError('E_OPERATE_CONFIG_INVALID', 'Custom profile is incomplete.');
450
+ }
451
+ return success(request.action, { data: profile, message: 'Custom profile is valid.' });
452
+ }
453
+ async function sources(request) {
454
+ const protocol = await loadOperatingProtocol();
455
+ const providers = protocol.listOperatingProviders();
456
+ if (request.action === 'sources.list')
457
+ return success(request.action, { data: providers });
458
+ const id = argument(request, 'source');
459
+ const provider = providers.find((entry) => entry.id === id);
460
+ if (!provider) {
461
+ throw new OperateError('E_OPERATE_CONFIG_INVALID', `Unknown evidence source: ${id}.`);
462
+ }
463
+ if (request.action !== 'sources.test')
464
+ return success(request.action, { data: provider });
465
+ let observation;
466
+ if (id === 'repository' || id === 'planr') {
467
+ observation = (await executeGitReadOnly(request.projectRoot, ['ls-files'])).trim()
468
+ ? 'tracked-files-readable'
469
+ : 'repository-readable-empty';
470
+ }
471
+ else if (id === 'git') {
472
+ observation = (await executeGitReadOnly(request.projectRoot, ['rev-parse', 'HEAD'])).trim();
473
+ }
474
+ else if (id === 'github') {
475
+ observation =
476
+ (await executeGitHubReadOnly(request.projectRoot, ['auth', 'status'])).trim() ||
477
+ 'authenticated';
478
+ }
479
+ else if (id === 'linear') {
480
+ const [{ resolveApiKey }, { createLinearClient, validateToken }] = await Promise.all([
481
+ import('../credentials-service.js'),
482
+ import('../linear-service.js'),
483
+ ]);
484
+ const token = await resolveApiKey('linear');
485
+ if (!token) {
486
+ throw new OperateError('E_OPERATE_EVIDENCE_REJECTED', 'Linear credentials are unavailable.');
487
+ }
488
+ const viewer = await validateToken(createLinearClient(token));
489
+ observation = `viewer:${viewer.id}`;
490
+ }
491
+ else if (id === 'file-import') {
492
+ const preferences = parseStrictJson(await readOperatingLocalFile(request.projectRoot, (paths) => `${paths.localRoot}/preferences.json`));
493
+ const workspaceRoots = parseStrictJson(await readOperatingLocalFile(request.projectRoot, (paths) => paths.roots));
494
+ const importPaths = Array.isArray(preferences.importPaths)
495
+ ? preferences.importPaths.filter((entry) => typeof entry === 'string')
496
+ : [];
497
+ if (importPaths.length === 0 ||
498
+ !workspaceRoots.roots ||
499
+ typeof workspaceRoots.roots !== 'object' ||
500
+ Array.isArray(workspaceRoots.roots)) {
501
+ throw new OperateError('E_OPERATE_EVIDENCE_REJECTED', 'No workspace-contained JSON/CSV evidence files are configured.');
502
+ }
503
+ const roots = Object.entries(workspaceRoots.roots)
504
+ .filter((entry) => typeof entry[1] === 'string')
505
+ .map(([componentId, root]) => ({ componentId, root }));
506
+ for (const configuredPath of importPaths) {
507
+ await readImportedEvidenceFile({
508
+ projectRoot: request.projectRoot,
509
+ configuredPath,
510
+ roots,
511
+ maxBytes: 1_000_000,
512
+ });
513
+ }
514
+ observation = `files:${importPaths.length}`;
515
+ }
516
+ else {
517
+ observation = 'import-parser-available';
518
+ }
519
+ return success(request.action, {
520
+ data: { provider, healthy: true, observation, writeBoundary: 'none' },
521
+ });
522
+ }
523
+ async function status(request) {
524
+ await validateOperatingConfiguration(request.projectRoot);
525
+ await assertCommittedOperatingView(request.projectRoot);
526
+ const store = new OperatingEventStore(request.projectRoot);
527
+ const state = await store.state();
528
+ const activeCycle = [...state.cycles]
529
+ .filter((cycle) => !['closed', 'cancelled'].includes(cycle.state))
530
+ .sort((left, right) => left.id.localeCompare(right.id))
531
+ .at(-1);
532
+ return success(request.action, {
533
+ data: state,
534
+ message: activeCycle?.state === 'blocked'
535
+ ? `Operating Board is blocked on ${state.summary.openGaps} evidence or advisor readiness gap(s).`
536
+ : state.summary.quiet
537
+ ? 'Operating Board is quiet.'
538
+ : `${state.summary.surfacedFindings} finding(s) are surfaced.`,
539
+ });
540
+ }
541
+ async function run(request) {
542
+ const requestedRuntime = option(request, 'runtime', 'auto');
543
+ const deferAdvisors = !option(request, 'offline', false) &&
544
+ !option(request, 'dryRun', false) &&
545
+ !option(request, 'reviewOnly', false) &&
546
+ (await usesNativeOperatingAdvisors(request.projectRoot, requestedRuntime));
547
+ const result = await runOperatingCycle({
548
+ projectRoot: request.projectRoot,
549
+ focus: stringList(request.options.focus),
550
+ depth: option(request, 'depth', 'standard'),
551
+ runtime: requestedRuntime,
552
+ offline: option(request, 'offline', false),
553
+ reviewOnly: option(request, 'reviewOnly', false),
554
+ preview: option(request, 'preview', false),
555
+ dryRun: option(request, 'dryRun', false),
556
+ confirmed: option(request, 'yes', false),
557
+ quiet: option(request, 'json', false),
558
+ deferAdvisors,
559
+ });
560
+ const projected = result.state?.cycles.find((cycle) => cycle.id === result.cycle.id);
561
+ const counts = {
562
+ findings: result.state?.findings.filter((entry) => entry.cycleId === result.cycle.id).length ?? 0,
563
+ decisions: result.state?.decisions.filter((entry) => entry.cycleId === result.cycle.id).length ?? 0,
564
+ gaps: result.state?.dataGaps.filter((entry) => entry.cycleId === result.cycle.id).length ?? 0,
565
+ specs: result.state?.specLinks.filter((entry) => entry.cycleId === result.cycle.id).length ?? 0,
566
+ artifacts: 0,
567
+ };
568
+ const nextActions = result.preview
569
+ ? ['planr operate run --offline']
570
+ : result.nativeHandoff
571
+ ? [
572
+ `planr operate adapter prepare --cycle-id ${result.nativeHandoff.cycleId} --evidence-digest ${result.nativeHandoff.evidenceDigest} --idempotency-key native-${result.nativeHandoff.cycleId}-${result.nativeHandoff.phase} --role ${result.nativeHandoff.roles.join(',')}`,
573
+ ]
574
+ : [`planr operate review ${result.cycle.id}`];
575
+ return success(request.action, {
576
+ message: result.preview
577
+ ? 'Operating cycle preview is ready; no provider calls or writes were made.'
578
+ : result.dryRun
579
+ ? 'Operating cycle dry-run completed without committing state.'
580
+ : result.nativeHandoff
581
+ ? `Operating cycle is awaiting isolated native ${result.nativeHandoff.phase} execution.`
582
+ : projected?.state === 'blocked'
583
+ ? 'Operating cycle is blocked on evidence or advisor readiness.'
584
+ : 'Operating cycle is ready for human review.',
585
+ cycleId: result.cycle.id,
586
+ state: projected?.state ?? result.cycle.state,
587
+ paths: {
588
+ cycle: `.planr/operate/cycles/${result.cycle.id}`,
589
+ brief: `.planr/operate/cycles/${result.cycle.id}/brief.md`,
590
+ },
591
+ counts,
592
+ warnings: [...new Set([...(result.cycle.warnings ?? []), ...stringList(projected?.warnings)])],
593
+ nextActions,
594
+ data: result,
595
+ next: nextActions,
596
+ });
597
+ }
598
+ async function reviewOrBrief(request) {
599
+ const cycleId = argument(request, 'cycleId');
600
+ const data = await readOperatingReview({
601
+ projectRoot: request.projectRoot,
602
+ cycleId,
603
+ brief: request.action === 'brief',
604
+ });
605
+ return success(request.action, {
606
+ data,
607
+ message: request.action === 'review'
608
+ ? 'This is the mandatory human review gate. No route has been applied.'
609
+ : undefined,
610
+ });
611
+ }
612
+ async function collections(request) {
613
+ const [collection, operation] = request.action.split('.');
614
+ const singular = collection === 'cycles'
615
+ ? 'cycleId'
616
+ : collection === 'findings'
617
+ ? 'findingId'
618
+ : collection === 'decisions'
619
+ ? 'decisionId'
620
+ : collection === 'gaps'
621
+ ? 'gapId'
622
+ : collection === 'routes'
623
+ ? 'routeId'
624
+ : collection === 'evidence'
625
+ ? 'evidenceId'
626
+ : 'migrationId';
627
+ const id = argument(request, singular) ?? argument(request, 'id');
628
+ const data = await readOperatingCollection({
629
+ projectRoot: request.projectRoot,
630
+ collection: collection,
631
+ ...(operation === 'show' ? { id } : {}),
632
+ });
633
+ if (operation === 'show' && !data) {
634
+ throw new OperateError('E_OPERATE_STATE_INVALID', `Unknown ${collection} record ${id}.`);
635
+ }
636
+ return success(request.action, { data });
637
+ }
638
+ async function cycleMutation(request) {
639
+ const cycleId = argument(request, 'cycleId');
640
+ if (!cycleId)
641
+ throw new OperateError('E_OPERATE_STATE_INVALID', 'Cycle ID is required.');
642
+ const action = request.action.slice('cycles.'.length);
643
+ return success(request.action, {
644
+ data: await transitionOperatingCycle({
645
+ projectRoot: request.projectRoot,
646
+ cycleId,
647
+ action,
648
+ confirmed: option(request, 'yes', false),
649
+ }),
650
+ });
651
+ }
652
+ async function findingMutation(request) {
653
+ const findingId = argument(request, 'findingId');
654
+ if (!findingId)
655
+ throw new OperateError('E_OPERATE_STATE_INVALID', 'Finding ID is required.');
656
+ return success(request.action, {
657
+ data: await governOperatingFinding({
658
+ projectRoot: request.projectRoot,
659
+ findingId,
660
+ action: request.action.slice('findings.'.length),
661
+ confirmed: option(request, 'yes', false),
662
+ reason: option(request, 'reason', undefined),
663
+ impact: request.options.impact,
664
+ confidence: request.options.confidence,
665
+ ease: request.options.ease,
666
+ }),
667
+ });
668
+ }
669
+ async function routeMutation(request) {
670
+ const routeId = argument(request, 'routeId');
671
+ if (!routeId)
672
+ throw new OperateError('E_OPERATE_STATE_INVALID', 'Route ID is required.');
673
+ const data = (await applyOrRollbackRoute({
674
+ projectRoot: request.projectRoot,
675
+ routeId,
676
+ action: request.action.endsWith('.apply') ? 'apply' : 'rollback',
677
+ previewDigest: option(request, 'previewDigest', undefined),
678
+ preview: option(request, 'preview', false) || option(request, 'dryRun', false),
679
+ confirmed: option(request, 'yes', false),
680
+ }));
681
+ return success(request.action, {
682
+ data,
683
+ nextActions: data.state === 'awaiting-artifact-review'
684
+ ? [
685
+ `planr operate routes apply ${routeId} --preview`,
686
+ `planr operate routes apply ${routeId} --preview-digest <generated-preview-digest> --yes`,
687
+ ]
688
+ : [],
689
+ });
690
+ }
691
+ async function answerMutation(request) {
692
+ if (request.action === 'gaps.verify') {
693
+ const gapId = argument(request, 'gapId');
694
+ if (!gapId)
695
+ throw new OperateError('E_OPERATE_STATE_INVALID', 'Gap ID is required.');
696
+ return success(request.action, {
697
+ data: await verifyOperatingGap({
698
+ projectRoot: request.projectRoot,
699
+ gapId,
700
+ evidenceRefs: stringList(request.options.evidenceRef),
701
+ confirmed: option(request, 'yes', false),
702
+ }),
703
+ });
704
+ }
705
+ const value = String(option(request, 'value', request.stdin ?? '')).trim();
706
+ if (request.action === 'decisions.decide') {
707
+ const decisionId = argument(request, 'decisionId');
708
+ if (!decisionId)
709
+ throw new OperateError('E_OPERATE_STATE_INVALID', 'Decision ID is required.');
710
+ return success(request.action, {
711
+ data: await decideOperatingDecision({
712
+ projectRoot: request.projectRoot,
713
+ decisionId,
714
+ value,
715
+ reason: option(request, 'reason', undefined),
716
+ confirmed: option(request, 'yes', false),
717
+ }),
718
+ });
719
+ }
720
+ const gapId = argument(request, 'gapId');
721
+ if (!gapId)
722
+ throw new OperateError('E_OPERATE_STATE_INVALID', 'Gap ID is required.');
723
+ return success(request.action, {
724
+ data: await answerOperatingGap({
725
+ projectRoot: request.projectRoot,
726
+ gapId,
727
+ value,
728
+ confirmed: option(request, 'yes', false),
729
+ }),
730
+ });
731
+ }
732
+ async function maintenance(request) {
733
+ if (request.action.startsWith('cache.')) {
734
+ return success(request.action, {
735
+ data: await operatingCacheAction({
736
+ projectRoot: request.projectRoot,
737
+ action: request.action.endsWith('purge') ? 'purge' : 'status',
738
+ confirmed: option(request, 'yes', false),
739
+ }),
740
+ });
741
+ }
742
+ if (request.action.startsWith('integrity.')) {
743
+ return success(request.action, {
744
+ data: await operatingIntegrityAction({
745
+ projectRoot: request.projectRoot,
746
+ action: request.action.endsWith('enable') ? 'enable' : 'status',
747
+ confirmed: option(request, 'yes', false),
748
+ }),
749
+ });
750
+ }
751
+ if (request.action === 'diagnostics.export') {
752
+ return success(request.action, {
753
+ data: await exportOperatingDiagnostics({
754
+ projectRoot: request.projectRoot,
755
+ output: option(request, 'output', undefined),
756
+ }),
757
+ });
758
+ }
759
+ if (request.action === 'security.repair') {
760
+ return success(request.action, {
761
+ data: await repairOperatingSecurity({
762
+ projectRoot: request.projectRoot,
763
+ confirmed: option(request, 'yes', false),
764
+ }),
765
+ });
766
+ }
767
+ if (request.action === 'migrate.inspect') {
768
+ return success(request.action, {
769
+ data: await inspectOperatingMigration({ projectRoot: request.projectRoot }),
770
+ });
771
+ }
772
+ if (request.action === 'migrate.apply') {
773
+ return success(request.action, {
774
+ data: await applyOperatingMigration({
775
+ projectRoot: request.projectRoot,
776
+ confirmed: option(request, 'yes', false),
777
+ }),
778
+ });
779
+ }
780
+ if (request.action === 'migrations.rollback') {
781
+ const migrationId = argument(request, 'migrationId');
782
+ if (!migrationId) {
783
+ throw new OperateError('E_OPERATE_STATE_INVALID', 'Migration ID is required.');
784
+ }
785
+ return success(request.action, {
786
+ data: await rollbackOperatingMigration({
787
+ projectRoot: request.projectRoot,
788
+ migrationId,
789
+ confirmed: option(request, 'yes', false),
790
+ }),
791
+ });
792
+ }
793
+ return success(request.action, {
794
+ data: await operateAdapterLifecycle({
795
+ projectRoot: request.projectRoot,
796
+ action: request.action.slice('adapter.'.length),
797
+ cycleId: option(request, 'cycleId', undefined),
798
+ evidenceDigest: option(request, 'evidenceDigest', undefined),
799
+ lease: option(request, 'lease', undefined),
800
+ idempotencyKey: option(request, 'idempotencyKey', undefined),
801
+ role: option(request, 'role', undefined),
802
+ stdin: request.stdin,
803
+ }),
804
+ });
805
+ }
806
+ const HANDLERS = {
807
+ inspect,
808
+ demo,
809
+ init: initialize,
810
+ 'config.show': showConfig,
811
+ 'config.validate': showConfig,
812
+ 'config.edit': showConfig,
813
+ 'profiles.list': profiles,
814
+ 'profiles.show': profiles,
815
+ 'profiles.validate': profiles,
816
+ 'sources.list': sources,
817
+ 'sources.show': sources,
818
+ 'sources.test': sources,
819
+ run,
820
+ review: reviewOrBrief,
821
+ brief: reviewOrBrief,
822
+ status,
823
+ 'cycles.list': collections,
824
+ 'cycles.show': collections,
825
+ 'cycles.resume': cycleMutation,
826
+ 'cycles.cancel': cycleMutation,
827
+ 'cycles.recover': cycleMutation,
828
+ 'cycles.close': cycleMutation,
829
+ 'findings.list': collections,
830
+ 'findings.show': collections,
831
+ 'findings.accept': findingMutation,
832
+ 'findings.reject': findingMutation,
833
+ 'findings.supersede': findingMutation,
834
+ 'routes.list': collections,
835
+ 'routes.show': collections,
836
+ 'routes.apply': routeMutation,
837
+ 'routes.rollback': routeMutation,
838
+ 'decisions.list': collections,
839
+ 'decisions.show': collections,
840
+ 'decisions.decide': answerMutation,
841
+ 'gaps.list': collections,
842
+ 'gaps.show': collections,
843
+ 'gaps.answer': answerMutation,
844
+ 'gaps.verify': answerMutation,
845
+ 'evidence.list': collections,
846
+ 'evidence.show': collections,
847
+ 'migrate.inspect': maintenance,
848
+ 'migrate.apply': maintenance,
849
+ 'migrations.list': collections,
850
+ 'migrations.show': collections,
851
+ 'migrations.rollback': maintenance,
852
+ 'cache.status': maintenance,
853
+ 'cache.purge': maintenance,
854
+ 'integrity.status': maintenance,
855
+ 'integrity.enable': maintenance,
856
+ 'diagnostics.export': maintenance,
857
+ 'security.repair': maintenance,
858
+ 'adapter.prepare': maintenance,
859
+ 'adapter.record': maintenance,
860
+ 'adapter.resume': maintenance,
861
+ 'adapter.finalize': maintenance,
862
+ 'adapter.cancel': maintenance,
863
+ };
864
+ /**
865
+ * Stable runtime-neutral Operating Board facade. Public CLI adapters only parse
866
+ * arguments and render this structured result; all state and security semantics
867
+ * live behind this function.
868
+ */
869
+ export async function executeOperateAction(request) {
870
+ try {
871
+ const handler = HANDLERS[request.action];
872
+ if (!handler) {
873
+ throw new OperateError('E_OPERATE_ACTION_UNKNOWN', `Unknown Operating Board action: ${request.action}.`);
874
+ }
875
+ return await handler(request);
876
+ }
877
+ catch (error) {
878
+ return failure(request.action, error);
879
+ }
880
+ }
881
+ //# sourceMappingURL=index.js.map