@cassiomc1/forgeloop 1.3.0 → 1.5.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.
- package/.github/copilot-instructions.md +1 -0
- package/AGENTS.md +1 -0
- package/CLAUDE.md +1 -0
- package/DOCS_INDEX.md +7 -0
- package/EXECUTION_STATE.md +40 -0
- package/LOOP_ENGINEERING.md +54 -5
- package/LOOP_SYSTEM_DESIGN.md +22 -1
- package/PROTOCOL_INTEGRATION.md +41 -0
- package/README.md +38 -0
- package/TERMINOLOGY.md +15 -0
- package/THIRD_PARTY_NOTICES.md +15 -0
- package/THREAT_MODEL.md +20 -1
- package/docs/ARTIFACT_REFERENCE.md +43 -0
- package/docs/CLI_REFERENCE.md +97 -3
- package/docs/CROSS_HARNESS_CONTINUITY.md +23 -0
- package/docs/DOCUMENTATION_GUIDE.md +14 -0
- package/docs/GETTING_STARTED.md +1 -0
- package/docs/MCP.md +126 -0
- package/docs/RECIPES.md +82 -0
- package/docs/RELEASE_CHECKLIST_1_4.md +38 -0
- package/docs/RELEASE_CHECKLIST_1_5_MCP.md +78 -0
- package/docs/TROUBLESHOOTING.md +111 -1
- package/docs/UNIVERSAL_INTEGRATION.md +48 -0
- package/package.json +14 -3
- package/schemas/task-recovery.schema.json +61 -0
- package/src/cli.js +173 -347
- package/src/commands/audit.js +5 -0
- package/src/commands/inspect.js +6 -0
- package/src/commands/progress.js +6 -2
- package/src/commands/status.js +17 -0
- package/src/commands/task-create.js +39 -1
- package/src/commands/task-list.js +14 -1
- package/src/commands/task-lock-status.js +2 -2
- package/src/commands/task-recover.js +202 -0
- package/src/commands/task-repair-legacy-recovery.js +417 -0
- package/src/commands/task-resume.js +172 -0
- package/src/commands/task-scope.js +23 -4
- package/src/commands/task-show.js +18 -4
- package/src/commands/validate-protocol.js +19 -2
- package/src/core/artifact-registry.js +12 -0
- package/src/core/audit.js +20 -4
- package/src/core/bundles.js +15 -0
- package/src/core/cli-command-definitions.js +50 -4
- package/src/core/command-executors.js +387 -0
- package/src/core/command-input.js +107 -0
- package/src/core/command-runtime.js +106 -0
- package/src/core/completion-artifacts.js +2 -3
- package/src/core/completion-ownership.js +88 -0
- package/src/core/error-codes.js +118 -1
- package/src/core/events.js +130 -1
- package/src/core/filesystem.js +55 -6
- package/src/core/inspect.js +27 -0
- package/src/core/integration-invocation-policy.js +170 -0
- package/src/core/integration-limits.js +20 -0
- package/src/core/integration-resources.js +127 -0
- package/src/core/next-action-model.js +60 -0
- package/src/core/next-action.js +31 -0
- package/src/core/phase.js +2 -1
- package/src/core/project-root.js +21 -0
- package/src/core/protocol-info.js +13 -0
- package/src/core/reconcile-closure.js +32 -10
- package/src/core/recovery-history.js +116 -0
- package/src/core/schema-validation.js +1 -0
- package/src/core/task-claim-state.js +272 -0
- package/src/core/task-command.js +5 -1
- package/src/core/task-conflict-inspection.js +321 -0
- package/src/core/task-context.js +32 -29
- package/src/core/task-discovery.js +14 -1
- package/src/core/task-lock.js +216 -22
- package/src/core/task-paths.js +3 -2
- package/src/core/task-recovery-migration.js +192 -0
- package/src/core/task-recovery.js +205 -0
- package/src/core/task-scope.js +33 -1
- package/src/core/templates.js +1 -0
- package/src/core/transaction.js +28 -2
- package/src/integration.js +47 -0
|
@@ -0,0 +1,387 @@
|
|
|
1
|
+
import { runDoctor } from "../commands/doctor.js";
|
|
2
|
+
import { runStatus } from "../commands/status.js";
|
|
3
|
+
import { runValidateState } from "../commands/validate-state.js";
|
|
4
|
+
import { runClearState } from "../commands/clear-state.js";
|
|
5
|
+
import { inspectTarget } from "../commands/inspect.js";
|
|
6
|
+
import { runInit } from "../commands/init.js";
|
|
7
|
+
import { runRoute } from "../commands/route.js";
|
|
8
|
+
import { runValidateReceipt } from "../commands/validate-receipt.js";
|
|
9
|
+
import { runValidateProtocol } from "../commands/validate-protocol.js";
|
|
10
|
+
import { runUpdate } from "../commands/update.js";
|
|
11
|
+
import { runActivate } from "../commands/activate.js";
|
|
12
|
+
import { runAdvance } from "../commands/advance.js";
|
|
13
|
+
import { runPreflight } from "../commands/preflight.js";
|
|
14
|
+
import { runComplete } from "../commands/complete.js";
|
|
15
|
+
import { runAudit } from "../commands/audit.js";
|
|
16
|
+
import { runReport } from "../commands/report.js";
|
|
17
|
+
import { runPolicy } from "../commands/policy.js";
|
|
18
|
+
import { runPolicyDiscover } from "../commands/policy-discover.js";
|
|
19
|
+
import { runPolicyStatus } from "../commands/policy-status.js";
|
|
20
|
+
import { runPolicyDiff } from "../commands/policy-diff.js";
|
|
21
|
+
import { runRuleVerify } from "../commands/rule-verify.js";
|
|
22
|
+
import { runBaseline } from "../commands/baseline.js";
|
|
23
|
+
import { runProfileInterview } from "../commands/profile-interview.js";
|
|
24
|
+
import { runBundle } from "../commands/bundle.js";
|
|
25
|
+
import { runPrepareCompletion } from "../commands/prepare-completion.js";
|
|
26
|
+
import { runRecordCheck } from "../commands/record-check.js";
|
|
27
|
+
import { runCheck } from "../commands/run-check.js";
|
|
28
|
+
import { reconcileClosure } from "../commands/reconcile-closure.js";
|
|
29
|
+
import { runRecordTerminalResult } from "../commands/record-terminal-result.js";
|
|
30
|
+
import { runRecordDiagnosis } from "../commands/record-diagnosis.js";
|
|
31
|
+
import { runProgress } from "../commands/progress.js";
|
|
32
|
+
import { runRecordDecisionCriterion } from "../commands/record-decision-criterion.js";
|
|
33
|
+
import { runNext } from "../commands/next.js";
|
|
34
|
+
import { runContinuity } from "../commands/continuity.js";
|
|
35
|
+
import { runRecordContinuity } from "../commands/record-continuity.js";
|
|
36
|
+
import { runReconcileContinuity } from "../commands/reconcile-continuity.js";
|
|
37
|
+
import { runClearContinuity } from "../commands/clear-continuity.js";
|
|
38
|
+
import { runTaskCreate } from "../commands/task-create.js";
|
|
39
|
+
import { runTaskList } from "../commands/task-list.js";
|
|
40
|
+
import { runTaskShow } from "../commands/task-show.js";
|
|
41
|
+
import { runTaskScope } from "../commands/task-scope.js";
|
|
42
|
+
import { runTaskMigrate } from "../commands/task-migrate.js";
|
|
43
|
+
import { runMigrateProtocol } from "../commands/migrate-protocol.js";
|
|
44
|
+
import { runTaskUnlock } from "../commands/task-unlock.js";
|
|
45
|
+
import { runTaskRecover } from "../commands/task-recover.js";
|
|
46
|
+
import { runTaskResume } from "../commands/task-resume.js";
|
|
47
|
+
import { runTaskRepairLegacyRecovery } from "../commands/task-repair-legacy-recovery.js";
|
|
48
|
+
import { runTaskLockStatus } from "../commands/task-lock-status.js";
|
|
49
|
+
import { runProtocolInfo } from "../commands/protocol-info.js";
|
|
50
|
+
|
|
51
|
+
/**
|
|
52
|
+
* Canonical transport-neutral command executors.
|
|
53
|
+
*
|
|
54
|
+
* Every entry executes exactly one ForgeLoop command and returns a
|
|
55
|
+
* structured `{ result, exitCode }` envelope without any terminal output.
|
|
56
|
+
* Exit codes preserve CLI-equivalent semantics: a non-zero exit code is a
|
|
57
|
+
* deterministic protocol/domain outcome, not an invocation failure.
|
|
58
|
+
*
|
|
59
|
+
* The CLI renders these results; MCP and other integrations consume them
|
|
60
|
+
* directly. No ownership, recovery, or lifecycle logic may live here.
|
|
61
|
+
*/
|
|
62
|
+
export const COMMAND_EXECUTORS = {
|
|
63
|
+
"protocol-info": async ({ packageVersion }) => ({
|
|
64
|
+
result: await runProtocolInfo({ packageVersion }),
|
|
65
|
+
exitCode: 0,
|
|
66
|
+
}),
|
|
67
|
+
init: async ({ target, packageRoot, packageVersion, options }) => ({
|
|
68
|
+
result: await runInit({ target, dryRun: options.dryRun, packageRoot, packageVersion }),
|
|
69
|
+
exitCode: 0,
|
|
70
|
+
}),
|
|
71
|
+
doctor: async ({ target, packageRoot, options }) => {
|
|
72
|
+
const result = await runDoctor({
|
|
73
|
+
target,
|
|
74
|
+
packageRoot,
|
|
75
|
+
adoptPaths: options.adopt,
|
|
76
|
+
strict: options.strict,
|
|
77
|
+
fix: options.fix,
|
|
78
|
+
});
|
|
79
|
+
return { result, exitCode: result.ok ? 0 : 1 };
|
|
80
|
+
},
|
|
81
|
+
route: async ({ target, packageRoot, options }) => ({
|
|
82
|
+
result: await runRoute({
|
|
83
|
+
target,
|
|
84
|
+
packageRoot,
|
|
85
|
+
workType: options.workType,
|
|
86
|
+
surfaces: options.surfaces,
|
|
87
|
+
risks: options.risks,
|
|
88
|
+
platforms: options.platforms,
|
|
89
|
+
behaviorChange: options.behaviorChange,
|
|
90
|
+
executableChange: options.executableChange,
|
|
91
|
+
taskId: options.taskId,
|
|
92
|
+
}),
|
|
93
|
+
exitCode: 0,
|
|
94
|
+
}),
|
|
95
|
+
activate: async ({ target, packageRoot }) => ({
|
|
96
|
+
result: await runActivate({ target, packageRoot }),
|
|
97
|
+
exitCode: 0,
|
|
98
|
+
}),
|
|
99
|
+
preflight: async ({ target, packageRoot, options }) => {
|
|
100
|
+
const result = await runPreflight({ target, packageRoot, strict: options.strict, taskId: options.taskId });
|
|
101
|
+
return { result, exitCode: result.status === "READY" ? 0 : 1 };
|
|
102
|
+
},
|
|
103
|
+
advance: async ({ target, packageRoot, options }) => ({
|
|
104
|
+
result: await runAdvance({ target, packageRoot, to: options.to, taskId: options.taskId }),
|
|
105
|
+
exitCode: 0,
|
|
106
|
+
}),
|
|
107
|
+
next: async ({ target, packageRoot, options }) => ({
|
|
108
|
+
result: await runNext({ target, packageRoot, taskId: options.taskId }),
|
|
109
|
+
exitCode: 0,
|
|
110
|
+
}),
|
|
111
|
+
continuity: async ({ target, packageRoot, options }) => ({
|
|
112
|
+
result: await runContinuity({ target, packageRoot, taskId: options.taskId }),
|
|
113
|
+
exitCode: 0,
|
|
114
|
+
}),
|
|
115
|
+
"record-continuity": async ({ target, packageRoot, options }) => ({
|
|
116
|
+
result: await runRecordContinuity({
|
|
117
|
+
target,
|
|
118
|
+
packageRoot,
|
|
119
|
+
focusId: options.continuityFocusId,
|
|
120
|
+
focusSummary: options.continuityFocusSummary,
|
|
121
|
+
remaining: options.continuityRemaining,
|
|
122
|
+
knownIssues: options.continuityKnownIssues,
|
|
123
|
+
changedAreas: options.continuityChangedAreas,
|
|
124
|
+
inspectFirst: options.continuityInspectFirst,
|
|
125
|
+
resumeNote: options.continuityResumeNote,
|
|
126
|
+
taskId: options.taskId,
|
|
127
|
+
}),
|
|
128
|
+
exitCode: 0,
|
|
129
|
+
}),
|
|
130
|
+
"reconcile-continuity": async ({ target, packageRoot, options }) => ({
|
|
131
|
+
result: await runReconcileContinuity({ target, packageRoot, taskId: options.taskId }),
|
|
132
|
+
exitCode: 0,
|
|
133
|
+
}),
|
|
134
|
+
"clear-continuity": async ({ target, options }) => ({
|
|
135
|
+
result: await runClearContinuity({ target, taskId: options.taskId }),
|
|
136
|
+
exitCode: 0,
|
|
137
|
+
}),
|
|
138
|
+
"prepare-completion": async ({ target, packageRoot, options }) => ({
|
|
139
|
+
result: await runPrepareCompletion({ target, packageRoot, taskId: options.taskId }),
|
|
140
|
+
exitCode: 0,
|
|
141
|
+
}),
|
|
142
|
+
"run-check": async ({ target, packageRoot, options }) => {
|
|
143
|
+
const result = await runCheck({
|
|
144
|
+
target,
|
|
145
|
+
packageRoot,
|
|
146
|
+
id: options.checkId,
|
|
147
|
+
requirement: options.checkRequirement,
|
|
148
|
+
argv: options.commandArgv,
|
|
149
|
+
details: options.checkDetails ?? undefined,
|
|
150
|
+
timeoutMs: options.timeoutMs ?? undefined,
|
|
151
|
+
taskId: options.taskId,
|
|
152
|
+
});
|
|
153
|
+
return { result, exitCode: result.check.status === "passed" ? 0 : 1 };
|
|
154
|
+
},
|
|
155
|
+
"record-check": async ({ target, packageRoot, options }) => ({
|
|
156
|
+
result: await runRecordCheck({
|
|
157
|
+
target,
|
|
158
|
+
packageRoot,
|
|
159
|
+
id: options.checkId,
|
|
160
|
+
kind: options.checkKind ?? "command",
|
|
161
|
+
requirement: options.checkRequirement,
|
|
162
|
+
status: options.checkStatus,
|
|
163
|
+
evidenceKind: options.checkEvidenceKind,
|
|
164
|
+
command: options.checkCommand ?? undefined,
|
|
165
|
+
result: options.checkResult ?? undefined,
|
|
166
|
+
...(options.checkExitCode === null ? {} : { exitCode: options.checkExitCode }),
|
|
167
|
+
details: options.checkDetails ?? undefined,
|
|
168
|
+
executionRef: options.checkExecutionRef ?? undefined,
|
|
169
|
+
provenance: options.checkProvenance ?? undefined,
|
|
170
|
+
taskId: options.taskId,
|
|
171
|
+
}),
|
|
172
|
+
exitCode: 0,
|
|
173
|
+
}),
|
|
174
|
+
"record-terminal-result": async ({ target, packageRoot, options }) => ({
|
|
175
|
+
result: await runRecordTerminalResult({
|
|
176
|
+
target,
|
|
177
|
+
packageRoot,
|
|
178
|
+
requirement: options.checkRequirement,
|
|
179
|
+
type: options.checkType,
|
|
180
|
+
status: options.checkStatus,
|
|
181
|
+
source: options.checkSource ?? options.checkCommand,
|
|
182
|
+
result: options.checkResult,
|
|
183
|
+
details: options.checkDetails ?? undefined,
|
|
184
|
+
taskId: options.taskId,
|
|
185
|
+
}),
|
|
186
|
+
exitCode: 0,
|
|
187
|
+
}),
|
|
188
|
+
"record-diagnosis": async ({ target, packageRoot, options }) => ({
|
|
189
|
+
result: await runRecordDiagnosis({
|
|
190
|
+
target,
|
|
191
|
+
packageRoot,
|
|
192
|
+
hypothesis: options.hypothesis,
|
|
193
|
+
failureClass: options.failureClass,
|
|
194
|
+
evidenceRefs: options.evidenceRefs,
|
|
195
|
+
settledBy: options.settledBy,
|
|
196
|
+
nextSafeAction: options.nextSafeAction,
|
|
197
|
+
taskId: options.taskId,
|
|
198
|
+
}),
|
|
199
|
+
exitCode: 0,
|
|
200
|
+
}),
|
|
201
|
+
progress: async ({ target, packageRoot, options }) => {
|
|
202
|
+
const result = await runProgress({ target, packageRoot, taskId: options.taskId });
|
|
203
|
+
return { result, exitCode: result.status === "STALLED" ? 1 : 0 };
|
|
204
|
+
},
|
|
205
|
+
"record-decision-criterion": async ({ target, packageRoot, options }) => ({
|
|
206
|
+
result: await runRecordDecisionCriterion({
|
|
207
|
+
target,
|
|
208
|
+
packageRoot,
|
|
209
|
+
decision: options.decision,
|
|
210
|
+
settledBy: options.settledBy,
|
|
211
|
+
taskId: options.taskId,
|
|
212
|
+
}),
|
|
213
|
+
exitCode: 0,
|
|
214
|
+
}),
|
|
215
|
+
complete: async ({ target, packageRoot, options }) => {
|
|
216
|
+
const result = await runComplete({ target, packageRoot, strict: options.strict, taskId: options.taskId });
|
|
217
|
+
return { result, exitCode: result.status === "VALID" ? 0 : 1 };
|
|
218
|
+
},
|
|
219
|
+
audit: async ({ target, packageRoot, options }) => {
|
|
220
|
+
const result = await runAudit({ target, packageRoot, strict: options.strict, taskId: options.taskId });
|
|
221
|
+
return { result, exitCode: result.status === "VALID" ? 0 : 1 };
|
|
222
|
+
},
|
|
223
|
+
report: async ({ target, packageRoot, options }) => {
|
|
224
|
+
const result = await runReport({ target, packageRoot, strict: options.strict, taskId: options.taskId });
|
|
225
|
+
return { result, exitCode: result.verdict === "VALID" ? 0 : 1 };
|
|
226
|
+
},
|
|
227
|
+
policy: async ({ target, packageRoot, options }) => ({
|
|
228
|
+
result: await runPolicy({ target, packageRoot, name: options.policy, taskId: options.taskId }),
|
|
229
|
+
exitCode: 0,
|
|
230
|
+
}),
|
|
231
|
+
"policy-discover": async ({ target, packageRoot, options }) => ({
|
|
232
|
+
result: await runPolicyDiscover({ target, packageRoot, write: options.write }),
|
|
233
|
+
exitCode: 0,
|
|
234
|
+
}),
|
|
235
|
+
"policy-status": async ({ target, packageRoot, options }) => {
|
|
236
|
+
const result = await runPolicyStatus({ target, packageRoot, taskId: options.taskId });
|
|
237
|
+
return { result, exitCode: result.status === "VALID" ? 0 : 1 };
|
|
238
|
+
},
|
|
239
|
+
"policy-diff": async ({ target, packageRoot, options }) => ({
|
|
240
|
+
result: await runPolicyDiff({ target, packageRoot, taskId: options.taskId, before: options.before, after: options.after }),
|
|
241
|
+
exitCode: 0,
|
|
242
|
+
}),
|
|
243
|
+
"rule-verify": async ({ target, packageRoot, options }) => {
|
|
244
|
+
const result = await runRuleVerify({ target, packageRoot, rule: options.rule });
|
|
245
|
+
return { result, exitCode: result.status === "VALID" ? 0 : 1 };
|
|
246
|
+
},
|
|
247
|
+
baseline: async ({ target, packageRoot, options }) => ({
|
|
248
|
+
result: await runBaseline({
|
|
249
|
+
target,
|
|
250
|
+
packageRoot,
|
|
251
|
+
record: options.record,
|
|
252
|
+
update: options.update,
|
|
253
|
+
policyResetAuthorized: options.policyResetAuthorized,
|
|
254
|
+
}),
|
|
255
|
+
exitCode: 0,
|
|
256
|
+
}),
|
|
257
|
+
"profile-interview": async ({ target, packageRoot, options }) => ({
|
|
258
|
+
result: await runProfileInterview({ target, packageRoot, dryRun: options.dryRun }),
|
|
259
|
+
exitCode: 0,
|
|
260
|
+
}),
|
|
261
|
+
bundle: async ({ target, packageRoot, options }) => ({
|
|
262
|
+
result: await runBundle({ target, packageRoot, taskId: options.taskId }),
|
|
263
|
+
exitCode: 0,
|
|
264
|
+
}),
|
|
265
|
+
inspect: async ({ target, packageRoot, options }) => {
|
|
266
|
+
const result = await inspectTarget({ target, packageRoot, contractFile: options.contractFile, taskId: options.taskId });
|
|
267
|
+
return { result, exitCode: result.ok ? 0 : 1 };
|
|
268
|
+
},
|
|
269
|
+
"validate-receipt": async ({ target, packageRoot, options }) => ({
|
|
270
|
+
result: await runValidateReceipt({ target, packageRoot, file: options.file, taskId: options.taskId }),
|
|
271
|
+
exitCode: 0,
|
|
272
|
+
}),
|
|
273
|
+
"validate-protocol": async ({ target, packageRoot, options }) => {
|
|
274
|
+
const result = await runValidateProtocol({
|
|
275
|
+
target,
|
|
276
|
+
packageRoot,
|
|
277
|
+
stateFile: options.stateFile,
|
|
278
|
+
receiptFile: options.receiptFile,
|
|
279
|
+
routeFile: options.routeFile,
|
|
280
|
+
contractFile: options.contractFile,
|
|
281
|
+
continuityFile: options.continuityFile,
|
|
282
|
+
taskBriefFiles: options.taskBriefFiles,
|
|
283
|
+
delegatedResultFiles: options.delegatedResultFiles,
|
|
284
|
+
taskId: options.taskId,
|
|
285
|
+
});
|
|
286
|
+
return { result, exitCode: result.status === "VALID" ? 0 : 1 };
|
|
287
|
+
},
|
|
288
|
+
status: async ({ target, packageRoot, options }) => ({
|
|
289
|
+
result: await runStatus({ target, packageRoot, contractFile: options.contractFile, taskId: options.taskId }),
|
|
290
|
+
exitCode: 0,
|
|
291
|
+
}),
|
|
292
|
+
"validate-state": async ({ target, packageRoot, options }) => {
|
|
293
|
+
const result = await runValidateState({ target, packageRoot, taskId: options.taskId });
|
|
294
|
+
return { result, exitCode: result.ok ? 0 : 1 };
|
|
295
|
+
},
|
|
296
|
+
"clear-state": async ({ target, options }) => ({
|
|
297
|
+
result: await runClearState({ target, taskId: options.taskId }),
|
|
298
|
+
exitCode: 0,
|
|
299
|
+
}),
|
|
300
|
+
"task-create": async ({ target, packageRoot, options }) => ({
|
|
301
|
+
result: await runTaskCreate({
|
|
302
|
+
target,
|
|
303
|
+
packageRoot,
|
|
304
|
+
taskId: options.taskId,
|
|
305
|
+
claims: options.claims,
|
|
306
|
+
contractFile: options.contractFile,
|
|
307
|
+
}),
|
|
308
|
+
exitCode: 0,
|
|
309
|
+
}),
|
|
310
|
+
"task-list": async ({ target, packageRoot }) => ({
|
|
311
|
+
result: await runTaskList({ target, packageRoot }),
|
|
312
|
+
exitCode: 0,
|
|
313
|
+
}),
|
|
314
|
+
"task-show": async ({ target, packageRoot, options }) => ({
|
|
315
|
+
result: await runTaskShow({ target, packageRoot, taskId: options.taskId }),
|
|
316
|
+
exitCode: 0,
|
|
317
|
+
}),
|
|
318
|
+
"task-lock-status": async ({ target, packageRoot, options }) => ({
|
|
319
|
+
result: await runTaskLockStatus({ target, packageRoot, taskId: options.taskId }),
|
|
320
|
+
exitCode: 0,
|
|
321
|
+
}),
|
|
322
|
+
"task-scope": async ({ target, packageRoot, options }) => ({
|
|
323
|
+
result: await runTaskScope({
|
|
324
|
+
target,
|
|
325
|
+
packageRoot,
|
|
326
|
+
taskId: options.taskId,
|
|
327
|
+
claims: options.claims,
|
|
328
|
+
}),
|
|
329
|
+
exitCode: 0,
|
|
330
|
+
}),
|
|
331
|
+
"task-migrate": async ({ target, packageRoot, options }) => ({
|
|
332
|
+
result: await runTaskMigrate({ target, packageRoot, dryRun: options.dryRun }),
|
|
333
|
+
exitCode: 0,
|
|
334
|
+
}),
|
|
335
|
+
"migrate-protocol": async ({ target, packageRoot, options }) => ({
|
|
336
|
+
result: await runMigrateProtocol({ target, packageRoot, to: options.to, dryRun: options.dryRun }),
|
|
337
|
+
exitCode: 0,
|
|
338
|
+
}),
|
|
339
|
+
"task-unlock": async ({ target, packageRoot, options }) => ({
|
|
340
|
+
result: await runTaskUnlock({ target, packageRoot, taskId: options.taskId, force: options.force, staleOnly: options.staleOnly }),
|
|
341
|
+
exitCode: 0,
|
|
342
|
+
}),
|
|
343
|
+
"task-recover": async ({ target, packageRoot, options }) => ({
|
|
344
|
+
result: await runTaskRecover({
|
|
345
|
+
target,
|
|
346
|
+
packageRoot,
|
|
347
|
+
taskId: options.taskId,
|
|
348
|
+
acknowledgeRecovery: options.acknowledgeRecovery,
|
|
349
|
+
operatorAuthorized: options.operatorAuthorized,
|
|
350
|
+
}),
|
|
351
|
+
exitCode: 0,
|
|
352
|
+
}),
|
|
353
|
+
"task-resume": async ({ target, packageRoot, options }) => ({
|
|
354
|
+
result: await runTaskResume({ target, packageRoot, taskId: options.taskId, claims: options.claims }),
|
|
355
|
+
exitCode: 0,
|
|
356
|
+
}),
|
|
357
|
+
"task-repair-legacy-recovery": async ({ target, packageRoot, options }) => ({
|
|
358
|
+
result: await runTaskRepairLegacyRecovery({
|
|
359
|
+
target,
|
|
360
|
+
packageRoot,
|
|
361
|
+
taskId: options.taskId,
|
|
362
|
+
acknowledgeRecovery: options.acknowledgeRecovery,
|
|
363
|
+
}),
|
|
364
|
+
exitCode: 0,
|
|
365
|
+
}),
|
|
366
|
+
"reconcile-closure": async ({ target, packageRoot, options }) => ({
|
|
367
|
+
result: await reconcileClosure({
|
|
368
|
+
target,
|
|
369
|
+
packageRoot,
|
|
370
|
+
taskId: options.taskId,
|
|
371
|
+
checkId: options.checkId,
|
|
372
|
+
checkRequirement: options.checkRequirement,
|
|
373
|
+
checkDetails: options.checkDetails,
|
|
374
|
+
commandArgv: options.commandArgv,
|
|
375
|
+
}),
|
|
376
|
+
exitCode: 0,
|
|
377
|
+
}),
|
|
378
|
+
update: async ({ target, packageRoot, packageVersion, options }) => {
|
|
379
|
+
const result = await runUpdate({ target, dryRun: options.dryRun, packageRoot, packageVersion });
|
|
380
|
+
return { result, exitCode: result.conflicts.length === 0 ? 0 : 1 };
|
|
381
|
+
},
|
|
382
|
+
};
|
|
383
|
+
|
|
384
|
+
export const EXECUTOR_EXCEPTIONS = Object.freeze([
|
|
385
|
+
// Bootstrap/presentation-only behaviors intentionally without executors:
|
|
386
|
+
// none currently. Every canonical command definition must have an executor.
|
|
387
|
+
]);
|
|
@@ -0,0 +1,107 @@
|
|
|
1
|
+
import { continuityOptionDefaults, validateContinuityOptions } from "./continuity-cli-options.js";
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* Transport-neutral option defaults shared by the CLI parser and the
|
|
5
|
+
* programmatic command runtime so every executor observes the same
|
|
6
|
+
* normalized input shape regardless of transport.
|
|
7
|
+
*/
|
|
8
|
+
export function defaultCommandInputValues() {
|
|
9
|
+
return {
|
|
10
|
+
path: ".",
|
|
11
|
+
dryRun: false,
|
|
12
|
+
json: false,
|
|
13
|
+
strict: false,
|
|
14
|
+
fix: false,
|
|
15
|
+
adopt: [],
|
|
16
|
+
workType: null,
|
|
17
|
+
surfaces: [],
|
|
18
|
+
risks: [],
|
|
19
|
+
platforms: [],
|
|
20
|
+
behaviorChange: false,
|
|
21
|
+
executableChange: false,
|
|
22
|
+
to: null,
|
|
23
|
+
file: null,
|
|
24
|
+
contractFile: null,
|
|
25
|
+
routeFile: null,
|
|
26
|
+
stateFile: null,
|
|
27
|
+
receiptFile: null,
|
|
28
|
+
continuityFile: null,
|
|
29
|
+
taskBriefFiles: [],
|
|
30
|
+
delegatedResultFiles: [],
|
|
31
|
+
checkId: null,
|
|
32
|
+
checkKind: null,
|
|
33
|
+
checkRequirement: null,
|
|
34
|
+
checkStatus: null,
|
|
35
|
+
checkEvidenceKind: null,
|
|
36
|
+
checkCommand: null,
|
|
37
|
+
checkResult: null,
|
|
38
|
+
checkExitCode: null,
|
|
39
|
+
checkDetails: null,
|
|
40
|
+
checkExecutionRef: null,
|
|
41
|
+
checkProvenance: null,
|
|
42
|
+
commandArgv: [],
|
|
43
|
+
checkType: null,
|
|
44
|
+
checkSource: null,
|
|
45
|
+
policy: null,
|
|
46
|
+
taskId: null,
|
|
47
|
+
help: false,
|
|
48
|
+
version: false,
|
|
49
|
+
...continuityOptionDefaults(),
|
|
50
|
+
};
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
/**
|
|
54
|
+
* Canonical semantic validation shared by the CLI argv parser and
|
|
55
|
+
* programmatic integrations. JSON-Schema style checks cannot express these
|
|
56
|
+
* cross-field rules, so every transport must run this after its own parsing.
|
|
57
|
+
*/
|
|
58
|
+
export function validateForgeLoopCommandInput({ command, input, help = false } = {}) {
|
|
59
|
+
const options = input ?? {};
|
|
60
|
+
if (!command) return;
|
|
61
|
+
|
|
62
|
+
if (command === "policy" && !options.policy) {
|
|
63
|
+
throw new Error("policy requires a name");
|
|
64
|
+
}
|
|
65
|
+
if (command !== "policy" && options.policy) {
|
|
66
|
+
throw new Error(`Policy name is not valid for ${command}`);
|
|
67
|
+
}
|
|
68
|
+
if (command === "bundle" && !options.taskId) {
|
|
69
|
+
throw new Error("bundle requires --task");
|
|
70
|
+
}
|
|
71
|
+
if (command === "task-create" && !options.taskId) {
|
|
72
|
+
throw new Error("task-create requires --task");
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
validateContinuityOptions(command, options);
|
|
76
|
+
|
|
77
|
+
if (command === "record-check" && !help) {
|
|
78
|
+
if (!options.checkId) throw new Error("record-check requires --id");
|
|
79
|
+
if (!options.checkRequirement) throw new Error("record-check requires --requirement");
|
|
80
|
+
if (!options.checkStatus) throw new Error("record-check requires --status");
|
|
81
|
+
if (!options.checkEvidenceKind) throw new Error("record-check requires --evidence-kind");
|
|
82
|
+
if (!options.checkCommand && !options.checkResult) throw new Error("record-check requires --command or --result");
|
|
83
|
+
}
|
|
84
|
+
if (command === "run-check" && !help) {
|
|
85
|
+
if (!options.checkId) throw new Error("run-check requires --id");
|
|
86
|
+
if (!options.checkRequirement) throw new Error("run-check requires --requirement");
|
|
87
|
+
if (options.checkKind || options.checkStatus || options.checkEvidenceKind || options.checkCommand
|
|
88
|
+
|| options.checkResult || options.checkExitCode !== null || options.checkExecutionRef || options.checkProvenance) {
|
|
89
|
+
throw new Error("run-check accepts only --id, --requirement, --details, --timeout-ms, and -- <argv>");
|
|
90
|
+
}
|
|
91
|
+
if (!Array.isArray(options.commandArgv) || options.commandArgv.length === 0) {
|
|
92
|
+
throw new Error("run-check requires -- followed by an exact command argv");
|
|
93
|
+
}
|
|
94
|
+
}
|
|
95
|
+
if (command === "reconcile-closure" && !help) {
|
|
96
|
+
if (!options.taskId) throw new Error("reconcile-closure requires --task");
|
|
97
|
+
if (!options.checkId) throw new Error("reconcile-closure requires --id");
|
|
98
|
+
if (!options.checkRequirement) throw new Error("reconcile-closure requires --requirement");
|
|
99
|
+
if (options.checkKind || options.checkStatus || options.checkEvidenceKind || options.checkCommand
|
|
100
|
+
|| options.checkResult || options.checkExitCode !== null || options.checkExecutionRef || options.checkProvenance) {
|
|
101
|
+
throw new Error("reconcile-closure accepts only --id, --requirement, --details, and -- <argv>");
|
|
102
|
+
}
|
|
103
|
+
if (!Array.isArray(options.commandArgv) || options.commandArgv.length === 0) {
|
|
104
|
+
throw new Error("reconcile-closure requires -- followed by an exact command argv");
|
|
105
|
+
}
|
|
106
|
+
}
|
|
107
|
+
}
|
|
@@ -0,0 +1,106 @@
|
|
|
1
|
+
import { resolveTarget } from "./filesystem.js";
|
|
2
|
+
import { getPackageRoot } from "./templates.js";
|
|
3
|
+
import { CLI_COMMAND_DEFINITIONS } from "./cli-command-definitions.js";
|
|
4
|
+
import { COMMAND_EXECUTORS } from "./command-executors.js";
|
|
5
|
+
import { defaultCommandInputValues, validateForgeLoopCommandInput } from "./command-input.js";
|
|
6
|
+
import { PROTOCOL_VERSION } from "./protocol.js";
|
|
7
|
+
|
|
8
|
+
export const FORGELOOP_INTEGRATION_RUNTIME_VERSION = 1;
|
|
9
|
+
|
|
10
|
+
async function readPackageVersion(packageRoot) {
|
|
11
|
+
const { readFile } = await import("node:fs/promises");
|
|
12
|
+
const path = await import("node:path");
|
|
13
|
+
const packageJson = JSON.parse(await readFile(path.join(packageRoot, "package.json"), "utf8"));
|
|
14
|
+
return packageJson.version;
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
/**
|
|
18
|
+
* Transport-neutral ForgeLoop command execution.
|
|
19
|
+
*
|
|
20
|
+
* Resolves the project target, validates the command and its structured
|
|
21
|
+
* input, invokes the canonical executor, and returns a deterministic
|
|
22
|
+
* envelope. Performs zero terminal output, never spawns a shell to run
|
|
23
|
+
* ForgeLoop itself, never bypasses task ownership/recovery guards, and never
|
|
24
|
+
* retries mutations.
|
|
25
|
+
*
|
|
26
|
+
* A non-zero exitCode is a protocol/domain outcome (e.g. preflight BLOCKED),
|
|
27
|
+
* not an invocation failure: `ok` stays true. `ok:false` means the command
|
|
28
|
+
* could not be executed (unknown command, invalid input, canonical error).
|
|
29
|
+
*/
|
|
30
|
+
export async function executeForgeLoopCommand({
|
|
31
|
+
command,
|
|
32
|
+
projectPath = ".",
|
|
33
|
+
input = {},
|
|
34
|
+
} = {}) {
|
|
35
|
+
const metadata = Object.freeze({
|
|
36
|
+
protocolVersion: PROTOCOL_VERSION,
|
|
37
|
+
integrationApiVersion: FORGELOOP_INTEGRATION_RUNTIME_VERSION,
|
|
38
|
+
});
|
|
39
|
+
|
|
40
|
+
if (typeof command !== "string" || !CLI_COMMAND_DEFINITIONS[command]) {
|
|
41
|
+
return {
|
|
42
|
+
ok: false,
|
|
43
|
+
command: command ?? null,
|
|
44
|
+
exitCode: 1,
|
|
45
|
+
result: null,
|
|
46
|
+
error: { code: "E_COMMAND_UNSUPPORTED", message: `Unsupported ForgeLoop command: ${command ?? "(missing)"}` },
|
|
47
|
+
metadata,
|
|
48
|
+
};
|
|
49
|
+
}
|
|
50
|
+
if (CLI_COMMAND_DEFINITIONS[command].bootstrapOnly) {
|
|
51
|
+
return {
|
|
52
|
+
ok: false,
|
|
53
|
+
command,
|
|
54
|
+
exitCode: 1,
|
|
55
|
+
result: null,
|
|
56
|
+
error: { code: "E_COMMAND_UNSUPPORTED", message: `Command ${command} is not available through the programmatic runtime` },
|
|
57
|
+
metadata,
|
|
58
|
+
};
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
const executor = COMMAND_EXECUTORS[command];
|
|
62
|
+
if (typeof executor !== "function") {
|
|
63
|
+
return {
|
|
64
|
+
ok: false,
|
|
65
|
+
command,
|
|
66
|
+
exitCode: 1,
|
|
67
|
+
result: null,
|
|
68
|
+
error: { code: "E_COMMAND_UNSUPPORTED", message: `Command ${command} has no canonical executor` },
|
|
69
|
+
metadata,
|
|
70
|
+
};
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
try {
|
|
74
|
+
const options = {
|
|
75
|
+
...defaultCommandInputValues(),
|
|
76
|
+
...(input ?? {}),
|
|
77
|
+
};
|
|
78
|
+
validateForgeLoopCommandInput({ command, input: options });
|
|
79
|
+
|
|
80
|
+
const target = await resolveTarget(process.cwd(), projectPath);
|
|
81
|
+
const packageRoot = getPackageRoot();
|
|
82
|
+
const packageVersion = await readPackageVersion(packageRoot);
|
|
83
|
+
|
|
84
|
+
const { result, exitCode } = await executor({ target, packageRoot, packageVersion, options });
|
|
85
|
+
return {
|
|
86
|
+
ok: true,
|
|
87
|
+
command,
|
|
88
|
+
exitCode,
|
|
89
|
+
result,
|
|
90
|
+
error: null,
|
|
91
|
+
metadata: Object.freeze({ ...metadata, packageVersion }),
|
|
92
|
+
};
|
|
93
|
+
} catch (error) {
|
|
94
|
+
return {
|
|
95
|
+
ok: false,
|
|
96
|
+
command,
|
|
97
|
+
exitCode: 1,
|
|
98
|
+
result: null,
|
|
99
|
+
error: {
|
|
100
|
+
code: error.code ?? "E_COMMAND_EXECUTION_FAILED",
|
|
101
|
+
message: error.message,
|
|
102
|
+
},
|
|
103
|
+
metadata,
|
|
104
|
+
};
|
|
105
|
+
}
|
|
106
|
+
}
|
|
@@ -22,7 +22,6 @@ import { normalizeRequirements } from "./evidence-readiness.js";
|
|
|
22
22
|
import { classifyCommandResolution, validateVerificationAuthority } from "./verification-capability.js";
|
|
23
23
|
import { readExecutionArtifact, validateExecutionBinding } from "./execution.js";
|
|
24
24
|
import { taskArtifactPath, taskExecutionPath } from "./task-paths.js";
|
|
25
|
-
import { readTaskDescriptor } from "./task-descriptor.js";
|
|
26
25
|
import { assertClaimsCoverChangedPaths } from "./task-scope.js";
|
|
27
26
|
import { discoverTasks } from "./task-discovery.js";
|
|
28
27
|
|
|
@@ -254,8 +253,8 @@ export async function prepareCompletion({
|
|
|
254
253
|
|
|
255
254
|
let writeClaims = [];
|
|
256
255
|
try {
|
|
257
|
-
const
|
|
258
|
-
writeClaims =
|
|
256
|
+
const tasks = taskId ? await discoverTasks(target, packageRoot) : [];
|
|
257
|
+
writeClaims = tasks.find((task) => task.taskId === taskId && task.healthy !== false)?.writeClaims ?? [];
|
|
259
258
|
} catch {
|
|
260
259
|
// descriptor may not exist
|
|
261
260
|
}
|