@cassiomc1/forgeloop 1.13.0 → 1.14.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (211) hide show
  1. package/AGENT_COMPATIBILITY.md +8 -0
  2. package/DOCS_INDEX.md +35 -3
  3. package/ENG/nodejs-backend-development-eng.md +2 -2
  4. package/ENG/sec-code-eng.md +7 -7
  5. package/EXECUTION_STATE.md +12 -0
  6. package/LOOP_ENGINEERING.md +28 -2
  7. package/ORCHESTRATOR_INTEGRATION.md +9 -5
  8. package/PROTOCOL_INTEGRATION.md +55 -2
  9. package/README.md +40 -25
  10. package/TERMINOLOGY.md +2 -0
  11. package/THREAT_MODEL.md +140 -1
  12. package/completions/_forgeloop +19 -1
  13. package/completions/forgeloop.bash +37 -1
  14. package/completions/forgeloop.fish +123 -1
  15. package/docs/ADVISORY_CONTEXT.md +25 -0
  16. package/docs/AGENT_BROWSER_ADAPTER.md +81 -0
  17. package/docs/AGENT_BROWSER_VERIFICATION.md +6 -0
  18. package/docs/AGENT_PROTOCOL_SUMMARY.md +27 -2
  19. package/docs/AGENT_SKILL.md +66 -0
  20. package/docs/ARTIFACT_REFERENCE.md +123 -0
  21. package/docs/AUDIT_UX.md +46 -0
  22. package/docs/BROWSER_VERIFICATION.md +136 -0
  23. package/docs/CLI_REFERENCE.md +366 -6
  24. package/docs/CODE_ATTESTATION.md +2 -2
  25. package/docs/DOCUMENTATION_GUIDE.md +32 -11
  26. package/docs/JEV_BENCHMARKS.md +31 -0
  27. package/docs/MODEL_ROUTING.md +37 -0
  28. package/docs/OPENSRC_ADAPTER.md +241 -0
  29. package/docs/PACKAGE_CONTENTS.md +35 -8
  30. package/docs/PROVIDERS.md +126 -0
  31. package/docs/PROVIDER_ARCHITECTURE.md +199 -0
  32. package/docs/RECIPES.md +9 -0
  33. package/docs/RELEASE_CHECKLIST.md +38 -5
  34. package/docs/SECURITY_REVIEW.md +71 -0
  35. package/docs/SEMANTIC_DECISION_PLANE.md +71 -0
  36. package/docs/TEST_INTELLIGENCE.md +29 -0
  37. package/docs/TEST_PRUNING.md +14 -0
  38. package/docs/TROUBLESHOOTING.md +198 -1
  39. package/docs/UNIVERSAL_INTEGRATION.md +31 -0
  40. package/docs/assets/diagrams/forgeloop-code-attestation-flow.html +2 -2
  41. package/docs/assets/diagrams/forgeloop-code-attestation-flow.receipt.json +5 -5
  42. package/docs/assets/diagrams/forgeloop-code-attestation-flow.svg +1 -1
  43. package/docs/assets/diagrams/forgeloop-engineering-flow.html +39 -26
  44. package/docs/assets/diagrams/forgeloop-engineering-flow.receipt.json +6 -6
  45. package/docs/assets/diagrams/forgeloop-engineering-flow.svg +26 -26
  46. package/docs/assets/diagrams/forgeloop-verification-trust-flow.html +2 -1
  47. package/docs/assets/diagrams/forgeloop-verification-trust-flow.receipt.json +5 -5
  48. package/docs/assets/diagrams/forgeloop-verification-trust-flow.svg +1 -1
  49. package/docs/diagrams/README.md +13 -9
  50. package/docs/diagrams/forgeloop-code-attestation-flow.workflow.json +1 -1
  51. package/docs/diagrams/forgeloop-engineering-flow.workflow.json +24 -19
  52. package/docs/diagrams/forgeloop-verification-trust-flow.workflow.json +1 -0
  53. package/docs/diagrams/reviews/forgeloop-code-attestation-flow.review.json +4 -4
  54. package/docs/diagrams/reviews/forgeloop-engineering-flow.review.json +4 -4
  55. package/docs/diagrams/reviews/forgeloop-verification-trust-flow.review.json +4 -4
  56. package/docs/documentation-manifest.json +750 -5
  57. package/docs/protocol-requirements.json +24 -0
  58. package/package.json +30 -3
  59. package/schemas/config.schema.json +14 -0
  60. package/schemas/context-plan.schema.json +18 -0
  61. package/schemas/semantic-decision.schema.json +46 -0
  62. package/schemas/test-utility.schema.json +44 -0
  63. package/scripts/CI_VALIDATORS.md +6 -6
  64. package/scripts/benchmark-jev.mjs +5 -0
  65. package/scripts/benchmark-test-intelligence.mjs +4 -0
  66. package/scripts/generate-agent-protocol-summary.mjs +4 -1
  67. package/scripts/generate-forgeloop-skill.mjs +133 -0
  68. package/scripts/jev-smoke.mjs +19 -0
  69. package/skills/forgeloop/README.md +9 -0
  70. package/skills/forgeloop/SKILL.md +77 -0
  71. package/skills/forgeloop/references/lifecycle.md +9 -0
  72. package/skills/forgeloop/references/recovery.md +7 -0
  73. package/skills/forgeloop/references/verification.md +7 -0
  74. package/src/adapters/agent-browser/assertions.js +47 -0
  75. package/src/adapters/agent-browser/commands.js +54 -0
  76. package/src/adapters/agent-browser/index.js +3 -0
  77. package/src/adapters/agent-browser/locator.js +40 -0
  78. package/src/adapters/agent-browser/process.js +215 -0
  79. package/src/adapters/agent-browser/provider.js +313 -0
  80. package/src/adapters/emulated-services/constants.js +24 -0
  81. package/src/adapters/emulated-services/index.js +7 -0
  82. package/src/adapters/emulated-services/process.js +162 -0
  83. package/src/adapters/emulated-services/provider.js +282 -0
  84. package/src/adapters/opensrc/normalize.js +90 -0
  85. package/src/adapters/opensrc/process.js +248 -0
  86. package/src/adapters/opensrc/provider.js +338 -0
  87. package/src/adapters/opensrc/search.js +264 -0
  88. package/src/adapters/typesafe/client.js +28 -0
  89. package/src/adapters/typesafe/engine.js +63 -0
  90. package/src/adapters/typesafe/normalize.js +41 -0
  91. package/src/cli.js +108 -0
  92. package/src/commands/checkpoint-revalidate.js +176 -0
  93. package/src/commands/context-plan.js +38 -0
  94. package/src/commands/contract-create.js +264 -0
  95. package/src/commands/contract-revise.js +236 -0
  96. package/src/commands/decision-show.js +14 -0
  97. package/src/commands/decision-status.js +22 -0
  98. package/src/commands/discover.js +41 -0
  99. package/src/commands/doctor.js +15 -0
  100. package/src/commands/gate-record.js +205 -0
  101. package/src/commands/gate-revalidate.js +137 -0
  102. package/src/commands/model-route.js +32 -0
  103. package/src/commands/route.js +146 -18
  104. package/src/commands/semantic-plan.js +17 -0
  105. package/src/commands/task-abandon.js +224 -0
  106. package/src/commands/task-migrate-contract-bootstrap-repair.js +288 -0
  107. package/src/commands/task-repair-contract-bootstrap.js +263 -0
  108. package/src/commands/test-inventory.js +5 -0
  109. package/src/commands/test-prune-plan.js +5 -0
  110. package/src/commands/test-prune-probe.js +5 -0
  111. package/src/commands/test-utility.js +5 -0
  112. package/src/commands/validate-protocol.js +10 -1
  113. package/src/core/artifact-registry.js +24 -0
  114. package/src/core/audit-ux.js +514 -0
  115. package/src/core/browser-verification/constants.js +149 -0
  116. package/src/core/browser-verification/normalize.js +254 -0
  117. package/src/core/browser-verification/provider.js +519 -0
  118. package/src/core/browser-verification/service.js +115 -0
  119. package/src/core/checkpoint-revalidation.js +319 -0
  120. package/src/core/cli-command-definitions.js +241 -0
  121. package/src/core/command-executors.js +110 -0
  122. package/src/core/command-input.js +115 -43
  123. package/src/core/completion-artifacts.js +14 -5
  124. package/src/core/completion.js +4 -6
  125. package/src/core/config.js +3 -0
  126. package/src/core/context-compiler/budget.js +9 -0
  127. package/src/core/context-compiler/candidates.js +39 -0
  128. package/src/core/context-compiler/compiler.js +63 -0
  129. package/src/core/context-compiler/fingerprint.js +11 -0
  130. package/src/core/context-compiler/policy.js +13 -0
  131. package/src/core/context-compiler/result.js +23 -0
  132. package/src/core/contract-bootstrap-recovery.js +655 -0
  133. package/src/core/contract-revision.js +210 -0
  134. package/src/core/decision/artifact.js +69 -0
  135. package/src/core/decision/benchmarks.js +103 -0
  136. package/src/core/decision/cache.js +27 -0
  137. package/src/core/decision/constants.js +58 -0
  138. package/src/core/decision/cutover.js +34 -0
  139. package/src/core/decision/engine.js +22 -0
  140. package/src/core/decision/errors.js +68 -0
  141. package/src/core/decision/events.js +101 -0
  142. package/src/core/decision/freshness.js +19 -0
  143. package/src/core/decision/normalizers/index.js +115 -0
  144. package/src/core/decision/policy.js +18 -0
  145. package/src/core/decision/projection.js +16 -0
  146. package/src/core/decision/question-registry.js +201 -0
  147. package/src/core/decision/request.js +26 -0
  148. package/src/core/decision/resolver.js +130 -0
  149. package/src/core/decision/result.js +58 -0
  150. package/src/core/decision/service.js +156 -0
  151. package/src/core/decision/state-builder.js +65 -0
  152. package/src/core/decision/task-bindings.js +30 -0
  153. package/src/core/decision/test-provider.js +32 -0
  154. package/src/core/decision/thresholds.js +15 -0
  155. package/src/core/error-codes.js +278 -0
  156. package/src/core/events.js +226 -57
  157. package/src/core/evidence-readiness.js +9 -0
  158. package/src/core/execution-prerequisites.js +14 -0
  159. package/src/core/execution-profile.js +63 -38
  160. package/src/core/gate-provenance.js +124 -0
  161. package/src/core/integration-invocation-policy.js +27 -4
  162. package/src/core/integration-resources.js +86 -61
  163. package/src/core/model-router/constants.js +10 -0
  164. package/src/core/model-router/policy.js +103 -0
  165. package/src/core/model-router/router.js +37 -0
  166. package/src/core/next-action-model.js +58 -0
  167. package/src/core/next-action-phases.js +130 -42
  168. package/src/core/next-action-refresh.js +43 -9
  169. package/src/core/next-action-review-phase.js +7 -2
  170. package/src/core/next-action.js +35 -7
  171. package/src/core/phase.js +128 -10
  172. package/src/core/preflight-consistency.js +23 -9
  173. package/src/core/preflight-loaders.js +37 -5
  174. package/src/core/protocol-info.js +65 -0
  175. package/src/core/protocol.js +20 -0
  176. package/src/core/reconcile-closure.js +128 -52
  177. package/src/core/recovery-history.js +1 -0
  178. package/src/core/resumability.js +154 -44
  179. package/src/core/route-artifact.js +15 -1
  180. package/src/core/router.js +67 -1
  181. package/src/core/runtime-context.js +118 -61
  182. package/src/core/schema-validation.js +3 -0
  183. package/src/core/security-review/constants.js +64 -0
  184. package/src/core/security-review/normalize.js +245 -0
  185. package/src/core/security-review/provider.js +204 -0
  186. package/src/core/security-review/service.js +134 -0
  187. package/src/core/semantic-planning/constants.js +19 -0
  188. package/src/core/semantic-planning/projection.js +94 -0
  189. package/src/core/semantic-planning/service.js +15 -0
  190. package/src/core/sources.js +37 -0
  191. package/src/core/task-claim-state.js +201 -1
  192. package/src/core/task-conflict-inspection.js +31 -5
  193. package/src/core/task-paths.js +13 -0
  194. package/src/core/task-recovery.js +1 -0
  195. package/src/core/templates.js +3 -0
  196. package/src/core/test-intelligence/benchmarks.js +68 -0
  197. package/src/core/test-intelligence/inventory.js +73 -0
  198. package/src/core/test-intelligence/prune.js +90 -0
  199. package/src/core/test-intelligence/semantic-state.js +15 -0
  200. package/src/core/test-intelligence/service.js +40 -0
  201. package/src/core/test-intelligence/utility.js +50 -0
  202. package/src/core/trace.js +11 -7
  203. package/src/core/transaction.js +1 -0
  204. package/src/integration.d.ts +492 -0
  205. package/src/integration.js +54 -0
  206. package/src/providers/README.md +47 -0
  207. package/src/providers/capabilities.js +46 -0
  208. package/src/providers/errors.js +15 -0
  209. package/src/providers/index.js +29 -0
  210. package/src/providers/json-snapshot.js +105 -0
  211. package/src/providers/registry.js +152 -0
@@ -0,0 +1,94 @@
1
+ import { FAILURE_CLASSES, DIAGNOSIS_STRATEGIES, REVIEW_FOCUSES } from "./constants.js";
2
+
3
+ function strings(value) {
4
+ return Array.isArray(value) ? value.filter((item) => typeof item === "string" && item.trim()).map((item) => item.trim()) : [];
5
+ }
6
+
7
+ function text(input) {
8
+ return [input?.message, input?.error, input?.failure, input?.objective, ...(input?.paths ?? []), ...(input?.risks ?? [])]
9
+ .filter((value) => typeof value === "string").join(" ").toLowerCase();
10
+ }
11
+
12
+ function unique(values) { return [...new Set(values)]; }
13
+
14
+ function validateRecommendation(recommendation, allowed, label) {
15
+ if (recommendation === undefined || recommendation === null) return null;
16
+ const values = typeof recommendation === "string" ? [recommendation]
17
+ : recommendation && typeof recommendation === "object" && !Array.isArray(recommendation)
18
+ ? Object.entries(recommendation).filter(([, value]) => value === true).map(([key]) => key)
19
+ : strings(recommendation);
20
+ const unknown = values.filter((value) => !allowed.includes(value));
21
+ if (unknown.length > 0) {
22
+ const error = new Error(`${label} contains unknown semantic values: ${unknown.join(", ")}`);
23
+ error.code = "E_DECISION_RESULT_INVALID";
24
+ throw error;
25
+ }
26
+ return unique(values);
27
+ }
28
+
29
+ export function projectFailureTriage({ input = {}, semanticRecommendation = null } = {}) {
30
+ const candidate = semanticRecommendation?.failureClass ?? semanticRecommendation;
31
+ const value = validateRecommendation(candidate, FAILURE_CLASSES, "failure recommendation");
32
+ const inferred = /timeout|timed out/.test(text(input)) ? "timeout"
33
+ : /network|dns|connection|environment/.test(text(input)) ? "network/environment"
34
+ : /ownership|protocol|ledger|phase/.test(text(input)) ? "ownership/protocol mismatch"
35
+ : "unknown";
36
+ return {
37
+ decisionKind: "FAILURE_TRIAGE",
38
+ questionSetId: "failure-v1",
39
+ candidates: [...FAILURE_CLASSES],
40
+ ranked: unique([...(value ?? []), inferred]),
41
+ semanticRequired: true,
42
+ unknownEscalates: !value,
43
+ authority: "SEMANTIC_DECISION",
44
+ evidenceAuthority: "NONE",
45
+ };
46
+ }
47
+
48
+ export function projectDiagnosisPriority({ input = {}, semanticRecommendation = null } = {}) {
49
+ const candidate = semanticRecommendation?.priority ?? semanticRecommendation;
50
+ const value = validateRecommendation(candidate, DIAGNOSIS_STRATEGIES, "diagnosis recommendation");
51
+ const inferred = /race|concurr|lock/.test(text(input)) ? "inspect concurrency path"
52
+ : /state|ledger|phase|fingerprint/.test(text(input)) ? "inspect state transition"
53
+ : /depend|package|module/.test(text(input)) ? "inspect dependency behavior"
54
+ : "run narrower experiment";
55
+ return {
56
+ decisionKind: "DIAGNOSIS_PRIORITY",
57
+ questionSetId: "diagnosis-v1",
58
+ candidates: [...DIAGNOSIS_STRATEGIES],
59
+ ranked: unique([...(value ?? []), inferred]),
60
+ previouslyFailed: strings(input.previouslyFailed),
61
+ semanticRequired: true,
62
+ unknownEscalates: !value,
63
+ authority: "SEMANTIC_DECISION",
64
+ evidenceAuthority: "NONE",
65
+ };
66
+ }
67
+
68
+ export function projectReviewPlan({ input = {}, semanticRecommendation = null } = {}) {
69
+ const candidate = semanticRecommendation?.reviewFocus ?? semanticRecommendation;
70
+ const value = validateRecommendation(candidate, REVIEW_FOCUSES, "review recommendation");
71
+ const body = text(input);
72
+ const mandatory = ["needs_full_diff_context"];
73
+ if (/security|secret|credential|auth|permission/.test(body)) mandatory.push("needs_security_review");
74
+ if (/api|public|compatib/.test(body)) mandatory.push("needs_api_compatibility_review");
75
+ if (/race|concurr|lock/.test(body)) mandatory.push("needs_concurrency_review");
76
+ if (/package|release|dependency/.test(body)) mandatory.push("needs_package_review", "needs_dependency_review");
77
+ if (/doc|readme|guide/.test(body)) mandatory.push("needs_docs_review");
78
+ if (/migration|schema|database/.test(body)) mandatory.push("needs_migration_review");
79
+ if (/performance|timeout|latency/.test(body)) mandatory.push("needs_performance_review");
80
+ if (input.fullDiff === true || body.includes("full diff")) mandatory.push("needs_full_diff_context");
81
+ const ranked = unique([...mandatory, ...(value ?? [])]);
82
+ if (ranked.length === 0) ranked.push("needs_full_diff_context");
83
+ if (input.primaryModel === true || input.ambiguity === true) ranked.push("needs_primary_model");
84
+ return {
85
+ decisionKind: "REVIEW_PLAN",
86
+ questionSetId: "review-v1",
87
+ mandatory: unique(mandatory),
88
+ ranked: unique(ranked),
89
+ semanticRequired: true,
90
+ unknownEscalates: !value,
91
+ authority: "SEMANTIC_DECISION",
92
+ evidenceAuthority: "NONE",
93
+ };
94
+ }
@@ -0,0 +1,15 @@
1
+ import { DECISION_ERROR_CODES, decisionError } from "../decision/errors.js";
2
+ import { projectDiagnosisPriority, projectFailureTriage, projectReviewPlan } from "./projection.js";
3
+
4
+ const PROJECTORS = Object.freeze({
5
+ failure: projectFailureTriage,
6
+ diagnosis: projectDiagnosisPriority,
7
+ review: projectReviewPlan,
8
+ });
9
+
10
+ export function runSemanticPlan({ kind, input = {}, semanticRecommendation = null } = {}) {
11
+ const projector = PROJECTORS[kind];
12
+ if (!projector) throw decisionError(DECISION_ERROR_CODES.QUESTION_SET_UNKNOWN, `Unknown semantic plan kind: ${kind}`);
13
+ return projector({ input, semanticRecommendation });
14
+ }
15
+
@@ -1,5 +1,19 @@
1
+ import { CONTRACT_PRESET_IDS } from "./contract-presets.js";
1
2
  import { PROTOCOL_VERSION } from "./protocol.js";
2
3
 
4
+ const CONTRACT_PRESET_PREFIX = "contract-preset:";
5
+ const BUILTIN_CONTRACT_PRESET_REFS = new Set(
6
+ CONTRACT_PRESET_IDS.map((presetId) => `${CONTRACT_PRESET_PREFIX}${presetId}`),
7
+ );
8
+
9
+ export function isBuiltinContractPresetRef(ref) {
10
+ return typeof ref === "string" && BUILTIN_CONTRACT_PRESET_REFS.has(ref);
11
+ }
12
+
13
+ export function externalContractSourceRefs(refs = []) {
14
+ return refs.filter((ref) => !isBuiltinContractPresetRef(ref));
15
+ }
16
+
3
17
  export const SOURCE_REGISTRY_SCHEMA_VERSION = 1;
4
18
  export const SOURCE_KINDS = Object.freeze([
5
19
  "user-request",
@@ -84,3 +98,26 @@ export function assertSourceProvenance(registry, refs = [], { expectedKind } = {
84
98
  }
85
99
  return true;
86
100
  }
101
+
102
+ export function assertContractPresetRefs(refs = []) {
103
+ for (const ref of refs) {
104
+ if (typeof ref === "string" && ref.startsWith(CONTRACT_PRESET_PREFIX) && !isBuiltinContractPresetRef(ref)) {
105
+ const error = new Error(`Unknown built-in contract preset source: ${ref}`);
106
+ error.code = "E_PROFILE_SOURCE_UNKNOWN";
107
+ throw error;
108
+ }
109
+ }
110
+ return true;
111
+ }
112
+
113
+ export function assertContractSourceProvenance(registry, refs = []) {
114
+ assertContractPresetRefs(refs);
115
+ const externalRefs = externalContractSourceRefs(refs);
116
+ if (externalRefs.length === 0) return true;
117
+ if (!registry) {
118
+ const error = new Error("External contract source references require a source registry");
119
+ error.code = "E_PROFILE_SOURCE_MISSING";
120
+ throw error;
121
+ }
122
+ return assertSourceProvenance(registry, externalRefs);
123
+ }
@@ -6,7 +6,7 @@ import {
6
6
  E_TASK_RECOVERY_INCONSISTENT,
7
7
  } from "./error-codes.js";
8
8
  import { validateCompletionOwnershipProof } from "./completion-ownership.js";
9
- import { validateEventLedger } from "./events.js";
9
+ import { validateEventLedger, validateStateLedgerCoherence } from "./events.js";
10
10
  import { classifyRecoveryHistory } from "./recovery-history.js";
11
11
  import { readTaskDescriptor } from "./task-descriptor.js";
12
12
  import { normalizeWriteClaims } from "./task-scope.js";
@@ -15,6 +15,35 @@ import {
15
15
  validateTaskRecoveryConsistency,
16
16
  } from "./task-recovery.js";
17
17
  import { readWorkState } from "./work-state.js";
18
+ import { readContract } from "./contract.js";
19
+ import { readPersistedRoute } from "./route-artifact.js";
20
+ import {
21
+ CONTRACT_BOOTSTRAP_REPAIR_EVENT,
22
+ resolveEffectiveContractBootstrapRepairAnchor,
23
+ resolveCanonicalPostRepairCheckpointBinding,
24
+ resolveCanonicalPostRepairRouteBinding,
25
+ sameCanonicalGuideList,
26
+ } from "./contract-bootstrap-recovery.js";
27
+ import { canonicalFingerprint } from "./artifacts.js";
28
+ import { stateIdentityErrors } from "./completion-relationships.js";
29
+ import { routeCheckpointMustMatchCurrentRoute } from "./resumability.js";
30
+
31
+ const REPAIR_PHASE_EVENTS = Object.freeze({
32
+ CONTRACT_READY: "CONTRACT_VALIDATED",
33
+ ROUTED: "ROUTE_VALIDATED",
34
+ DESIGNING: "DESIGN_GATE_STARTED",
35
+ PLANNED: "PLAN_RECORDED",
36
+ EXECUTING: "EXECUTION_STARTED",
37
+ VERIFYING: "VERIFICATION_STARTED",
38
+ DIAGNOSING: "VERIFICATION_STARTED",
39
+ CORRECTING: "VERIFICATION_STARTED",
40
+ REVIEWING: "REVIEW_STARTED",
41
+ COMPLETE: "COMPLETION_VALIDATED",
42
+ });
43
+
44
+ const REPAIR_ROUTE_PHASES = new Set([
45
+ "ROUTED", "DESIGNING", "PLANNED", "EXECUTING", "VERIFYING", "DIAGNOSING", "CORRECTING", "REVIEWING", "COMPLETE",
46
+ ]);
18
47
 
19
48
  function ownershipError(message, cause = null) {
20
49
  return {
@@ -24,6 +53,164 @@ function ownershipError(message, cause = null) {
24
53
  };
25
54
  }
26
55
 
56
+ function repairInvalid(message) {
57
+ return ownershipError(message, { code: "E_CONTRACT_BOOTSTRAP_REPAIR_INVALID" });
58
+ }
59
+
60
+ function lateRouteCheckpointErrors(events, state, marker) {
61
+ if (routeCheckpointMustMatchCurrentRoute(state.phase) || !REPAIR_PHASE_EVENTS[state.phase]) return [];
62
+ const checkpoint = resolveCanonicalPostRepairCheckpointBinding(events, marker);
63
+ if (!checkpoint) {
64
+ return [repairInvalid("Repaired late-phase state lacks an immutable route checkpoint binding")];
65
+ }
66
+ if (state.routeFingerprint !== checkpoint.routeFingerprint
67
+ || !sameCanonicalGuideList(state.selectedGuides, checkpoint.selectedGuides)) {
68
+ return [repairInvalid("Late-phase checkpoint route identity was rewritten after the canonical checkpoint binding")];
69
+ }
70
+ return [];
71
+ }
72
+
73
+ function repairArtifactErrors(artifacts) {
74
+ return [
75
+ ...(artifacts?.contractError
76
+ ? [repairInvalid(`Current contract artifact is invalid after a recorded repair: ${artifacts.contractError}`)]
77
+ : []),
78
+ ...(artifacts?.routeError
79
+ ? [repairInvalid(`Current route artifact is invalid after a recorded repair: ${artifacts.routeError}`)]
80
+ : []),
81
+ ];
82
+ }
83
+
84
+ function repairAnchorErrors(details, state, artifacts) {
85
+ const errors = [];
86
+ if (state.phase !== details.reconstructedPhase) {
87
+ errors.push(repairInvalid(
88
+ `Work-state phase ${state.phase} does not match repair anchor phase ${details.reconstructedPhase}`,
89
+ ));
90
+ }
91
+ if (canonicalFingerprint(state) !== details.reconstructedStateFingerprint) {
92
+ errors.push(repairInvalid("Work-state fingerprint does not match the repair anchor"));
93
+ }
94
+ const stateRouteFingerprint = state.routeFingerprint ?? null;
95
+ const currentRouteFingerprint = artifacts?.route?.fingerprint ?? null;
96
+ if (stateRouteFingerprint !== details.routeFingerprint
97
+ || currentRouteFingerprint !== details.routeFingerprint) {
98
+ errors.push(repairInvalid("Current route identity does not match the repair anchor"));
99
+ }
100
+ return errors;
101
+ }
102
+
103
+ function evolvedRepairErrors(anchor, events, state, artifacts) {
104
+ const errors = [];
105
+ const contract = artifacts?.contract ?? null;
106
+ const route = artifacts?.route ?? null;
107
+ if (!contract) errors.push(repairInvalid("Current contract is unavailable after a recorded repair"));
108
+ const routeRequired = REPAIR_ROUTE_PHASES.has(state.phase) || state.routeFingerprint !== undefined;
109
+ if (routeRequired && !route) {
110
+ errors.push(repairInvalid("Current route is required by the evolved task state but is unavailable"));
111
+ }
112
+ if (contract) {
113
+ const routeErrors = stateIdentityErrors({ contract, route, state });
114
+ const checkpointMustMatch = routeCheckpointMustMatchCurrentRoute(state.phase);
115
+ errors.push(...routeErrors
116
+ .filter((error) => checkpointMustMatch || error.code !== "E_ROUTE_GUIDE_MISMATCH")
117
+ .map((error) => repairInvalid(error.message)));
118
+ }
119
+ const currentRouteFingerprint = route?.fingerprint ?? null;
120
+ const binding = currentRouteFingerprint !== anchor.details.routeFingerprint
121
+ ? resolveCanonicalPostRepairRouteBinding(events, anchor.sourceMarker, currentRouteFingerprint)
122
+ : null;
123
+ if (currentRouteFingerprint !== anchor.details.routeFingerprint && !binding) {
124
+ errors.push(repairInvalid("Changed route identity lacks a canonical post-repair route binding"));
125
+ }
126
+ errors.push(...lateRouteCheckpointErrors(events, state, anchor.sourceMarker));
127
+ errors.push(...validateStateLedgerCoherence(state, events).map((error) => repairInvalid(error.message)));
128
+ const requiredEvent = REPAIR_PHASE_EVENTS[state.phase];
129
+ if (requiredEvent && !events.some((event) => event.event === requiredEvent)) {
130
+ errors.push(repairInvalid(`Current work-state phase ${state.phase} is not supported by the repair ledger history`));
131
+ }
132
+ return errors;
133
+ }
134
+
135
+ function validateContractBootstrapRepairConsistency(taskId, events, state, artifacts) {
136
+ const anchor = resolveEffectiveContractBootstrapRepairAnchor(events);
137
+ if (!anchor) {
138
+ if (!events.some((event) => event.event === CONTRACT_BOOTSTRAP_REPAIR_EVENT)) return [];
139
+ return [repairInvalid("Contract bootstrap repair marker is invalid")];
140
+ }
141
+ if (!state) return [repairInvalid("Work-state is missing after a recorded contract bootstrap repair")];
142
+ const details = anchor.details;
143
+ const errors = [];
144
+ if (state.taskId !== taskId) errors.push(repairInvalid("Work-state taskId does not match the repaired task"));
145
+ if (!Number.isInteger(state.revision) || state.revision < 0) {
146
+ return [...errors, repairInvalid("Work-state revision is missing or invalid after a recorded repair")];
147
+ }
148
+ const anchorRevision = details.reconstructedStateRevision;
149
+ if (state.revision < anchorRevision) {
150
+ return [...errors, repairInvalid("Work-state revision rolled back behind the repair anchor")];
151
+ }
152
+ if (state.contractFingerprint !== details.contractFingerprint) {
153
+ errors.push(repairInvalid("Work-state contract fingerprint does not match repair-time contract identity"));
154
+ }
155
+ errors.push(...repairArtifactErrors(artifacts));
156
+ return state.revision === anchorRevision
157
+ ? [...errors, ...repairAnchorErrors(details, state, artifacts)]
158
+ : [...errors, ...evolvedRepairErrors(anchor, events, state, artifacts)];
159
+ }
160
+
161
+ /**
162
+ * Async cross-artifact consistency collector for a recorded contract bootstrap
163
+ * repair. The pure classifier stays filesystem-free; this reads the current
164
+ * task-scoped contract and route artifacts through their canonical readers so
165
+ * schema validation, artifact bounds, task-scoped paths, and fingerprint
166
+ * semantics stay consistent with the rest of the protocol.
167
+ *
168
+ * With no repair marker this performs no extra work.
169
+ */
170
+ async function collectContractBootstrapRepairConsistency(target, packageRoot, taskId, events, state) {
171
+ const anchor = resolveEffectiveContractBootstrapRepairAnchor(events);
172
+ if (!anchor) return null;
173
+ const details = anchor.details;
174
+ const artifacts = { contract: null, route: null, contractError: null, routeError: null };
175
+
176
+ // Contract binding: the current contract must exist, validate, and bind to
177
+ // the marker and work state.
178
+ try {
179
+ const contract = await readContract(target, packageRoot, { taskId });
180
+ artifacts.contract = contract;
181
+ if (contract.fingerprint !== details.contractFingerprint) {
182
+ artifacts.contractError = `contract fingerprint ${contract.fingerprint} does not match marker contract fingerprint ${details.contractFingerprint}`;
183
+ } else if (state && state.contractFingerprint !== details.contractFingerprint) {
184
+ artifacts.contractError = "work-state contract fingerprint does not match marker contract fingerprint";
185
+ }
186
+ } catch (error) {
187
+ artifacts.contractError = error.code === "ARTIFACT_MISSING"
188
+ ? "contract artifact is missing after a recorded repair"
189
+ : `contract artifact is invalid after a recorded repair: ${error.message}`;
190
+ }
191
+
192
+ // Route binding is anchored exactly at repair time, then follows the
193
+ // current state/route identity after the checkpoint advances.
194
+ const routeRequired = state && (REPAIR_ROUTE_PHASES.has(state.phase)
195
+ || state.routeFingerprint !== undefined);
196
+ if (routeRequired || details.routeFingerprint !== null) {
197
+ try {
198
+ const route = await readPersistedRoute(target, packageRoot, { taskId });
199
+ artifacts.route = route;
200
+ if (route.value?.contractFingerprint !== undefined
201
+ && route.value.contractFingerprint !== details.contractFingerprint) {
202
+ artifacts.routeError = "route artifact contract binding does not match repair anchor contract fingerprint";
203
+ }
204
+ } catch (error) {
205
+ artifacts.routeError = error.code === "ARTIFACT_MISSING"
206
+ ? "route artifact is missing after a routed repair"
207
+ : `route artifact is invalid after a routed repair: ${error.message}`;
208
+ }
209
+ }
210
+
211
+ return artifacts;
212
+ }
213
+
27
214
  function appendClaims(target, claims, errors, source) {
28
215
  if (claims === undefined || claims === null) return;
29
216
  try {
@@ -85,6 +272,19 @@ export async function collectTaskClaimEvidence(target, {
85
272
  if (ledger.events.some((event) => event.taskId !== taskId)) {
86
273
  errors.push(ownershipError(`Task event ledger contains an event for a different task`));
87
274
  }
275
+ if (state) {
276
+ for (const error of validateStateLedgerCoherence(state, ledger.events)) {
277
+ if (![
278
+ "E_CHECKPOINT_REVALIDATION_UNSAFE",
279
+ "E_CONTRACT_REVISION_UNSAFE",
280
+ ].includes(error.code)) continue;
281
+ errors.push(ownershipError(`Task state and event ledger are incoherent: ${error.message}`, error));
282
+ }
283
+ }
284
+
285
+ const repairArtifacts = await collectContractBootstrapRepairConsistency(target, packageRoot, taskId, ledger.events, state);
286
+ const repairConsistencyErrors = validateContractBootstrapRepairConsistency(taskId, ledger.events, state, repairArtifacts);
287
+ errors.push(...repairConsistencyErrors);
88
288
 
89
289
  const history = classifyRecoveryHistory(ledger.events);
90
290
  const descriptorClaims = [];
@@ -64,6 +64,27 @@ function lastMeaningfulActivity(state, events) {
64
64
  return candidates.sort((left, right) => Date.parse(right.at) - Date.parse(left.at))[0] ?? null;
65
65
  }
66
66
 
67
+ function isInitialTaskLedger(events, taskId) {
68
+ return events.length >= 2
69
+ && events[0]?.taskId === taskId
70
+ && events[0]?.event === "TASK_RECEIVED"
71
+ && events[1]?.taskId === taskId
72
+ && events[1]?.event === "TRANSACTION_COMMITTED"
73
+ && events[1]?.details?.operation === "task-create"
74
+ && !events.slice(2).some((event) => [
75
+ "CONTRACT_VALIDATED", "ROUTE_VALIDATED", "PREFLIGHT_READY", "EXECUTION_STARTED",
76
+ "VERIFICATION_STARTED", "VERIFICATION_RECORDED", "REVIEW_STARTED", "COMPLETION_VALIDATED",
77
+ "TASK_RECOVERY_RECORDED", "OPERATOR_RECOVERY_RECORDED", "TASK_RECOVERY_RESUMED",
78
+ ].includes(event.event));
79
+ }
80
+
81
+ function deriveEarlyPhase(events, taskId) {
82
+ if (!isInitialTaskLedger(events, taskId)) return null;
83
+ return events.some((event) => event.event === "DISCOVERY_STARTED")
84
+ ? "DISCOVERING"
85
+ : "RECEIVED";
86
+ }
87
+
67
88
  /**
68
89
  * Deterministic classification of a potentially conflicting task from
69
90
  * machine-readable state only. REVIEWING plus an old timestamp alone is never
@@ -97,6 +118,7 @@ export function classifyConflictEvidence(evidence, {
97
118
  totalChecks,
98
119
  verificationEvidenceCount,
99
120
  recoveryStatus,
121
+ earlyPhase,
100
122
  } = evidence;
101
123
 
102
124
  if (!ledgerValid) {
@@ -110,7 +132,8 @@ export function classifyConflictEvidence(evidence, {
110
132
  "RECEIVED", "DISCOVERING", "CONTRACT_READY", "ROUTED", "DESIGNING", "PLANNED",
111
133
  ...POST_EXECUTION_PHASES,
112
134
  ]);
113
- if (!phase || !KNOWN_PHASES.has(phase)) {
135
+ const effectivePhase = phase ?? earlyPhase;
136
+ if (!effectivePhase || !KNOWN_PHASES.has(effectivePhase)) {
114
137
  return {
115
138
  classification: "INCONSISTENT",
116
139
  reasonCodes: ["E_PHASE_UNKNOWN"],
@@ -151,7 +174,7 @@ export function classifyConflictEvidence(evidence, {
151
174
  };
152
175
  }
153
176
 
154
- if (phase === "COMPLETE") {
177
+ if (effectivePhase === "COMPLETE") {
155
178
  return {
156
179
  classification: "COMPLETE",
157
180
  reasonCodes: ["TASK_COMPLETE"],
@@ -175,7 +198,7 @@ export function classifyConflictEvidence(evidence, {
175
198
  };
176
199
  }
177
200
 
178
- if (POST_EXECUTION_PHASES.has(phase)) {
201
+ if (POST_EXECUTION_PHASES.has(effectivePhase)) {
179
202
  if (staleOnlyRepositoryDrift) {
180
203
  return {
181
204
  classification: "RECOVERABLE",
@@ -262,6 +285,9 @@ export async function inspectTaskConflictState(target, {
262
285
  const ledgerErrors = [...ownershipEvidence.ledger.errors, ...coherenceErrors];
263
286
 
264
287
  const repository = await currentRepositoryFingerprint(target);
288
+ const initialTask = !state && ledgerValid && ownershipEvidence.descriptor
289
+ && isInitialTaskLedger(ledgerEvents, taskId)
290
+ && !ownershipEvidence.recovery;
265
291
  const meaningfulActivity = lastMeaningfulActivity(state, ledgerEvents);
266
292
  const recoveryConsistencyErrors = claimProjection.ownershipErrors ?? claimProjection.errors ?? [];
267
293
  const healthReasonCodes = [
@@ -275,7 +301,7 @@ export async function inspectTaskConflictState(target, {
275
301
  healthy: healthReasonCodes.length === 0,
276
302
  healthReasonCodes,
277
303
  descriptorHealthy: true,
278
- phase: state?.phase ?? null,
304
+ phase: state?.phase ?? (initialTask ? deriveEarlyPhase(ledgerEvents, taskId) : null),
279
305
  lastUpdated: state?.lastUpdated ?? null,
280
306
  lastMeaningfulActivityAt: meaningfulActivity?.at ?? null,
281
307
  lastMeaningfulEventType: meaningfulActivity?.type ?? null,
@@ -283,7 +309,7 @@ export async function inspectTaskConflictState(target, {
283
309
  lockStatus: lockClassification.status,
284
310
  lockId: lockInfo?.lockId ?? null,
285
311
  lockExpiresAt: lockClassification.expiresAt ?? null,
286
- freshnessStatus: freshness?.status ?? "UNKNOWN",
312
+ freshnessStatus: freshness?.status ?? (initialTask ? "FRESH" : "UNKNOWN"),
287
313
  freshnessReasons: freshness?.reasons ?? [],
288
314
  ledgerValid,
289
315
  ledgerLastSeq: ledgerEvents.at(-1)?.seq ?? 0,
@@ -27,6 +27,8 @@ export const TASK_ARTIFACT_FILES = Object.freeze({
27
27
  verificationScope: "verification-scope.json",
28
28
  attestations: "attestations",
29
29
  structuralQuality: "structural-quality",
30
+ decisions: "decisions",
31
+ testUtility: "test-utility.json",
30
32
  });
31
33
 
32
34
  export const POLICY_ROOT = ".forgeloop/policy";
@@ -106,6 +108,15 @@ export function taskEvaluationPath(taskId, evaluationId) {
106
108
  return `${taskDirectory(taskId)}/${TASK_ARTIFACT_FILES.evaluations}/${evaluationId}.json`;
107
109
  }
108
110
 
111
+ export function taskDecisionDirectory(taskId) {
112
+ return taskArtifactPath(taskId, "decisions");
113
+ }
114
+
115
+ export function taskDecisionPath(taskId, decisionId) {
116
+ assertArtifactId(decisionId, /^[A-Za-z0-9][A-Za-z0-9_-]{0,127}$/, "decision ID");
117
+ return `${taskDecisionDirectory(taskId)}/${decisionId}.json`;
118
+ }
119
+
109
120
  function assertArtifactId(value, pattern, label) {
110
121
  if (typeof value !== "string" || !pattern.test(value)) {
111
122
  throw new Error(`Invalid ${label}: ${value}`);
@@ -217,6 +228,8 @@ export function buildTaskArtifactPaths(taskId) {
217
228
  actions: `${dir}/${TASK_ARTIFACT_FILES.actions}`,
218
229
  approvals: `${dir}/${TASK_ARTIFACT_FILES.approvals}`,
219
230
  evaluations: `${dir}/${TASK_ARTIFACT_FILES.evaluations}`,
231
+ decisions: `${dir}/${TASK_ARTIFACT_FILES.decisions}`,
232
+ testUtility: `${dir}/${TASK_ARTIFACT_FILES.testUtility}`,
220
233
  usage: `${dir}/${TASK_ARTIFACT_FILES.usage}`,
221
234
  lock: taskLockPath(taskId),
222
235
  policySnapshot: `${dir}/${TASK_ARTIFACT_FILES.policySnapshot}`,
@@ -12,6 +12,7 @@ export const TASK_RECOVERY_SCHEMA_VERSION = 1;
12
12
  export const TASK_RECOVERY_EVENT_TYPES = Object.freeze(new Set([
13
13
  "TASK_RECOVERY_RECORDED",
14
14
  "OPERATOR_RECOVERY_RECORDED",
15
+ "TASK_ABANDONED",
15
16
  LEGACY_RECOVERY_MIGRATION_EVENT,
16
17
  ]));
17
18
 
@@ -88,6 +88,9 @@ export const TEMPLATE_PATHS = [
88
88
  "schemas/execution-profile-benchmark-run.schema.json",
89
89
  "schemas/execution-profile-benchmark-aggregate.schema.json",
90
90
  "schemas/structural-quality.schema.json",
91
+ "schemas/semantic-decision.schema.json",
92
+ "schemas/context-plan.schema.json",
93
+ "schemas/test-utility.schema.json",
91
94
  ];
92
95
 
93
96
  export function getPackageRoot() {
@@ -0,0 +1,68 @@
1
+ import { canonicalFingerprint } from "../artifacts.js";
2
+ import { buildTestUtilityArtifact } from "./utility.js";
3
+
4
+ const FIXTURE_TESTS = Object.freeze([
5
+ { testId: "protected-security", file: "tests/security.test.js", framework: "node:test", suite: "", name: "security", line: 1, signals: { security: true } },
6
+ { testId: "protected-public-api", file: "tests/api.test.js", framework: "node:test", suite: "", name: "api", line: 1, signals: { publicApi: true } },
7
+ { testId: "semantic-duplicate", file: "tests/duplicate.test.js", framework: "node:test", suite: "", name: "duplicate", line: 1, signals: {} },
8
+ { testId: "unique-branch", file: "tests/required.test.js", framework: "node:test", suite: "", name: "required", line: 1, signals: { requiredBehavior: true } },
9
+ { testId: "obsolete-test", file: "tests/obsolete.test.js", framework: "node:test", suite: "", name: "obsolete", line: 1, signals: {} },
10
+ { testId: "slow-required", file: "tests/critical.test.js", framework: "node:test", suite: "", name: "critical", line: 1, signals: { criticalPath: true } },
11
+ { testId: "flaky-low-signal", file: "tests/flaky.test.js", framework: "node:test", suite: "", name: "flaky", line: 1, signals: {} },
12
+ ]);
13
+
14
+ const CASES = Object.freeze([
15
+ { id: "protected-security", expected: "KEEP_RISK_GUARD", protected: true },
16
+ { id: "protected-public-api", expected: "KEEP_UNIQUE", protected: true },
17
+ { id: "semantic-duplicate", expected: "REDUNDANT_CANDIDATE", protected: false },
18
+ { id: "unique-branch", expected: "KEEP_REQUIRED", protected: true },
19
+ { id: "obsolete-test", expected: "OBSOLETE_CANDIDATE", protected: false },
20
+ { id: "slow-required", expected: "KEEP_RECOVERY_INVARIANT", protected: true },
21
+ { id: "flaky-low-signal", expected: "FLAKY_LOW_SIGNAL", protected: false },
22
+ ]);
23
+
24
+ function precision(cases, label) {
25
+ const predicted = cases.filter((item) => item.observed === label);
26
+ if (predicted.length === 0) return null;
27
+ return predicted.filter((item) => item.expected === label).length / predicted.length;
28
+ }
29
+
30
+ function recall(cases, label) {
31
+ const expected = cases.filter((item) => item.expected === label);
32
+ if (expected.length === 0) return null;
33
+ return expected.filter((item) => item.observed === label).length / expected.length;
34
+ }
35
+
36
+ export function runTestIntelligenceBenchmark() {
37
+ const candidateIds = ["semantic-duplicate", "obsolete-test", "flaky-low-signal"];
38
+ const decisions = { candidateIds, tests: {
39
+ "semantic-duplicate": { semantic_duplicate: true },
40
+ "obsolete-test": { likely_obsolete: true },
41
+ "flaky-low-signal": { likely_flaky_low_signal: true },
42
+ } };
43
+ const confidence = Object.fromEntries(
44
+ candidateIds.flatMap((id, index) => (
45
+ Object.keys(decisions.tests[id]).map((dimension) => [`test_${index}_${dimension}`, 0.95])
46
+ )),
47
+ );
48
+ const observedTests = buildTestUtilityArtifact({ taskId: "benchmark", inventory: { schemaVersion: 1, source: "FIXTURE", tests: FIXTURE_TESTS }, semanticDecision: { decisionId: "benchmark-decision", decision: decisions, confidence } }).tests;
49
+ const cases = CASES.map((item) => ({ ...item, observed: observedTests.find((test) => test.testId === item.id)?.classification ?? "UNKNOWN" }));
50
+ const protectedFalsePositives = cases.filter((item) => item.protected && ["REDUNDANT_CANDIDATE", "OBSOLETE_CANDIDATE", "SAFE_TO_REMOVE"].includes(item.observed)).length;
51
+ const summary = {
52
+ schemaVersion: 1,
53
+ status: "OFFLINE_DETERMINISTIC_FIXTURE",
54
+ caseCount: CASES.length,
55
+ metrics: {
56
+ redundantCandidatePrecision: precision(cases, "REDUNDANT_CANDIDATE"),
57
+ redundantCandidateRecall: recall(cases, "REDUNDANT_CANDIDATE"),
58
+ obsoleteCandidatePrecision: precision(cases, "OBSOLETE_CANDIDATE"),
59
+ obsoleteCandidateRecall: recall(cases, "OBSOLETE_CANDIDATE"),
60
+ protectedTestFalsePositiveRate: protectedFalsePositives / CASES.filter((item) => item.protected).length,
61
+ safeToRemoveFalsePositiveRate: precision(cases, "SAFE_TO_REMOVE") === null ? null : 1 - precision(cases, "SAFE_TO_REMOVE"),
62
+ rewriteRecommendationPrecision: null,
63
+ },
64
+ deletionAuthority: false,
65
+ cases,
66
+ };
67
+ return { ...summary, fingerprint: canonicalFingerprint(summary) };
68
+ }