@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
package/src/core/phase.js CHANGED
@@ -11,11 +11,16 @@ import { readPersistedRoute } from "./route-artifact.js";
11
11
  import { assertWorkPhase, isValidTransition } from "./protocol.js";
12
12
  import { readWorkState, writeWorkState } from "./work-state.js";
13
13
  import { evaluateCompletion } from "./completion.js";
14
- import { evaluatePreflight, validatePersistedPreflight } from "./preflight.js";
14
+ import { evaluatePreflight } from "./preflight.js";
15
15
  import { requiredEvidenceForTarget } from "./completion-artifacts.js";
16
16
  import { assertCompletionRelationships, assertStateIdentity } from "./completion-relationships.js";
17
17
  import { createReceipt, validateReceipt } from "./receipt.js";
18
18
  import { assertExecutionPrerequisites, hasExecutionStarted } from "./execution-prerequisites.js";
19
+ import { taskArtifactPath } from "./task-paths.js";
20
+ import { discoverTasks } from "./task-discovery.js";
21
+ import { assertNoScopeConflicts, assertScopeClean } from "./task-scope.js";
22
+ import { readTaskDescriptor } from "./task-descriptor.js";
23
+ import { E_TASK_SCOPE_REQUIRED } from "./error-codes.js";
19
24
 
20
25
  function phaseError(code, message, artifacts = []) {
21
26
  const error = new Error(message);
@@ -44,15 +49,19 @@ const LATE_PHASES = new Set([
44
49
  "COMPLETE",
45
50
  ]);
46
51
 
47
- async function assertPersistedStateIdentity(target, state, toPhase, packageRoot) {
52
+ async function assertPersistedStateIdentity(target, state, toPhase, packageRoot, options = {}) {
53
+ const scopedTaskId = options.taskId ?? null;
48
54
  const requireContract = LATE_PHASES.has(state.phase)
49
55
  || ["CONTRACT_READY", "ROUTED", "EXECUTING"].includes(toPhase);
50
56
  const requireRoute = LATE_PHASES.has(state.phase)
51
57
  || ["ROUTED", "EXECUTING"].includes(toPhase);
58
+ const contractRel = options.contractPath ?? (scopedTaskId ? taskArtifactPath(scopedTaskId, "contract") : ARTIFACT_PATHS.contract);
59
+ const routeRel = options.routePath ?? (scopedTaskId ? taskArtifactPath(scopedTaskId, "route") : ARTIFACT_PATHS.route);
60
+
52
61
  let contract = null;
53
62
  let route = null;
54
63
  try {
55
- contract = await readContract(target, packageRoot);
64
+ contract = await readContract(target, packageRoot, { taskId: scopedTaskId, contractPath: options.contractPath });
56
65
  } catch (error) {
57
66
  if (error.code === "ARTIFACT_MISSING" && !requireContract) {
58
67
  contract = null;
@@ -60,12 +69,12 @@ async function assertPersistedStateIdentity(target, state, toPhase, packageRoot)
60
69
  throw phaseError(
61
70
  requireContract ? "E_PHASE_PREREQUISITE_MISSING" : error.code ?? "E_CONTRACT_INVALID",
62
71
  `${requireContract ? `Phase ${toPhase} requires current contract` : "Unable to validate current contract"}: ${error.message}`,
63
- [ARTIFACT_PATHS.contract],
72
+ [contractRel],
64
73
  );
65
74
  }
66
75
  }
67
76
  try {
68
- route = await readPersistedRoute(target, packageRoot);
77
+ route = await readPersistedRoute(target, packageRoot, { taskId: scopedTaskId, routePath: options.routePath });
69
78
  } catch (error) {
70
79
  if (error.code === "ARTIFACT_MISSING" && !requireRoute) {
71
80
  route = null;
@@ -73,7 +82,7 @@ async function assertPersistedStateIdentity(target, state, toPhase, packageRoot)
73
82
  throw phaseError(
74
83
  requireRoute ? "E_PHASE_PREREQUISITE_MISSING" : error.code ?? "E_ROUTE_INVALID",
75
84
  `${requireRoute ? `Phase ${toPhase} requires persisted route` : "Unable to validate persisted route"}: ${error.message}`,
76
- [ARTIFACT_PATHS.route],
85
+ [routeRel],
77
86
  );
78
87
  }
79
88
  }
@@ -101,24 +110,28 @@ function reconcileImplementationStep(state, toPhase) {
101
110
  };
102
111
  }
103
112
 
104
- async function assertPhasePrerequisites(target, state, toPhase, packageRoot, authorityContext, runtimeContext) {
113
+ async function assertPhasePrerequisites(target, state, toPhase, packageRoot, authorityContext, runtimeContext, options = {}) {
114
+ const scopedTaskId = options.taskId ?? null;
115
+ const contractRel = options.contractPath ?? (scopedTaskId ? taskArtifactPath(scopedTaskId, "contract") : ARTIFACT_PATHS.contract);
116
+ const routeRel = options.routePath ?? (scopedTaskId ? taskArtifactPath(scopedTaskId, "route") : ARTIFACT_PATHS.route);
117
+
105
118
  if (toPhase === "CONTRACT_READY" || toPhase === "ROUTED" || toPhase === "EXECUTING") {
106
119
  try {
107
- await readContract(target, packageRoot);
120
+ await readContract(target, packageRoot, { taskId: scopedTaskId, contractPath: options.contractPath });
108
121
  } catch (error) {
109
- throw phaseError("E_PHASE_PREREQUISITE_MISSING", `Phase ${toPhase} requires ${ARTIFACT_PATHS.contract}: ${error.message}`, [ARTIFACT_PATHS.contract]);
122
+ throw phaseError("E_PHASE_PREREQUISITE_MISSING", `Phase ${toPhase} requires ${contractRel}: ${error.message}`, [contractRel]);
110
123
  }
111
124
  }
112
125
  if (toPhase === "ROUTED" || toPhase === "EXECUTING") {
113
126
  try {
114
- await readPersistedRoute(target, packageRoot);
127
+ await readPersistedRoute(target, packageRoot, { taskId: scopedTaskId, routePath: options.routePath });
115
128
  } catch (error) {
116
- throw phaseError("E_PHASE_PREREQUISITE_MISSING", `Phase ${toPhase} requires ${ARTIFACT_PATHS.route}: ${error.message}`, [ARTIFACT_PATHS.route]);
129
+ throw phaseError("E_PHASE_PREREQUISITE_MISSING", `Phase ${toPhase} requires ${routeRel}: ${error.message}`, [routeRel]);
117
130
  }
118
131
  }
119
132
  if (hasExecutionStarted(toPhase)) {
120
133
  try {
121
- await assertExecutionPrerequisites({ target, state, packageRoot });
134
+ await assertExecutionPrerequisites({ target, state, packageRoot, ...options, taskId: scopedTaskId });
122
135
  } catch (error) {
123
136
  throw phaseError(error.code, error.message, error.artifacts);
124
137
  }
@@ -127,7 +140,7 @@ async function assertPhasePrerequisites(target, state, toPhase, packageRoot, aut
127
140
  throw phaseError("E_PHASE_EVIDENCE_MISSING", "COMPLETE requires verification evidence");
128
141
  }
129
142
  if (toPhase === "COMPLETE") {
130
- const completion = await evaluateCompletion({ target, packageRoot, persist: false, authorityContext, runtimeContext });
143
+ const completion = await evaluateCompletion({ target, packageRoot, persist: false, authorityContext, runtimeContext, taskId: scopedTaskId, ...options });
131
144
  if (completion.status !== "VALID") {
132
145
  throw phaseError("E_COMPLETION_REJECTED", "COMPLETE requires a valid completion audit", completion.errors.flatMap((error) => error.artifacts ?? []));
133
146
  }
@@ -141,37 +154,76 @@ export async function advanceWorkState(target, toPhase, options = {}) {
141
154
  now = new Date().toISOString(),
142
155
  authorityContext,
143
156
  runtimeContext,
157
+ taskId = null,
158
+ statePath = null,
159
+ contractPath = null,
160
+ routePath = null,
161
+ receiptPath = null,
162
+ eventsPath = null,
144
163
  } = normalizedOptions;
145
164
  assertWorkPhase(toPhase);
146
- const state = await readWorkState(target, packageRoot);
147
- if (!state) throw phaseError("E_PHASE_PREREQUISITE_MISSING", "Cannot advance without work state", [ARTIFACT_PATHS.state]);
148
- await assertPhasePrerequisites(target, state, toPhase, packageRoot, authorityContext, runtimeContext);
149
- await assertPersistedStateIdentity(target, state, toPhase, packageRoot);
165
+
166
+ const state = await readWorkState(target, { packageRoot, taskId, statePath });
167
+ const stateRel = statePath ?? (taskId ? taskArtifactPath(taskId, "state") : ARTIFACT_PATHS.state);
168
+ if (!state) throw phaseError("E_PHASE_PREREQUISITE_MISSING", "Cannot advance without work state", [stateRel]);
169
+
170
+ const eventsRel = eventsPath ?? (taskId ? taskArtifactPath(taskId, "events") : ARTIFACT_PATHS.events);
171
+ const receiptRel = receiptPath ?? (taskId ? taskArtifactPath(taskId, "receipt") : ARTIFACT_PATHS.receipt);
172
+
173
+ await assertPhasePrerequisites(target, state, toPhase, packageRoot, authorityContext, runtimeContext, { taskId, statePath, contractPath, routePath, receiptPath, eventsPath });
174
+ await assertPersistedStateIdentity(target, state, toPhase, packageRoot, { taskId, contractPath, routePath });
175
+
176
+ if (toPhase === "EXECUTING" && taskId) {
177
+ // Multi-task checkout scope checks
178
+ const discovered = await discoverTasks(target, packageRoot);
179
+ let descriptor = null;
180
+ try {
181
+ const descArtifact = await readTaskDescriptor(target, taskId, packageRoot);
182
+ descriptor = descArtifact.value;
183
+ } catch {
184
+ // Descriptor might not exist if legacy
185
+ }
186
+
187
+ const nonCompleteTasks = discovered.filter((t) => t.phase !== "COMPLETE");
188
+ if (nonCompleteTasks.length > 1) {
189
+ const claims = descriptor?.writeClaims ?? [];
190
+ if (claims.length === 0) {
191
+ throw phaseError(
192
+ E_TASK_SCOPE_REQUIRED,
193
+ "Multiple tasks exist in the repository. Entering EXECUTING requires declared non-empty write claims. Use 'forgeloop task-scope' first.",
194
+ [descriptor ? taskArtifactPath(taskId, "descriptor") : stateRel],
195
+ );
196
+ }
197
+ assertNoScopeConflicts(claims, discovered, taskId);
198
+ await assertScopeClean(target, claims);
199
+ }
200
+ }
201
+
150
202
  if (!isValidTransition(state.phase, toPhase)) {
151
203
  throw phaseError("E_PHASE_TRANSITION_INVALID", `Invalid work-state transition: ${state.phase} -> ${toPhase}`);
152
204
  }
153
- const ledger = await validateEventLedger(target, packageRoot);
205
+ const ledger = await validateEventLedger(target, packageRoot, { taskId, eventsPath });
154
206
  if (!ledger.valid) {
155
207
  const first = ledger.errors[0];
156
- throw phaseError(first.code, first.message, [ARTIFACT_PATHS.events]);
208
+ throw phaseError(first.code, first.message, [eventsRel]);
157
209
  }
158
210
  if (ledger.events.some((event) => event.taskId !== state.taskId)) {
159
211
  throw phaseError(
160
212
  "E_PHASE_CHRONOLOGY_INVALID",
161
213
  "Cannot advance work state with lifecycle events from a different task",
162
- [ARTIFACT_PATHS.events, ARTIFACT_PATHS.state],
214
+ [eventsRel, stateRel],
163
215
  );
164
216
  }
165
217
  const coherenceErrors = validateStateLedgerCoherence(state, ledger.events);
166
218
  if (coherenceErrors.length > 0) {
167
- throw phaseError(coherenceErrors[0].code, coherenceErrors[0].message, [ARTIFACT_PATHS.state, ARTIFACT_PATHS.events]);
219
+ throw phaseError(coherenceErrors[0].code, coherenceErrors[0].message, [stateRel, eventsRel]);
168
220
  }
169
221
  const eventType = PHASE_EVENTS[toPhase];
170
222
  const reenteringVerification = toPhase === "VERIFYING" && ["CORRECTING", "REVIEWING"].includes(state.phase);
171
223
  if (toPhase === "VERIFYING" && state.phase === "REVIEWING") {
172
224
  let currentReceipt = null;
173
225
  try {
174
- const receiptArtifact = await readJsonArtifact(target, ARTIFACT_PATHS.receipt, "execution-receipt", packageRoot);
226
+ const receiptArtifact = await readJsonArtifact(target, receiptRel, "execution-receipt", packageRoot);
175
227
  currentReceipt = receiptArtifact?.value;
176
228
  } catch {
177
229
  // If receipt is not present, pass null
@@ -186,7 +238,7 @@ export async function advanceWorkState(target, toPhase, options = {}) {
186
238
  throw phaseError(
187
239
  firstError.code ?? "E_COMPLETION_RECOVERY_UNAUTHORIZED",
188
240
  firstError.message ?? "REVIEWING -> VERIFYING requires authorized completion recovery",
189
- [ARTIFACT_PATHS.state, ARTIFACT_PATHS.events],
241
+ [stateRel, eventsRel],
190
242
  );
191
243
  }
192
244
  }
@@ -195,7 +247,7 @@ export async function advanceWorkState(target, toPhase, options = {}) {
195
247
  throw phaseError(
196
248
  "E_PHASE_PREREQUISITE_MISSING",
197
249
  "CORRECTING -> VERIFYING requires a diagnosed hypothesis",
198
- [ARTIFACT_PATHS.state],
250
+ [stateRel],
199
251
  );
200
252
  }
201
253
  }
@@ -203,13 +255,13 @@ export async function advanceWorkState(target, toPhase, options = {}) {
203
255
  && ledger.events.some((event) => event.taskId === state.taskId && event.event === "REVIEW_STARTED");
204
256
  if (eventType && !reenteringVerification && !repeatedReview
205
257
  && ledger.events.some((event) => event.taskId === state.taskId && event.event === eventType)) {
206
- throw phaseError("E_PHASE_CHRONOLOGY_INVALID", `Lifecycle milestone already exists: ${eventType}`, [ARTIFACT_PATHS.events]);
258
+ throw phaseError("E_PHASE_CHRONOLOGY_INVALID", `Lifecycle milestone already exists: ${eventType}`, [eventsRel]);
207
259
  }
208
260
  const milestoneIndex = reenteringVerification || repeatedReview ? -1 : LIFECYCLE_MILESTONES.indexOf(eventType);
209
261
  if (milestoneIndex >= 0) {
210
262
  const lastMilestone = ledger.events.reduce((last, event) => Math.max(last, LIFECYCLE_MILESTONES.indexOf(event.event)), -1);
211
263
  if (lastMilestone !== milestoneIndex - 1) {
212
- throw phaseError("E_PHASE_CHRONOLOGY_INVALID", `Phase ${toPhase} cannot append ${eventType} after the current lifecycle ledger`, [ARTIFACT_PATHS.events]);
264
+ throw phaseError("E_PHASE_CHRONOLOGY_INVALID", `Phase ${toPhase} cannot append ${eventType} after the current lifecycle ledger`, [eventsRel]);
213
265
  }
214
266
  }
215
267
  const reconciled = reconcileImplementationStep(state, toPhase);
@@ -225,10 +277,10 @@ export async function advanceWorkState(target, toPhase, options = {}) {
225
277
  if (reenteringVerification) delete next.lastCompletionAttempt;
226
278
  let nextReceipt = null;
227
279
  try {
228
- const receipt = await readJsonArtifact(target, ARTIFACT_PATHS.receipt, "execution-receipt", packageRoot);
229
- const contract = await readContract(target, packageRoot);
230
- const route = await readPersistedRoute(target, packageRoot);
231
- const preflight = await evaluatePreflight({ target, packageRoot });
280
+ const receipt = await readJsonArtifact(target, receiptRel, "execution-receipt", packageRoot);
281
+ const contract = await readContract(target, packageRoot, { taskId, contractPath });
282
+ const route = await readPersistedRoute(target, packageRoot, { taskId, routePath });
283
+ const preflight = await evaluatePreflight({ target, packageRoot, taskId, contractPath, routePath, statePath });
232
284
  const requiredEvidence = await requiredEvidenceForTarget({
233
285
  target,
234
286
  contract,
@@ -280,9 +332,9 @@ export async function advanceWorkState(target, toPhase, options = {}) {
280
332
  } catch (error) {
281
333
  if (error.code !== "ARTIFACT_MISSING") throw error;
282
334
  }
283
- await writeWorkState(target, next, { packageRoot });
335
+ await writeWorkState(target, next, { packageRoot, taskId, statePath });
284
336
  if (nextReceipt) {
285
- await writeJsonArtifact(target, ARTIFACT_PATHS.receipt, nextReceipt, "execution-receipt", packageRoot);
337
+ await writeJsonArtifact(target, receiptRel, nextReceipt, "execution-receipt", packageRoot);
286
338
  }
287
339
  if (eventType) await appendProtocolEvent(target, {
288
340
  taskId: state.taskId,
@@ -293,6 +345,6 @@ export async function advanceWorkState(target, toPhase, options = {}) {
293
345
  : eventType === "REVIEW_STARTED"
294
346
  ? { verificationCycle: next.verificationCycle ?? 1 }
295
347
  : undefined,
296
- }, packageRoot);
348
+ }, packageRoot, { taskId, eventsPath });
297
349
  return next;
298
350
  }
@@ -14,6 +14,8 @@ import {
14
14
  validatePersistedPreflight,
15
15
  } from "./preflight-model.js";
16
16
 
17
+ import { taskArtifactPath } from "./task-paths.js";
18
+
17
19
  async function readOptionalIdentityArtifact(readArtifact, invalidCode, artifactPath) {
18
20
  try {
19
21
  return await readArtifact();
@@ -23,36 +25,44 @@ async function readOptionalIdentityArtifact(readArtifact, invalidCode, artifactP
23
25
  }
24
26
  }
25
27
 
26
- export async function assertPreflightPersistenceSafety(target, packageRoot, taskId) {
28
+ export async function assertPreflightPersistenceSafety(target, packageRoot, taskIdOrOptions = {}, maybeOptions = {}) {
29
+ const options = typeof taskIdOrOptions === "object" && taskIdOrOptions !== null ? taskIdOrOptions : maybeOptions;
30
+ const taskId = typeof taskIdOrOptions === "string" ? taskIdOrOptions : (options.taskId ?? null);
31
+ const contractRel = options.contractPath ?? (taskId && taskId !== "unknown" ? taskArtifactPath(taskId, "contract") : ARTIFACT_PATHS.contract);
32
+ const routeRel = options.routePath ?? (taskId && taskId !== "unknown" ? taskArtifactPath(taskId, "route") : ARTIFACT_PATHS.route);
33
+ const stateRel = options.statePath ?? (taskId && taskId !== "unknown" ? taskArtifactPath(taskId, "state") : ARTIFACT_PATHS.state);
34
+ const eventsRel = options.eventsPath ?? (taskId && taskId !== "unknown" ? taskArtifactPath(taskId, "events") : ARTIFACT_PATHS.events);
35
+
27
36
  let state;
28
37
  try {
29
- state = await readWorkState(target, packageRoot);
38
+ state = await readWorkState(target, { packageRoot, taskId, statePath: options.statePath });
30
39
  } catch (error) {
31
- throw preflightError("E_STATE_INVALID", error.message, [ARTIFACT_PATHS.state]);
40
+ throw preflightError("E_STATE_INVALID", error.message, [stateRel]);
32
41
  }
33
42
  const contract = await readOptionalIdentityArtifact(
34
- () => readContract(target, packageRoot),
43
+ () => readContract(target, packageRoot, { taskId, contractPath: options.contractPath }),
35
44
  "E_CONTRACT_INVALID",
36
- ARTIFACT_PATHS.contract,
45
+ contractRel,
37
46
  );
38
47
  const route = await readOptionalIdentityArtifact(
39
- () => readPersistedRoute(target, packageRoot),
48
+ () => readPersistedRoute(target, packageRoot, { taskId, routePath: options.routePath }),
40
49
  "E_ROUTE_INVALID",
41
- ARTIFACT_PATHS.route,
50
+ routeRel,
42
51
  );
43
52
  if (state && (contract || route)) assertStateIdentity({ contract, route, state });
44
53
 
45
- if (taskId === "unknown") return null;
46
- const ledger = await validateEventLedger(target, packageRoot);
54
+ const effectiveTaskId = taskId ?? contract?.value?.taskId ?? state?.taskId ?? null;
55
+ if (effectiveTaskId === "unknown" || !effectiveTaskId) return null;
56
+ const ledger = await validateEventLedger(target, packageRoot, { taskId, eventsPath: options.eventsPath });
47
57
  if (!ledger.valid) {
48
58
  const first = ledger.errors[0];
49
- throw preflightError(first.code, first.message, [ARTIFACT_PATHS.events]);
59
+ throw preflightError(first.code, first.message, [eventsRel]);
50
60
  }
51
- if (ledger.events.some((event) => event.taskId !== taskId)) {
61
+ if (ledger.events.some((event) => event.taskId !== effectiveTaskId)) {
52
62
  throw preflightError(
53
63
  "E_PHASE_CHRONOLOGY_INVALID",
54
64
  "Preflight cannot append events to a ledger owned by a different task",
55
- [ARTIFACT_PATHS.events, ARTIFACT_PATHS.contract],
65
+ [eventsRel, contractRel],
56
66
  );
57
67
  }
58
68
  return ledger;
@@ -159,11 +169,11 @@ export function assertExistingReadyLifecycleCompatibility(ledger, result) {
159
169
  }
160
170
  }
161
171
 
162
- export async function appendActivationEvents(target, packageRoot, ledger, result) {
172
+ export async function appendActivationEvents(target, packageRoot, ledger, result, options = {}) {
163
173
  const events = [...(ledger?.events ?? [])];
164
174
  const hasEvent = (eventName) => events.some((event) => event.event === eventName && event.taskId === result.taskId);
165
175
  const append = async (input) => {
166
- const event = await appendProtocolEvent(target, input, packageRoot);
176
+ const event = await appendProtocolEvent(target, input, packageRoot, options);
167
177
  events.push(event);
168
178
  };
169
179
 
@@ -6,8 +6,9 @@ import { assertSourceProvenance } from "./sources.js";
6
6
  import { readPersistedRoute } from "./route-artifact.js";
7
7
  import { ensureWithin, readBytes } from "./filesystem.js";
8
8
  import { sha256 } from "./manifest.js";
9
- import { findProfilePath, validateProfileSources } from "./profile.js";
9
+ import { findProfilePath } from "./profile.js";
10
10
  import { issue } from "./preflight-model.js";
11
+ import { taskGatePath, taskArtifactPath } from "./task-paths.js";
11
12
 
12
13
  export async function readProfile(target) {
13
14
  const relativePath = await findProfilePath(target);
@@ -36,23 +37,25 @@ export async function optionalConfig(target, packageRoot, errors) {
36
37
  }
37
38
  }
38
39
 
39
- export async function loadContract(target, packageRoot, errors) {
40
+ export async function loadContract(target, packageRoot, errors, options = {}) {
40
41
  try {
41
- return await readContract(target, packageRoot);
42
+ return await readContract(target, packageRoot, options);
42
43
  } catch (error) {
43
- errors.push(issue(error.code === "ARTIFACT_MISSING" ? "E_CONTRACT_MISSING" : "E_CONTRACT_INVALID", error.message, [ARTIFACT_PATHS.contract]));
44
+ const path = options.contractPath ?? (options.taskId ? taskArtifactPath(options.taskId, "contract") : ARTIFACT_PATHS.contract);
45
+ errors.push(issue(error.code === "ARTIFACT_MISSING" ? "E_CONTRACT_MISSING" : "E_CONTRACT_INVALID", error.message, [path]));
44
46
  return null;
45
47
  }
46
48
  }
47
49
 
48
- export async function loadRoute(target, packageRoot, errors) {
50
+ export async function loadRoute(target, packageRoot, errors, options = {}) {
49
51
  try {
50
- return await readPersistedRoute(target, packageRoot);
52
+ return await readPersistedRoute(target, packageRoot, options);
51
53
  } catch (error) {
54
+ const path = options.routePath ?? (options.taskId ? taskArtifactPath(options.taskId, "route") : ARTIFACT_PATHS.route);
52
55
  const code = error.code === "ARTIFACT_MISSING"
53
56
  ? "E_ROUTE_MISSING"
54
57
  : ["E_ROUTE_REASON_MISSING", "E_ROUTE_INVALID"].includes(error.code) ? error.code : "E_ROUTE_INVALID";
55
- errors.push(issue(code, error.message, [ARTIFACT_PATHS.route]));
58
+ errors.push(issue(code, error.message, [path]));
56
59
  return null;
57
60
  }
58
61
  }
@@ -74,22 +77,24 @@ export async function loadSources(target, contract, packageRoot, errors) {
74
77
  return registry;
75
78
  }
76
79
 
77
- export async function inspectGates(target, contract, route, packageRoot, errors, config = {}) {
80
+ export async function inspectGates(target, contract, route, packageRoot, errors, config = {}, options = {}) {
78
81
  if (!route) return { required: [], satisfied: [], records: {} };
79
82
  const guideGates = await requiredGatesForGuides(route.value.guides, packageRoot);
80
83
  const required = [...new Set([...guideGates, ...(config.requiredGates ?? [])])].sort();
81
84
  const satisfied = [];
82
85
  const records = {};
86
+ const taskId = options.taskId ?? null;
83
87
  for (const gate of required) {
84
88
  let artifact;
89
+ const defaultGateRel = taskId ? taskGatePath(taskId, gate) : `${ARTIFACT_PATHS.gates}/${gate}.json`;
85
90
  try {
86
- artifact = await readGateIfPresent(target, gate, packageRoot);
91
+ artifact = await readGateIfPresent(target, gate, packageRoot, { ...options, taskId });
87
92
  } catch (error) {
88
- errors.push(issue(error.code === "ARTIFACT_MISSING" ? "E_GATE_UNVERIFIED" : "E_GATE_INVALID", error.message, [`${ARTIFACT_PATHS.gates}/${gate}.json`], { gate }));
93
+ errors.push(issue(error.code === "ARTIFACT_MISSING" ? "E_GATE_UNVERIFIED" : "E_GATE_INVALID", error.message, [defaultGateRel], { gate }));
89
94
  continue;
90
95
  }
91
96
  if (!artifact) {
92
- errors.push(issue("E_GATE_UNVERIFIED", `Required gate is missing or unverified: ${gate}`, [`${ARTIFACT_PATHS.gates}/${gate}.json`], { gate }));
97
+ errors.push(issue("E_GATE_UNVERIFIED", `Required gate is missing or unverified: ${gate}`, [defaultGateRel], { gate }));
93
98
  continue;
94
99
  }
95
100
  records[gate] = artifact;
@@ -8,7 +8,6 @@ import {
8
8
  issue,
9
9
  preflightError,
10
10
  sortIssues,
11
- validatePersistedPreflight,
12
11
  } from "./preflight-model.js";
13
12
  import {
14
13
  loadContract,
@@ -28,31 +27,36 @@ import {
28
27
  import { PROFILE_PATH } from "./target-layout.js";
29
28
  import { readPersistedRoute } from "./route-artifact.js";
30
29
  import { validateEventLedger } from "./events.js";
30
+ import { taskArtifactPath } from "./task-paths.js";
31
31
 
32
32
  const PREVIEW_DECISION_LIMIT = 10;
33
33
  const PREVIEW_DECISION_MAX_LENGTH = 240;
34
34
 
35
35
  export { validatePersistedPreflight } from "./preflight-model.js";
36
36
 
37
- export async function evaluatePreflight({ target, packageRoot, strict = false } = {}) {
37
+ export async function evaluatePreflight({ target, packageRoot, strict = false, taskId = null, contractPath = null, routePath = null, statePath = null } = {}) {
38
38
  const errors = [];
39
39
  const profile = await readProfile(target);
40
40
  const profileProvenance = await validateProfileSources(target, packageRoot);
41
41
  errors.push(...(profileProvenance.errors ?? []));
42
- const contract = await loadContract(target, packageRoot, errors);
43
- const route = await loadRoute(target, packageRoot, errors);
42
+ const contract = await loadContract(target, packageRoot, errors, { taskId, contractPath });
43
+ const route = await loadRoute(target, packageRoot, errors, { taskId, routePath });
44
44
  const config = await optionalConfig(target, packageRoot, errors);
45
45
  const effectiveStrict = strict || config.complianceMode === "strict";
46
46
  if (effectiveStrict && profile.status !== "verified") {
47
47
  errors.push(issue("E_PROFILE_UNVERIFIED", "Strict preflight requires a verified project profile", [PROFILE_PATH]));
48
48
  }
49
49
 
50
+ const contractRelPath = contractPath ?? (taskId ? taskArtifactPath(taskId, "contract") : ARTIFACT_PATHS.contract);
51
+ const routeRelPath = routePath ?? (taskId ? taskArtifactPath(taskId, "route") : ARTIFACT_PATHS.route);
52
+ const stateRelPath = statePath ?? (taskId ? taskArtifactPath(taskId, "state") : ARTIFACT_PATHS.state);
53
+
50
54
  const unresolvedDecisions = contract?.value?.unresolvedDecisions ?? [];
51
55
  if (unresolvedDecisions.length > 0) {
52
56
  errors.push(issue(
53
57
  "E_CONTRACT_UNRESOLVED_DECISION",
54
58
  "The current contract contains unresolved blocking decisions.",
55
- [ARTIFACT_PATHS.contract],
59
+ [contractRelPath],
56
60
  {
57
61
  decisions: unresolvedDecisions
58
62
  .slice(0, PREVIEW_DECISION_LIMIT)
@@ -67,32 +71,32 @@ export async function evaluatePreflight({ target, packageRoot, strict = false }
67
71
  if (route && contract) {
68
72
  assertRouteInvariants(route.value);
69
73
  if (route.value.contractFingerprint !== undefined && route.value.contractFingerprint !== contract.fingerprint) {
70
- errors.push(issue("E_ROUTE_STALE", "Routing result was created for a different contract", [ARTIFACT_PATHS.route, ARTIFACT_PATHS.contract]));
74
+ errors.push(issue("E_ROUTE_STALE", "Routing result was created for a different contract", [routeRelPath, contractRelPath]));
71
75
  }
72
76
  }
73
77
 
74
78
  const sources = await loadSources(target, contract, packageRoot, errors);
75
- const gates = await inspectGates(target, contract, route, packageRoot, errors, config);
79
+ const gates = await inspectGates(target, contract, route, packageRoot, errors, config, { taskId });
76
80
 
77
81
  let state = null;
78
82
  try {
79
- state = await readWorkState(target, packageRoot);
83
+ state = await readWorkState(target, { packageRoot, taskId, statePath });
80
84
  } catch (error) {
81
- errors.push(issue("E_STATE_INVALID", error.message, [ARTIFACT_PATHS.state]));
85
+ errors.push(issue("E_STATE_INVALID", error.message, [stateRelPath]));
82
86
  }
83
87
  if (state && route && JSON.stringify(state.selectedGuides) !== JSON.stringify(route.value.guides)) {
84
- errors.push(issue("E_ROUTE_GUIDE_MISMATCH", "work-state.selectedGuides must equal routing-result.guides", [ARTIFACT_PATHS.route, ARTIFACT_PATHS.state]));
88
+ errors.push(issue("E_ROUTE_GUIDE_MISMATCH", "work-state.selectedGuides must equal routing-result.guides", [routeRelPath, stateRelPath]));
85
89
  }
86
90
  if (state && contract && state.contractFingerprint !== contract.fingerprint) {
87
- errors.push(issue("E_CONTRACT_STALE", "work-state references a different contract", [ARTIFACT_PATHS.contract, ARTIFACT_PATHS.state]));
91
+ errors.push(issue("E_CONTRACT_STALE", "work-state references a different contract", [contractRelPath, stateRelPath]));
88
92
  }
89
93
 
90
94
  const sortedErrors = sortIssues(errors);
91
- const taskId = contract?.value?.taskId ?? state?.taskId ?? "unknown";
95
+ const effectiveTaskId = taskId ?? contract?.value?.taskId ?? state?.taskId ?? "unknown";
92
96
  return {
93
97
  schemaVersion: 1,
94
98
  protocolVersion: 1,
95
- taskId,
99
+ taskId: effectiveTaskId,
96
100
  status: sortedErrors.length === 0 ? "READY" : "BLOCKED",
97
101
  profile: { ...profile, provenance: profileProvenance.status },
98
102
  contract: contract
@@ -128,20 +132,31 @@ export async function validateReadyProtocolConsistency(options = {}) {
128
132
  });
129
133
  }
130
134
 
131
- export async function runPreflight({ target, packageRoot, strict = false, persist = true } = {}) {
132
- let result = await evaluatePreflight({ target, packageRoot, strict });
135
+ export async function runPreflight({
136
+ target,
137
+ packageRoot,
138
+ strict = false,
139
+ persist = true,
140
+ taskId = null,
141
+ contractPath = null,
142
+ routePath = null,
143
+ statePath = null,
144
+ preflightPath = null,
145
+ eventsPath = null,
146
+ } = {}) {
147
+ let result = await evaluatePreflight({ target, packageRoot, strict, taskId, contractPath, routePath, statePath });
133
148
  if (!persist) return result;
134
149
 
135
- let ledger = await assertPreflightPersistenceSafety(target, packageRoot, result.taskId);
150
+ let ledger = await assertPreflightPersistenceSafety(target, packageRoot, { taskId, eventsPath });
136
151
  assertPreflightResultPersistenceSafety(result);
137
152
  assertExistingReadyLifecycleCompatibility(ledger, result);
138
153
 
139
154
  let contract = null;
140
155
  let route = null;
141
156
  if (result.contract.status === "valid" && result.routing.status === "valid") {
142
- contract = await readContract(target, packageRoot);
143
- route = await readPersistedRoute(target, packageRoot);
144
- const state = await ensureResumableState({ target, packageRoot, contract, route });
157
+ contract = await readContract(target, packageRoot, { taskId, contractPath });
158
+ route = await readPersistedRoute(target, packageRoot, { taskId, routePath });
159
+ const state = await ensureResumableState({ target, packageRoot, contract, route, taskId, statePath });
145
160
  if (state) {
146
161
  await synchronizePreflightState({
147
162
  target,
@@ -152,22 +167,26 @@ export async function runPreflight({ target, packageRoot, strict = false, persis
152
167
  requiredGates: result.requiredGates,
153
168
  satisfiedGates: result.satisfiedGates,
154
169
  complianceMode: result.policy?.complianceMode,
170
+ statePath,
171
+ taskId,
155
172
  });
156
- result = await evaluatePreflight({ target, packageRoot, strict });
173
+ result = await evaluatePreflight({ target, packageRoot, strict, taskId, contractPath, routePath, statePath });
157
174
  assertPreflightResultPersistenceSafety(result);
158
175
  assertExistingReadyLifecycleCompatibility(ledger, result);
159
- ledger = await assertPreflightPersistenceSafety(target, packageRoot, result.taskId);
176
+ ledger = await assertPreflightPersistenceSafety(target, packageRoot, { taskId, eventsPath });
160
177
  }
161
178
  }
162
179
 
163
180
  if (result.taskId !== "unknown") {
164
- await appendActivationEvents(target, packageRoot, ledger, result);
165
- const afterEvents = await validateEventLedger(target, packageRoot);
181
+ await appendActivationEvents(target, packageRoot, ledger, result, { eventsPath, taskId });
182
+ const afterEvents = await validateEventLedger(target, packageRoot, { eventsPath, taskId });
166
183
  if (!afterEvents.valid) {
167
184
  const first = afterEvents.errors[0];
168
- throw preflightError(first.code, first.message, [ARTIFACT_PATHS.events]);
185
+ const evRel = eventsPath ?? (taskId ? taskArtifactPath(taskId, "events") : ARTIFACT_PATHS.events);
186
+ throw preflightError(first.code, first.message, [evRel]);
169
187
  }
170
188
  }
171
- await writeJsonArtifact(target, ARTIFACT_PATHS.preflight, result, "preflight", packageRoot);
189
+ const relPath = preflightPath ?? (taskId ? taskArtifactPath(taskId, "preflight") : ARTIFACT_PATHS.preflight);
190
+ await writeJsonArtifact(target, relPath, result, "preflight", packageRoot);
172
191
  return result;
173
192
  }
@@ -111,17 +111,8 @@ export const WORK_PHASES = Object.freeze([
111
111
  "BLOCKED",
112
112
  ]);
113
113
 
114
- export const GUIDE_IDS = Object.freeze([
115
- "premium",
116
- "clean",
117
- "test",
118
- "security",
119
- "design",
120
- "performance",
121
- "accessibility",
122
- "games",
123
- "taste",
124
- ]);
114
+ import { GUIDE_IDS } from "./guide-registry.js";
115
+ export { GUIDE_IDS };
125
116
 
126
117
  export const GUIDE_ORDER = GUIDE_IDS;
127
118
 
@@ -2,7 +2,7 @@ import { GUIDE_IDS, PROTOCOL_VERSION } from "./protocol.js";
2
2
  import { assertSchema, readSchema } from "./schema-validation.js";
3
3
  import { assertEvidenceList, evidenceMatches } from "./evidence.js";
4
4
  import { assertJsonLimits } from "./json-safety.js";
5
- import { assertCheck, assertCheckList } from "./checks.js";
5
+ import { assertCheck } from "./checks.js";
6
6
  import { assertCoverageList } from "./coverage.js";
7
7
 
8
8
  const RECEIPT_SCHEMA_VERSION = 1;
@@ -17,8 +17,8 @@ function section(id, label, status, details = null) {
17
17
  return { id, label, status, ...(details ? { details } : {}) };
18
18
  }
19
19
 
20
- export async function evaluateReport({ target, packageRoot, strict = false } = {}) {
21
- const audit = await evaluateAudit({ target, packageRoot, strict });
20
+ export async function evaluateReport({ target, packageRoot, strict = false, ...options } = {}) {
21
+ const audit = await evaluateAudit({ target, packageRoot, strict, ...options });
22
22
  const completion = audit.completion;
23
23
  const preflight = completion.preflight;
24
24
  const errorCodes = new Set(audit.errors.map((error) => error.code));