@cassiomc1/forgeloop 1.12.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 (249) hide show
  1. package/.github/copilot-instructions.md +1 -1
  2. package/AGENTS.md +1 -1
  3. package/AGENT_COMPATIBILITY.md +8 -0
  4. package/CLAUDE.md +1 -1
  5. package/CONTRIBUTING.md +90 -0
  6. package/DOCS_INDEX.md +46 -12
  7. package/ENG/c-development-eng.md +112 -0
  8. package/ENG/cpp-development-eng.md +109 -0
  9. package/ENG/dotnet-aspnetcore-development-eng.md +401 -0
  10. package/ENG/go-development-eng.md +103 -0
  11. package/ENG/java-development-eng.md +125 -0
  12. package/ENG/nodejs-backend-development-eng.md +605 -0
  13. package/ENG/php-development-eng.md +104 -0
  14. package/ENG/rust-development-eng.md +422 -0
  15. package/ENG/sec-code-eng.md +7 -7
  16. package/ENG/sql-development-eng.md +108 -0
  17. package/ENG/swift-development-eng.md +111 -0
  18. package/ENG/typescript-development-eng.md +108 -0
  19. package/EXECUTION_STATE.md +12 -0
  20. package/GUIDE_ROUTER.md +418 -9
  21. package/LOOP_ENGINEERING.md +28 -2
  22. package/ORCHESTRATOR_INTEGRATION.md +9 -5
  23. package/PROTOCOL_INTEGRATION.md +55 -2
  24. package/QUALITY_SCORECARD.md +1 -0
  25. package/README.md +78 -52
  26. package/TERMINOLOGY.md +2 -0
  27. package/THIRD_PARTY_NOTICES.md +19 -7
  28. package/THREAT_MODEL.md +140 -1
  29. package/completions/_forgeloop +22 -4
  30. package/completions/forgeloop.bash +40 -4
  31. package/completions/forgeloop.fish +130 -1
  32. package/docs/ADVISORY_CONTEXT.md +25 -0
  33. package/docs/AGENT_BROWSER_ADAPTER.md +81 -0
  34. package/docs/AGENT_BROWSER_VERIFICATION.md +6 -0
  35. package/docs/AGENT_PROTOCOL_SUMMARY.md +81 -3
  36. package/docs/AGENT_SKILL.md +66 -0
  37. package/docs/ARTIFACT_REFERENCE.md +123 -0
  38. package/docs/AUDIT_UX.md +46 -0
  39. package/docs/BROWSER_VERIFICATION.md +136 -0
  40. package/docs/CLI_REFERENCE.md +392 -10
  41. package/docs/CODE_ATTESTATION.md +2 -2
  42. package/docs/DOCUMENTATION_GUIDE.md +34 -12
  43. package/docs/GETTING_STARTED.md +59 -0
  44. package/docs/JEV_BENCHMARKS.md +31 -0
  45. package/docs/MODEL_ROUTING.md +37 -0
  46. package/docs/OPENSRC_ADAPTER.md +241 -0
  47. package/docs/PACKAGE_CONTENTS.md +60 -19
  48. package/docs/PROVIDERS.md +126 -0
  49. package/docs/PROVIDER_ARCHITECTURE.md +199 -0
  50. package/docs/RECIPES.md +32 -0
  51. package/docs/RELEASE_CHECKLIST.md +66 -5
  52. package/docs/SECURITY_REVIEW.md +71 -0
  53. package/docs/SEMANTIC_DECISION_PLANE.md +71 -0
  54. package/docs/TEST_INTELLIGENCE.md +29 -0
  55. package/docs/TEST_PRUNING.md +14 -0
  56. package/docs/TROUBLESHOOTING.md +298 -3
  57. package/docs/UNIVERSAL_INTEGRATION.md +31 -0
  58. package/docs/assets/diagrams/forgeloop-code-attestation-flow.html +2 -2
  59. package/docs/assets/diagrams/forgeloop-code-attestation-flow.receipt.json +5 -5
  60. package/docs/assets/diagrams/forgeloop-code-attestation-flow.svg +1 -1
  61. package/docs/assets/diagrams/forgeloop-engineering-flow.html +39 -26
  62. package/docs/assets/diagrams/forgeloop-engineering-flow.receipt.json +6 -6
  63. package/docs/assets/diagrams/forgeloop-engineering-flow.svg +26 -26
  64. package/docs/assets/diagrams/forgeloop-verification-trust-flow.html +2 -1
  65. package/docs/assets/diagrams/forgeloop-verification-trust-flow.receipt.json +5 -5
  66. package/docs/assets/diagrams/forgeloop-verification-trust-flow.svg +1 -1
  67. package/docs/diagrams/README.md +13 -9
  68. package/docs/diagrams/forgeloop-code-attestation-flow.workflow.json +1 -1
  69. package/docs/diagrams/forgeloop-engineering-flow.workflow.json +24 -19
  70. package/docs/diagrams/forgeloop-verification-trust-flow.workflow.json +1 -0
  71. package/docs/diagrams/reviews/forgeloop-code-attestation-flow.review.json +4 -4
  72. package/docs/diagrams/reviews/forgeloop-engineering-flow.review.json +4 -4
  73. package/docs/diagrams/reviews/forgeloop-verification-trust-flow.review.json +4 -4
  74. package/docs/documentation-manifest.json +1397 -0
  75. package/docs/protocol-requirements.json +101 -0
  76. package/package.json +46 -4
  77. package/schemas/config.schema.json +14 -0
  78. package/schemas/context-plan.schema.json +18 -0
  79. package/schemas/routing-input.schema.json +1 -1
  80. package/schemas/semantic-decision.schema.json +46 -0
  81. package/schemas/test-utility.schema.json +44 -0
  82. package/scripts/CI_VALIDATORS.md +84 -11
  83. package/scripts/benchmark-jev.mjs +5 -0
  84. package/scripts/benchmark-test-intelligence.mjs +4 -0
  85. package/scripts/generate-agent-protocol-summary.mjs +40 -1
  86. package/scripts/generate-forgeloop-skill.mjs +133 -0
  87. package/scripts/jev-smoke.mjs +19 -0
  88. package/skills/forgeloop/README.md +9 -0
  89. package/skills/forgeloop/SKILL.md +77 -0
  90. package/skills/forgeloop/references/lifecycle.md +9 -0
  91. package/skills/forgeloop/references/recovery.md +7 -0
  92. package/skills/forgeloop/references/verification.md +7 -0
  93. package/src/adapters/agent-browser/assertions.js +47 -0
  94. package/src/adapters/agent-browser/commands.js +54 -0
  95. package/src/adapters/agent-browser/index.js +3 -0
  96. package/src/adapters/agent-browser/locator.js +40 -0
  97. package/src/adapters/agent-browser/process.js +215 -0
  98. package/src/adapters/agent-browser/provider.js +313 -0
  99. package/src/adapters/emulated-services/constants.js +24 -0
  100. package/src/adapters/emulated-services/index.js +7 -0
  101. package/src/adapters/emulated-services/process.js +162 -0
  102. package/src/adapters/emulated-services/provider.js +282 -0
  103. package/src/adapters/opensrc/normalize.js +90 -0
  104. package/src/adapters/opensrc/process.js +248 -0
  105. package/src/adapters/opensrc/provider.js +338 -0
  106. package/src/adapters/opensrc/search.js +264 -0
  107. package/src/adapters/typesafe/client.js +28 -0
  108. package/src/adapters/typesafe/engine.js +63 -0
  109. package/src/adapters/typesafe/normalize.js +41 -0
  110. package/src/cli.js +108 -0
  111. package/src/commands/checkpoint-revalidate.js +176 -0
  112. package/src/commands/context-plan.js +38 -0
  113. package/src/commands/contract-create.js +264 -0
  114. package/src/commands/contract-revise.js +236 -0
  115. package/src/commands/decision-show.js +14 -0
  116. package/src/commands/decision-status.js +22 -0
  117. package/src/commands/discover.js +41 -0
  118. package/src/commands/doctor.js +15 -0
  119. package/src/commands/gate-record.js +205 -0
  120. package/src/commands/gate-revalidate.js +137 -0
  121. package/src/commands/model-route.js +32 -0
  122. package/src/commands/next.js +19 -7
  123. package/src/commands/route.js +146 -18
  124. package/src/commands/semantic-plan.js +17 -0
  125. package/src/commands/task-abandon.js +224 -0
  126. package/src/commands/task-create.js +84 -25
  127. package/src/commands/task-list.js +22 -2
  128. package/src/commands/task-migrate-contract-bootstrap-repair.js +288 -0
  129. package/src/commands/task-repair-contract-bootstrap.js +263 -0
  130. package/src/commands/test-inventory.js +5 -0
  131. package/src/commands/test-prune-plan.js +5 -0
  132. package/src/commands/test-prune-probe.js +5 -0
  133. package/src/commands/test-utility.js +5 -0
  134. package/src/commands/validate-protocol.js +10 -1
  135. package/src/config/guides.json +44 -0
  136. package/src/core/artifact-registry.js +24 -0
  137. package/src/core/audit-ux.js +514 -0
  138. package/src/core/browser-verification/constants.js +149 -0
  139. package/src/core/browser-verification/normalize.js +254 -0
  140. package/src/core/browser-verification/provider.js +519 -0
  141. package/src/core/browser-verification/service.js +115 -0
  142. package/src/core/build-script.js +151 -0
  143. package/src/core/c-cpp-project.js +143 -0
  144. package/src/core/checkpoint-revalidation.js +319 -0
  145. package/src/core/cli-command-definitions.js +249 -1
  146. package/src/core/command-executors.js +115 -3
  147. package/src/core/command-input.js +212 -102
  148. package/src/core/completion-artifacts.js +14 -5
  149. package/src/core/completion.js +4 -6
  150. package/src/core/config.js +3 -0
  151. package/src/core/context-compiler/budget.js +9 -0
  152. package/src/core/context-compiler/candidates.js +39 -0
  153. package/src/core/context-compiler/compiler.js +63 -0
  154. package/src/core/context-compiler/fingerprint.js +11 -0
  155. package/src/core/context-compiler/policy.js +13 -0
  156. package/src/core/context-compiler/result.js +23 -0
  157. package/src/core/contract-bootstrap-recovery.js +655 -0
  158. package/src/core/contract-presets.js +82 -0
  159. package/src/core/contract-revision.js +210 -0
  160. package/src/core/decision/artifact.js +69 -0
  161. package/src/core/decision/benchmarks.js +103 -0
  162. package/src/core/decision/cache.js +27 -0
  163. package/src/core/decision/constants.js +58 -0
  164. package/src/core/decision/cutover.js +34 -0
  165. package/src/core/decision/engine.js +22 -0
  166. package/src/core/decision/errors.js +68 -0
  167. package/src/core/decision/events.js +101 -0
  168. package/src/core/decision/freshness.js +19 -0
  169. package/src/core/decision/normalizers/index.js +115 -0
  170. package/src/core/decision/policy.js +18 -0
  171. package/src/core/decision/projection.js +16 -0
  172. package/src/core/decision/question-registry.js +201 -0
  173. package/src/core/decision/request.js +26 -0
  174. package/src/core/decision/resolver.js +130 -0
  175. package/src/core/decision/result.js +58 -0
  176. package/src/core/decision/service.js +156 -0
  177. package/src/core/decision/state-builder.js +65 -0
  178. package/src/core/decision/task-bindings.js +30 -0
  179. package/src/core/decision/test-provider.js +32 -0
  180. package/src/core/decision/thresholds.js +15 -0
  181. package/src/core/error-codes.js +281 -3
  182. package/src/core/events.js +226 -57
  183. package/src/core/evidence-readiness.js +9 -0
  184. package/src/core/execution-prerequisites.js +14 -0
  185. package/src/core/execution-profile.js +63 -38
  186. package/src/core/filesystem.js +1 -10
  187. package/src/core/gate-provenance.js +124 -0
  188. package/src/core/go-project.js +206 -0
  189. package/src/core/integration-invocation-policy.js +27 -4
  190. package/src/core/integration-resources.js +86 -61
  191. package/src/core/java-project.js +403 -0
  192. package/src/core/model-router/constants.js +10 -0
  193. package/src/core/model-router/policy.js +103 -0
  194. package/src/core/model-router/router.js +37 -0
  195. package/src/core/multi-language-project.js +117 -0
  196. package/src/core/next-action-model.js +58 -0
  197. package/src/core/next-action-phases.js +130 -42
  198. package/src/core/next-action-refresh.js +43 -9
  199. package/src/core/next-action-review-phase.js +7 -2
  200. package/src/core/next-action.js +35 -7
  201. package/src/core/next-explanation.js +63 -0
  202. package/src/core/phase.js +128 -10
  203. package/src/core/php-project.js +85 -0
  204. package/src/core/preflight-consistency.js +23 -9
  205. package/src/core/preflight-loaders.js +37 -5
  206. package/src/core/project-detection.js +1760 -52
  207. package/src/core/protocol-info.js +65 -0
  208. package/src/core/protocol.js +20 -0
  209. package/src/core/reconcile-closure.js +132 -53
  210. package/src/core/recovery-history.js +1 -0
  211. package/src/core/resumability.js +154 -44
  212. package/src/core/route-artifact.js +15 -1
  213. package/src/core/router.js +223 -4
  214. package/src/core/runtime-context.js +118 -61
  215. package/src/core/rust-project.js +400 -0
  216. package/src/core/schema-validation.js +3 -0
  217. package/src/core/security-review/constants.js +64 -0
  218. package/src/core/security-review/normalize.js +245 -0
  219. package/src/core/security-review/provider.js +204 -0
  220. package/src/core/security-review/service.js +134 -0
  221. package/src/core/semantic-planning/constants.js +19 -0
  222. package/src/core/semantic-planning/projection.js +94 -0
  223. package/src/core/semantic-planning/service.js +15 -0
  224. package/src/core/sources.js +37 -0
  225. package/src/core/sql-project.js +141 -0
  226. package/src/core/swift-project.js +200 -0
  227. package/src/core/task-claim-state.js +201 -1
  228. package/src/core/task-conflict-inspection.js +31 -5
  229. package/src/core/task-paths.js +13 -0
  230. package/src/core/task-recovery.js +1 -0
  231. package/src/core/templates.js +3 -0
  232. package/src/core/test-intelligence/benchmarks.js +68 -0
  233. package/src/core/test-intelligence/inventory.js +73 -0
  234. package/src/core/test-intelligence/prune.js +90 -0
  235. package/src/core/test-intelligence/semantic-state.js +15 -0
  236. package/src/core/test-intelligence/service.js +40 -0
  237. package/src/core/test-intelligence/utility.js +50 -0
  238. package/src/core/trace.js +11 -7
  239. package/src/core/transaction.js +1 -0
  240. package/src/core/typescript-project.js +349 -0
  241. package/src/core/xml-structure.js +123 -0
  242. package/src/integration.d.ts +492 -0
  243. package/src/integration.js +54 -0
  244. package/src/providers/README.md +47 -0
  245. package/src/providers/capabilities.js +46 -0
  246. package/src/providers/errors.js +15 -0
  247. package/src/providers/index.js +29 -0
  248. package/src/providers/json-snapshot.js +105 -0
  249. package/src/providers/registry.js +152 -0
@@ -0,0 +1,82 @@
1
+ import { createContract } from "./contract.js";
2
+
3
+ const PRESET_DEFINITIONS = Object.freeze({
4
+ documentation: Object.freeze({
5
+ objective: "Complete the requested documentation change with verified project facts.",
6
+ constraints: ["Preserve repository-local instructions and existing public documentation contracts."],
7
+ risks: [],
8
+ verification: [{ id: "verify-documentation", text: "Relevant documentation checks pass.", type: "VERIFICATION" }],
9
+ successCriteria: [{ id: "complete-documentation", text: "The requested documentation change is complete and accurate.", type: "PRODUCT" }],
10
+ stopConditions: ["A required project fact or source cannot be confirmed."],
11
+ }),
12
+ bug: Object.freeze({
13
+ objective: "Diagnose and correct the requested bug without changing unrelated behavior.",
14
+ constraints: ["Preserve the existing public contract unless a breaking change is explicitly requested."],
15
+ risks: [],
16
+ verification: [{ id: "verify-bug-fix", text: "A focused regression check and proportional regression checks pass.", type: "VERIFICATION" }],
17
+ successCriteria: [{ id: "complete-bug-fix", text: "The reported behavior is corrected and the regression is covered.", type: "PRODUCT" }],
18
+ stopConditions: ["The failure cannot be reproduced or narrowed with available evidence."],
19
+ }),
20
+ feature: Object.freeze({
21
+ objective: "Implement the requested feature within the confirmed project scope.",
22
+ constraints: ["Preserve existing architecture and public contracts unless a change is explicitly requested."],
23
+ risks: [],
24
+ verification: [{ id: "verify-feature", text: "Focused behavior checks and proportional regression checks pass.", type: "VERIFICATION" }],
25
+ successCriteria: [{ id: "complete-feature", text: "The requested feature behavior is implemented and documented where applicable.", type: "PRODUCT" }],
26
+ stopConditions: ["Required product behavior or acceptance facts remain unresolved."],
27
+ }),
28
+ release: Object.freeze({
29
+ objective: "Prepare the requested release with independently verified artifact and publication evidence.",
30
+ constraints: [
31
+ "Do not publish, deploy, or mutate external release state without explicit authority.",
32
+ "Separate local checks, repository integration, publication, and deployment evidence.",
33
+ ],
34
+ risks: ["publication"],
35
+ verification: [{ id: "verify-release", text: "Release identity, package/artifact integrity, and required checks are independently verified.", type: "VERIFICATION" }],
36
+ successCriteria: [{
37
+ id: "publish-release",
38
+ text: "The requested release publication status is independently verified.",
39
+ type: "PUBLICATION",
40
+ requiredPublicationStatus: "published",
41
+ }],
42
+ stopConditions: ["Publication authority, target, or identity evidence is not available."],
43
+ }),
44
+ });
45
+
46
+ export const CONTRACT_PRESET_IDS = Object.freeze(Object.keys(PRESET_DEFINITIONS));
47
+
48
+ function normalizedClaims(claims) {
49
+ return [...new Set((Array.isArray(claims) ? claims : [])
50
+ .filter((claim) => typeof claim === "string" && claim.trim() !== "")
51
+ .map((claim) => claim.trim()))];
52
+ }
53
+
54
+ export function createPresetContract({ taskId, preset, claims = [] } = {}) {
55
+ if (!Object.prototype.hasOwnProperty.call(PRESET_DEFINITIONS, preset)) {
56
+ const error = new Error(`Unknown contract preset: ${preset}. Expected one of ${CONTRACT_PRESET_IDS.join(", ")}`);
57
+ error.code = "E_CONTRACT_PRESET_UNKNOWN";
58
+ throw error;
59
+ }
60
+ const definition = PRESET_DEFINITIONS[preset];
61
+ const confirmedClaims = normalizedClaims(claims);
62
+ const unresolvedDecisions = confirmedClaims.length > 0
63
+ ? []
64
+ : ["Confirm the concrete deliverables and affected behavior before implementation."];
65
+ return createContract({
66
+ taskId,
67
+ objective: definition.objective,
68
+ deliverables: confirmedClaims.length > 0 ? confirmedClaims : ["Deliverables confirmed by task scope."],
69
+ constraints: definition.constraints,
70
+ risks: definition.risks,
71
+ verification: definition.verification,
72
+ successCriteria: definition.successCriteria,
73
+ stopConditions: definition.stopConditions,
74
+ unresolvedDecisions,
75
+ sourceRefs: [`contract-preset:${preset}`],
76
+ });
77
+ }
78
+
79
+ export function contractPresetDefinition(preset) {
80
+ if (!Object.prototype.hasOwnProperty.call(PRESET_DEFINITIONS, preset)) return null;
81
+ return structuredClone(PRESET_DEFINITIONS[preset]);
82
+ }
@@ -0,0 +1,210 @@
1
+ import { canonicalFingerprint } from "./artifacts.js";
2
+
3
+ export const CONTRACT_REVISED_EVENT = "CONTRACT_REVISED";
4
+
5
+ const FINGERPRINT = /^[a-f0-9]{64}$/;
6
+ const REVISION_PHASES = new Set(["CONTRACT_READY", "ROUTED", "PLANNED"]);
7
+ const REVISION_DETAIL_KEYS = Object.freeze([
8
+ "previousContractFingerprint",
9
+ "contractFingerprint",
10
+ "previousPhase",
11
+ "phase",
12
+ "previousStateRevision",
13
+ "revisedStateRevision",
14
+ "previousStateFingerprint",
15
+ "revisedStateFingerprint",
16
+ "previousRouteFingerprint",
17
+ ]);
18
+
19
+ function invalid(message) {
20
+ const error = new Error(message);
21
+ error.code = "E_CONTRACT_REVISION_UNSAFE";
22
+ return error;
23
+ }
24
+
25
+ export function isFingerprint(value) {
26
+ return typeof value === "string" && FINGERPRINT.test(value);
27
+ }
28
+
29
+ export function assertContractRevisedDetails(details) {
30
+ if (!details || typeof details !== "object" || Array.isArray(details)
31
+ || Object.keys(details).length !== REVISION_DETAIL_KEYS.length
32
+ || REVISION_DETAIL_KEYS.some((key) => !Object.prototype.hasOwnProperty.call(details, key))) {
33
+ throw invalid(`${CONTRACT_REVISED_EVENT} requires its exact contract-transition detail set`);
34
+ }
35
+ for (const key of ["previousContractFingerprint", "contractFingerprint", "previousStateFingerprint", "revisedStateFingerprint"]) {
36
+ if (!isFingerprint(details[key])) throw invalid(`${CONTRACT_REVISED_EVENT} details.${key} must be a lowercase SHA-256 fingerprint`);
37
+ }
38
+ if (details.previousContractFingerprint === details.contractFingerprint) {
39
+ throw invalid(`${CONTRACT_REVISED_EVENT} must change the contract fingerprint`);
40
+ }
41
+ if (!REVISION_PHASES.has(details.previousPhase) || !REVISION_PHASES.has(details.phase)) {
42
+ throw invalid(`${CONTRACT_REVISED_EVENT} details phase is unsupported`);
43
+ }
44
+ const expectedPhase = details.previousPhase === "PLANNED" ? "ROUTED" : details.previousPhase;
45
+ if (details.phase !== expectedPhase) {
46
+ throw invalid(`${CONTRACT_REVISED_EVENT} phase transition is invalid: ${details.previousPhase} -> ${details.phase}`);
47
+ }
48
+ for (const key of ["previousStateRevision", "revisedStateRevision"]) {
49
+ if (!Number.isInteger(details[key]) || details[key] < 0) {
50
+ throw invalid(`${CONTRACT_REVISED_EVENT} details.${key} must be a non-negative integer`);
51
+ }
52
+ }
53
+ if (details.revisedStateRevision !== details.previousStateRevision + 1) {
54
+ throw invalid(`${CONTRACT_REVISED_EVENT} state revisions must advance exactly once`);
55
+ }
56
+ if (details.previousRouteFingerprint !== null && !isFingerprint(details.previousRouteFingerprint)) {
57
+ throw invalid(`${CONTRACT_REVISED_EVENT} details.previousRouteFingerprint must be a fingerprint or null`);
58
+ }
59
+ if (details.previousPhase === "CONTRACT_READY" && details.previousRouteFingerprint !== null) {
60
+ throw invalid(`${CONTRACT_REVISED_EVENT} CONTRACT_READY revisions cannot have a route fingerprint`);
61
+ }
62
+ if (details.previousPhase !== "CONTRACT_READY" && !isFingerprint(details.previousRouteFingerprint)) {
63
+ throw invalid(`${CONTRACT_REVISED_EVENT} routed revisions require the previous route fingerprint`);
64
+ }
65
+ return details;
66
+ }
67
+
68
+ function eventHash(event) {
69
+ const { hash, ...body } = event ?? {};
70
+ return canonicalFingerprint(body);
71
+ }
72
+
73
+ function validRevisionCommit(revisionEvent, commitEvent) {
74
+ return commitEvent?.event === "TRANSACTION_COMMITTED"
75
+ && commitEvent.taskId === revisionEvent.taskId
76
+ && commitEvent.seq === revisionEvent.seq + 1
77
+ && commitEvent.previousHash === revisionEvent.hash
78
+ && revisionEvent.hash === eventHash(revisionEvent)
79
+ && commitEvent.hash === eventHash(commitEvent)
80
+ && commitEvent.details
81
+ && typeof commitEvent.details === "object"
82
+ && !Array.isArray(commitEvent.details)
83
+ && Object.keys(commitEvent.details).length === 2
84
+ && typeof commitEvent.details.transactionId === "string"
85
+ && commitEvent.details.transactionId.length > 0
86
+ && commitEvent.details.operation === "contract-revise";
87
+ }
88
+
89
+ export function resolveContractRevisionBoundary(events, revisionEvent) {
90
+ if (!Array.isArray(events) || !revisionEvent || revisionEvent.event !== CONTRACT_REVISED_EVENT) return null;
91
+ const index = events.findIndex((event) => event === revisionEvent
92
+ || (event?.seq === revisionEvent.seq
93
+ && event?.taskId === revisionEvent.taskId
94
+ && event?.event === CONTRACT_REVISED_EVENT));
95
+ if (index < 0) return null;
96
+ const commitEvent = events[index + 1];
97
+ return validRevisionCommit(revisionEvent, commitEvent)
98
+ ? { revisionEvent, transactionCommitEvent: commitEvent }
99
+ : null;
100
+ }
101
+
102
+ export function resolveCanonicalContractEvolution(events, {
103
+ taskId,
104
+ sourceSeq = 0,
105
+ sourceContractFingerprint,
106
+ targetContractFingerprint,
107
+ } = {}) {
108
+ if (!Array.isArray(events)
109
+ || typeof taskId !== "string" || !taskId
110
+ || !Number.isInteger(sourceSeq) || sourceSeq < 0
111
+ || !isFingerprint(sourceContractFingerprint)
112
+ || !isFingerprint(targetContractFingerprint)) return null;
113
+
114
+ let currentFingerprint = sourceContractFingerprint;
115
+ const revisionEvents = [];
116
+ for (const event of events) {
117
+ if (event?.seq <= sourceSeq || event?.event !== CONTRACT_REVISED_EVENT) continue;
118
+ if (event.taskId !== taskId) return null;
119
+ try {
120
+ assertContractRevisedDetails(event.details);
121
+ } catch {
122
+ return null;
123
+ }
124
+ if (!resolveContractRevisionBoundary(events, event)
125
+ || event.details.previousContractFingerprint !== currentFingerprint) {
126
+ return null;
127
+ }
128
+ const executionStarted = events.some((candidate) => candidate.taskId === taskId
129
+ && candidate.event === "EXECUTION_STARTED"
130
+ && candidate.seq < event.seq);
131
+ if (executionStarted) return null;
132
+ revisionEvents.push(event);
133
+ currentFingerprint = event.details.contractFingerprint;
134
+ }
135
+ return currentFingerprint === targetContractFingerprint
136
+ ? { valid: true, contractFingerprint: currentFingerprint, revisionEvents }
137
+ : null;
138
+ }
139
+
140
+ export function latestContractRevision(events = []) {
141
+ return events.filter((event) => event.event === CONTRACT_REVISED_EVENT).at(-1) ?? null;
142
+ }
143
+
144
+ export function validateContractRevisionEventBindings(events = []) {
145
+ const errors = [];
146
+ const revisions = events.filter((event) => event.event === CONTRACT_REVISED_EVENT);
147
+ let currentContractFingerprint = null;
148
+ let currentStateRevision = null;
149
+ for (const event of revisions) {
150
+ let details;
151
+ try {
152
+ details = assertContractRevisedDetails(event.details);
153
+ } catch (error) {
154
+ errors.push({ code: error.code ?? "E_CONTRACT_REVISION_UNSAFE", message: `event ${event.seq}: ${error.message}` });
155
+ continue;
156
+ }
157
+ if (!resolveContractRevisionBoundary(events, event)) {
158
+ errors.push({ code: "E_CONTRACT_REVISION_UNSAFE", message: `event ${event.seq} must be immediately followed by its contract-revise transaction commit` });
159
+ }
160
+ if (events.some((candidate) => candidate.taskId === event.taskId
161
+ && candidate.event === "EXECUTION_STARTED"
162
+ && candidate.seq < event.seq)) {
163
+ errors.push({ code: "E_CONTRACT_REVISION_UNSAFE", message: `event ${event.seq} occurs after execution started` });
164
+ }
165
+ if (currentContractFingerprint === null) {
166
+ const initial = events.find((candidate) => candidate.taskId === event.taskId
167
+ && candidate.event === "CONTRACT_VALIDATED"
168
+ && candidate.seq < event.seq);
169
+ const initialFingerprint = initial?.fingerprint ?? initial?.details?.contractFingerprint;
170
+ if (!initial || initialFingerprint !== details.previousContractFingerprint) {
171
+ errors.push({ code: "E_CONTRACT_REVISION_UNSAFE", message: `event ${event.seq} does not continue the initial contract identity` });
172
+ }
173
+ } else if (details.previousContractFingerprint !== currentContractFingerprint) {
174
+ errors.push({ code: "E_CONTRACT_REVISION_UNSAFE", message: `event ${event.seq} is disconnected from the previous contract revision` });
175
+ }
176
+ if (currentStateRevision !== null && details.previousStateRevision < currentStateRevision) {
177
+ errors.push({ code: "E_CONTRACT_REVISION_UNSAFE", message: `event ${event.seq} rolls back the contract revision state` });
178
+ }
179
+ currentContractFingerprint = details.contractFingerprint;
180
+ currentStateRevision = details.revisedStateRevision;
181
+ }
182
+ return errors;
183
+ }
184
+
185
+ export function validateContractRevisionCurrentBinding(state, events = []) {
186
+ const errors = [];
187
+ const revisions = events.filter((event) => event.event === CONTRACT_REVISED_EVENT);
188
+ if (revisions.length === 0) return errors;
189
+ const first = revisions[0];
190
+ const latest = revisions.at(-1);
191
+ const initial = events.find((event) => event.taskId === state?.taskId && event.event === "CONTRACT_VALIDATED");
192
+ const evolution = resolveCanonicalContractEvolution(events, {
193
+ taskId: state?.taskId,
194
+ sourceSeq: initial?.seq ?? 0,
195
+ sourceContractFingerprint: initial?.fingerprint ?? first.details?.previousContractFingerprint,
196
+ targetContractFingerprint: state?.contractFingerprint,
197
+ });
198
+ if (!evolution) {
199
+ errors.push({ code: "E_CONTRACT_REVISION_UNSAFE", message: "current contract identity is not the final canonical contract revision target" });
200
+ }
201
+ if (Number.isInteger(state?.revision) && state.revision < latest.details.revisedStateRevision) {
202
+ errors.push({ code: "E_CONTRACT_REVISION_UNSAFE", message: `current state revision precedes contract revision event ${latest.seq}` });
203
+ }
204
+ if (state?.revision === latest.details.revisedStateRevision
205
+ && (state.phase !== latest.details.phase
206
+ || canonicalFingerprint(state) !== latest.details.revisedStateFingerprint)) {
207
+ errors.push({ code: "E_CONTRACT_REVISION_UNSAFE", message: `event ${latest.seq} does not bind the current revised state` });
208
+ }
209
+ return errors;
210
+ }
@@ -0,0 +1,69 @@
1
+ import { canonicalFingerprint, readJsonArtifact, writeJsonArtifact } from "../artifacts.js";
2
+ import { ensureWithin, fileExists } from "../filesystem.js";
3
+ import { taskDecisionPath } from "../task-paths.js";
4
+ import { assertSecretFree } from "../receipt.js";
5
+ import { DECISION_AUTHORITY, DECISION_EVIDENCE_AUTHORITY, DECISION_POLICY_VERSION, PINNED_JEV_MODEL } from "./constants.js";
6
+ import { DECISION_ERROR_CODES, decisionError } from "./errors.js";
7
+ import { getTaskTransaction, withTaskTransaction } from "../transaction.js";
8
+
9
+ export function buildDecisionArtifact(input = {}) {
10
+ const artifact = {
11
+ schemaVersion: 1,
12
+ protocolVersion: 1,
13
+ taskId: input.taskId,
14
+ decisionId: input.decisionId,
15
+ decisionKind: input.decisionKind,
16
+ engine: "typesafe-jev",
17
+ model: input.model ?? PINNED_JEV_MODEL,
18
+ questionSetId: input.questionSetId,
19
+ questionSetVersion: input.questionSetVersion ?? 1,
20
+ questionSetFingerprint: input.questionSetFingerprint,
21
+ ...(input.questionSet ? { questionSet: input.questionSet } : {}),
22
+ policyVersion: input.policyVersion ?? DECISION_POLICY_VERSION,
23
+ stateFingerprint: input.stateFingerprint,
24
+ taskStateFingerprint: input.taskStateFingerprint ?? null,
25
+ semanticStateFingerprint: input.semanticStateFingerprint ?? null,
26
+ policyFingerprint: input.policyFingerprint,
27
+ repositoryFingerprint: input.repositoryFingerprint ?? null,
28
+ contractFingerprint: input.contractFingerprint ?? null,
29
+ routeFingerprint: input.routeFingerprint ?? null,
30
+ verificationCycle: input.verificationCycle ?? null,
31
+ candidateSetFingerprint: input.candidateSetFingerprint ?? null,
32
+ answers: input.answers ?? {},
33
+ confidence: input.confidence ?? {},
34
+ decision: input.decision ?? {},
35
+ usage: input.usage ?? { inputTokens: null, outputTokens: null, reportedBy: "UNKNOWN" },
36
+ latencyMs: input.latencyMs ?? null,
37
+ authority: DECISION_AUTHORITY,
38
+ evidenceAuthority: DECISION_EVIDENCE_AUTHORITY,
39
+ lifecycleAuthority: false,
40
+ completionAuthority: false,
41
+ ownershipAuthority: false,
42
+ installationAuthority: false,
43
+ recordedAt: input.recordedAt ?? new Date().toISOString(),
44
+ };
45
+ assertSecretFree(artifact);
46
+ if (artifact.model !== PINNED_JEV_MODEL || artifact.policyVersion === 0) throw decisionError(DECISION_ERROR_CODES.RESULT_INVALID, "Semantic decision artifact is not pinned.");
47
+ return artifact;
48
+ }
49
+
50
+ export async function readDecisionArtifact(target, taskId, decisionId, packageRoot) {
51
+ return readJsonArtifact(target, taskDecisionPath(taskId, decisionId), "semantic-decision", packageRoot);
52
+ }
53
+
54
+ export async function writeDecisionArtifact(target, taskId, decisionId, artifact, packageRoot, options = {}) {
55
+ if (!(await getTaskTransaction(target))) {
56
+ return withTaskTransaction({ target, taskId, packageRoot, operation: "semantic-decision" }, async () => (
57
+ writeDecisionArtifact(target, taskId, decisionId, artifact, packageRoot, options)
58
+ ));
59
+ }
60
+ const relativePath = taskDecisionPath(taskId, decisionId);
61
+ if (await fileExists(ensureWithin(target, relativePath))) {
62
+ throw decisionError(DECISION_ERROR_CODES.IMMUTABLE, `Semantic decision artifact already exists: ${decisionId}`);
63
+ }
64
+ return writeJsonArtifact(target, relativePath, artifact, "semantic-decision", packageRoot, { ...options, taskId, operation: "semantic-decision" });
65
+ }
66
+
67
+ export function decisionArtifactFingerprint(artifact) {
68
+ return canonicalFingerprint(artifact);
69
+ }
@@ -0,0 +1,103 @@
1
+ import { canonicalFingerprint } from "../artifacts.js";
2
+ import { projectModelRoute } from "../model-router/router.js";
3
+ import { createTypesafeEngine } from "../../adapters/typesafe/engine.js";
4
+ import { getQuestionSet } from "./question-registry.js";
5
+ import { DECISION_DEFAULT_POLICY } from "./constants.js";
6
+
7
+ const SCENARIOS = Object.freeze([
8
+ { id: "docs-only", input: { workType: "documentation" } },
9
+ { id: "ordinary-code", input: { workType: "code", executableChange: true } },
10
+ { id: "security-code", input: { workType: "code", risks: ["secrets"], executableChange: true } },
11
+ ]);
12
+
13
+ export function runJevBenchmark() {
14
+ const scenarios = SCENARIOS.map((scenario) => {
15
+ const route = projectModelRoute({ input: scenario.input });
16
+ return {
17
+ id: scenario.id,
18
+ floor: route.floor,
19
+ resolved: route.resolved,
20
+ jevCalls: 0,
21
+ jevInputTokens: null,
22
+ jevLatencyMs: null,
23
+ jevCacheHits: 0,
24
+ jevLowConfidenceEscalations: 0,
25
+ mainModelCalls: null,
26
+ mainModelInputTokens: null,
27
+ mainModelOutputTokens: null,
28
+ candidateContextItems: null,
29
+ selectedContextItems: null,
30
+ candidateChars: null,
31
+ selectedChars: null,
32
+ guideSectionsConsidered: null,
33
+ guideSectionsLoaded: null,
34
+ filesRead: null,
35
+ repositorySearches: null,
36
+ contextRefreshes: null,
37
+ modelTurns: null,
38
+ toolCalls: null,
39
+ wallClockMs: null,
40
+ verification: "NOT_MEASURED",
41
+ quality: "NOT_MEASURED",
42
+ fallback: true,
43
+ };
44
+ });
45
+ const summary = {
46
+ schemaVersion: 1,
47
+ engine: "typesafe-jev",
48
+ model: "jev-1.13.0",
49
+ status: "OFFLINE_DETERMINISTIC_BASELINE",
50
+ scenarios,
51
+ metrics: {
52
+ scenarioCount: scenarios.length,
53
+ jevCalls: 0,
54
+ jevInputTokens: null,
55
+ jevLatencyMs: null,
56
+ jevCacheHits: 0,
57
+ jevLowConfidenceEscalations: 0,
58
+ mainModelCalls: null,
59
+ mainModelInputTokens: null,
60
+ mainModelOutputTokens: null,
61
+ candidateContextItems: null,
62
+ selectedContextItems: null,
63
+ candidateChars: null,
64
+ selectedChars: null,
65
+ guideSectionsConsidered: null,
66
+ guideSectionsLoaded: null,
67
+ filesRead: null,
68
+ repositorySearches: null,
69
+ contextRefreshes: null,
70
+ modelTurns: null,
71
+ toolCalls: null,
72
+ wallClockMs: null,
73
+ verification: "NOT_MEASURED",
74
+ quality: "NOT_MEASURED",
75
+ inputTokens: null,
76
+ outputTokens: null,
77
+ tokenReduction: null,
78
+ },
79
+ };
80
+ return { ...summary, fingerprint: canonicalFingerprint(summary) };
81
+ }
82
+
83
+ export async function runJevBenchmarkLive({ engine = null, policy = DECISION_DEFAULT_POLICY } = {}) {
84
+ const resolvedEngine = engine ?? createTypesafeEngine({ policy });
85
+ const started = Date.now();
86
+ const scenarios = [];
87
+ for (const scenario of SCENARIOS) {
88
+ const questionSet = getQuestionSet(scenario.id === "docs-only" ? "intake-v1" : scenario.id === "security-code" ? "route-v1" : "context-v1");
89
+ const result = await resolvedEngine.evaluate({
90
+ taskId: `benchmark-${scenario.id}`,
91
+ decisionKind: questionSet.decisionKind,
92
+ questionSetId: questionSet.id,
93
+ questionSet,
94
+ state: { benchmark: true, input: scenario.input },
95
+ });
96
+ scenarios.push({ id: scenario.id, decisionKind: questionSet.decisionKind, jevCalls: 1, usage: result.usage ?? null, latencyMs: result.latencyMs ?? null, status: "PROVIDER_REPORTED" });
97
+ }
98
+ const summary = {
99
+ schemaVersion: 1, engine: "typesafe-jev", model: "jev-1.13.0", status: "LIVE_PROVIDER_REPORTED", scenarios,
100
+ metrics: { scenarioCount: scenarios.length, jevCalls: scenarios.length, inputTokens: scenarios.reduce((sum, item) => sum + (item.usage?.inputTokens ?? 0), 0), outputTokens: scenarios.reduce((sum, item) => sum + (item.usage?.outputTokens ?? 0), 0), wallClockMs: Date.now() - started, verification: "NOT_MEASURED", quality: "NOT_MEASURED" },
101
+ };
102
+ return { ...summary, fingerprint: canonicalFingerprint(summary) };
103
+ }
@@ -0,0 +1,27 @@
1
+ import { canonicalFingerprint } from "../artifacts.js";
2
+ import { DECISION_ERROR_CODES, decisionError } from "./errors.js";
3
+
4
+ export function decisionCacheKey(input = {}) {
5
+ return canonicalFingerprint({
6
+ engine: input.engine,
7
+ model: input.model,
8
+ questionSetFingerprint: input.questionSetFingerprint,
9
+ stateFingerprint: input.stateFingerprint,
10
+ policyFingerprint: input.policyFingerprint,
11
+ candidateSetFingerprint: input.candidateSetFingerprint ?? null,
12
+ });
13
+ }
14
+
15
+ export function createDecisionCache() {
16
+ const values = new Map();
17
+ return Object.freeze({
18
+ get(input) { return values.get(decisionCacheKey(input)) ?? null; },
19
+ set(input, value) {
20
+ if (!value || typeof value !== "object") throw decisionError(DECISION_ERROR_CODES.CACHE_INVALID, "Only structured semantic decisions may be cached.");
21
+ const key = decisionCacheKey(input);
22
+ values.set(key, Object.freeze({ key, value: structuredClone(value) }));
23
+ return key;
24
+ },
25
+ size() { return values.size; },
26
+ });
27
+ }
@@ -0,0 +1,58 @@
1
+ export const DECISION_ENGINE_ID = "typesafe-jev";
2
+ export const PINNED_JEV_MODEL = "jev-1.13.0";
3
+ export const DECISION_POLICY_VERSION = 1;
4
+ export const DECISION_AUTHORITY = "SEMANTIC_DECISION";
5
+ export const DECISION_EVIDENCE_AUTHORITY = "NONE";
6
+
7
+ export const DECISION_KINDS = Object.freeze([
8
+ "INTAKE",
9
+ "CONTRACT_APPLICABILITY",
10
+ "ROUTE",
11
+ "EXECUTION_PROFILE",
12
+ "CONTEXT_PLAN",
13
+ "MODEL_ROUTE",
14
+ "FAILURE_TRIAGE",
15
+ "DIAGNOSIS_PRIORITY",
16
+ "REVIEW_PLAN",
17
+ "TASK_OVERLAP",
18
+ "TEST_UTILITY",
19
+ "TEST_PRUNE",
20
+ ]);
21
+
22
+ export const DECISION_STATUS = Object.freeze(["RECORDED", "SUPERSEDED"]);
23
+
24
+ export const DECISION_DEFAULT_POLICY = Object.freeze({
25
+ required: true,
26
+ provider: DECISION_ENGINE_ID,
27
+ model: PINNED_JEV_MODEL,
28
+ policyVersion: DECISION_POLICY_VERSION,
29
+ requestTimeoutMs: 8_000,
30
+ maxRetries: 1,
31
+ cache: true,
32
+ });
33
+
34
+ export const DECISION_LIMITS = Object.freeze({
35
+ maxStateBytes: 48_000,
36
+ maxStringChars: 8_000,
37
+ maxItems: 128,
38
+ maxDepth: 8,
39
+ maxCandidates: 64,
40
+ });
41
+
42
+ export const DECISION_QUESTION_SETS = Object.freeze({
43
+ "intake-v1": Object.freeze({ id: "intake-v1", version: 1, decisionKind: "INTAKE" }),
44
+ "contract-v1": Object.freeze({ id: "contract-v1", version: 1, decisionKind: "CONTRACT_APPLICABILITY" }),
45
+ "route-v1": Object.freeze({ id: "route-v1", version: 1, decisionKind: "ROUTE" }),
46
+ "execution-profile-v1": Object.freeze({ id: "execution-profile-v1", version: 1, decisionKind: "EXECUTION_PROFILE" }),
47
+ "context-v1": Object.freeze({ id: "context-v1", version: 1, decisionKind: "CONTEXT_PLAN" }),
48
+ "model-route-v1": Object.freeze({ id: "model-route-v1", version: 1, decisionKind: "MODEL_ROUTE" }),
49
+ "failure-v1": Object.freeze({ id: "failure-v1", version: 1, decisionKind: "FAILURE_TRIAGE" }),
50
+ "diagnosis-v1": Object.freeze({ id: "diagnosis-v1", version: 1, decisionKind: "DIAGNOSIS_PRIORITY" }),
51
+ "review-v1": Object.freeze({ id: "review-v1", version: 1, decisionKind: "REVIEW_PLAN" }),
52
+ "task-overlap-v1": Object.freeze({ id: "task-overlap-v1", version: 1, decisionKind: "TASK_OVERLAP" }),
53
+ "test-utility-v1": Object.freeze({ id: "test-utility-v1", version: 1, decisionKind: "TEST_UTILITY" }),
54
+ "test-prune-v1": Object.freeze({ id: "test-prune-v1", version: 1, decisionKind: "TEST_PRUNE" }),
55
+ });
56
+
57
+ export const SEMANTIC_DECISION_RECORDED_EVENT = "SEMANTIC_DECISION_RECORDED";
58
+ export const SEMANTIC_DECISION_SUPERSEDED_EVENT = "SEMANTIC_DECISION_SUPERSEDED";
@@ -0,0 +1,34 @@
1
+ import { DECISION_ERROR_CODES, decisionError } from "./errors.js";
2
+ import { assertRequiredFreshDecision, resolveRequiredSemanticDecision } from "./resolver.js";
3
+
4
+ export const DECISION_CUTOVER_MODE = "FAIL_CLOSED";
5
+ export const OFFLINE_SAFE_OPERATIONS = Object.freeze([
6
+ "protocol-info", "doctor", "status", "task-show", "task-list", "history", "trace", "audit",
7
+ "validate-state", "validate-protocol", "inspect", "task-recover", "task-abandon", "complete",
8
+ ]);
9
+
10
+ export function assertRequiredDecision({ decision = null, artifact = null, operation = "semantic-decision", currentBindings = {}, expectedDecisionKind, expectedTaskId, expectedQuestionSetId, ledger = [] } = {}) {
11
+ const candidate = artifact ?? decision;
12
+ if (!candidate || typeof candidate !== "object" || Array.isArray(candidate)) {
13
+ throw decisionError(DECISION_ERROR_CODES.REQUIRED, `A fresh ForgeLoop semantic decision is required for ${operation}.`);
14
+ }
15
+ if (candidate.model !== "jev-1.13.0" || candidate.engine !== "typesafe-jev") {
16
+ throw decisionError(DECISION_ERROR_CODES.MODEL_UNSUPPORTED, "Semantic decision is not bound to the pinned ForgeLoop Jev model.");
17
+ }
18
+ if (!candidate.taskId || !candidate.decisionId || !candidate.questionSetId || !candidate.stateFingerprint
19
+ || !candidate.questionSetFingerprint || !candidate.policyFingerprint || !candidate.authority) {
20
+ throw decisionError(DECISION_ERROR_CODES.REQUIRED, `A canonical persisted semantic decision is required for ${operation}.`);
21
+ }
22
+ return assertRequiredFreshDecision({
23
+ artifact: candidate,
24
+ currentBindings,
25
+ expectedDecisionKind,
26
+ expectedTaskId,
27
+ expectedQuestionSetId,
28
+ ledger,
29
+ });
30
+ }
31
+
32
+ export { assertRequiredFreshDecision, resolveRequiredSemanticDecision };
33
+
34
+ export function isOfflineSafeOperation(operation) { return OFFLINE_SAFE_OPERATIONS.includes(operation); }
@@ -0,0 +1,22 @@
1
+ import { DECISION_ENGINE_ID, PINNED_JEV_MODEL } from "./constants.js";
2
+ import { validateDecisionRequest } from "./request.js";
3
+ import { normalizeDecisionPolicy } from "./policy.js";
4
+ import { decisionError, DECISION_ERROR_CODES } from "./errors.js";
5
+
6
+ export function assertDecisionEngine(engine) {
7
+ if (!engine || engine.id !== DECISION_ENGINE_ID || engine.model !== PINNED_JEV_MODEL || typeof engine.evaluate !== "function" || typeof engine.health !== "function") {
8
+ throw decisionError(DECISION_ERROR_CODES.ENGINE_UNAVAILABLE, "A pinned typesafe-jev decision engine is required.");
9
+ }
10
+ return engine;
11
+ }
12
+
13
+ export function createDecisionEngine({ provider, policy } = {}) {
14
+ const normalizedPolicy = normalizeDecisionPolicy(policy);
15
+ assertDecisionEngine(provider);
16
+ return Object.freeze({
17
+ id: DECISION_ENGINE_ID,
18
+ model: PINNED_JEV_MODEL,
19
+ async evaluate(input) { return provider.evaluate(validateDecisionRequest(input), normalizedPolicy); },
20
+ async health() { return provider.health(normalizedPolicy); },
21
+ });
22
+ }