@cassiomc1/forgeloop 1.13.0 → 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 (211) hide show
  1. package/AGENT_COMPATIBILITY.md +8 -0
  2. package/DOCS_INDEX.md +35 -3
  3. package/ENG/nodejs-backend-development-eng.md +2 -2
  4. package/ENG/sec-code-eng.md +7 -7
  5. package/EXECUTION_STATE.md +12 -0
  6. package/LOOP_ENGINEERING.md +28 -2
  7. package/ORCHESTRATOR_INTEGRATION.md +9 -5
  8. package/PROTOCOL_INTEGRATION.md +55 -2
  9. package/README.md +40 -25
  10. package/TERMINOLOGY.md +2 -0
  11. package/THREAT_MODEL.md +140 -1
  12. package/completions/_forgeloop +19 -1
  13. package/completions/forgeloop.bash +37 -1
  14. package/completions/forgeloop.fish +123 -1
  15. package/docs/ADVISORY_CONTEXT.md +25 -0
  16. package/docs/AGENT_BROWSER_ADAPTER.md +81 -0
  17. package/docs/AGENT_BROWSER_VERIFICATION.md +6 -0
  18. package/docs/AGENT_PROTOCOL_SUMMARY.md +27 -2
  19. package/docs/AGENT_SKILL.md +66 -0
  20. package/docs/ARTIFACT_REFERENCE.md +123 -0
  21. package/docs/AUDIT_UX.md +46 -0
  22. package/docs/BROWSER_VERIFICATION.md +136 -0
  23. package/docs/CLI_REFERENCE.md +366 -6
  24. package/docs/CODE_ATTESTATION.md +2 -2
  25. package/docs/DOCUMENTATION_GUIDE.md +32 -11
  26. package/docs/JEV_BENCHMARKS.md +31 -0
  27. package/docs/MODEL_ROUTING.md +37 -0
  28. package/docs/OPENSRC_ADAPTER.md +241 -0
  29. package/docs/PACKAGE_CONTENTS.md +35 -8
  30. package/docs/PROVIDERS.md +126 -0
  31. package/docs/PROVIDER_ARCHITECTURE.md +199 -0
  32. package/docs/RECIPES.md +9 -0
  33. package/docs/RELEASE_CHECKLIST.md +38 -5
  34. package/docs/SECURITY_REVIEW.md +71 -0
  35. package/docs/SEMANTIC_DECISION_PLANE.md +71 -0
  36. package/docs/TEST_INTELLIGENCE.md +29 -0
  37. package/docs/TEST_PRUNING.md +14 -0
  38. package/docs/TROUBLESHOOTING.md +198 -1
  39. package/docs/UNIVERSAL_INTEGRATION.md +31 -0
  40. package/docs/assets/diagrams/forgeloop-code-attestation-flow.html +2 -2
  41. package/docs/assets/diagrams/forgeloop-code-attestation-flow.receipt.json +5 -5
  42. package/docs/assets/diagrams/forgeloop-code-attestation-flow.svg +1 -1
  43. package/docs/assets/diagrams/forgeloop-engineering-flow.html +39 -26
  44. package/docs/assets/diagrams/forgeloop-engineering-flow.receipt.json +6 -6
  45. package/docs/assets/diagrams/forgeloop-engineering-flow.svg +26 -26
  46. package/docs/assets/diagrams/forgeloop-verification-trust-flow.html +2 -1
  47. package/docs/assets/diagrams/forgeloop-verification-trust-flow.receipt.json +5 -5
  48. package/docs/assets/diagrams/forgeloop-verification-trust-flow.svg +1 -1
  49. package/docs/diagrams/README.md +13 -9
  50. package/docs/diagrams/forgeloop-code-attestation-flow.workflow.json +1 -1
  51. package/docs/diagrams/forgeloop-engineering-flow.workflow.json +24 -19
  52. package/docs/diagrams/forgeloop-verification-trust-flow.workflow.json +1 -0
  53. package/docs/diagrams/reviews/forgeloop-code-attestation-flow.review.json +4 -4
  54. package/docs/diagrams/reviews/forgeloop-engineering-flow.review.json +4 -4
  55. package/docs/diagrams/reviews/forgeloop-verification-trust-flow.review.json +4 -4
  56. package/docs/documentation-manifest.json +750 -5
  57. package/docs/protocol-requirements.json +24 -0
  58. package/package.json +30 -3
  59. package/schemas/config.schema.json +14 -0
  60. package/schemas/context-plan.schema.json +18 -0
  61. package/schemas/semantic-decision.schema.json +46 -0
  62. package/schemas/test-utility.schema.json +44 -0
  63. package/scripts/CI_VALIDATORS.md +6 -6
  64. package/scripts/benchmark-jev.mjs +5 -0
  65. package/scripts/benchmark-test-intelligence.mjs +4 -0
  66. package/scripts/generate-agent-protocol-summary.mjs +4 -1
  67. package/scripts/generate-forgeloop-skill.mjs +133 -0
  68. package/scripts/jev-smoke.mjs +19 -0
  69. package/skills/forgeloop/README.md +9 -0
  70. package/skills/forgeloop/SKILL.md +77 -0
  71. package/skills/forgeloop/references/lifecycle.md +9 -0
  72. package/skills/forgeloop/references/recovery.md +7 -0
  73. package/skills/forgeloop/references/verification.md +7 -0
  74. package/src/adapters/agent-browser/assertions.js +47 -0
  75. package/src/adapters/agent-browser/commands.js +54 -0
  76. package/src/adapters/agent-browser/index.js +3 -0
  77. package/src/adapters/agent-browser/locator.js +40 -0
  78. package/src/adapters/agent-browser/process.js +215 -0
  79. package/src/adapters/agent-browser/provider.js +313 -0
  80. package/src/adapters/emulated-services/constants.js +24 -0
  81. package/src/adapters/emulated-services/index.js +7 -0
  82. package/src/adapters/emulated-services/process.js +162 -0
  83. package/src/adapters/emulated-services/provider.js +282 -0
  84. package/src/adapters/opensrc/normalize.js +90 -0
  85. package/src/adapters/opensrc/process.js +248 -0
  86. package/src/adapters/opensrc/provider.js +338 -0
  87. package/src/adapters/opensrc/search.js +264 -0
  88. package/src/adapters/typesafe/client.js +28 -0
  89. package/src/adapters/typesafe/engine.js +63 -0
  90. package/src/adapters/typesafe/normalize.js +41 -0
  91. package/src/cli.js +108 -0
  92. package/src/commands/checkpoint-revalidate.js +176 -0
  93. package/src/commands/context-plan.js +38 -0
  94. package/src/commands/contract-create.js +264 -0
  95. package/src/commands/contract-revise.js +236 -0
  96. package/src/commands/decision-show.js +14 -0
  97. package/src/commands/decision-status.js +22 -0
  98. package/src/commands/discover.js +41 -0
  99. package/src/commands/doctor.js +15 -0
  100. package/src/commands/gate-record.js +205 -0
  101. package/src/commands/gate-revalidate.js +137 -0
  102. package/src/commands/model-route.js +32 -0
  103. package/src/commands/route.js +146 -18
  104. package/src/commands/semantic-plan.js +17 -0
  105. package/src/commands/task-abandon.js +224 -0
  106. package/src/commands/task-migrate-contract-bootstrap-repair.js +288 -0
  107. package/src/commands/task-repair-contract-bootstrap.js +263 -0
  108. package/src/commands/test-inventory.js +5 -0
  109. package/src/commands/test-prune-plan.js +5 -0
  110. package/src/commands/test-prune-probe.js +5 -0
  111. package/src/commands/test-utility.js +5 -0
  112. package/src/commands/validate-protocol.js +10 -1
  113. package/src/core/artifact-registry.js +24 -0
  114. package/src/core/audit-ux.js +514 -0
  115. package/src/core/browser-verification/constants.js +149 -0
  116. package/src/core/browser-verification/normalize.js +254 -0
  117. package/src/core/browser-verification/provider.js +519 -0
  118. package/src/core/browser-verification/service.js +115 -0
  119. package/src/core/checkpoint-revalidation.js +319 -0
  120. package/src/core/cli-command-definitions.js +241 -0
  121. package/src/core/command-executors.js +110 -0
  122. package/src/core/command-input.js +115 -43
  123. package/src/core/completion-artifacts.js +14 -5
  124. package/src/core/completion.js +4 -6
  125. package/src/core/config.js +3 -0
  126. package/src/core/context-compiler/budget.js +9 -0
  127. package/src/core/context-compiler/candidates.js +39 -0
  128. package/src/core/context-compiler/compiler.js +63 -0
  129. package/src/core/context-compiler/fingerprint.js +11 -0
  130. package/src/core/context-compiler/policy.js +13 -0
  131. package/src/core/context-compiler/result.js +23 -0
  132. package/src/core/contract-bootstrap-recovery.js +655 -0
  133. package/src/core/contract-revision.js +210 -0
  134. package/src/core/decision/artifact.js +69 -0
  135. package/src/core/decision/benchmarks.js +103 -0
  136. package/src/core/decision/cache.js +27 -0
  137. package/src/core/decision/constants.js +58 -0
  138. package/src/core/decision/cutover.js +34 -0
  139. package/src/core/decision/engine.js +22 -0
  140. package/src/core/decision/errors.js +68 -0
  141. package/src/core/decision/events.js +101 -0
  142. package/src/core/decision/freshness.js +19 -0
  143. package/src/core/decision/normalizers/index.js +115 -0
  144. package/src/core/decision/policy.js +18 -0
  145. package/src/core/decision/projection.js +16 -0
  146. package/src/core/decision/question-registry.js +201 -0
  147. package/src/core/decision/request.js +26 -0
  148. package/src/core/decision/resolver.js +130 -0
  149. package/src/core/decision/result.js +58 -0
  150. package/src/core/decision/service.js +156 -0
  151. package/src/core/decision/state-builder.js +65 -0
  152. package/src/core/decision/task-bindings.js +30 -0
  153. package/src/core/decision/test-provider.js +32 -0
  154. package/src/core/decision/thresholds.js +15 -0
  155. package/src/core/error-codes.js +278 -0
  156. package/src/core/events.js +226 -57
  157. package/src/core/evidence-readiness.js +9 -0
  158. package/src/core/execution-prerequisites.js +14 -0
  159. package/src/core/execution-profile.js +63 -38
  160. package/src/core/gate-provenance.js +124 -0
  161. package/src/core/integration-invocation-policy.js +27 -4
  162. package/src/core/integration-resources.js +86 -61
  163. package/src/core/model-router/constants.js +10 -0
  164. package/src/core/model-router/policy.js +103 -0
  165. package/src/core/model-router/router.js +37 -0
  166. package/src/core/next-action-model.js +58 -0
  167. package/src/core/next-action-phases.js +130 -42
  168. package/src/core/next-action-refresh.js +43 -9
  169. package/src/core/next-action-review-phase.js +7 -2
  170. package/src/core/next-action.js +35 -7
  171. package/src/core/phase.js +128 -10
  172. package/src/core/preflight-consistency.js +23 -9
  173. package/src/core/preflight-loaders.js +37 -5
  174. package/src/core/protocol-info.js +65 -0
  175. package/src/core/protocol.js +20 -0
  176. package/src/core/reconcile-closure.js +128 -52
  177. package/src/core/recovery-history.js +1 -0
  178. package/src/core/resumability.js +154 -44
  179. package/src/core/route-artifact.js +15 -1
  180. package/src/core/router.js +67 -1
  181. package/src/core/runtime-context.js +118 -61
  182. package/src/core/schema-validation.js +3 -0
  183. package/src/core/security-review/constants.js +64 -0
  184. package/src/core/security-review/normalize.js +245 -0
  185. package/src/core/security-review/provider.js +204 -0
  186. package/src/core/security-review/service.js +134 -0
  187. package/src/core/semantic-planning/constants.js +19 -0
  188. package/src/core/semantic-planning/projection.js +94 -0
  189. package/src/core/semantic-planning/service.js +15 -0
  190. package/src/core/sources.js +37 -0
  191. package/src/core/task-claim-state.js +201 -1
  192. package/src/core/task-conflict-inspection.js +31 -5
  193. package/src/core/task-paths.js +13 -0
  194. package/src/core/task-recovery.js +1 -0
  195. package/src/core/templates.js +3 -0
  196. package/src/core/test-intelligence/benchmarks.js +68 -0
  197. package/src/core/test-intelligence/inventory.js +73 -0
  198. package/src/core/test-intelligence/prune.js +90 -0
  199. package/src/core/test-intelligence/semantic-state.js +15 -0
  200. package/src/core/test-intelligence/service.js +40 -0
  201. package/src/core/test-intelligence/utility.js +50 -0
  202. package/src/core/trace.js +11 -7
  203. package/src/core/transaction.js +1 -0
  204. package/src/integration.d.ts +492 -0
  205. package/src/integration.js +54 -0
  206. package/src/providers/README.md +47 -0
  207. package/src/providers/capabilities.js +46 -0
  208. package/src/providers/errors.js +15 -0
  209. package/src/providers/index.js +29 -0
  210. package/src/providers/json-snapshot.js +105 -0
  211. package/src/providers/registry.js +152 -0
@@ -0,0 +1,319 @@
1
+ import { canonicalFingerprint } from "./artifacts.js";
2
+ import {
3
+ resolveCanonicalRouteEvolution,
4
+ resolveCanonicalPostRepairRouteBinding,
5
+ resolveEffectiveContractBootstrapRepairAnchor,
6
+ } from "./contract-bootstrap-recovery.js";
7
+ import { resolveCanonicalContractEvolution } from "./contract-revision.js";
8
+
9
+ export const CHECKPOINT_REVALIDATED_EVENT = "CHECKPOINT_REVALIDATED";
10
+ export const CHECKPOINT_REVALIDATION_PHASES = Object.freeze(["ROUTED"]);
11
+
12
+ const REPOSITORY_FINGERPRINT_KEYS = Object.freeze(["branch", "head"]);
13
+ const REVALIDATION_DETAIL_KEYS = Object.freeze([
14
+ "phase",
15
+ "previousRepositoryFingerprint",
16
+ "repositoryFingerprint",
17
+ "contractFingerprint",
18
+ "routeFingerprint",
19
+ "previousStateRevision",
20
+ "revalidatedStateRevision",
21
+ "previousStateFingerprint",
22
+ "revalidatedStateFingerprint",
23
+ ]);
24
+
25
+ function invalid(message) {
26
+ const error = new Error(message);
27
+ error.code = "E_CHECKPOINT_REVALIDATION_UNSAFE";
28
+ return error;
29
+ }
30
+
31
+ function isFingerprint(value) {
32
+ return typeof value === "string" && /^[a-f0-9]{64}$/.test(value);
33
+ }
34
+
35
+ export function assertRepositoryFingerprint(value, label = "repository fingerprint", { requireHead = false } = {}) {
36
+ if (!value || typeof value !== "object" || Array.isArray(value)
37
+ || Object.keys(value).length !== REPOSITORY_FINGERPRINT_KEYS.length
38
+ || REPOSITORY_FINGERPRINT_KEYS.some((key) => !Object.prototype.hasOwnProperty.call(value, key))) {
39
+ throw invalid(`${label} must contain exactly branch and head`);
40
+ }
41
+ for (const key of REPOSITORY_FINGERPRINT_KEYS) {
42
+ if (value[key] !== null && (typeof value[key] !== "string" || !value[key].trim())) {
43
+ throw invalid(`${label}.${key} must be a non-empty string or null`);
44
+ }
45
+ }
46
+ if (requireHead && typeof value.head !== "string") {
47
+ throw invalid(`${label}.head must be available`);
48
+ }
49
+ return value;
50
+ }
51
+
52
+ export function assertCheckpointRevalidatedDetails(details) {
53
+ if (!details || typeof details !== "object" || Array.isArray(details)
54
+ || Object.keys(details).length !== REVALIDATION_DETAIL_KEYS.length
55
+ || REVALIDATION_DETAIL_KEYS.some((key) => !Object.prototype.hasOwnProperty.call(details, key))) {
56
+ throw invalid(`${CHECKPOINT_REVALIDATED_EVENT} requires its exact state-transition detail set`);
57
+ }
58
+ if (!CHECKPOINT_REVALIDATION_PHASES.includes(details.phase)) {
59
+ throw invalid(`${CHECKPOINT_REVALIDATED_EVENT} details.phase is unsupported`);
60
+ }
61
+ assertRepositoryFingerprint(details.previousRepositoryFingerprint, `${CHECKPOINT_REVALIDATED_EVENT} details.previousRepositoryFingerprint`);
62
+ assertRepositoryFingerprint(details.repositoryFingerprint, `${CHECKPOINT_REVALIDATED_EVENT} details.repositoryFingerprint`, { requireHead: true });
63
+ if (JSON.stringify(details.previousRepositoryFingerprint) === JSON.stringify(details.repositoryFingerprint)) {
64
+ throw invalid(`${CHECKPOINT_REVALIDATED_EVENT} must record repository drift`);
65
+ }
66
+ for (const key of ["contractFingerprint", "routeFingerprint", "previousStateFingerprint", "revalidatedStateFingerprint"]) {
67
+ if (!isFingerprint(details[key])) throw invalid(`${CHECKPOINT_REVALIDATED_EVENT} details.${key} must be a lowercase SHA-256 fingerprint`);
68
+ }
69
+ for (const key of ["previousStateRevision", "revalidatedStateRevision"]) {
70
+ if (!Number.isInteger(details[key]) || details[key] < 0) {
71
+ throw invalid(`${CHECKPOINT_REVALIDATED_EVENT} details.${key} must be a non-negative integer`);
72
+ }
73
+ }
74
+ if (details.revalidatedStateRevision !== details.previousStateRevision + 1) {
75
+ throw invalid(`${CHECKPOINT_REVALIDATED_EVENT} state revisions must advance exactly once`);
76
+ }
77
+ return details;
78
+ }
79
+
80
+ function sameRepositoryFingerprint(left, right) {
81
+ return left?.branch === right?.branch && left?.head === right?.head;
82
+ }
83
+
84
+ function bindingError(message) {
85
+ return { code: "E_CHECKPOINT_REVALIDATION_UNSAFE", message };
86
+ }
87
+
88
+ function eventHash(event) {
89
+ const { hash, ...body } = event ?? {};
90
+ return canonicalFingerprint(body);
91
+ }
92
+
93
+ function routeEvolutionAfter(
94
+ events,
95
+ sourceEvent,
96
+ sourceRouteFingerprint,
97
+ targetRouteFingerprint,
98
+ targetContractFingerprint = sourceEvent.details?.contractFingerprint,
99
+ ) {
100
+ const sourceIndex = events.indexOf(sourceEvent);
101
+ if (sourceIndex < 0 || !isFingerprint(sourceRouteFingerprint) || !isFingerprint(targetRouteFingerprint)) {
102
+ return false;
103
+ }
104
+
105
+ const contractFingerprint = sourceEvent.details?.contractFingerprint;
106
+ if (targetContractFingerprint !== contractFingerprint) {
107
+ const contractEvolution = resolveCanonicalContractEvolution(events, {
108
+ taskId: sourceEvent.taskId,
109
+ sourceSeq: sourceEvent.seq,
110
+ sourceContractFingerprint: contractFingerprint,
111
+ targetContractFingerprint,
112
+ });
113
+ if (!contractEvolution?.revisionEvents?.length) return false;
114
+
115
+ let currentRouteFingerprint = sourceRouteFingerprint;
116
+ let currentContractFingerprint = contractFingerprint;
117
+ let currentSeq = sourceEvent.seq;
118
+ for (const revisionEvent of contractEvolution.revisionEvents) {
119
+ const boundaryRouteFingerprint = revisionEvent.details.previousRouteFingerprint;
120
+ if (!isFingerprint(boundaryRouteFingerprint)) return false;
121
+ const preRevisionEvents = events.filter((event) => event.seq <= revisionEvent.seq);
122
+ const preRevisionRoute = resolveCanonicalRouteEvolution(preRevisionEvents, {
123
+ taskId: sourceEvent.taskId,
124
+ sourceSeq: currentSeq,
125
+ sourceRouteFingerprint: currentRouteFingerprint,
126
+ targetRouteFingerprint: boundaryRouteFingerprint,
127
+ contractFingerprint: currentContractFingerprint,
128
+ });
129
+ if (!preRevisionRoute) return false;
130
+ currentRouteFingerprint = boundaryRouteFingerprint;
131
+ currentContractFingerprint = revisionEvent.details.contractFingerprint;
132
+ currentSeq = revisionEvent.seq;
133
+ }
134
+ const postRevisionRoute = resolveCanonicalRouteEvolution(events, {
135
+ taskId: sourceEvent.taskId,
136
+ sourceSeq: currentSeq,
137
+ sourceRouteFingerprint: currentRouteFingerprint,
138
+ targetRouteFingerprint,
139
+ contractFingerprint: currentContractFingerprint,
140
+ });
141
+ return Boolean(postRevisionRoute);
142
+ }
143
+
144
+ const canonical = resolveCanonicalRouteEvolution(events, {
145
+ taskId: sourceEvent.taskId,
146
+ sourceSeq: sourceEvent.seq,
147
+ sourceRouteFingerprint,
148
+ targetRouteFingerprint,
149
+ contractFingerprint,
150
+ });
151
+ if (!canonical) return false;
152
+
153
+ const anchor = resolveEffectiveContractBootstrapRepairAnchor(events);
154
+ if (anchor?.details?.contractFingerprint === contractFingerprint) {
155
+ const binding = resolveCanonicalPostRepairRouteBinding(
156
+ events,
157
+ anchor.sourceMarker,
158
+ targetRouteFingerprint,
159
+ );
160
+ return Boolean(binding?.reboundEvents?.some(({ reboundEvent }) =>
161
+ reboundEvent.seq > sourceEvent.seq
162
+ && reboundEvent.details.routeFingerprint === targetRouteFingerprint));
163
+ }
164
+ return canonical.valid;
165
+ }
166
+
167
+ /**
168
+ * Resolves the only valid transaction witness for a checkpoint revalidation.
169
+ * The witness is intentionally positional: a later transaction commit cannot
170
+ * retroactively authorize an earlier revalidation event.
171
+ */
172
+ export function resolveCheckpointRevalidationBoundary(events = [], revalidationEvent) {
173
+ const index = events.findIndex((candidate) => candidate === revalidationEvent
174
+ || (candidate?.seq === revalidationEvent?.seq
175
+ && candidate?.taskId === revalidationEvent?.taskId
176
+ && candidate?.event === CHECKPOINT_REVALIDATED_EVENT));
177
+ if (index < 0) return null;
178
+ const commitEvent = events[index + 1];
179
+ if (!commitEvent
180
+ || revalidationEvent?.event !== CHECKPOINT_REVALIDATED_EVENT
181
+ || commitEvent.event !== "TRANSACTION_COMMITTED"
182
+ || commitEvent.seq !== revalidationEvent.seq + 1
183
+ || commitEvent.previousHash !== revalidationEvent.hash
184
+ || revalidationEvent.hash !== eventHash(revalidationEvent)
185
+ || commitEvent.hash !== eventHash(commitEvent)
186
+ || commitEvent.taskId !== revalidationEvent.taskId
187
+ || typeof commitEvent.details?.transactionId !== "string"
188
+ || !commitEvent.details.transactionId
189
+ || commitEvent.details.operation !== "checkpoint-revalidate") {
190
+ return null;
191
+ }
192
+ return { revalidationEvent, transactionCommitEvent: commitEvent };
193
+ }
194
+
195
+ export function validateCheckpointRevalidationEventBindings(events = []) {
196
+ const errors = [];
197
+ const revalidations = events.filter((event) => event.event === CHECKPOINT_REVALIDATED_EVENT);
198
+ for (let index = 0; index < revalidations.length; index += 1) {
199
+ const event = revalidations[index];
200
+ let details;
201
+ try {
202
+ details = assertCheckpointRevalidatedDetails(event.details);
203
+ } catch (error) {
204
+ errors.push(bindingError(`event ${event.seq}: ${error.message}`));
205
+ continue;
206
+ }
207
+ if (!resolveCheckpointRevalidationBoundary(events, event)) {
208
+ errors.push(bindingError(`event ${event.seq} must be immediately followed by its checkpoint-revalidate transaction commit`));
209
+ }
210
+ const previous = revalidations[index - 1]?.details;
211
+ if (previous) {
212
+ if (!sameRepositoryFingerprint(details.previousRepositoryFingerprint, previous.repositoryFingerprint)) {
213
+ errors.push(bindingError(`event ${event.seq} is disconnected from the previous checkpoint revalidation repository`));
214
+ }
215
+ if (details.previousStateRevision < previous.revalidatedStateRevision) {
216
+ errors.push(bindingError(`event ${event.seq} rolls back the previous checkpoint revalidation revision`));
217
+ }
218
+ if (details.previousStateRevision === previous.revalidatedStateRevision
219
+ && details.previousStateFingerprint !== previous.revalidatedStateFingerprint) {
220
+ errors.push(bindingError(`event ${event.seq} does not continue the previous checkpoint revalidation state fingerprint`));
221
+ }
222
+ const contractEvolved = details.contractFingerprint !== previous.contractFingerprint
223
+ && resolveCanonicalContractEvolution(events, {
224
+ taskId: event.taskId,
225
+ sourceSeq: revalidations[index - 1].seq,
226
+ sourceContractFingerprint: previous.contractFingerprint,
227
+ targetContractFingerprint: details.contractFingerprint,
228
+ });
229
+ if (details.contractFingerprint !== previous.contractFingerprint && !contractEvolved) {
230
+ errors.push(bindingError(`event ${event.seq} changes the checkpoint revalidation contract identity without canonical contract provenance`));
231
+ }
232
+ if (details.routeFingerprint !== previous.routeFingerprint) {
233
+ const evolved = details.previousStateRevision > previous.revalidatedStateRevision
234
+ && routeEvolutionAfter(
235
+ events,
236
+ revalidations[index - 1],
237
+ previous.routeFingerprint,
238
+ details.routeFingerprint,
239
+ details.contractFingerprint,
240
+ );
241
+ if (!evolved) {
242
+ errors.push(bindingError(`event ${event.seq} changes the checkpoint revalidation route identity without canonical route provenance`));
243
+ }
244
+ }
245
+ } else {
246
+ const anchor = resolveEffectiveContractBootstrapRepairAnchor(events);
247
+ if (anchor?.details?.reconstructedStateRevision === details.previousStateRevision
248
+ && anchor.details.reconstructedStateFingerprint !== details.previousStateFingerprint) {
249
+ errors.push(bindingError(`event ${event.seq} does not bind the repaired checkpoint state fingerprint`));
250
+ }
251
+ }
252
+ }
253
+ return errors;
254
+ }
255
+
256
+ function validateCurrentContractBinding(state, events, event) {
257
+ const details = event.details;
258
+ if (!state?.contractFingerprint || details?.contractFingerprint === state.contractFingerprint) return null;
259
+ const evolved = Number.isInteger(state.revision)
260
+ && Number.isInteger(details?.revalidatedStateRevision)
261
+ && state.revision > details.revalidatedStateRevision
262
+ && resolveCanonicalContractEvolution(events, {
263
+ taskId: event.taskId,
264
+ sourceSeq: event.seq,
265
+ sourceContractFingerprint: details.contractFingerprint,
266
+ targetContractFingerprint: state.contractFingerprint,
267
+ });
268
+ return evolved ? null : bindingError(`event ${event.seq} does not bind the current contract identity through canonical contract provenance`);
269
+ }
270
+
271
+ function validateCurrentRouteBinding(state, events, event) {
272
+ const details = event.details;
273
+ if (!state?.routeFingerprint || details?.routeFingerprint === state.routeFingerprint) return null;
274
+ const routeChangedAfterCheckpoint = Number.isInteger(state.revision)
275
+ && Number.isInteger(details?.revalidatedStateRevision)
276
+ && state.revision > details.revalidatedStateRevision;
277
+ const evolved = routeChangedAfterCheckpoint
278
+ && routeEvolutionAfter(events, event, details.routeFingerprint, state.routeFingerprint, state.contractFingerprint);
279
+ return evolved ? null : bindingError(`event ${event.seq} does not bind the current route identity through canonical route provenance`);
280
+ }
281
+
282
+ function validateLatestCheckpointBinding(state, latest) {
283
+ if (!state || !latest || state.revision !== latest.details.revalidatedStateRevision) return [];
284
+ const details = latest.details;
285
+ const errors = [];
286
+ if (!sameRepositoryFingerprint(state.repositoryFingerprint, details.repositoryFingerprint)) {
287
+ errors.push(bindingError(`event ${latest.seq} does not bind the current repository fingerprint`));
288
+ }
289
+ if (state.phase !== details.phase || canonicalFingerprint(state) !== details.revalidatedStateFingerprint) {
290
+ errors.push(bindingError(`event ${latest.seq} does not bind the current checkpoint state`));
291
+ }
292
+ return errors;
293
+ }
294
+
295
+ export function validateCheckpointRevalidationCurrentBinding(state, events = []) {
296
+ const errors = [];
297
+ const revalidations = events.filter((event) => event.event === CHECKPOINT_REVALIDATED_EVENT);
298
+ const latest = revalidations.at(-1);
299
+ for (const event of revalidations) {
300
+ const contractError = validateCurrentContractBinding(state, events, event);
301
+ if (contractError) errors.push(contractError);
302
+ const routeError = validateCurrentRouteBinding(state, events, event);
303
+ if (routeError) errors.push(routeError);
304
+ }
305
+ errors.push(...validateLatestCheckpointBinding(state, latest));
306
+ return errors;
307
+ }
308
+
309
+ /**
310
+ * Validates the semantic chain of checkpoint-revalidation events. The event
311
+ * hash proves bytes were not changed accidentally; these checks prove that a
312
+ * rehashed event still describes a valid state transition.
313
+ */
314
+ export function validateCheckpointRevalidationBindings(state, events = []) {
315
+ return [
316
+ ...validateCheckpointRevalidationEventBindings(events),
317
+ ...validateCheckpointRevalidationCurrentBinding(state, events),
318
+ ];
319
+ }
@@ -71,6 +71,188 @@ export const CLI_COMMAND_DEFINITIONS = Object.freeze({
71
71
  mayExecuteExternalProcess: false,
72
72
  description: "Reports versioning, lifecycle, command, guide, and public error compatibility metadata for external harnesses.",
73
73
  }),
74
+ "decision-status": Object.freeze({
75
+ name: "decision-status",
76
+ category: "diagnostics",
77
+ mutation: "READ_ONLY",
78
+ options: Object.freeze({
79
+ ...CLI_COMMON_OPTIONS,
80
+ "--health": Object.freeze({ targetKey: "health", parseType: "boolean", takesValue: false, description: "perform a bounded live Jev health check when credentials are configured" }),
81
+ "--json": Object.freeze({ targetKey: "json", parseType: "boolean", takesValue: false, description: "emit decision-plane status as JSON" }),
82
+ }),
83
+ writes: [], removes: [], mayExecuteExternalProcess: true,
84
+ description: "Reports the pinned semantic decision-plane configuration without exposing credentials.",
85
+ }),
86
+ "decision-show": Object.freeze({
87
+ name: "decision-show",
88
+ category: "diagnostics",
89
+ mutation: "READ_ONLY",
90
+ options: Object.freeze({
91
+ ...CLI_COMMON_OPTIONS,
92
+ ...CLI_TASK_OPTION,
93
+ "--decision": Object.freeze({ targetKey: "decisionId", parseType: "string", takesValue: true, valueName: "id", missingValueMessage: "--decision requires an ID", description: "decision artifact ID" }),
94
+ "--json": Object.freeze({ targetKey: "json", parseType: "boolean", takesValue: false, description: "emit the decision artifact as JSON" }),
95
+ }),
96
+ writes: [], removes: [], mayExecuteExternalProcess: false,
97
+ description: "Shows one persisted semantic decision artifact without performing a live request.",
98
+ }),
99
+ "context-plan": Object.freeze({
100
+ name: "context-plan",
101
+ category: "diagnostics",
102
+ mutation: "READ_ONLY",
103
+ options: Object.freeze({
104
+ ...CLI_COMMON_OPTIONS,
105
+ ...CLI_TASK_OPTION,
106
+ "--decision": Object.freeze({ targetKey: "decisionId", parseType: "string", takesValue: true, valueName: "id", missingValueMessage: "--decision requires an ID", description: "persisted Jev decision artifact ID" }),
107
+ "--profile": Object.freeze({ targetKey: "profile", parseType: "string", takesValue: true, valueName: "profile", missingValueMessage: "--profile requires light, balanced, or full", description: "bounded context budget profile" }),
108
+ "--json": Object.freeze({ targetKey: "json", parseType: "boolean", takesValue: false, description: "emit the bounded context plan as JSON" }),
109
+ }),
110
+ writes: [], removes: [], mayExecuteExternalProcess: false,
111
+ description: "Compiles a bounded non-authoritative context plan from deterministic candidates.",
112
+ }),
113
+ "model-route": Object.freeze({
114
+ name: "model-route",
115
+ category: "diagnostics",
116
+ mutation: "READ_ONLY",
117
+ options: Object.freeze({
118
+ ...CLI_COMMON_OPTIONS,
119
+ ...CLI_TASK_OPTION,
120
+ "--decision": Object.freeze({ targetKey: "decisionId", parseType: "string", takesValue: true, valueName: "id", missingValueMessage: "--decision requires an ID", description: "persisted Jev decision artifact ID" }),
121
+ "--work": Object.freeze({ targetKey: "workType", parseType: "string", takesValue: true, valueName: "type", missingValueMessage: "--work requires a type", description: "declared work type" }),
122
+ "--surface": Object.freeze({ targetKey: "surfaces", parseType: "string", takesValue: true, valueName: "value", repeatable: true, missingValueMessage: "--surface requires a value", description: "affected surface" }),
123
+ "--risk": Object.freeze({ targetKey: "risks", parseType: "string", takesValue: true, valueName: "value", repeatable: true, missingValueMessage: "--risk requires a value", description: "task risk" }),
124
+ "--platform": Object.freeze({ targetKey: "platforms", parseType: "string", takesValue: true, valueName: "value", repeatable: true, missingValueMessage: "--platform requires a value", description: "affected platform" }),
125
+ "--behavior-change": Object.freeze({ targetKey: "behaviorChange", parseType: "boolean", takesValue: false, description: "declare behavior change" }),
126
+ "--executable-change": Object.freeze({ targetKey: "executableChange", parseType: "boolean", takesValue: false, description: "declare executable/configuration change" }),
127
+ "--generation-required": Object.freeze({ targetKey: "generationRequired", parseType: "boolean", takesValue: false, description: "declare that generation is required" }),
128
+ "--architecture-change": Object.freeze({ targetKey: "architectureChange", parseType: "boolean", takesValue: false, description: "declare an architectural change" }),
129
+ "--ambiguous": Object.freeze({ targetKey: "ambiguity", parseType: "boolean", takesValue: false, description: "declare unresolved ambiguity" }),
130
+ "--json": Object.freeze({ targetKey: "json", parseType: "boolean", takesValue: false, description: "emit model-route projection as JSON" }),
131
+ }),
132
+ writes: [], removes: [], mayExecuteExternalProcess: false,
133
+ description: "Projects a deterministic model-routing floor with advisory Jev escalation only.",
134
+ }),
135
+ "semantic-plan": Object.freeze({
136
+ name: "semantic-plan",
137
+ category: "diagnostics",
138
+ mutation: "READ_ONLY",
139
+ options: Object.freeze({
140
+ ...CLI_COMMON_OPTIONS,
141
+ ...CLI_TASK_OPTION,
142
+ "--decision": Object.freeze({ targetKey: "decisionId", parseType: "string", takesValue: true, valueName: "id", missingValueMessage: "--decision requires an ID", description: "persisted Jev decision artifact ID" }),
143
+ "--kind": Object.freeze({ targetKey: "semanticPlanKind", parseType: "string", takesValue: true, valueName: "failure|diagnosis|review", missingValueMessage: "--kind requires failure, diagnosis, or review", description: "bounded semantic planning projection" }),
144
+ "--input": Object.freeze({ targetKey: "semanticPlanInput", parseType: "json-object", takesValue: true, valueName: "json", missingValueMessage: "--input requires a JSON object", description: "bounded failure, diagnosis, or review context" }),
145
+ "--json": Object.freeze({ targetKey: "json", parseType: "boolean", takesValue: false, description: "emit semantic plan projection as JSON" }),
146
+ }),
147
+ writes: [], removes: [], mayExecuteExternalProcess: false,
148
+ description: "Projects fail-closed failure triage, diagnosis priority, or review planning without authority.",
149
+ }),
150
+ "test-inventory": Object.freeze({
151
+ name: "test-inventory",
152
+ category: "diagnostics",
153
+ mutation: "READ_ONLY",
154
+ options: Object.freeze({
155
+ ...CLI_COMMON_OPTIONS,
156
+ "--json": Object.freeze({ targetKey: "json", parseType: "boolean", takesValue: false, description: "emit deterministic test inventory as JSON" }),
157
+ }),
158
+ writes: [], removes: [], mayExecuteExternalProcess: false,
159
+ description: "Discovers tests deterministically and assigns stable semantic test IDs without pruning authority.",
160
+ }),
161
+ "test-utility": Object.freeze({
162
+ name: "test-utility",
163
+ category: "diagnostics",
164
+ mutation: "MUTATING",
165
+ options: Object.freeze({
166
+ ...CLI_COMMON_OPTIONS,
167
+ ...CLI_TASK_OPTION,
168
+ "--json": Object.freeze({ targetKey: "json", parseType: "boolean", takesValue: false, description: "emit test utility analysis as JSON" }),
169
+ }),
170
+ writes: [".forgeloop/task-state/<taskKey>/test-utility.json"], removes: [], mayExecuteExternalProcess: false,
171
+ description: "Persists non-evidence test utility analysis; never deletes tests or authorizes pruning.",
172
+ }),
173
+ "test-prune-plan": Object.freeze({
174
+ name: "test-prune-plan", category: "diagnostics", mutation: "READ_ONLY",
175
+ options: Object.freeze({ ...CLI_COMMON_OPTIONS, ...CLI_TASK_OPTION, "--json": Object.freeze({ targetKey: "json", parseType: "boolean", takesValue: false, description: "emit test prune plan as JSON" }) }),
176
+ writes: [], removes: [], mayExecuteExternalProcess: false,
177
+ description: "Projects KEEP, PROBE_REMOVAL, or BLOCKED without deleting tests.",
178
+ }),
179
+ "test-prune-probe": Object.freeze({
180
+ name: "test-prune-probe", category: "verification", mutation: "EXTERNAL_EXECUTION",
181
+ options: Object.freeze({ ...CLI_COMMON_OPTIONS, ...CLI_TASK_OPTION, "--test": Object.freeze({ targetKey: "testId", parseType: "string", takesValue: true, valueName: "id", missingValueMessage: "--test requires a test ID", description: "stable test ID to probe in isolation" }), "--json": Object.freeze({ targetKey: "json", parseType: "boolean", takesValue: false, description: "emit test prune probe as JSON" }) }),
182
+ writes: [], removes: [], mayExecuteExternalProcess: true,
183
+ description: "Runs only an isolated, fail-closed removal probe; never modifies the live worktree.",
184
+ }),
185
+ discover: Object.freeze({
186
+ name: "discover",
187
+ category: "lifecycle",
188
+ mutation: "MUTATING",
189
+ options: Object.freeze({
190
+ ...CLI_COMMON_OPTIONS,
191
+ ...CLI_TASK_OPTION,
192
+ "--json": Object.freeze({ targetKey: "json", parseType: "boolean", takesValue: false, description: "emit structured discovery output as JSON" }),
193
+ }),
194
+ writes: [".forgeloop/task-state/<taskKey>/events.ndjson"],
195
+ removes: [],
196
+ mayExecuteExternalProcess: false,
197
+ description: "Records the canonical initial discovery milestone for a newly created task.",
198
+ }),
199
+ "contract-create": Object.freeze({
200
+ name: "contract-create",
201
+ category: "lifecycle",
202
+ mutation: "MUTATING",
203
+ options: Object.freeze({
204
+ ...CLI_COMMON_OPTIONS,
205
+ ...CLI_TASK_OPTION,
206
+ "--contract-file": Object.freeze({ targetKey: "contractFile", parseType: "string", takesValue: true, valueName: "path", missingValueMessage: "--contract-file requires a path", description: "validated JSON contract relative to the target" }),
207
+ "--preset": Object.freeze({ targetKey: "preset", parseType: "string", takesValue: true, valueName: "name", missingValueMessage: "--preset requires a name", description: "bounded contract preset: documentation, bug, feature, or release" }),
208
+ "--json": Object.freeze({ targetKey: "json", parseType: "boolean", takesValue: false, description: "emit structured contract output as JSON" }),
209
+ }),
210
+ writes: [".forgeloop/task-state/<taskKey>/contract.json", ".forgeloop/task-state/<taskKey>/work-state.json", ".forgeloop/task-state/<taskKey>/events.ndjson"],
211
+ removes: [],
212
+ mayExecuteExternalProcess: false,
213
+ description: "Persists a validated contract and materializes the first real lifecycle checkpoint.",
214
+ }),
215
+ "contract-revise": Object.freeze({
216
+ name: "contract-revise",
217
+ category: "project-maintenance",
218
+ mutation: "MUTATING",
219
+ options: Object.freeze({
220
+ ...CLI_COMMON_OPTIONS,
221
+ ...CLI_TASK_OPTION,
222
+ "--contract-file": Object.freeze({ targetKey: "contractFile", parseType: "string", takesValue: true, valueName: "path", missingValueMessage: "--contract-file requires a path", description: "validated replacement JSON contract relative to the target" }),
223
+ "--preset": Object.freeze({ targetKey: "preset", parseType: "string", takesValue: true, valueName: "name", missingValueMessage: "--preset requires a name", description: "bounded replacement contract preset: documentation, bug, feature, or release" }),
224
+ "--json": Object.freeze({ targetKey: "json", parseType: "boolean", takesValue: false, description: "emit structured contract revision output as JSON" }),
225
+ }),
226
+ writes: [".forgeloop/task-state/<taskKey>/contract.json", ".forgeloop/task-state/<taskKey>/work-state.json", ".forgeloop/task-state/<taskKey>/events.ndjson"],
227
+ removes: [],
228
+ mayExecuteExternalProcess: false,
229
+ description: "Replaces an existing contract canonically before execution while preserving task identity and invalidating derived evidence.",
230
+ }),
231
+ "gate-record": Object.freeze({
232
+ name: "gate-record", category: "lifecycle", mutation: "MUTATING",
233
+ options: Object.freeze({ ...CLI_COMMON_OPTIONS, ...CLI_TASK_OPTION,
234
+ "--gate": Object.freeze({ targetKey: "gate", parseType: "string", takesValue: true, valueName: "name", missingValueMessage: "--gate requires a name", description: "required gate name" }),
235
+ "--status": Object.freeze({ targetKey: "gateStatus", parseType: "string", takesValue: true, valueName: "status", missingValueMessage: "--status requires a status", description: "satisfied, unverified, or blocked" }),
236
+ "--artifact": Object.freeze({ targetKey: "gateArtifacts", parseType: "string", takesValue: true, repeatable: true, valueName: "path", missingValueMessage: "--artifact requires a path", description: "project-relative evidence artifact" }),
237
+ "--decision": Object.freeze({ targetKey: "gateDecisions", parseType: "string", takesValue: true, repeatable: true, valueName: "text", missingValueMessage: "--decision requires text", description: "caller-recorded gate decision" }),
238
+ "--unknown": Object.freeze({ targetKey: "gateUnknowns", parseType: "string", takesValue: true, repeatable: true, valueName: "text", missingValueMessage: "--unknown requires text", description: "known unresolved item" }),
239
+ "--assumption": Object.freeze({ targetKey: "gateAssumptions", parseType: "string", takesValue: true, repeatable: true, valueName: "text", missingValueMessage: "--assumption requires text", description: "approved local assumption" }),
240
+ "--evidence-file": Object.freeze({ targetKey: "gateEvidenceFile", parseType: "string", takesValue: true, valueName: "path", missingValueMessage: "--evidence-file requires a path", description: "bounded local descriptive evidence JSON" }),
241
+ "--json": Object.freeze({ targetKey: "json", parseType: "boolean", takesValue: false, description: "emit structured gate output as JSON" }),
242
+ }),
243
+ writes: [".forgeloop/task-state/<taskKey>/gates/<gate>.json"], removes: [], mayExecuteExternalProcess: false,
244
+ description: "Records a required pre-execution gate with ForgeLoop-owned artifact hashes.",
245
+ }),
246
+ "gate-revalidate": Object.freeze({
247
+ name: "gate-revalidate", category: "lifecycle", mutation: "MUTATING",
248
+ options: Object.freeze({ ...CLI_COMMON_OPTIONS, ...CLI_TASK_OPTION,
249
+ "--gate": Object.freeze({ targetKey: "gate", parseType: "string", takesValue: true, valueName: "name", missingValueMessage: "--gate requires a name", description: "satisfied gate to refresh after safe artifact drift" }),
250
+ "--acknowledge-stale": Object.freeze({ targetKey: "acknowledgeStale", parseType: "boolean", takesValue: false, description: "explicitly acknowledge refresh of stale gate artifacts inside active task claims" }),
251
+ "--json": Object.freeze({ targetKey: "json", parseType: "boolean", takesValue: false, description: "emit structured gate revalidation output as JSON" }),
252
+ }),
253
+ writes: [".forgeloop/task-state/<taskKey>/gates/<gate>.json", ".forgeloop/task-state/<taskKey>/events.ndjson"], removes: [], mayExecuteExternalProcess: false,
254
+ description: "Refreshes a stale satisfied gate after execution only when current identity and active claims prove the change is safe.",
255
+ }),
74
256
  init: Object.freeze({
75
257
  name: "init",
76
258
  category: "project-maintenance",
@@ -976,6 +1158,20 @@ export const CLI_COMMAND_DEFINITIONS = Object.freeze({
976
1158
  mayExecuteExternalProcess: false,
977
1159
  description: "Removes work-state.json for the active task only, preserving sibling contract, routing, and ledger files.",
978
1160
  }),
1161
+ "checkpoint-revalidate": Object.freeze({
1162
+ name: "checkpoint-revalidate",
1163
+ category: "project-maintenance",
1164
+ mutation: "MUTATING",
1165
+ options: Object.freeze({
1166
+ ...CLI_COMMON_OPTIONS,
1167
+ ...CLI_TASK_OPTION,
1168
+ "--json": Object.freeze({ targetKey: "json", parseType: "boolean", takesValue: false, description: "emit structured revalidation output as JSON" }),
1169
+ }),
1170
+ writes: [".forgeloop/task-state/<taskKey>/work-state.json", ".forgeloop/task-state/<taskKey>/events.ndjson"],
1171
+ removes: [],
1172
+ mayExecuteExternalProcess: false,
1173
+ description: "Revalidates a safe ROUTED checkpoint after repository-only drift without changing contract or route identity.",
1174
+ }),
979
1175
  "reconcile-closure": Object.freeze({
980
1176
  name: "reconcile-closure",
981
1177
  category: "lifecycle",
@@ -1170,6 +1366,51 @@ export const CLI_COMMAND_DEFINITIONS = Object.freeze({
1170
1366
  mayExecuteExternalProcess: false,
1171
1367
  description: "Caller-acknowledged recovery of a STALE or ABANDONED task; records durable state and releases effective write claims.",
1172
1368
  }),
1369
+ "task-abandon": Object.freeze({
1370
+ name: "task-abandon",
1371
+ category: "project-maintenance",
1372
+ mutation: "MUTATING",
1373
+ options: Object.freeze({
1374
+ ...CLI_COMMON_OPTIONS,
1375
+ ...CLI_TASK_OPTION,
1376
+ "--acknowledge-abandonment": Object.freeze({ targetKey: "acknowledgeAbandonment", parseType: "boolean", takesValue: false, description: "explicitly acknowledge abandonment of an active task (required; not completion authority)" }),
1377
+ "--json": Object.freeze({ targetKey: "json", parseType: "boolean", takesValue: false, description: "emit structured abandonment output as JSON" }),
1378
+ }),
1379
+ writes: [".forgeloop/task-state/<taskKey>/recovery.json", ".forgeloop/task-state/<taskKey>/events.ndjson"],
1380
+ removes: [],
1381
+ mayExecuteExternalProcess: false,
1382
+ description: "Explicitly abandons an active non-terminal task; records append-only evidence and releases claims without completion authority.",
1383
+ }),
1384
+ "task-repair-contract-bootstrap": Object.freeze({
1385
+ name: "task-repair-contract-bootstrap",
1386
+ category: "project-maintenance",
1387
+ mutation: "MUTATING",
1388
+ options: Object.freeze({
1389
+ ...CLI_COMMON_OPTIONS,
1390
+ ...CLI_TASK_OPTION,
1391
+ "--acknowledge-repair": Object.freeze({ targetKey: "acknowledgeRepair", parseType: "boolean", takesValue: false, description: "explicit caller acknowledgement of the exact append-only repair (required)" }),
1392
+ "--json": Object.freeze({ targetKey: "json", parseType: "boolean", takesValue: false, description: "emit structured repair output as JSON" }),
1393
+ }),
1394
+ writes: [".forgeloop/task-state/<taskKey>/work-state.json", ".forgeloop/task-state/<taskKey>/events.ndjson"],
1395
+ removes: [],
1396
+ mayExecuteExternalProcess: false,
1397
+ description: "Repairs the exact duplicate contract bootstrap defect without rewriting history; idempotent and append-only.",
1398
+ }),
1399
+ "task-migrate-contract-bootstrap-repair": Object.freeze({
1400
+ name: "task-migrate-contract-bootstrap-repair",
1401
+ category: "project-maintenance",
1402
+ mutation: "MUTATING",
1403
+ options: Object.freeze({
1404
+ ...CLI_COMMON_OPTIONS,
1405
+ ...CLI_TASK_OPTION,
1406
+ "--acknowledge-migration": Object.freeze({ targetKey: "acknowledgeMigration", parseType: "boolean", takesValue: false, description: "fresh explicit caller acknowledgement of the exact legacy marker migration (required)" }),
1407
+ "--json": Object.freeze({ targetKey: "json", parseType: "boolean", takesValue: false, description: "emit structured migration output as JSON" }),
1408
+ }),
1409
+ writes: [".forgeloop/task-state/<taskKey>/events.ndjson"],
1410
+ removes: [],
1411
+ mayExecuteExternalProcess: false,
1412
+ description: "Migrates the exact legacy contract bootstrap repair marker by appending a bound migration event; the original marker and artifacts remain unchanged.",
1413
+ }),
1173
1414
  "task-repair-legacy-recovery": Object.freeze({
1174
1415
  name: "task-repair-legacy-recovery",
1175
1416
  category: "project-maintenance",