@cassiomc1/forgeloop 1.1.1 → 1.2.2

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 (79) hide show
  1. package/.cursor/rules/project-loop.mdc +1 -1
  2. package/.github/copilot-instructions.md +1 -1
  3. package/AGENTS.md +1 -1
  4. package/CLAUDE.md +1 -1
  5. package/DOCS_INDEX.md +3 -0
  6. package/ENG/design-code-eng.md +124 -0
  7. package/ENG/premium-sites-studio-eng.md +28 -0
  8. package/ENG/taste-frontend-eng.md +3 -2
  9. package/ENG/test-code-eng.md +45 -0
  10. package/LOOP_ENGINEERING.md +74 -0
  11. package/LOOP_SYSTEM_DESIGN.md +9 -5
  12. package/ORCHESTRATOR_INTEGRATION.md +41 -6
  13. package/PROTOCOL_INTEGRATION.md +13 -0
  14. package/README.md +40 -6
  15. package/TERMINOLOGY.md +10 -0
  16. package/THIRD_PARTY_NOTICES.md +58 -1
  17. package/THREAT_MODEL.md +12 -1
  18. package/docs/ARTIFACT_REFERENCE.md +152 -2
  19. package/docs/CLI_REFERENCE.md +346 -30
  20. package/docs/CROSS_HARNESS_CONTINUITY.md +1 -0
  21. package/docs/DOCUMENTATION_GUIDE.md +41 -4
  22. package/docs/GETTING_STARTED.md +39 -8
  23. package/docs/RECIPES.md +66 -7
  24. package/docs/TROUBLESHOOTING.md +279 -6
  25. package/package.json +1 -1
  26. package/schemas/policy-baseline.schema.json +26 -0
  27. package/schemas/policy-discovery.schema.json +45 -0
  28. package/schemas/policy-lock.schema.json +16 -0
  29. package/schemas/policy-rules.schema.json +48 -0
  30. package/schemas/policy-snapshot.schema.json +16 -0
  31. package/src/cli.js +102 -1
  32. package/src/commands/baseline.js +120 -0
  33. package/src/commands/init.js +304 -6
  34. package/src/commands/next.js +15 -1
  35. package/src/commands/policy-diff.js +51 -0
  36. package/src/commands/policy-discover.js +42 -0
  37. package/src/commands/policy-status.js +33 -0
  38. package/src/commands/profile-interview.js +50 -0
  39. package/src/commands/progress.js +51 -0
  40. package/src/commands/reconcile-closure.js +49 -0
  41. package/src/commands/record-decision-criterion.js +34 -0
  42. package/src/commands/record-diagnosis.js +49 -0
  43. package/src/commands/rule-verify.js +36 -0
  44. package/src/commands/validate-receipt.js +38 -3
  45. package/src/core/artifact-registry.js +60 -0
  46. package/src/core/audit.js +24 -0
  47. package/src/core/cli-command-definitions.js +163 -7
  48. package/src/core/cli-metadata.js +1 -1
  49. package/src/core/completion-artifacts.js +29 -3
  50. package/src/core/completion.js +101 -10
  51. package/src/core/diagnosis-model.js +214 -0
  52. package/src/core/diagnosis.js +171 -0
  53. package/src/core/error-codes.js +292 -0
  54. package/src/core/events.js +47 -1
  55. package/src/core/execution-prerequisites.js +38 -20
  56. package/src/core/execution.js +20 -3
  57. package/src/core/native-adapters.js +14 -4
  58. package/src/core/next-action-model.js +40 -5
  59. package/src/core/next-action.js +234 -91
  60. package/src/core/phase.js +29 -0
  61. package/src/core/policy-adapters.js +276 -0
  62. package/src/core/policy-baseline.js +144 -0
  63. package/src/core/policy-diff.js +133 -0
  64. package/src/core/policy-discovery.js +225 -0
  65. package/src/core/policy-engine.js +533 -0
  66. package/src/core/policy-mutation.js +139 -0
  67. package/src/core/preflight-consistency.js +23 -15
  68. package/src/core/preflight-model.js +10 -2
  69. package/src/core/preflight.js +65 -1
  70. package/src/core/progress.js +143 -0
  71. package/src/core/protocol.js +8 -0
  72. package/src/core/reconcile-closure.js +173 -0
  73. package/src/core/schema-validation.js +6 -0
  74. package/src/core/settlement-model.js +85 -0
  75. package/src/core/settlement.js +78 -0
  76. package/src/core/task-context.js +11 -0
  77. package/src/core/task-discovery.js +67 -1
  78. package/src/core/task-paths.js +9 -0
  79. package/src/core/templates.js +5 -0
@@ -5,6 +5,7 @@ import { evaluatePreflight, validatePersistedPreflight } from "./preflight.js";
5
5
  import { readPersistedRoute } from "./route-artifact.js";
6
6
  import { stateIdentityErrors } from "./completion-relationships.js";
7
7
  import { classifyLoadedWorkState } from "./work-state.js";
8
+ import { taskArtifactPath } from "./task-paths.js";
8
9
 
9
10
  const START_EXECUTION_EVENTS = Object.freeze([
10
11
  "CONTRACT_VALIDATED",
@@ -128,34 +129,51 @@ function prerequisiteLedgerErrors(ledger, taskId, preflight, route) {
128
129
  return errors;
129
130
  }
130
131
 
131
- export async function evaluateStartExecutionPrerequisites({ target, state, packageRoot } = {}) {
132
+ export async function evaluateStartExecutionPrerequisites({
133
+ target,
134
+ state,
135
+ packageRoot,
136
+ taskId = null,
137
+ contractPath = null,
138
+ routePath = null,
139
+ statePath = null,
140
+ preflightPath = null,
141
+ eventsPath = null,
142
+ } = {}) {
143
+ const effectiveTaskId = taskId ?? null;
144
+ const contractRel = contractPath ?? (effectiveTaskId ? taskArtifactPath(effectiveTaskId, "contract") : ARTIFACT_PATHS.contract);
145
+ const routeRel = routePath ?? (effectiveTaskId ? taskArtifactPath(effectiveTaskId, "route") : ARTIFACT_PATHS.route);
146
+ const stateRel = statePath ?? (effectiveTaskId ? taskArtifactPath(effectiveTaskId, "state") : ARTIFACT_PATHS.state);
147
+ const preflightRel = preflightPath ?? (effectiveTaskId ? taskArtifactPath(effectiveTaskId, "preflight") : ARTIFACT_PATHS.preflight);
148
+ const eventsRel = eventsPath ?? (effectiveTaskId ? taskArtifactPath(effectiveTaskId, "events") : ARTIFACT_PATHS.events);
149
+
132
150
  const errors = [];
133
151
  const requiredArtifacts = [
134
- ARTIFACT_PATHS.state,
135
- ARTIFACT_PATHS.contract,
136
- ARTIFACT_PATHS.route,
137
- ARTIFACT_PATHS.preflight,
138
- ARTIFACT_PATHS.events,
152
+ stateRel,
153
+ contractRel,
154
+ routeRel,
155
+ preflightRel,
156
+ eventsRel,
139
157
  ];
140
158
  if (!state) {
141
159
  return {
142
- errors: [issue("E_PHASE_PREREQUISITE_MISSING", "EXECUTING requires a work state", [ARTIFACT_PATHS.state])],
160
+ errors: [issue("E_PHASE_PREREQUISITE_MISSING", "EXECUTING requires a work state", [stateRel])],
143
161
  requiredArtifacts,
144
162
  };
145
163
  }
146
164
 
147
165
  const contract = await load(
148
- () => readContract(target, packageRoot),
166
+ () => readContract(target, packageRoot, { taskId: effectiveTaskId, contractPath }),
149
167
  "E_PHASE_PREREQUISITE_MISSING",
150
- `EXECUTING requires ${ARTIFACT_PATHS.contract}`,
151
- [ARTIFACT_PATHS.contract],
168
+ `EXECUTING requires ${contractRel}`,
169
+ [contractRel],
152
170
  errors,
153
171
  );
154
172
  const route = await load(
155
- () => readPersistedRoute(target, packageRoot),
173
+ () => readPersistedRoute(target, packageRoot, { taskId: effectiveTaskId, routePath }),
156
174
  "E_PHASE_PREREQUISITE_MISSING",
157
- `EXECUTING requires ${ARTIFACT_PATHS.route}`,
158
- [ARTIFACT_PATHS.route],
175
+ `EXECUTING requires ${routeRel}`,
176
+ [routeRel],
159
177
  errors,
160
178
  );
161
179
  if (!contract || !route) return { errors, requiredArtifacts, contract, route };
@@ -164,7 +182,7 @@ export async function evaluateStartExecutionPrerequisites({ target, state, packa
164
182
  errors.push(issue(
165
183
  "E_ROUTE_STALE",
166
184
  "EXECUTING requires work state and route to match the current contract",
167
- [ARTIFACT_PATHS.state, ARTIFACT_PATHS.route, ARTIFACT_PATHS.contract],
185
+ [stateRel, routeRel, contractRel],
168
186
  ));
169
187
  }
170
188
  errors.push(...stateIdentityErrors({ contract, route, state }));
@@ -172,14 +190,14 @@ export async function evaluateStartExecutionPrerequisites({ target, state, packa
172
190
  const freshness = await classifyLoadedWorkState({
173
191
  target,
174
192
  state,
175
- contractFile: ARTIFACT_PATHS.contract,
193
+ contractFile: contractRel,
176
194
  });
177
195
  errors.push(...freshnessErrors(state, freshness));
178
196
 
179
- const preflight = await evaluatePreflight({ target, packageRoot });
197
+ const preflight = await evaluatePreflight({ target, packageRoot, taskId: effectiveTaskId, contractPath, routePath, statePath });
180
198
  let persistedPreflight = null;
181
199
  try {
182
- persistedPreflight = await readJsonArtifact(target, ARTIFACT_PATHS.preflight, "preflight", packageRoot);
200
+ persistedPreflight = await readJsonArtifact(target, preflightRel, "preflight", packageRoot);
183
201
  } catch {
184
202
  // validatePersistedPreflight reports the stable, actionable preflight reason.
185
203
  }
@@ -189,16 +207,16 @@ export async function evaluateStartExecutionPrerequisites({ target, state, packa
189
207
  errors.push(issue(
190
208
  "E_PREFLIGHT_GATES_STALE",
191
209
  "Work state gate sets do not match the current preflight evaluation",
192
- [ARTIFACT_PATHS.state, ARTIFACT_PATHS.preflight, ARTIFACT_PATHS.gates],
210
+ [stateRel, preflightRel, ARTIFACT_PATHS.gates],
193
211
  ));
194
212
  }
195
213
 
196
- const ledger = await validateEventLedger(target, packageRoot);
214
+ const ledger = await validateEventLedger(target, packageRoot, { taskId: effectiveTaskId, eventsPath });
197
215
  errors.push(...prerequisiteLedgerErrors(ledger, contract.value.taskId, preflight, route));
198
216
  errors.push(...validateStateLedgerCoherence(state, ledger.events).map((error) => issue(
199
217
  error.code,
200
218
  error.message,
201
- [ARTIFACT_PATHS.state, ARTIFACT_PATHS.events],
219
+ [stateRel, eventsRel],
202
220
  )));
203
221
  errors.push(...persistedPreflightErrors);
204
222
  return { errors, requiredArtifacts, contract, route, preflight, persistedPreflight, ledger };
@@ -2,14 +2,14 @@ import { randomUUID } from "node:crypto";
2
2
  import { spawn } from "node:child_process";
3
3
  import { readdir } from "node:fs/promises";
4
4
  import path from "node:path";
5
- import { fileExists } from "./filesystem.js";
5
+ import { ensureWithin, fileExists } from "./filesystem.js";
6
6
  import {
7
7
  ARTIFACT_PATHS,
8
8
  executionArtifactPath,
9
9
  readJsonArtifact,
10
10
  writeJsonArtifact,
11
11
  } from "./artifacts.js";
12
- import { taskExecutionPath } from "./task-paths.js";
12
+ import { taskArtifactPath, taskExecutionPath } from "./task-paths.js";
13
13
  import {
14
14
  resolveExecutionResolution,
15
15
  validateVerificationAuthority,
@@ -77,6 +77,23 @@ function executeProcess(argv, cwd) {
77
77
  });
78
78
  }
79
79
 
80
+ /**
81
+ * Resolves where a new execution artifact should be written. Task-scoped
82
+ * execution artifacts require a real modern task namespace (a task.json
83
+ * descriptor). A descriptor-less task is legacy: writing task-scoped here
84
+ * would create a phantom `.forgeloop/task-state/<key>/executions/` namespace
85
+ * that corrupts task discovery. Reads already fall back across both
86
+ * locations, so a legacy execution stays resolvable.
87
+ */
88
+ export async function resolveExecutionArtifactPath(target, taskId, executionId) {
89
+ if (!taskId) return executionArtifactPath(executionId);
90
+ const descriptorRel = taskArtifactPath(taskId, "descriptor");
91
+ if (await fileExists(ensureWithin(target, descriptorRel))) {
92
+ return taskExecutionPath(taskId, executionId);
93
+ }
94
+ return executionArtifactPath(executionId);
95
+ }
96
+
80
97
  export async function runCommandExecution({
81
98
  target,
82
99
  packageRoot,
@@ -153,7 +170,7 @@ export async function runCommandExecution({
153
170
  status: processResult.exitCode === 0 && !processResult.spawnError ? "passed" : "failed",
154
171
  exitCode: processResult.exitCode,
155
172
  };
156
- const execPath = executionPath ?? (taskId ? taskExecutionPath(taskId, executionId) : executionArtifactPath(executionId));
173
+ const execPath = executionPath ?? await resolveExecutionArtifactPath(target, taskId, executionId);
157
174
  const written = await writeJsonArtifact(target, execPath, execution, "execution", packageRoot);
158
175
  return {
159
176
  path: written.path,
@@ -41,10 +41,20 @@ Before changing product or executable files, establish the ForgeLoop contract,
41
41
  route, required gates, and READY preflight.
42
42
 
43
43
  Before creating or activating new lifecycle state:
44
- If \`.forgeloop/work-state.json\` exists, inspect the existing task,
45
- reconcile continuity when present, inspect the checkout, and run
46
- \`forgeloop next\`. A change of harness, model, provider, IDE, process,
47
- terminal, or session does not create a new task.
44
+
45
+ 1. Inspect existing ForgeLoop tasks first.
46
+ 2. Use \`forgeloop task-list --json\` to discover current task namespaces.
47
+ 3. If an existing task is selected or identifiable, use
48
+ \`forgeloop next --task <id> --json\` before creating another task.
49
+ 4. Reconcile continuity when the selected task has continuity state.
50
+ 5. Inspect the checkout before resuming work.
51
+
52
+ A change of harness, model, provider, IDE, process, terminal, or session
53
+ does not create a new task.
54
+
55
+ Legacy singleton state such as \`.forgeloop/work-state.json\` remains
56
+ supported only for backward compatibility and must not be treated as the
57
+ primary modern discovery mechanism.
48
58
 
49
59
  Use the project-local ForgeLoop CLI for lifecycle-owned protocol state.
50
60
  Never manually synthesize lifecycle chronology or assign ForgeLoop COMPLETE.
@@ -13,13 +13,24 @@ export const NEXT_ACTIONS = Object.freeze({
13
13
  CONTINUE_IMPLEMENTATION: "CONTINUE_IMPLEMENTATION",
14
14
  RECORD_VERIFICATION: "RECORD_VERIFICATION",
15
15
  DIAGNOSE: "DIAGNOSE",
16
+ RECORD_DIAGNOSIS: "RECORD_DIAGNOSIS",
16
17
  CORRECT: "CORRECT",
18
+ CHANGE_STRATEGY: "CHANGE_STRATEGY",
17
19
  ENTER_REVIEWING: "ENTER_REVIEWING",
18
20
  RECORD_TERMINAL_RESULT: "RECORD_TERMINAL_RESULT",
19
21
  PREPARE_COMPLETION: "PREPARE_COMPLETION",
20
22
  RUN_COMPLETE: "RUN_COMPLETE",
21
23
  RESOLVE_STALE_ROUTE: "RESOLVE_STALE_ROUTE",
22
24
  RESOLVE_BLOCKER: "RESOLVE_BLOCKER",
25
+ RESTORE_POLICY: "RESTORE_POLICY",
26
+ REVERIFY_AFTER_POLICY_CHANGE: "REVERIFY_AFTER_POLICY_CHANGE",
27
+ VERIFY_RULE: "VERIFY_RULE",
28
+ RESOLVE_INERT_CHECK: "RESOLVE_INERT_CHECK",
29
+ RUN_REQUIRED_CHECK: "RUN_REQUIRED_CHECK",
30
+ REPAIR_CHECKER: "REPAIR_CHECKER",
31
+ REPAIR_POLICY: "REPAIR_POLICY",
32
+ RESTORE_BASELINE: "RESTORE_BASELINE",
33
+ CONTINUE_WITH_EXISTING_BASELINE: "CONTINUE_WITH_EXISTING_BASELINE",
23
34
  NONE: "NONE",
24
35
  });
25
36
 
@@ -32,13 +43,20 @@ export function result({
32
43
  commandSpecs = [],
33
44
  requiredArtifacts = [],
34
45
  missingArtifacts = [],
46
+ progress = undefined,
35
47
  }) {
36
48
  const normalizedReasons = reasons
37
- .map((reason) => ({
38
- code: reason.code ?? "E_NEXT_ACTION_BLOCKED",
39
- message: reason.message ?? String(reason),
40
- artifacts: uniqueSorted(reason.artifacts ?? []),
41
- }))
49
+ .map((reason) => {
50
+ const base = {
51
+ code: reason.code ?? "E_NEXT_ACTION_BLOCKED",
52
+ message: reason.message ?? String(reason),
53
+ artifacts: uniqueSorted(reason.artifacts ?? []),
54
+ };
55
+ if (reason.resolution) {
56
+ base.resolution = structuredClone(reason.resolution);
57
+ }
58
+ return base;
59
+ })
42
60
  .sort((left, right) => left.code.localeCompare(right.code)
43
61
  || left.artifacts.join("\0").localeCompare(right.artifacts.join("\0"))
44
62
  || left.message.localeCompare(right.message));
@@ -57,6 +75,7 @@ export function result({
57
75
  .sort((left, right) => JSON.stringify(left).localeCompare(JSON.stringify(right))),
58
76
  requiredArtifacts: uniqueSorted(requiredArtifacts),
59
77
  missingArtifacts: uniqueSorted(missingArtifacts),
78
+ ...(progress ? { progress: structuredClone(progress) } : {}),
60
79
  };
61
80
  }
62
81
 
@@ -91,6 +110,22 @@ export function recordCheckCommandSpec(requirement) {
91
110
  };
92
111
  }
93
112
 
113
+ export function recordDiagnosisCommandSpec() {
114
+ return {
115
+ commandId: "record-diagnosis",
116
+ executable: "forgeloop",
117
+ subcommand: "record-diagnosis",
118
+ argv: ["record-diagnosis"],
119
+ requiredInputs: [
120
+ { name: "hypothesis", option: "--hypothesis=<text>" },
121
+ { name: "failureClass", option: "--failure-class=<class>" },
122
+ { name: "evidenceRef", option: "--evidence-ref=<check-id>", repeatable: true },
123
+ { name: "settledBy", option: "--settled-by=<text>" },
124
+ { name: "nextSafeAction", option: "--next-safe-action=<text>" },
125
+ ],
126
+ };
127
+ }
128
+
94
129
  export function recordTerminalResultCommandSpec(requirement) {
95
130
  const reqId = requirement.id ?? requirement;
96
131
  const type = requirement.type ?? "PUBLICATION";