@cassiomc1/forgeloop 1.0.0 → 1.1.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 (95) hide show
  1. package/.cursor/rules/project-loop.mdc +3 -2
  2. package/.github/copilot-instructions.md +1 -0
  3. package/AGENTS.md +2 -1
  4. package/CLAUDE.md +1 -0
  5. package/DOCS_INDEX.md +36 -0
  6. package/ENG/design-code-eng.md +31 -0
  7. package/ENG/documentation-quality-eng.md +400 -0
  8. package/EXECUTION_STATE.md +23 -13
  9. package/GUIDE_ROUTER.md +23 -8
  10. package/LOOP_ENGINEERING.md +77 -12
  11. package/PROTOCOL_INTEGRATION.md +9 -6
  12. package/README.md +82 -39
  13. package/docs/ARTIFACT_REFERENCE.md +442 -0
  14. package/docs/CLI_REFERENCE.md +892 -0
  15. package/docs/CROSS_HARNESS_CONTINUITY.md +198 -0
  16. package/docs/DOCUMENTATION_GUIDE.md +161 -0
  17. package/docs/GETTING_STARTED.md +348 -0
  18. package/docs/RECIPES.md +250 -0
  19. package/docs/TROUBLESHOOTING.md +345 -0
  20. package/docs/assets/forgeloop-flow.svg +1 -1
  21. package/docs/forgeloop-flow.mmd +1 -1
  22. package/package.json +13 -2
  23. package/schemas/task-descriptor.schema.json +56 -0
  24. package/schemas/work-state.schema.json +18 -1
  25. package/scripts/CI_VALIDATORS.md +7 -0
  26. package/src/cli.js +280 -388
  27. package/src/commands/advance.js +5 -2
  28. package/src/commands/audit.js +11 -1
  29. package/src/commands/clear-continuity.js +5 -2
  30. package/src/commands/clear-state.js +5 -2
  31. package/src/commands/complete.js +9 -1
  32. package/src/commands/continuity.js +5 -2
  33. package/src/commands/inspect.js +10 -2
  34. package/src/commands/next.js +5 -2
  35. package/src/commands/preflight.js +9 -1
  36. package/src/commands/prepare-completion.js +5 -2
  37. package/src/commands/reconcile-continuity.js +5 -2
  38. package/src/commands/record-check.js +7 -1
  39. package/src/commands/record-continuity.js +21 -14
  40. package/src/commands/record-terminal-result.js +7 -1
  41. package/src/commands/route.js +22 -18
  42. package/src/commands/run-check.js +52 -44
  43. package/src/commands/status.js +18 -12
  44. package/src/commands/task-create.js +94 -0
  45. package/src/commands/task-list.js +48 -0
  46. package/src/commands/task-migrate.js +34 -0
  47. package/src/commands/task-scope.js +75 -0
  48. package/src/commands/task-show.js +81 -0
  49. package/src/commands/task-unlock.js +35 -0
  50. package/src/commands/validate-protocol.js +37 -20
  51. package/src/commands/validate-state.js +24 -18
  52. package/src/config/guides.json +42 -0
  53. package/src/core/activation.js +8 -4
  54. package/src/core/artifact-registry.js +166 -0
  55. package/src/core/audit.js +65 -12
  56. package/src/core/bundles.js +76 -50
  57. package/src/core/cli-command-definitions.js +611 -0
  58. package/src/core/cli-metadata.js +23 -0
  59. package/src/core/completion-artifacts.js +161 -74
  60. package/src/core/completion.js +134 -76
  61. package/src/core/continuity.js +20 -13
  62. package/src/core/contract.js +6 -3
  63. package/src/core/error-codes.js +197 -0
  64. package/src/core/events.js +19 -14
  65. package/src/core/execution.js +38 -6
  66. package/src/core/gate-artifact.js +12 -9
  67. package/src/core/gates.js +4 -2
  68. package/src/core/guide-metadata.js +7 -11
  69. package/src/core/guide-registry.js +29 -0
  70. package/src/core/inspect.js +7 -4
  71. package/src/core/native-adapters.js +6 -0
  72. package/src/core/phase.js +85 -33
  73. package/src/core/preflight-consistency.js +24 -14
  74. package/src/core/preflight-loaders.js +16 -11
  75. package/src/core/preflight.js +44 -25
  76. package/src/core/protocol.js +2 -11
  77. package/src/core/receipt.js +1 -1
  78. package/src/core/report.js +2 -2
  79. package/src/core/repository.js +46 -12
  80. package/src/core/resumability.js +6 -4
  81. package/src/core/route-artifact.js +9 -5
  82. package/src/core/router.js +11 -7
  83. package/src/core/schema-validation.js +1 -0
  84. package/src/core/task-command.js +41 -0
  85. package/src/core/task-context.js +126 -0
  86. package/src/core/task-descriptor.js +81 -0
  87. package/src/core/task-discovery.js +116 -0
  88. package/src/core/task-identity.js +76 -0
  89. package/src/core/task-lock.js +209 -0
  90. package/src/core/task-migration-validation.js +140 -0
  91. package/src/core/task-migration.js +361 -0
  92. package/src/core/task-paths.js +96 -0
  93. package/src/core/task-scope.js +179 -0
  94. package/src/core/templates.js +3 -9
  95. package/src/core/work-state.js +24 -13
@@ -10,7 +10,8 @@ import { completionRelationshipErrors } from "./completion-relationships.js";
10
10
  import { assertSafePath, ensureWithin, fileExists } from "./filesystem.js";
11
11
  import { evaluateStartExecutionPrerequisites, hasExecutionStarted } from "./execution-prerequisites.js";
12
12
  import { isRecoverableCompletionEvidenceCode } from "./completion-recovery.js";
13
- import { classifyRequirement, evaluateTerminalRequirements } from "./evidence-readiness.js";
13
+ import { evaluateTerminalRequirements } from "./evidence-readiness.js";
14
+ import { taskArtifactPath } from "./task-paths.js";
14
15
 
15
16
  function issue(code, message, artifacts = [], details = {}) {
16
17
  return { code, message, artifacts, ...details };
@@ -56,6 +57,7 @@ function repairNext(error) {
56
57
  return "Split ordinary verification from terminal lifecycle criteria.";
57
58
  case "E_PUBLICATION_REQUIREMENT_PENDING":
58
59
  return "The contract explicitly requires publication. Record authoritative publication evidence or revise the contract if publication is not in scope.";
60
+ case "E_PRODUCTION_READINESS_REQUIREMENT_PENDING":
59
61
  case "E_PRODUCTION_REQUIREMENT_PENDING":
60
62
  return "The contract explicitly requires production readiness. Record authoritative deployment/readiness evidence before completion.";
61
63
  case "E_TERMINAL_REQUIREMENT_PENDING":
@@ -99,28 +101,25 @@ function withRepairGuidance(error) {
99
101
  message: error.message ?? String(error),
100
102
  artifacts: error.artifacts ?? [],
101
103
  };
102
- if (!normalized.next) normalized.next = repairNext(normalized);
103
- return normalized;
104
+ if (normalized.next) return normalized;
105
+ return { ...normalized, next: repairNext(normalized) };
104
106
  }
105
107
 
106
- function sortIssues(errors) {
107
- const unique = [...new Map(errors.map((rawError) => {
108
- const error = withRepairGuidance(rawError);
109
- return [
110
- `${error.code}\0${(error.artifacts ?? []).join("\0")}\0${error.message}`,
111
- error,
112
- ];
113
- })).values()];
114
- return unique.sort((left, right) => left.code.localeCompare(right.code)
115
- || left.artifacts.join("\0").localeCompare(right.artifacts.join("\0"))
116
- || left.message.localeCompare(right.message));
108
+ function sortIssues(issues) {
109
+ return issues
110
+ .map(withRepairGuidance)
111
+ .sort((a, b) => (a.code || "").localeCompare(b.code || "") || (a.message || "").localeCompare(b.message || ""));
117
112
  }
118
113
 
119
- async function loadRequired(loader, code, message, artifacts, errors) {
114
+ async function loadRequired(readArtifact, errorCode, errorMessage, artifacts, errors) {
120
115
  try {
121
- return await loader();
116
+ return await readArtifact();
122
117
  } catch (error) {
123
- errors.push(issue(error.code === "ARTIFACT_MISSING" ? code : `${code.replace(/_MISSING$/, "")}_INVALID`, `${message}: ${error.message}`, artifacts));
118
+ if (error.code === "ARTIFACT_MISSING") {
119
+ errors.push(issue(errorCode, errorMessage, artifacts));
120
+ return null;
121
+ }
122
+ errors.push(issue(error.code ?? errorCode, error.message ?? errorMessage, artifacts));
124
123
  return null;
125
124
  }
126
125
  }
@@ -133,54 +132,75 @@ function publicationStatus(receipt) {
133
132
  return receipt.changedPaths?.length > 0 ? "local-only" : "not-published";
134
133
  }
135
134
 
136
- async function validateLedger(target, taskId, state, errors, packageRoot) {
137
- await assertSafePath(target, ARTIFACT_PATHS.events);
138
- const eventsPath = ensureWithin(target, ARTIFACT_PATHS.events);
139
- if (!(await fileExists(eventsPath))) {
140
- errors.push(issue("E_PHASE_CHRONOLOGY_INVALID", "Protocol event ledger is required before completion", [ARTIFACT_PATHS.events]));
135
+ async function validateLedger(target, scopedTaskId, contractTaskId, state, errors, packageRoot, options = {}) {
136
+ const eventsRel = options.eventsPath ?? (scopedTaskId ? taskArtifactPath(scopedTaskId, "events") : ARTIFACT_PATHS.events);
137
+ const stateRel = options.statePath ?? (scopedTaskId ? taskArtifactPath(scopedTaskId, "state") : ARTIFACT_PATHS.state);
138
+ await assertSafePath(target, eventsRel);
139
+ const eventsFilePath = ensureWithin(target, eventsRel);
140
+ if (!(await fileExists(eventsFilePath))) {
141
+ errors.push(issue("E_PHASE_CHRONOLOGY_INVALID", "Protocol event ledger is required before completion", [eventsRel]));
141
142
  return { valid: false, events: [], errors: [] };
142
143
  }
143
- const ledger = await validateEventLedger(target, packageRoot);
144
- for (const error of ledger.errors) errors.push({ ...error, artifacts: [ARTIFACT_PATHS.events] });
144
+ const ledger = await validateEventLedger(target, packageRoot, { taskId: scopedTaskId, eventsPath: options.eventsPath });
145
+ for (const error of ledger.errors) errors.push({ ...error, artifacts: [eventsRel] });
145
146
  for (const error of validateStateLedgerCoherence(state, ledger.events)) {
146
- errors.push({ ...error, artifacts: [ARTIFACT_PATHS.state, ARTIFACT_PATHS.events] });
147
+ errors.push({ ...error, artifacts: [stateRel, eventsRel] });
147
148
  }
148
149
  const requiredEvents = LIFECYCLE_MILESTONES.slice(0, state?.phase === "COMPLETE" ? undefined : -1);
149
- const observed = new Set(ledger.events.filter((event) => event.taskId === taskId).map((event) => event.event));
150
+ const observed = new Set(ledger.events.filter((event) => event.taskId === contractTaskId).map((event) => event.event));
150
151
  for (const event of requiredEvents) {
151
152
  if (!observed.has(event)) {
152
- errors.push(issue("E_PHASE_CHRONOLOGY_INVALID", `Required protocol event is missing: ${event}`, [ARTIFACT_PATHS.events], { event }));
153
+ errors.push(issue("E_PHASE_CHRONOLOGY_INVALID", `Required protocol event is missing: ${event}`, [eventsRel], { event }));
153
154
  }
154
155
  }
155
- if (ledger.events.some((event) => event.taskId !== taskId)) {
156
- errors.push(issue("E_PHASE_CHRONOLOGY_INVALID", "Protocol events must belong to the current task", [ARTIFACT_PATHS.events]));
156
+ if (ledger.events.some((event) => event.taskId !== contractTaskId)) {
157
+ errors.push(issue("E_PHASE_CHRONOLOGY_INVALID", "Protocol events must belong to the current task", [eventsRel]));
157
158
  }
158
159
  if (state?.phase !== "COMPLETE" && observed.has("COMPLETION_VALIDATED")) {
159
- errors.push(issue("E_PHASE_CHRONOLOGY_INVALID", "COMPLETION_VALIDATED requires COMPLETE state", [ARTIFACT_PATHS.events]));
160
+ errors.push(issue("E_PHASE_CHRONOLOGY_INVALID", "COMPLETION_VALIDATED requires COMPLETE state", [eventsRel]));
160
161
  }
161
162
  return ledger;
162
163
  }
163
164
 
164
- export async function evaluateCompletion({ target, packageRoot, strict = false, authorityContext, runtimeContext } = {}) {
165
+ export async function evaluateCompletion({
166
+ target,
167
+ packageRoot,
168
+ strict = false,
169
+ authorityContext,
170
+ runtimeContext,
171
+ taskId = null,
172
+ contractPath = null,
173
+ routePath = null,
174
+ statePath = null,
175
+ receiptPath = null,
176
+ eventsPath = null,
177
+ preflightPath = null,
178
+ } = {}) {
165
179
  const errors = [];
166
- const preflight = await evaluatePreflight({ target, packageRoot, strict });
180
+ const preflight = await evaluatePreflight({ target, packageRoot, strict, taskId, contractPath, routePath, statePath });
167
181
  errors.push(...preflight.errors);
182
+
183
+ const contractRel = contractPath ?? (taskId ? taskArtifactPath(taskId, "contract") : ARTIFACT_PATHS.contract);
184
+ const routeRel = routePath ?? (taskId ? taskArtifactPath(taskId, "route") : ARTIFACT_PATHS.route);
185
+ const stateRel = statePath ?? (taskId ? taskArtifactPath(taskId, "state") : ARTIFACT_PATHS.state);
186
+ const receiptRel = receiptPath ?? (taskId ? taskArtifactPath(taskId, "receipt") : ARTIFACT_PATHS.receipt);
187
+
168
188
  const contract = await loadRequired(
169
- () => readContract(target, packageRoot),
189
+ () => readContract(target, packageRoot, { taskId, contractPath }),
170
190
  "E_CONTRACT_MISSING",
171
191
  "Current contract is not available",
172
- [ARTIFACT_PATHS.contract],
192
+ [contractRel],
173
193
  errors,
174
194
  );
175
195
  const route = await loadRequired(
176
- () => readPersistedRoute(target, packageRoot),
196
+ () => readPersistedRoute(target, packageRoot, { taskId, routePath }),
177
197
  "E_ROUTE_MISSING",
178
198
  "Persisted routing result is not available",
179
- [ARTIFACT_PATHS.route],
199
+ [routeRel],
180
200
  errors,
181
201
  );
182
202
  const state = await loadRequired(
183
- () => readWorkState(target, packageRoot).then((value) => {
203
+ () => readWorkState(target, { packageRoot, taskId, statePath }).then((value) => {
184
204
  if (!value) {
185
205
  const error = new Error("Work state is missing");
186
206
  error.code = "ARTIFACT_MISSING";
@@ -190,19 +210,19 @@ export async function evaluateCompletion({ target, packageRoot, strict = false,
190
210
  }),
191
211
  "E_STATE_MISSING",
192
212
  "Work state is not available",
193
- [ARTIFACT_PATHS.state],
213
+ [stateRel],
194
214
  errors,
195
215
  );
196
216
  const receipt = await loadRequired(
197
- () => readJsonArtifact(target, ARTIFACT_PATHS.receipt, "execution-receipt", packageRoot),
217
+ () => readJsonArtifact(target, receiptRel, "execution-receipt", packageRoot),
198
218
  "E_RECEIPT_MISSING",
199
219
  "Execution receipt is not available",
200
- [ARTIFACT_PATHS.receipt],
220
+ [receiptRel],
201
221
  errors,
202
222
  );
203
223
 
204
224
  if (state && hasExecutionStarted(state.phase)) {
205
- const prerequisites = await evaluateStartExecutionPrerequisites({ target, state, packageRoot });
225
+ const prerequisites = await evaluateStartExecutionPrerequisites({ target, state, packageRoot, taskId, statePath, contractPath, routePath });
206
226
  errors.push(...prerequisites.errors);
207
227
  }
208
228
 
@@ -215,27 +235,35 @@ export async function evaluateCompletion({ target, packageRoot, strict = false,
215
235
  runtimeContext,
216
236
  });
217
237
  } catch (error) {
218
- errors.push(issue(error.code ?? "E_RECEIPT_INVALID", `Execution receipt is invalid: ${error.message}`, [ARTIFACT_PATHS.receipt]));
238
+ errors.push(issue(error.code ?? "E_RECEIPT_INVALID", `Execution receipt is invalid: ${error.message}`, [receiptRel]));
219
239
  }
220
240
  }
221
241
 
222
242
  if (contract) {
223
- errors.push(...await validateChecksExecutionProvenance(state?.checks, {
224
- target,
225
- packageRoot,
226
- taskId: contract.value.taskId,
227
- artifactPath: ARTIFACT_PATHS.state,
228
- }));
229
- errors.push(...await validateChecksExecutionProvenance(receipt?.value?.checks, {
230
- target,
231
- packageRoot,
232
- taskId: contract.value.taskId,
233
- artifactPath: ARTIFACT_PATHS.receipt,
234
- }));
243
+ try {
244
+ await validateChecksExecutionProvenance(state?.checks, {
245
+ target,
246
+ packageRoot,
247
+ taskId: contract.value.taskId,
248
+ artifactPath: stateRel,
249
+ });
250
+ } catch (error) {
251
+ errors.push(issue(error.code ?? "E_EXECUTION_REF_INVALID", error.message, error.artifacts ?? [stateRel]));
252
+ }
253
+ try {
254
+ await validateChecksExecutionProvenance(receipt?.value?.checks, {
255
+ target,
256
+ packageRoot,
257
+ taskId: contract.value.taskId,
258
+ artifactPath: receiptRel,
259
+ });
260
+ } catch (error) {
261
+ errors.push(issue(error.code ?? "E_EXECUTION_REF_INVALID", error.message, error.artifacts ?? [receiptRel]));
262
+ }
235
263
  }
236
264
 
237
265
  if (state && !["REVIEWING", "COMPLETE"].includes(state.phase)) {
238
- errors.push(issue("E_PHASE_PREREQUISITE_MISSING", `Completion requires REVIEWING or COMPLETE state, found ${state.phase}`, [ARTIFACT_PATHS.state]));
266
+ errors.push(issue("E_PHASE_PREREQUISITE_MISSING", `Completion requires REVIEWING or COMPLETE state, found ${state.phase}`, [stateRel]));
239
267
  }
240
268
 
241
269
  let coverage = [];
@@ -263,7 +291,7 @@ export async function evaluateCompletion({ target, packageRoot, strict = false,
263
291
  }
264
292
 
265
293
  const ledger = contract && state
266
- ? await validateLedger(target, contract.value.taskId, state, errors, packageRoot)
294
+ ? await validateLedger(target, taskId, contract.value.taskId, state, errors, packageRoot, { eventsPath, statePath })
267
295
  : { valid: false, events: [], errors: [] };
268
296
 
269
297
  if (state) {
@@ -271,15 +299,15 @@ export async function evaluateCompletion({ target, packageRoot, strict = false,
271
299
  const classification = await classifyLoadedWorkState({
272
300
  target,
273
301
  state,
274
- contractFile: ARTIFACT_PATHS.contract,
302
+ contractFile: contractRel,
275
303
  });
276
304
  if (classification.status === "REVALIDATION_REQUIRED") {
277
305
  for (const reason of classification.reasons) {
278
- errors.push(issue(reason === "CONTRACT_CHANGED" ? "E_CONTRACT_STALE" : "E_PHASE_ARTIFACT_STALE", `State freshness check failed: ${reason}`, [ARTIFACT_PATHS.state]));
306
+ errors.push(issue(reason === "CONTRACT_CHANGED" ? "E_CONTRACT_STALE" : "E_PHASE_ARTIFACT_STALE", `State freshness check failed: ${reason}`, [stateRel]));
279
307
  }
280
308
  }
281
309
  } catch (error) {
282
- errors.push(issue("E_PHASE_ARTIFACT_STALE", error.message, [ARTIFACT_PATHS.state]));
310
+ errors.push(issue("E_PHASE_ARTIFACT_STALE", error.message, [stateRel]));
283
311
  }
284
312
  }
285
313
 
@@ -299,7 +327,7 @@ export async function evaluateCompletion({ target, packageRoot, strict = false,
299
327
  errors.push(issue(
300
328
  termErr.code,
301
329
  termErr.message,
302
- [ARTIFACT_PATHS.contract, ARTIFACT_PATHS.receipt],
330
+ [contractRel, receiptRel],
303
331
  { requirementId: termErr.requirementId },
304
332
  ));
305
333
  }
@@ -324,8 +352,38 @@ export async function evaluateCompletion({ target, packageRoot, strict = false,
324
352
  };
325
353
  }
326
354
 
327
- export async function runComplete({ target, packageRoot, strict = false, persist = true, authorityContext, runtimeContext } = {}) {
328
- const result = await evaluateCompletion({ target, packageRoot, strict, authorityContext, runtimeContext });
355
+ export async function runComplete({
356
+ target,
357
+ packageRoot,
358
+ strict = false,
359
+ persist = true,
360
+ authorityContext,
361
+ runtimeContext,
362
+ taskId = null,
363
+ contractPath = null,
364
+ routePath = null,
365
+ statePath = null,
366
+ receiptPath = null,
367
+ eventsPath = null,
368
+ preflightPath = null,
369
+ } = {}) {
370
+ const result = await evaluateCompletion({
371
+ target,
372
+ packageRoot,
373
+ strict,
374
+ authorityContext,
375
+ runtimeContext,
376
+ taskId,
377
+ contractPath,
378
+ routePath,
379
+ statePath,
380
+ receiptPath,
381
+ eventsPath,
382
+ preflightPath,
383
+ });
384
+
385
+ const receiptRel = receiptPath ?? (taskId ? taskArtifactPath(taskId, "receipt") : ARTIFACT_PATHS.receipt);
386
+
329
387
  const rejectionCodes = [...new Set(result.errors.map((error) => error.code))].sort();
330
388
  const evidenceOnlyRejection = rejectionCodes.length > 0
331
389
  && rejectionCodes.every(isRecoverableCompletionEvidenceCode);
@@ -336,7 +394,7 @@ export async function runComplete({ target, packageRoot, strict = false, persist
336
394
  "E_AUTHORITY_UNTRUSTED_SOURCE",
337
395
  ].includes(code));
338
396
  if (persist && result.status === "REJECTED" && evidenceOnlyRejection && !authorityRejection) {
339
- const state = await readWorkState(target, packageRoot);
397
+ const state = await readWorkState(target, { packageRoot, taskId, statePath });
340
398
  if (state?.phase === "REVIEWING") {
341
399
  const reasonCodes = rejectionCodes;
342
400
  const missingRequirementIds = [...new Set(result.errors.map((error) => error.requirementId).filter(Boolean))].sort();
@@ -364,11 +422,11 @@ export async function runComplete({ target, packageRoot, strict = false, persist
364
422
  };
365
423
  let receipt = null;
366
424
  try {
367
- receipt = await readJsonArtifact(target, ARTIFACT_PATHS.receipt, "execution-receipt", packageRoot);
425
+ receipt = await readJsonArtifact(target, receiptRel, "execution-receipt", packageRoot);
368
426
  } catch {
369
427
  // The evaluator already reports a missing or invalid receipt; evidence-only rejection can persist without one.
370
428
  }
371
- await writeWorkState(target, next, { packageRoot });
429
+ await writeWorkState(target, next, { packageRoot, taskId, statePath });
372
430
  let nextReceipt = null;
373
431
  if (receipt) {
374
432
  nextReceipt = await createReceipt({
@@ -376,9 +434,9 @@ export async function runComplete({ target, packageRoot, strict = false, persist
376
434
  stateFingerprint: canonicalFingerprint(next),
377
435
  verificationCycle: next.verificationCycle ?? receipt.value.verificationCycle ?? 1,
378
436
  }, packageRoot, { target, taskId: state.taskId, authorityContext, runtimeContext });
379
- await writeJsonArtifact(target, ARTIFACT_PATHS.receipt, nextReceipt, "execution-receipt", packageRoot);
437
+ await writeJsonArtifact(target, receiptRel, nextReceipt, "execution-receipt", packageRoot);
380
438
  }
381
- const ledger = await validateEventLedger(target, packageRoot);
439
+ const ledger = await validateEventLedger(target, packageRoot, { taskId, eventsPath });
382
440
  const lastRejection = ledger.events.filter((e) => e.taskId === state.taskId && e.event === "COMPLETION_REJECTED").at(-1);
383
441
  const lastDetails = lastRejection?.details ?? {};
384
442
  const isIdentical = lastRejection
@@ -399,14 +457,14 @@ export async function runComplete({ target, packageRoot, strict = false, persist
399
457
  stateFingerprint: canonicalFingerprint(next),
400
458
  ...(nextReceipt ? { receiptFingerprint: canonicalFingerprint(nextReceipt) } : {}),
401
459
  },
402
- }, packageRoot);
460
+ }, packageRoot, { taskId, eventsPath });
403
461
  }
404
462
  }
405
463
  }
406
464
  if (persist && result.status === "VALID") {
407
- const state = await readWorkState(target, packageRoot);
465
+ const state = await readWorkState(target, { packageRoot, taskId, statePath });
408
466
  if (state && state.phase !== "COMPLETE") {
409
- const receipt = await readJsonArtifact(target, ARTIFACT_PATHS.receipt, "execution-receipt", packageRoot);
467
+ const receipt = await readJsonArtifact(target, receiptRel, "execution-receipt", packageRoot);
410
468
  const next = {
411
469
  ...state,
412
470
  previousPhase: state.phase,
@@ -421,13 +479,13 @@ export async function runComplete({ target, packageRoot, strict = false, persist
421
479
  stateFingerprint: canonicalFingerprint(next),
422
480
  verificationCycle: next.verificationCycle ?? receipt.value.verificationCycle ?? 1,
423
481
  }, packageRoot, { target, taskId: state.taskId, authorityContext, runtimeContext });
424
- await writeWorkState(target, next, { packageRoot });
425
- await writeJsonArtifact(target, ARTIFACT_PATHS.receipt, nextReceipt, "execution-receipt", packageRoot);
482
+ await writeWorkState(target, next, { packageRoot, taskId, statePath });
483
+ await writeJsonArtifact(target, receiptRel, nextReceipt, "execution-receipt", packageRoot);
426
484
  }
427
- const contract = await readContract(target, packageRoot);
428
- const ledger = await validateEventLedger(target, packageRoot);
429
- if (!ledger.events.some((event) => event.event === "COMPLETION_VALIDATED")) {
430
- await appendProtocolEvent(target, { taskId: contract.value.taskId, event: "COMPLETION_VALIDATED" }, packageRoot);
485
+ const contract = await readContract(target, packageRoot, { taskId, contractPath });
486
+ const ledger = await validateEventLedger(target, packageRoot, { taskId, eventsPath });
487
+ if (!ledger.events.some((event) => event.taskId === contract.value.taskId && event.event === "COMPLETION_VALIDATED")) {
488
+ await appendProtocolEvent(target, { taskId: contract.value.taskId, event: "COMPLETION_VALIDATED" }, packageRoot, { taskId, eventsPath });
431
489
  }
432
490
  }
433
491
  return result;
@@ -11,6 +11,7 @@ import { assertSafePath, ensureWithin, fileExists } from "./filesystem.js";
11
11
  import { PROTOCOL_VERSION, WORK_PHASES } from "./protocol.js";
12
12
  import { assertSecretFree } from "./receipt.js";
13
13
  import { getPackageRoot } from "./templates.js";
14
+ import { taskArtifactPath } from "./task-paths.js";
14
15
 
15
16
  export const CONTINUITY_PATH = ARTIFACT_PATHS.continuity;
16
17
  export const CONTINUITY_SCHEMA_VERSION = 1;
@@ -182,8 +183,12 @@ export function createContinuity(input) {
182
183
  });
183
184
  }
184
185
 
185
- export async function readContinuity(target, packageRoot = getPackageRoot()) {
186
- const artifact = await readJsonArtifact(target, CONTINUITY_PATH, "continuity", packageRoot);
186
+ export async function readContinuity(target, options = {}) {
187
+ const packageRoot = typeof options === "string" ? options : (options?.packageRoot ?? getPackageRoot());
188
+ const relPath = typeof options === "object" && options !== null
189
+ ? (options.continuityPath ?? options.relativePath ?? (options.taskId ? taskArtifactPath(options.taskId, "continuity") : CONTINUITY_PATH))
190
+ : CONTINUITY_PATH;
191
+ const artifact = await readJsonArtifact(target, relPath, "continuity", packageRoot);
187
192
  return { ...artifact, value: assertContinuitySemantics(artifact.value) };
188
193
  }
189
194
 
@@ -192,25 +197,25 @@ export async function writeContinuity(target, operationalInput = {}, options = {
192
197
  let state = options.state;
193
198
  if (!state) {
194
199
  const { readWorkState } = await import("./work-state.js");
195
- state = await readWorkState(target, packageRoot);
200
+ state = await readWorkState(target, { packageRoot, taskId: options.taskId, statePath: options.statePath });
196
201
  }
197
- if (!state) throw continuityError("E_CONTINUITY_STATE_MISSING", "Cannot record continuity without work state", [ARTIFACT_PATHS.state]);
202
+ if (!state) throw continuityError("E_CONTINUITY_STATE_MISSING", "Cannot record continuity without work state", [options.statePath ?? (options.taskId ? taskArtifactPath(options.taskId, "state") : ARTIFACT_PATHS.state)]);
198
203
 
199
204
  let contract = options.contract;
200
205
  if (!contract) {
201
206
  const { readContract } = await import("./contract.js");
202
- contract = await readContract(target, packageRoot);
207
+ contract = await readContract(target, packageRoot, { taskId: options.taskId, contractPath: options.contractPath });
203
208
  }
204
209
  const currentContractFingerprint = contract?.fingerprint
205
210
  ?? (contract ? canonicalFingerprint(contract.value ?? contract) : null);
206
211
  if (!currentContractFingerprint) {
207
- throw continuityError("E_CONTINUITY_CONTRACT_MISMATCH", "Cannot bind continuity to the current contract", [ARTIFACT_PATHS.contract]);
212
+ throw continuityError("E_CONTINUITY_CONTRACT_MISMATCH", "Cannot bind continuity to the current contract", [options.contractPath ?? (options.taskId ? taskArtifactPath(options.taskId, "contract") : ARTIFACT_PATHS.contract)]);
208
213
  }
209
214
  if (state.contractFingerprint !== currentContractFingerprint) {
210
215
  throw continuityError(
211
216
  "E_CONTINUITY_CONTRACT_MISMATCH",
212
217
  "Work state contract fingerprint does not match the current contract",
213
- [ARTIFACT_PATHS.state, ARTIFACT_PATHS.contract],
218
+ [options.statePath ?? (options.taskId ? taskArtifactPath(options.taskId, "state") : ARTIFACT_PATHS.state), options.contractPath ?? (options.taskId ? taskArtifactPath(options.taskId, "contract") : ARTIFACT_PATHS.contract)],
214
219
  );
215
220
  }
216
221
  const value = createContinuity({
@@ -231,15 +236,17 @@ export async function writeContinuity(target, operationalInput = {}, options = {
231
236
  inspectFirst: operationalInput.inspectFirst ?? [],
232
237
  ...(operationalInput.resumeNote !== undefined ? { resumeNote: operationalInput.resumeNote } : {}),
233
238
  });
234
- return writeJsonArtifact(target, CONTINUITY_PATH, value, "continuity", packageRoot, {
239
+ const relPath = options.continuityPath ?? options.relativePath ?? (options.taskId ? taskArtifactPath(options.taskId, "continuity") : CONTINUITY_PATH);
240
+ return writeJsonArtifact(target, relPath, value, "continuity", packageRoot, {
235
241
  dryRun: options.dryRun ?? false,
236
242
  });
237
243
  }
238
244
 
239
- export async function clearContinuity(target) {
240
- await assertSafePath(target, CONTINUITY_PATH);
241
- const artifactPath = ensureWithin(target, CONTINUITY_PATH);
242
- if (!(await fileExists(artifactPath))) return { removed: false, path: CONTINUITY_PATH };
245
+ export async function clearContinuity(target, options = {}) {
246
+ const relPath = options.continuityPath ?? options.relativePath ?? (options.taskId ? taskArtifactPath(options.taskId, "continuity") : CONTINUITY_PATH);
247
+ await assertSafePath(target, relPath);
248
+ const artifactPath = ensureWithin(target, relPath);
249
+ if (!(await fileExists(artifactPath))) return { removed: false, path: relPath };
243
250
  await unlink(artifactPath);
244
- return { removed: true, path: CONTINUITY_PATH };
251
+ return { removed: true, path: relPath };
245
252
  }
@@ -1,5 +1,6 @@
1
1
  import { PROTOCOL_VERSION } from "./protocol.js";
2
2
  import { ARTIFACT_PATHS, canonicalFingerprint, readJsonArtifact, writeJsonArtifact } from "./artifacts.js";
3
+ import { taskArtifactPath } from "./task-paths.js";
3
4
  import { assertSchema, readSchema } from "./schema-validation.js";
4
5
  import { assertSecretFree } from "./receipt.js";
5
6
  import { REQUIREMENT_TYPES, isMixedTerminalRequirement } from "./evidence-readiness.js";
@@ -199,8 +200,9 @@ export async function validateContract(contract, packageRoot) {
199
200
  return contract;
200
201
  }
201
202
 
202
- export async function readContract(target, packageRoot) {
203
- const artifact = await readJsonArtifact(target, ARTIFACT_PATHS.contract, "current-contract", packageRoot);
203
+ export async function readContract(target, packageRoot, options = {}) {
204
+ const contractPath = options.contractPath ?? options.contractFile ?? options.relativePath ?? (options.taskId ? taskArtifactPath(options.taskId, "contract") : ARTIFACT_PATHS.contract);
205
+ const artifact = await readJsonArtifact(target, contractPath, "current-contract", packageRoot);
204
206
  await validateContract(artifact.value, packageRoot);
205
207
  return artifact;
206
208
  }
@@ -208,9 +210,10 @@ export async function readContract(target, packageRoot) {
208
210
  export async function writeContract(target, contract, packageRoot, options = {}) {
209
211
  assertSecretFree(contract);
210
212
  assertAssumptions(contract.assumptions ?? []);
213
+ const contractPath = options.contractPath ?? options.contractFile ?? options.relativePath ?? (options.taskId ? taskArtifactPath(options.taskId, "contract") : ARTIFACT_PATHS.contract);
211
214
  return writeJsonArtifact(
212
215
  target,
213
- ARTIFACT_PATHS.contract,
216
+ contractPath,
214
217
  contract,
215
218
  "current-contract",
216
219
  packageRoot,