@cassiomc1/forgeloop 1.14.0 → 2.0.0-rc.4

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 (250) hide show
  1. package/CONTRIBUTING.md +11 -7
  2. package/DOCS_INDEX.md +2 -1
  3. package/ENG/c-development-eng.md +2 -2
  4. package/ENG/cpp-development-eng.md +2 -2
  5. package/ENG/dotnet-aspnetcore-development-eng.md +7 -1
  6. package/ENG/flutter-development-eng.md +4 -2
  7. package/ENG/go-development-eng.md +2 -2
  8. package/ENG/java-development-eng.md +3 -3
  9. package/ENG/nodejs-backend-development-eng.md +8 -4
  10. package/ENG/php-development-eng.md +3 -3
  11. package/ENG/python-development-eng.md +205 -0
  12. package/ENG/rust-development-eng.md +42 -1
  13. package/ENG/sql-development-eng.md +4 -4
  14. package/ENG/swift-development-eng.md +5 -4
  15. package/ENG/test-code-eng.md +1 -1
  16. package/ENG/typescript-development-eng.md +4 -4
  17. package/GUIDE_ROUTER.md +6 -0
  18. package/LICENSE +133 -21
  19. package/LOOP_SYSTEM_DESIGN.md +10 -10
  20. package/README.md +30 -40
  21. package/THIRD_PARTY_NOTICES.md +3 -4
  22. package/completions/_forgeloop +11 -3
  23. package/completions/forgeloop.bash +19 -3
  24. package/completions/forgeloop.fish +61 -1
  25. package/docs/AGENT_PROTOCOL_SUMMARY.md +50 -40
  26. package/docs/ARTIFACT_REFERENCE.md +50 -37
  27. package/docs/CLI_REFERENCE.md +147 -3
  28. package/docs/DOCUMENTATION_GUIDE.md +6 -4
  29. package/docs/GETTING_STARTED.md +3 -3
  30. package/docs/PACKAGE_CONTENTS.md +14 -13
  31. package/docs/RELEASE_CHECKLIST.md +110 -10
  32. package/docs/SELF_HOSTED_TESTS.md +121 -0
  33. package/docs/TROUBLESHOOTING.md +33 -0
  34. package/docs/documentation-manifest.json +25 -12
  35. package/package.json +15 -10
  36. package/schemas/task-bundle.schema.json +7 -2
  37. package/scripts/generate-agent-protocol-summary.mjs +2 -2
  38. package/scripts/run-mcp-clean-tests.mjs +30 -0
  39. package/src/adapters/agent-browser/provider.js +14 -11
  40. package/src/adapters/emulated-services/provider.js +11 -4
  41. package/src/adapters/temporary-paths.js +18 -0
  42. package/src/cli.js +146 -210
  43. package/src/commands/action-show.js +5 -2
  44. package/src/commands/advance.js +5 -1
  45. package/src/commands/baseline.js +13 -11
  46. package/src/commands/checkpoint-revalidate.js +24 -8
  47. package/src/commands/complete.js +19 -3
  48. package/src/commands/contract-create.js +3 -1
  49. package/src/commands/doctor.js +36 -13
  50. package/src/commands/gate-record.js +20 -31
  51. package/src/commands/gate-revalidate.js +11 -13
  52. package/src/commands/handoff-list.js +6 -1
  53. package/src/commands/handoff-show.js +6 -1
  54. package/src/commands/migrate-protocol.js +2 -2
  55. package/src/commands/model-route.js +9 -1
  56. package/src/commands/next.js +7 -1
  57. package/src/commands/policy-diff.js +25 -4
  58. package/src/commands/progress.js +17 -11
  59. package/src/commands/record-diagnosis.js +3 -0
  60. package/src/commands/semantic-plan.js +26 -2
  61. package/src/commands/status.js +6 -1
  62. package/src/commands/storage-backup.js +15 -0
  63. package/src/commands/storage-migrate.js +5 -0
  64. package/src/commands/storage-migration-resume.js +5 -0
  65. package/src/commands/storage-migration-status.js +44 -0
  66. package/src/commands/storage-restore-resume.js +7 -0
  67. package/src/commands/storage-restore.js +8 -0
  68. package/src/commands/task-abandon.js +1 -1
  69. package/src/commands/task-list.js +8 -43
  70. package/src/commands/task-migrate.js +3 -3
  71. package/src/commands/task-recover.js +10 -2
  72. package/src/commands/task-scope.js +18 -2
  73. package/src/commands/task-show.js +16 -2
  74. package/src/commands/validate-protocol.js +29 -14
  75. package/src/commands/validate-receipt.js +8 -1
  76. package/src/config/guides.json +4 -0
  77. package/src/core/action-ledger-projection.js +10 -3
  78. package/src/core/actions.js +38 -75
  79. package/src/core/activation.js +11 -3
  80. package/src/core/approvals.js +17 -51
  81. package/src/core/artifact-registry.js +59 -2
  82. package/src/core/artifacts.js +81 -27
  83. package/src/core/attachment-paths.js +45 -0
  84. package/src/core/attestation-coverage.js +6 -1
  85. package/src/core/attestation-verifier.js +21 -2
  86. package/src/core/attestation.js +17 -6
  87. package/src/core/audit.js +6 -1
  88. package/src/core/bundles.js +160 -66
  89. package/src/core/checkpoint-revalidation.js +12 -10
  90. package/src/core/cli-command-definitions.js +109 -7
  91. package/src/core/code-manifest.js +9 -3
  92. package/src/core/command-executors.js +182 -126
  93. package/src/core/command-input.js +21 -1
  94. package/src/core/completion-artifacts.js +85 -36
  95. package/src/core/completion-ownership.js +19 -8
  96. package/src/core/completion-recovery-rebind.js +11 -1
  97. package/src/core/completion.js +60 -9
  98. package/src/core/conformance.js +2 -1
  99. package/src/core/continuity-lint.js +13 -2
  100. package/src/core/continuity-reconciliation.js +16 -3
  101. package/src/core/continuity.js +16 -8
  102. package/src/core/contract-bootstrap-recovery.js +42 -41
  103. package/src/core/contract-revision.js +8 -9
  104. package/src/core/decision/artifact.js +2 -1
  105. package/src/core/decision/events.js +9 -6
  106. package/src/core/decision/resolver.js +19 -11
  107. package/src/core/decision/service.js +63 -38
  108. package/src/core/decision/task-bindings.js +6 -1
  109. package/src/core/diagnosis-model.js +11 -0
  110. package/src/core/diagnosis.js +10 -1
  111. package/src/core/diagnostic-projection.js +29 -12
  112. package/src/core/diagnostic-record.js +28 -3
  113. package/src/core/efficiency.js +6 -1
  114. package/src/core/events.js +316 -97
  115. package/src/core/execution-prerequisites.js +22 -13
  116. package/src/core/execution.js +26 -40
  117. package/src/core/filesystem.js +11 -0
  118. package/src/core/gate-artifact.js +105 -19
  119. package/src/core/gate-provenance.js +6 -5
  120. package/src/core/handoff.js +26 -14
  121. package/src/core/history.js +39 -35
  122. package/src/core/hypothesis-projection.js +32 -0
  123. package/src/core/information-gain-projection.js +104 -100
  124. package/src/core/inspect.js +12 -3
  125. package/src/core/integration-invocation-policy.js +10 -0
  126. package/src/core/integration-resources.js +41 -34
  127. package/src/core/ledger-event-collection.js +189 -0
  128. package/src/core/ledger-relations.js +23 -0
  129. package/src/core/native-storage.js +12 -0
  130. package/src/core/next-action-continuity.js +31 -9
  131. package/src/core/next-action-phases.js +1 -1
  132. package/src/core/next-action-review-phase.js +2 -2
  133. package/src/core/next-action-verification-phase.js +2 -1
  134. package/src/core/next-action.js +21 -2
  135. package/src/core/phase.js +138 -93
  136. package/src/core/policy-engine.js +12 -13
  137. package/src/core/preflight-consistency.js +72 -59
  138. package/src/core/preflight-loaders.js +19 -12
  139. package/src/core/preflight-model.js +6 -1
  140. package/src/core/preflight.js +29 -12
  141. package/src/core/progress.js +21 -10
  142. package/src/core/protocol-info.js +3 -0
  143. package/src/core/protocol-migration.js +7 -6
  144. package/src/core/reconcile-closure.js +162 -24
  145. package/src/core/recovery-history.js +66 -47
  146. package/src/core/reflection.js +72 -77
  147. package/src/core/responsibility.js +25 -13
  148. package/src/core/route-artifact.js +13 -3
  149. package/src/core/runtime-context.js +5 -0
  150. package/src/core/schema-validation.js +8 -3
  151. package/src/core/settlement.js +12 -1
  152. package/src/core/signing/bundle-file.js +53 -0
  153. package/src/core/signing/bundle-reference.js +15 -0
  154. package/src/core/signing/sigstore.js +60 -33
  155. package/src/core/signing/statement-file.js +27 -0
  156. package/src/core/storage-runtime-registry.js +41 -0
  157. package/src/core/structural-quality/artifacts.js +20 -14
  158. package/src/core/structural-quality/service.js +81 -56
  159. package/src/core/target-layout.js +1 -1
  160. package/src/core/task-claim-state.js +52 -18
  161. package/src/core/task-command.js +21 -1
  162. package/src/core/task-conflict-inspection.js +112 -107
  163. package/src/core/task-descriptor.js +29 -22
  164. package/src/core/task-discovery.js +288 -90
  165. package/src/core/task-lock.js +66 -306
  166. package/src/core/task-migration-validation.js +5 -5
  167. package/src/core/task-migration.js +17 -287
  168. package/src/core/task-snapshot.js +51 -8
  169. package/src/core/task-storage-migration.js +18 -0
  170. package/src/core/templates.js +20 -8
  171. package/src/core/test-intelligence/prune.js +5 -1
  172. package/src/core/trace.js +154 -99
  173. package/src/core/trajectory-evaluation.js +15 -3
  174. package/src/core/trajectory-metrics.js +47 -32
  175. package/src/core/transaction.js +16 -288
  176. package/src/core/verification-scope.js +20 -12
  177. package/src/core/work-state.js +100 -59
  178. package/src/repository-index/binary-manager.js +15 -7
  179. package/src/storage/artifact-bytes.js +22 -0
  180. package/src/storage/attachment-binding.js +11 -0
  181. package/src/storage/attachment-files.js +82 -0
  182. package/src/storage/attachment-references.js +36 -0
  183. package/src/storage/backup.js +53 -0
  184. package/src/storage/capabilities.js +31 -0
  185. package/src/storage/connection-owner.js +119 -0
  186. package/src/storage/connection.js +376 -0
  187. package/src/storage/existing-project-scope.js +45 -0
  188. package/src/storage/export-destination.js +44 -0
  189. package/src/storage/exporter.js +284 -0
  190. package/src/storage/file-durability.js +46 -0
  191. package/src/storage/importer.js +461 -0
  192. package/src/storage/index.js +74 -0
  193. package/src/storage/leases.js +63 -0
  194. package/src/storage/ledger-event-snapshot.js +150 -0
  195. package/src/storage/ledger-relations.js +220 -0
  196. package/src/storage/legacy-maintenance-boundary.js +18 -0
  197. package/src/storage/legacy-signature.js +115 -0
  198. package/src/storage/maintenance-handoff.js +129 -0
  199. package/src/storage/maintenance-owner.js +42 -0
  200. package/src/storage/maintenance.js +310 -0
  201. package/src/storage/metadata-json.js +35 -0
  202. package/src/storage/migration-activate.js +59 -0
  203. package/src/storage/migration-archive.js +57 -0
  204. package/src/storage/migration-attachment-root.js +61 -0
  205. package/src/storage/migration-candidate.js +398 -0
  206. package/src/storage/migration-publication.js +143 -0
  207. package/src/storage/migration-rollback-admission.js +54 -0
  208. package/src/storage/migration-rollback-preparation.js +132 -0
  209. package/src/storage/migration-rollback-publication.js +183 -0
  210. package/src/storage/migration-rollback-release.js +66 -0
  211. package/src/storage/migration-rollback-source-stage.js +103 -0
  212. package/src/storage/migration-rollback.js +43 -0
  213. package/src/storage/migration-source-partition.js +56 -0
  214. package/src/storage/migration-source.js +255 -0
  215. package/src/storage/migration-terminal.js +39 -0
  216. package/src/storage/migration-validation.js +75 -0
  217. package/src/storage/migration.js +43 -0
  218. package/src/storage/operational-attachments.js +49 -0
  219. package/src/storage/operational-context.js +50 -0
  220. package/src/storage/project-backup.js +98 -0
  221. package/src/storage/project-bootstrap.js +24 -0
  222. package/src/storage/project-boundary.js +79 -0
  223. package/src/storage/project-read-snapshot.js +18 -0
  224. package/src/storage/project-replacement.js +137 -0
  225. package/src/storage/project-restore.js +203 -0
  226. package/src/storage/record-definitions.js +45 -0
  227. package/src/storage/repository.js +507 -0
  228. package/src/storage/restore-execution-provenance.js +26 -0
  229. package/src/storage/restore-journal.js +62 -0
  230. package/src/storage/restore-layout.js +30 -0
  231. package/src/storage/restore-preparation.js +84 -0
  232. package/src/storage/restore-replacement-archive.js +162 -0
  233. package/src/storage/restore-replacement-owner.js +31 -0
  234. package/src/storage/restore-replacement-preparation.js +128 -0
  235. package/src/storage/restore-replacement.js +142 -0
  236. package/src/storage/restore-terminal.js +32 -0
  237. package/src/storage/runtime.js +31 -0
  238. package/src/storage/schema.js +214 -0
  239. package/src/storage/singleton-import.js +84 -0
  240. package/src/storage/snapshot-continuity.js +16 -0
  241. package/src/storage/snapshot.js +36 -0
  242. package/src/storage/storage-marker.js +79 -0
  243. package/src/storage/task-guards.js +107 -0
  244. package/src/storage/topology.js +42 -0
  245. package/src/storage/transaction.js +179 -0
  246. package/src/storage/unit-of-work.js +667 -0
  247. package/src/storage/windows-process-incarnation.js +206 -0
  248. package/.forgeloop/forgeloop.gitignore +0 -4
  249. package/LICENSE-DOCS.md +0 -13
  250. package/src/core/transaction-maintenance.js +0 -70
@@ -1,5 +1,6 @@
1
- import { diagnosticEventsForTask } from "./diagnostic-projection.js";
2
- import { normalizeDiagnosticSnapshot, computeInformationGain } from "./information-gain.js";
1
+ import { ledgerRelationSet, ledgerGroupedSet } from "./ledger-relations.js";
2
+ import { isLedgerEventCollection, ledgerEventsOfTypes } from "./ledger-event-collection.js";
3
+ import { normalizeDiagnosticSnapshot, compareDiagnosticCycles, classifyGain, isEffectiveGain } from "./information-gain.js";
3
4
  import { computeFailureSignature } from "./failure-signature.js";
4
5
 
5
6
  function snapshotFor(event) {
@@ -19,23 +20,43 @@ const sameSortedSet = (a, b) =>
19
20
  // current diagnostic sequence inclusive.
20
21
  // Events inside an interval belong to the *current* cycle's knowledge state;
21
22
  // they are never attributed retroactively to the earlier diagnosis.
22
- function intervalEvents(taskEvents, fromExclusive, toInclusive) {
23
- return taskEvents.filter((event) =>
24
- event.seq > fromExclusive && event.seq <= toInclusive);
23
+ function* intervalEvents(taskEvents, fromExclusive, toInclusive) {
24
+ for (const event of taskEvents) if (event.seq > fromExclusive && event.seq <= toInclusive) yield event;
25
+ }
26
+
27
+ function orderedTaskEvents(events, taskId) {
28
+ const belongs = event => !taskId || !event.taskId || event.taskId === taskId;
29
+ if (!Array.isArray(events) && isLedgerEventCollection(events)) {
30
+ let previous = -Infinity;
31
+ let ordered = true;
32
+ for (const event of events) {
33
+ if (!Number.isFinite(event.seq) || event.seq < previous) { ordered = false; break; }
34
+ previous = event.seq;
35
+ }
36
+ if (ordered) return { *[Symbol.iterator]() { for (const event of ledgerEventsOfTypes(events, ["DIAGNOSTIC_CASE_RECORDED", "DIAGNOSIS_RECORDED", "VERIFICATION_STARTED", "VERIFICATION_RECORDED", "INTERVENTION_RECORDED", "HYPOTHESIS_DISPOSITION_RECORDED"])) if (belongs(event)) yield event; } };
37
+ }
38
+ return [...(events ?? [])].filter(belongs).sort((a, b) => (a.seq ?? 0) - (b.seq ?? 0));
39
+ }
40
+
41
+ function eliminatedHypothesis(previousDiagnostic, diagnostic) {
42
+ if (previousDiagnostic?.event !== "DIAGNOSTIC_CASE_RECORDED" || diagnostic.event !== "DIAGNOSTIC_CASE_RECORDED") return false;
43
+ const previous = previousDiagnostic.details?.hypotheses ?? [];
44
+ const current = diagnostic.details?.hypotheses ?? [];
45
+ const statements = new Set(current.map(hypothesis => `${hypothesis.statement}`.trim().toLowerCase()));
46
+ return previous.some(hypothesis => !current.some(candidate => candidate.id === hypothesis.id)
47
+ && !statements.has(`${hypothesis.statement}`.trim().toLowerCase()));
25
48
  }
26
49
 
27
50
  function failureStateByCycle(taskEvents) {
28
- const surfaces = new Map();
29
- const signatures = new Map();
51
+ const surfaces = ledgerGroupedSet();
52
+ const signatures = ledgerGroupedSet();
30
53
  const record = (cycle, details) => {
31
54
  if (!Number.isInteger(cycle)) cycle = Number(cycle) || 1;
32
- if (!surfaces.has(cycle)) surfaces.set(cycle, new Set());
33
- if (!signatures.has(cycle)) signatures.set(cycle, new Set());
34
55
  const requirement = details.requirement ?? details.id ?? details.checkId;
35
56
  if (!requirement) return;
36
57
  if (details.status === "failed" || details.status === "blocked") {
37
- surfaces.get(cycle).add(requirement);
38
- signatures.get(cycle).add(computeFailureSignature({
58
+ surfaces.add(cycle, requirement);
59
+ signatures.add(cycle, computeFailureSignature({
39
60
  requirement,
40
61
  status: details.status,
41
62
  exitCode: Number.isInteger(details.exitCode) ? details.exitCode : null,
@@ -44,13 +65,6 @@ function failureStateByCycle(taskEvents) {
44
65
  }
45
66
  };
46
67
  for (const event of taskEvents) {
47
- if (event.event === "VERIFICATION_STARTED") {
48
- const cycle = event.details?.verificationCycle;
49
- if (Number.isInteger(cycle)) {
50
- if (!surfaces.has(cycle)) surfaces.set(cycle, new Set());
51
- if (!signatures.has(cycle)) signatures.set(cycle, new Set());
52
- }
53
- }
54
68
  if (event.event === "VERIFICATION_RECORDED") {
55
69
  record(event.details?.verificationCycle ?? 1, event.details ?? {});
56
70
  }
@@ -102,46 +116,45 @@ function snapshotHasContentDelta(previousDetails, currentDetails) {
102
116
  || differs(prev.evidence, cur.evidence);
103
117
  }
104
118
 
105
- export function buildInformationGainProjection(events, taskId) {
106
- const taskEvents = (events ?? [])
107
- .filter((event) => !taskId || !event.taskId || event.taskId === taskId)
108
- .sort((a, b) => (a.seq ?? 0) - (b.seq ?? 0));
119
+ function intervalKnowledge(taskEvents, previousDiagnostic, diagnostic) {
120
+ const intervalStart = previousDiagnostic ? previousDiagnostic.seq : -Infinity;
121
+ let hypothesisDispositionChanged = false;
122
+ const knownUpToPrev = ledgerRelationSet();
123
+ for (const event of taskEvents) {
124
+ if (event.event === "INTERVENTION_RECORDED" && previousDiagnostic && (event.seq ?? 0) <= previousDiagnostic.seq) {
125
+ const fingerprint = event.details?.interventionSemanticFingerprint
126
+ ?? `${event.details?.intervention?.statement ?? ""}`.trim().toLowerCase();
127
+ if (fingerprint) knownUpToPrev.add(fingerprint);
128
+ }
129
+ }
130
+ let novelIntervention = false;
131
+ for (const event of intervalEvents(taskEvents, intervalStart, diagnostic.seq)) {
132
+ if (event.event === "HYPOTHESIS_DISPOSITION_RECORDED") hypothesisDispositionChanged = true;
133
+ if (event.event === "INTERVENTION_RECORDED") {
134
+ const fingerprint = event.details?.interventionSemanticFingerprint
135
+ ?? `${event.details?.intervention?.statement ?? ""}`.trim().toLowerCase();
136
+ if (fingerprint && !knownUpToPrev.has(fingerprint)) novelIntervention = true;
137
+ }
138
+ }
139
+ return { hypothesisDispositionChanged, novelIntervention };
140
+ }
109
141
 
110
- const diagnosticEvents = diagnosticEventsForTask(taskEvents, taskId)
111
- .sort((a, b) => (a.seq ?? 0) - (b.seq ?? 0));
112
- if (diagnosticEvents.length === 0) return [];
142
+ function* iterateInformationGainProjection(events, taskId) {
143
+ const taskEvents = orderedTaskEvents(events, taskId);
144
+ const diagnosticEvents = { *[Symbol.iterator]() {
145
+ for (const event of taskEvents) {
146
+ if (["DIAGNOSTIC_CASE_RECORDED", "DIAGNOSIS_RECORDED"].includes(event.event)
147
+ && (!taskId || event.taskId === taskId)) yield event;
148
+ }
149
+ } };
113
150
 
114
151
  const failure = failureStateByCycle(taskEvents);
115
152
 
116
- // Build final per-cycle entries first; effectiveGain is computed once at the
117
- // end from fully final dimensions (no post-mutation anywhere).
118
- const built = [];
119
153
  let previousDiagnostic = null;
154
+ let previousSnapshot = null;
120
155
  for (const diagnostic of diagnosticEvents) {
121
- const intervalStart = previousDiagnostic ? previousDiagnostic.seq : -Infinity;
122
- const interval = intervalEvents(taskEvents, intervalStart, diagnostic.seq);
123
-
124
- const hypothesisDispositionChanged =
125
- interval.some((event) => event.event === "HYPOTHESIS_DISPOSITION_RECORDED");
156
+ const { hypothesisDispositionChanged, novelIntervention } = intervalKnowledge(taskEvents, previousDiagnostic, diagnostic);
126
157
 
127
- // Intervention deltas recognize only genuinely NEW semantic interventions:
128
- // recorded after the previous diagnosis, attributed to the current
129
- // correction cycle, and never attempted before. Repeating an already-known
130
- // intervention — or merely having executed the previous cycle's corrective
131
- // action — is not new information.
132
- const knownUpToPrev = new Set(
133
- taskEvents
134
- .filter((event) => event.event === "INTERVENTION_RECORDED"
135
- && (previousDiagnostic ? (event.seq ?? 0) <= previousDiagnostic.seq : false))
136
- .map((event) => event.details?.interventionSemanticFingerprint
137
- ?? `${event.details?.intervention?.statement ?? ""}`.trim().toLowerCase())
138
- .filter(Boolean),
139
- );
140
- const novelInInterval = interval
141
- .filter((event) => event.event === "INTERVENTION_RECORDED")
142
- .map((event) => event.details?.interventionSemanticFingerprint
143
- ?? `${event.details?.intervention?.statement ?? ""}`.trim().toLowerCase())
144
- .filter((fingerprint) => fingerprint && !knownUpToPrev.has(fingerprint));
145
158
  // A genuinely new intervention counts as information only when the
146
159
  // diagnosis itself moves: an identical re-proposal of the previous
147
160
  // diagnosis after executing its already-known corrective action carries
@@ -149,18 +162,18 @@ export function buildInformationGainProjection(events, taskId) {
149
162
  const sameSemanticsAsPrevious = Boolean(previousDiagnostic)
150
163
  && !snapshotHasContentDelta(previousDiagnostic.details ?? {}, diagnostic.details ?? {});
151
164
  const interventionChanged = Boolean(previousDiagnostic)
152
- && novelInInterval.length > 0
165
+ && novelIntervention
153
166
  && !sameSemanticsAsPrevious;
154
167
 
155
168
  const cycle = diagnostic.details?.verificationCycle ?? 1;
156
169
  const previousCycle = previousDiagnostic?.details?.verificationCycle ?? null;
157
- const surface = [...(failure.surfaces.get(cycle) ?? new Set())].sort();
158
- const signatures = [...(failure.signatures.get(cycle) ?? new Set())].sort();
170
+ const surface = [...(failure.surfaces.values(cycle))].sort();
171
+ const signatures = [...(failure.signatures.values(cycle))].sort();
159
172
  const previousSurface = previousCycle !== null && previousCycle !== undefined
160
- ? [...(failure.surfaces.get(previousCycle) ?? new Set())].sort()
173
+ ? [...(failure.surfaces.values(previousCycle))].sort()
161
174
  : null;
162
175
  const previousSignatures = previousCycle !== null && previousCycle !== undefined
163
- ? [...(failure.signatures.get(previousCycle) ?? new Set())].sort()
176
+ ? [...(failure.signatures.values(previousCycle))].sort()
164
177
  : null;
165
178
  const hasPreviousFailureState = previousSurface !== null;
166
179
  const failureSurfaceChanged = hasPreviousFailureState
@@ -181,7 +194,7 @@ export function buildInformationGainProjection(events, taskId) {
181
194
 
182
195
  const snapshot = snapshotFor(diagnostic);
183
196
 
184
- built.push({
197
+ const entry = {
185
198
  verificationCycle: cycle,
186
199
  sequence: diagnostic.seq ?? null,
187
200
  diagnosticSequence: diagnostic.seq ?? null,
@@ -195,62 +208,53 @@ export function buildInformationGainProjection(events, taskId) {
195
208
  failureSurfaceChanged,
196
209
  interventionChanged,
197
210
  strategyChanged,
198
- hypothesisEliminated: false,
211
+ hypothesisEliminated: eliminatedHypothesis(previousDiagnostic, diagnostic),
199
212
  },
200
213
  evidence: {
201
214
  semanticRefs: [...(snapshot.evidenceRefs ?? [])].sort(),
202
215
  surface, signatures, strategyFingerprint,
203
216
  },
204
- });
205
-
217
+ };
218
+ const dimensions = compareDiagnosticCycles(previousSnapshot, snapshot, entry.dimensionsInput);
219
+ const classification = classifyGain(dimensions, { first: previousDiagnostic === null });
220
+ const effectiveGain = isEffectiveGain(dimensions, classification);
221
+ yield finalizeGainEntry(entry, { dimensions, classification, effectiveGain });
206
222
  previousDiagnostic = diagnostic;
223
+ previousSnapshot = snapshot;
207
224
  }
225
+ }
208
226
 
209
- // Hypothesis elimination: an id disappearing only counts as elimination when
210
- // no surviving hypothesis carries the same normalized statement — ID-only
211
- // churn is artificial novelty and must never create gain.
212
- for (let i = 1; i < built.length; i++) {
213
- if (built[i].sourceModel !== "STRUCTURED_DIAGNOSTIC_CASE_V1"
214
- || built[i - 1].sourceModel !== "STRUCTURED_DIAGNOSTIC_CASE_V1") continue;
215
- const previousHypotheses = diagnosticEvents[i - 1]?.details?.hypotheses ?? [];
216
- const currentHypotheses = diagnosticEvents[i]?.details?.hypotheses ?? [];
217
- const currentStatements = new Set(currentHypotheses.map(
218
- (hypothesis) => `${hypothesis.statement}`.trim().toLowerCase()));
219
- built[i].dimensionsInput.hypothesisEliminated = previousHypotheses.some((hypothesis) => {
220
- const survivedById = currentHypotheses.some((candidate) => candidate.id === hypothesis.id);
221
- return !survivedById
222
- && !currentStatements.has(`${hypothesis.statement}`.trim().toLowerCase());
223
- });
224
- }
225
-
226
- // Final classification + single-point effectiveGain computation.
227
- const entries = computeInformationGain(built.map((entry) => ({
227
+ function finalizeGainEntry(entry, { dimensions, classification, effectiveGain }) {
228
+ return Object.freeze({
228
229
  verificationCycle: entry.verificationCycle,
229
230
  sequence: entry.sequence,
230
- snapshot: entry.snapshot,
231
- context: entry.dimensionsInput,
232
- })));
233
-
234
- return built.map((entry, index) => {
235
- const { dimensions, classification, effectiveGain } = entries[index];
236
- return Object.freeze({
237
- verificationCycle: entry.verificationCycle,
238
- sequence: entry.sequence,
239
- diagnosticSequence: entry.diagnosticSequence,
240
- sourceModel: entry.sourceModel,
241
- evidence: Object.freeze({
242
- semanticRefs: Object.freeze(entry.evidence.semanticRefs),
243
- failureSurface: Object.freeze(entry.evidence.surface),
244
- failureSignatures: Object.freeze(entry.evidence.signatures),
245
- strategyFingerprint: entry.evidence.strategyFingerprint,
246
- }),
247
- dimensions: Object.freeze({ ...dimensions }),
248
- classification,
249
- effectiveGain,
250
- });
231
+ diagnosticSequence: entry.diagnosticSequence,
232
+ sourceModel: entry.sourceModel,
233
+ evidence: Object.freeze({
234
+ semanticRefs: Object.freeze(entry.evidence.semanticRefs),
235
+ failureSurface: Object.freeze(entry.evidence.surface),
236
+ failureSignatures: Object.freeze(entry.evidence.signatures),
237
+ strategyFingerprint: entry.evidence.strategyFingerprint,
238
+ }),
239
+ dimensions: Object.freeze({ ...dimensions }),
240
+ classification,
241
+ effectiveGain,
251
242
  });
252
243
  }
253
244
 
245
+ export function buildInformationGainProjection(events, taskId) {
246
+ return [...iterateInformationGainProjection(events, taskId)];
247
+ }
248
+
249
+ /** Internal latest-only use does not retain all diagnostic result objects. */
250
+ export function summarizeInformationGain(events, taskId, { verificationCycle = null } = {}) {
251
+ let latest = null;
252
+ for (const entry of iterateInformationGainProjection(events, taskId)) {
253
+ if (verificationCycle === null || verificationCycle === undefined || entry.verificationCycle === verificationCycle) latest = entry;
254
+ }
255
+ return evaluateStructuredDiagnosticStall(latest ? [latest] : []);
256
+ }
257
+
254
258
  // One canonical structured-stall policy (fail-fast):
255
259
  // The latest comparable diagnostic state that produces no effective
256
260
  // information gain is stalled and may not trigger another blind correction
@@ -17,6 +17,8 @@ import { evaluateProgress } from "./progress.js";
17
17
  import { readEvents } from "./events.js";
18
18
  import { taskStructuralQualityDirectory } from "./task-paths.js";
19
19
  import { projectStructuralQualityStatus } from "./structural-quality/status.js";
20
+ import { operationalArtifactExists } from "../storage/operational-context.js";
21
+ import { withProjectReadSnapshot } from "../storage/project-read-snapshot.js";
20
22
 
21
23
  function reasonCodesFor({ state, trace, progress }) {
22
24
  const codes = [];
@@ -169,7 +171,15 @@ function profileMetadata(bytes) {
169
171
 
170
172
  import { taskArtifactPath } from "./task-paths.js";
171
173
 
172
- export async function inspectTarget({ target, packageRoot, contractFile = null, authorityContext, runtimeContext, taskId = null, stateFile = null } = {}) {
174
+ async function inspectStatePresence(target, relativePath) {
175
+ return operationalArtifactExists(target, relativePath) ?? await fileExists(ensureWithin(target, relativePath));
176
+ }
177
+
178
+ export async function inspectTarget(options = {}) {
179
+ return withProjectReadSnapshot(options.target, () => inspectSelectedTarget(options));
180
+ }
181
+
182
+ async function inspectSelectedTarget({ target, packageRoot, contractFile = null, authorityContext, runtimeContext, taskId = null, stateFile = null } = {}) {
173
183
  let manifest = null;
174
184
  let manifestError = null;
175
185
  try {
@@ -184,8 +194,7 @@ export async function inspectTarget({ target, packageRoot, contractFile = null,
184
194
  ? { ...profileMetadata(await readBytes(profilePath)), path: profileRelativePath }
185
195
  : { mode: null, status: null };
186
196
  const effectiveStateRel = stateFile ?? (taskId ? taskArtifactPath(taskId, "state") : WORK_STATE_PATH);
187
- const statePath = ensureWithin(target, effectiveStateRel);
188
- const statePresent = await fileExists(statePath);
197
+ const statePresent = await inspectStatePresence(target, effectiveStateRel);
189
198
  const classifiedState = await readAndClassifyWorkState({ target, packageRoot, contractFile, taskId, stateFile: effectiveStateRel });
190
199
  const rawState = classifiedState?.state ?? null;
191
200
  const taskInfo = taskId ? await findTaskById(target, taskId, packageRoot) : null;
@@ -1,3 +1,4 @@
1
+ import { getStorageCapabilities } from "../storage/capabilities.js";
1
2
  import { CLI_COMMAND_DEFINITIONS } from "./cli-command-definitions.js";
2
3
  import { COMMAND_EXECUTORS } from "./command-executors.js";
3
4
  import { PROTOCOL_VERSION } from "./protocol.js";
@@ -24,6 +25,7 @@ export const INTEGRATION_RISK_CLASSES = Object.freeze({
24
25
  });
25
26
 
26
27
  const READ_ONLY_COMMANDS = Object.freeze(new Set([
28
+ "storage-migration-status",
27
29
  "protocol-info", "decision-status", "decision-show", "context-plan", "model-route", "semantic-plan", "test-inventory", "status", "next", "continuity", "reconcile-continuity",
28
30
  "task-list", "task-show", "task-lock-status", "progress", "audit", "report",
29
31
  "inspect", "validate-state", "validate-protocol", "validate-receipt",
@@ -69,6 +71,13 @@ const STATIC_RISK_CLASSES = Object.freeze({
69
71
  "usage-record": INTEGRATION_RISK_CLASSES.LOOP_MUTATION,
70
72
  init: INTEGRATION_RISK_CLASSES.MAINTENANCE,
71
73
  update: INTEGRATION_RISK_CLASSES.MAINTENANCE,
74
+ "storage-migration-resume": INTEGRATION_RISK_CLASSES.MAINTENANCE,
75
+ "storage-rollback-resume": INTEGRATION_RISK_CLASSES.MAINTENANCE,
76
+ "storage-rollback": INTEGRATION_RISK_CLASSES.MAINTENANCE,
77
+ "storage-migrate": INTEGRATION_RISK_CLASSES.MAINTENANCE,
78
+ "storage-backup": INTEGRATION_RISK_CLASSES.MAINTENANCE,
79
+ "storage-restore": INTEGRATION_RISK_CLASSES.MAINTENANCE,
80
+ "storage-restore-resume": INTEGRATION_RISK_CLASSES.MAINTENANCE,
72
81
  activate: INTEGRATION_RISK_CLASSES.MAINTENANCE,
73
82
  "task-migrate": INTEGRATION_RISK_CLASSES.MAINTENANCE,
74
83
  "migrate-protocol": INTEGRATION_RISK_CLASSES.MAINTENANCE,
@@ -143,6 +152,7 @@ export function getForgeLoopCapabilities({ packageVersion = null } = {}) {
143
152
  integrationApiVersion: FORGELOOP_INTEGRATION_RUNTIME_VERSION,
144
153
  executorParity: Object.keys(COMMAND_EXECUTORS).length === Object.keys(CLI_COMMAND_DEFINITIONS).length,
145
154
  features: {
155
+ operationalStorage: getStorageCapabilities(),
146
156
  taskClaimRecovery: {
147
157
  version: 1,
148
158
  durableRecoveryState: true,
@@ -1,7 +1,9 @@
1
1
  import path from "node:path";
2
+ import { withProjectStorage } from "../storage/project-boundary.js";
3
+ import { withProjectReadSnapshot } from "../storage/project-read-snapshot.js";
2
4
  import { runProtocolInfo } from "../commands/protocol-info.js";
3
5
  import { readContract } from "./contract.js";
4
- import { discoverTasks } from "./task-discovery.js";
6
+ import { discoverTaskSummaries } from "./task-discovery.js";
5
7
  import { resolveTaskClaimState } from "./task-claim-state.js";
6
8
  import { runStatus } from "../commands/status.js";
7
9
  import { runContinuity } from "../commands/continuity.js";
@@ -10,11 +12,11 @@ import { listApprovals } from "./approvals.js";
10
12
  import { buildTrajectoryMetrics } from "./trajectory-metrics.js";
11
13
  import { loadCapabilityPolicy } from "./capability-policy.js";
12
14
  import { readdir } from "node:fs/promises";
13
- import { readJsonArtifact } from "./artifacts.js";
15
+ import { getOperationalStore, listOperationalArtifactNames } from "../storage/operational-context.js";
16
+ import { readJsonArtifact, parseCapturedJsonArtifact } from "./artifacts.js";
14
17
  import { taskDirectory } from "./task-paths.js";
15
18
  import { resolveWorkspaceBindingStatus } from "./workspace-binding.js";
16
- import { listCanonicalHandoffs } from "./handoff.js";
17
- import { readHandoffAcceptanceLedger, resolveHandoffAcceptance } from "./handoff-acceptance.js";
19
+ import { runHandoffList } from "../commands/handoff-list.js";
18
20
  import { resolveResponsibilityStatus } from "./responsibility.js";
19
21
  import { readVerificationScope } from "./verification-scope.js";
20
22
  import { resolveAttestationStatus } from "./attestation.js";
@@ -118,6 +120,12 @@ function ownershipProjection(projection) {
118
120
  };
119
121
  }
120
122
 
123
+ async function resourceArtifactNames(projectPath, taskId, kind, directory) {
124
+ const names = listOperationalArtifactNames(projectPath, taskId, kind);
125
+ if (names !== null) return names;
126
+ try { return await readdir(directory); } catch { return []; }
127
+ }
128
+
121
129
  async function readForgeLoopIntegrationResourceCore(uri, {
122
130
  projectPath = ".",
123
131
  packageRoot = undefined,
@@ -135,13 +143,8 @@ async function readForgeLoopIntegrationResourceCore(uri, {
135
143
 
136
144
  if (uri === "protocol/info") return { uri, data: await runProtocolInfo({ packageVersion }) };
137
145
  if (uri === "project/tasks") {
138
- const tasks = await discoverTasks(projectPath, packageRoot);
139
- return { uri, data: { count: tasks.length, tasks: tasks.map((task) => ({
140
- taskId: task.taskId,
141
- healthy: task.healthy !== false,
142
- phase: task.phase ?? null,
143
- mutationAllowed: task.mutationAllowed !== false,
144
- })) } };
146
+ const tasks = await discoverTaskSummaries(projectPath, packageRoot);
147
+ return { uri, data: { count: tasks.length, tasks } };
145
148
  }
146
149
  if (TASK_RESOURCE_URIS.has(uri) && (typeof taskId !== "string" || !taskId)) {
147
150
  const error = new Error(`Resource ${uri} requires a taskId`);
@@ -164,27 +167,7 @@ async function readForgeLoopIntegrationResourceCore(uri, {
164
167
  return { uri, taskId, data: await resolveWorkspaceBindingStatus(projectPath, { packageRoot, taskId }) };
165
168
  }
166
169
  if (uri === "task/handoffs") {
167
- const handoffs = await listCanonicalHandoffs(projectPath, { packageRoot, taskId });
168
- const ledger = await readHandoffAcceptanceLedger(projectPath, packageRoot, { taskId });
169
- const projectedHandoffs = handoffs.map((handoff) => {
170
- const resolved = resolveHandoffAcceptance({
171
- events: ledger.events,
172
- handoff,
173
- ledgerValid: ledger.valid,
174
- ledgerErrors: ledger.errors,
175
- });
176
- return {
177
- ...handoff,
178
- acceptance: {
179
- status: resolved.status,
180
- consumerId: resolved.consumerId ?? null,
181
- harness: resolved.harness ?? null,
182
- acceptedAt: resolved.acceptedAt ?? null,
183
- ...(resolved.reasonCodes ? { reasonCodes: [...resolved.reasonCodes] } : {}),
184
- },
185
- };
186
- });
187
- return { uri, taskId, data: { taskId, count: projectedHandoffs.length, handoffs: projectedHandoffs } };
170
+ return { uri, taskId, data: await runHandoffList({ target: projectPath, packageRoot, taskId }) };
188
171
  }
189
172
  if (uri === "task/responsibility") {
190
173
  return { uri, taskId, data: await resolveResponsibilityStatus(projectPath, { packageRoot, taskId }) };
@@ -229,7 +212,7 @@ async function readForgeLoopIntegrationResourceCore(uri, {
229
212
  }
230
213
  if (uri === "task/evaluations") {
231
214
  const dir = path.join(projectPath, taskDirectory(taskId), "evaluations");
232
- let names = []; try { names = await readdir(dir); } catch { /* absent is an empty projection */ }
215
+ const names = await resourceArtifactNames(projectPath, taskId, "evaluations", dir);
233
216
  const evaluations = [];
234
217
  for (const name of names.filter((entry) => /^eval-[A-Za-z0-9_-]+\.json$/.test(entry)).sort()) {
235
218
  evaluations.push((await readJsonArtifact(projectPath, `${taskDirectory(taskId)}/evaluations/${name}`, "trajectory-evaluation", packageRoot)).value);
@@ -237,8 +220,13 @@ async function readForgeLoopIntegrationResourceCore(uri, {
237
220
  return { uri, taskId, data: { evaluations } };
238
221
  }
239
222
  if (uri === "task/decisions") {
223
+ const store = getOperationalStore(projectPath);
224
+ if (store) {
225
+ const decisions = await readCapturedDecisions(store, taskId, packageRoot);
226
+ return { uri, taskId, data: { taskId, decisions } };
227
+ }
240
228
  const dir = path.join(projectPath, taskDirectory(taskId), "decisions");
241
- let names = []; try { names = await readdir(dir); } catch { /* absent is an empty projection */ }
229
+ const names = await resourceArtifactNames(projectPath, taskId, "decisions", dir);
242
230
  const decisions = [];
243
231
  for (const name of names.filter((entry) => /^[A-Za-z0-9][A-Za-z0-9_-]{0,127}\.json$/.test(entry)).sort()) {
244
232
  decisions.push((await readJsonArtifact(projectPath, `${taskDirectory(taskId)}/decisions/${name}`, "semantic-decision", packageRoot)).value);
@@ -297,6 +285,15 @@ async function readForgeLoopIntegrationResourceCore(uri, {
297
285
  }
298
286
 
299
287
  export async function readForgeLoopIntegrationResource(uri, options = {}) {
288
+ const target = options.projectPath ?? ".";
289
+ const read = () => readSelectedIntegrationResource(uri, options);
290
+ // Decisions capture their finite catalog and bytes synchronously; other
291
+ // task projections retain the complete committed project snapshot.
292
+ return withProjectStorage(target, () => TASK_RESOURCE_URIS.has(uri) && uri !== "task/decisions"
293
+ ? withProjectReadSnapshot(target, read) : read(), { readOnly: true, runtimeContext: options.runtimeContext });
294
+ }
295
+
296
+ async function readSelectedIntegrationResource(uri, options) {
300
297
  if (uri === "task/audit-view") {
301
298
  if (typeof options.taskId !== "string" || !options.taskId) {
302
299
  const error = new Error("Resource task/audit-view requires a taskId"); error.code = "E_TASK_REQUIRED"; throw error;
@@ -328,3 +325,13 @@ export async function readForgeLoopIntegrationResource(uri, options = {}) {
328
325
  }
329
326
  return readForgeLoopIntegrationResourceCore(uri, options);
330
327
  }
328
+
329
+
330
+ async function readCapturedDecisions(store, taskId, packageRoot) {
331
+ const decisions = [];
332
+ for (const entry of store.captureDecisionTexts(taskId)) {
333
+ if (entry.error) throw entry.error;
334
+ decisions.push((await parseCapturedJsonArtifact(entry.text, entry.relativePath, "semantic-decision", packageRoot)).value);
335
+ }
336
+ return decisions;
337
+ }