@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
@@ -62,7 +62,21 @@ const SIGNALS = Object.freeze({
62
62
  platforms: new Set(["web", "mobile", "desktop", "server", "ci", "cross-platform"]),
63
63
  });
64
64
 
65
- const PROJECT_FRAMEWORKS = new Set(["flutter"]);
65
+ const PROJECT_FRAMEWORKS = new Set([
66
+ "flutter", "dotnet", "aspnetcore", "abp", "nodejs", "rust",
67
+ "c", "cpp", "java", "sql", "go", "typescript", "php", "swift",
68
+ ]);
69
+
70
+ const LANGUAGE_PROJECT_GUIDES = Object.freeze([
71
+ ["c", "PROJECT_C_CONFIRMED"],
72
+ ["cpp", "PROJECT_CPP_CONFIRMED"],
73
+ ["java", "PROJECT_JAVA_CONFIRMED"],
74
+ ["go", "PROJECT_GO_CONFIRMED"],
75
+ ["typescript", "PROJECT_TYPESCRIPT_CONFIRMED"],
76
+ ["php", "PROJECT_PHP_CONFIRMED"],
77
+ ["swift", "PROJECT_SWIFT_CONFIRMED"],
78
+ ["sql", "PROJECT_SQL_CONFIRMED"],
79
+ ]);
66
80
 
67
81
  export const PLATFORM_SEMANTICS = Object.freeze({
68
82
  web: Object.freeze({
@@ -134,8 +148,100 @@ function addFlutterProjectGuides(input, add) {
134
148
  || !["MATCH", "UNSCOPED"].includes(projectEvidence.scope)
135
149
  || !hasFlutterWorkContext(input.workType)) return;
136
150
  add("flutter", "PROJECT_FLUTTER_SDK_DEPENDENCY");
137
- add("clean", "PROJECT_FLUTTER_BASELINE");
138
- add("test", "PROJECT_FLUTTER_BASELINE");
151
+ }
152
+
153
+ function hasDotNetWorkContext(workType) {
154
+ return !["documentation", "ui-copy"].includes(workType);
155
+ }
156
+
157
+ function addDotNetProjectGuides(input, add) {
158
+ const projectEvidence = input.projectEvidence;
159
+ if (!projectEvidence?.frameworks.includes("dotnet")
160
+ || !["MATCH", "UNSCOPED"].includes(projectEvidence.scope)
161
+ || !hasDotNetWorkContext(input.workType)) return;
162
+ add("dotnet", "PROJECT_DOTNET_SDK_PROJECT");
163
+ if (projectEvidence.frameworks.includes("aspnetcore")) add("dotnet", "PROJECT_ASPNETCORE_CONFIRMED");
164
+ if (projectEvidence.frameworks.includes("abp")) add("dotnet", "PROJECT_ABP_CONFIRMED");
165
+ }
166
+
167
+ function hasNodeJsWorkContext(workType) {
168
+ return !["documentation", "ui-copy", "mobile-ui"].includes(workType);
169
+ }
170
+
171
+ function addNodeJsProjectGuides(input, add) {
172
+ const projectEvidence = input.projectEvidence;
173
+ if (!projectEvidence?.frameworks.includes("nodejs")
174
+ || !["MATCH", "UNSCOPED"].includes(projectEvidence.scope)
175
+ || !hasNodeJsWorkContext(input.workType)) return;
176
+ add("nodejs", "PROJECT_NODEJS_CONFIRMED");
177
+ if (projectEvidence.primarySignals.some((signal) => signal.includes(":dependencies.") || signal.includes(":optionalDependencies."))) {
178
+ add("nodejs", "PROJECT_NODEJS_BACKEND_FRAMEWORK");
179
+ }
180
+ if (projectEvidence.primarySignals.some((signal) => signal.includes(":scripts."))) {
181
+ add("nodejs", "PROJECT_NODEJS_RUNTIME_SCRIPT");
182
+ }
183
+ if (projectEvidence.primarySignals.some((signal) => signal.includes(":import="))) {
184
+ add("nodejs", "PROJECT_NODEJS_SERVER_RUNTIME");
185
+ }
186
+ }
187
+
188
+ function hasRustWorkContext(workType) {
189
+ return !["documentation", "ui-copy", "mobile-ui"].includes(workType);
190
+ }
191
+
192
+ function addRustProjectGuides(input, add) {
193
+ const projectEvidence = input.projectEvidence;
194
+ if (!projectEvidence?.frameworks.includes("rust")
195
+ || !["MATCH", "UNSCOPED"].includes(projectEvidence.scope)
196
+ || !hasRustWorkContext(input.workType)) return;
197
+ add("rust", "PROJECT_RUST_CONFIRMED");
198
+ if (projectEvidence.primarySignals.some((signal) => signal.endsWith(":package"))) {
199
+ add("rust", "PROJECT_RUST_CARGO_PACKAGE");
200
+ }
201
+ if (projectEvidence.primarySignals.some((signal) => signal.endsWith(":workspace"))) {
202
+ add("rust", "PROJECT_RUST_CARGO_WORKSPACE");
203
+ }
204
+ }
205
+
206
+ function hasLanguageWorkContext(workType) {
207
+ return !["documentation", "ui-copy"].includes(workType);
208
+ }
209
+
210
+ function addLanguageProjectGuides(input, add) {
211
+ const projectEvidence = input.projectEvidence;
212
+ if (!projectEvidence || !["MATCH", "UNSCOPED"].includes(projectEvidence.scope)
213
+ || !hasLanguageWorkContext(input.workType)) return;
214
+ for (const [guide, reason] of LANGUAGE_PROJECT_GUIDES) {
215
+ if (projectEvidence.frameworks.includes(guide)) add(guide, reason);
216
+ }
217
+ }
218
+
219
+ function addProjectBaselineGuides(input, add) {
220
+ const projectEvidence = input.projectEvidence;
221
+ if (!projectEvidence || !["MATCH", "UNSCOPED"].includes(projectEvidence.scope)) return;
222
+ if (projectEvidence.frameworks.includes("flutter") && hasFlutterWorkContext(input.workType)) {
223
+ add("clean", "PROJECT_FLUTTER_BASELINE");
224
+ add("test", "PROJECT_FLUTTER_BASELINE");
225
+ }
226
+ if (projectEvidence.frameworks.includes("dotnet") && hasDotNetWorkContext(input.workType)) {
227
+ add("clean", "PROJECT_DOTNET_BASELINE");
228
+ add("test", "PROJECT_DOTNET_BASELINE");
229
+ }
230
+ if (projectEvidence.frameworks.includes("nodejs") && hasNodeJsWorkContext(input.workType)) {
231
+ add("clean", "PROJECT_NODEJS_BASELINE");
232
+ add("test", "PROJECT_NODEJS_BASELINE");
233
+ }
234
+ if (projectEvidence.frameworks.includes("rust") && hasRustWorkContext(input.workType)) {
235
+ add("clean", "PROJECT_RUST_BASELINE");
236
+ add("test", "PROJECT_RUST_BASELINE");
237
+ }
238
+ if (hasLanguageWorkContext(input.workType)) {
239
+ for (const [language] of LANGUAGE_PROJECT_GUIDES) {
240
+ if (!projectEvidence.frameworks.includes(language)) continue;
241
+ add("clean", `PROJECT_${language.toUpperCase()}_BASELINE`);
242
+ add("test", `PROJECT_${language.toUpperCase()}_BASELINE`);
243
+ }
244
+ }
139
245
  }
140
246
 
141
247
  function flutterExclusionReason(projectEvidence, workType) {
@@ -146,6 +252,30 @@ function flutterExclusionReason(projectEvidence, workType) {
146
252
  return "NO_FLUTTER_PRIMARY_EVIDENCE";
147
253
  }
148
254
 
255
+ function dotNetExclusionReason(projectEvidence, workType) {
256
+ if (!projectEvidence) return "NO_DOTNET_PROJECT_EVIDENCE";
257
+ if (projectEvidence.scope === "NO_MATCH") return "NO_DOTNET_SCOPE_MATCH";
258
+ if (!projectEvidence.frameworks.includes("dotnet")) return "NO_DOTNET_PRIMARY_EVIDENCE";
259
+ if (!hasDotNetWorkContext(workType)) return "NO_DOTNET_EXECUTABLE_WORK";
260
+ return "NO_DOTNET_PRIMARY_EVIDENCE";
261
+ }
262
+
263
+ function nodeJsExclusionReason(projectEvidence, workType) {
264
+ if (!projectEvidence) return "NO_NODEJS_PROJECT_EVIDENCE";
265
+ if (projectEvidence.scope === "NO_MATCH") return "NO_NODEJS_SCOPE_MATCH";
266
+ if (!projectEvidence.frameworks.includes("nodejs")) return "NO_NODEJS_PRIMARY_EVIDENCE";
267
+ if (!hasNodeJsWorkContext(workType)) return "NO_NODEJS_EXECUTABLE_WORK";
268
+ return "NO_NODEJS_PRIMARY_EVIDENCE";
269
+ }
270
+
271
+ function rustExclusionReason(projectEvidence, workType) {
272
+ if (!projectEvidence) return "NO_RUST_PROJECT_EVIDENCE";
273
+ if (projectEvidence.scope === "NO_MATCH") return "NO_RUST_SCOPE_MATCH";
274
+ if (!projectEvidence.frameworks.includes("rust")) return "NO_RUST_PRIMARY_EVIDENCE";
275
+ if (!hasRustWorkContext(workType)) return "NO_RUST_EXECUTABLE_WORK";
276
+ return "NO_RUST_PRIMARY_EVIDENCE";
277
+ }
278
+
149
279
  function exclusionReasonForGuide(guide, projectEvidence, workType) {
150
280
  if (guide === "security") return "NO_TRUST_BOUNDARY";
151
281
  if (guide === "performance") return "NO_MEASURABLE_PERFORMANCE_RISK";
@@ -154,6 +284,16 @@ function exclusionReasonForGuide(guide, projectEvidence, workType) {
154
284
  if (guide === "taste") return "NO_TASTE_FRONTEND_CONTEXT";
155
285
  if (guide === "documentation") return "NO_DOCUMENTATION_SURFACE";
156
286
  if (guide === "flutter") return flutterExclusionReason(projectEvidence, workType);
287
+ if (guide === "dotnet") return dotNetExclusionReason(projectEvidence, workType);
288
+ if (guide === "nodejs") return nodeJsExclusionReason(projectEvidence, workType);
289
+ if (guide === "rust") return rustExclusionReason(projectEvidence, workType);
290
+ if (LANGUAGE_PROJECT_GUIDES.some(([language]) => language === guide)) {
291
+ if (!projectEvidence) return `NO_${guide.toUpperCase()}_PROJECT_EVIDENCE`;
292
+ if (projectEvidence.scope === "NO_MATCH") return `NO_${guide.toUpperCase()}_SCOPE_MATCH`;
293
+ if (!projectEvidence.frameworks.includes(guide)) return `NO_${guide.toUpperCase()}_PRIMARY_EVIDENCE`;
294
+ if (!hasLanguageWorkContext(workType)) return `NO_${guide.toUpperCase()}_EXECUTABLE_WORK`;
295
+ return `NO_${guide.toUpperCase()}_PRIMARY_EVIDENCE`;
296
+ }
157
297
  return "NO_BEHAVIOR_OR_EXECUTABLE_CHANGE";
158
298
  }
159
299
 
@@ -173,6 +313,64 @@ function reasonForSignal(prefix, signal) {
173
313
  return `${prefix}_${signal.toUpperCase().replaceAll("-", "_")}`;
174
314
  }
175
315
 
316
+ const SECURITY_TRUST_BOUNDARY_RISKS = Object.freeze([
317
+ "untrusted-input", "personal-data", "secrets", "external-service", "publication",
318
+ ]);
319
+
320
+ // Canonical deterministic safety floor. These are the route reason codes the
321
+ // router assigns when a trust-boundary signal selects the security guide. The
322
+ // semantic exclusion policy reads protection from this same source, so a
323
+ // mandatory safety guide can never drift out of protection (for example the
324
+ // previously missing external-service boundary).
325
+ const MANDATORY_SAFETY_REASONS = Object.freeze(new Set([
326
+ "SURFACE_AUTH",
327
+ ...SECURITY_TRUST_BOUNDARY_RISKS.map((risk) => reasonForSignal("RISK", risk)),
328
+ ]));
329
+
330
+ export function isMandatorySafetyReason(reason) {
331
+ return MANDATORY_SAFETY_REASONS.has(reason);
332
+ }
333
+
334
+ export function isMandatorySafetyGuide(guide, reasons) {
335
+ return guide === "security"
336
+ && Array.isArray(reasons)
337
+ && reasons.some((reason) => isMandatorySafetyReason(reason));
338
+ }
339
+
340
+ function applySemanticGuideRecommendation(selected, excluded, recommendation) {
341
+ const guideDecision = recommendation?.guideRelevance
342
+ ?? (recommendation?.rankedIds || recommendation?.excludedIds ? recommendation : null);
343
+ if (!guideDecision) return;
344
+ const excludedIds = new Set(Array.isArray(guideDecision.excludedIds) ? guideDecision.excludedIds : []);
345
+ const confidenceById = guideDecision.confidenceById ?? {};
346
+ for (const guide of [...selected.keys()]) {
347
+ const reasons = selected.get(guide);
348
+ if (isMandatorySafetyGuide(guide, reasons)) {
349
+ if (!reasons.includes("MANDATORY_SAFETY_GUIDE")) reasons.push("MANDATORY_SAFETY_GUIDE");
350
+ continue;
351
+ }
352
+ if (!excludedIds.has(guide)) {
353
+ if (!reasons.includes("JEV_RELEVANT")) reasons.push("JEV_RELEVANT");
354
+ continue;
355
+ }
356
+ if ((confidenceById[guide] ?? 0) < 0.75) {
357
+ if (!reasons.includes("JEV_LOW_CONFIDENCE_RETAINED")) reasons.push("JEV_LOW_CONFIDENCE_RETAINED");
358
+ continue;
359
+ }
360
+ selected.delete(guide);
361
+ excluded[guide] = ["JEV_EXCLUDED"];
362
+ }
363
+ const ranked = Array.isArray(guideDecision.rankedIds) ? guideDecision.rankedIds : [];
364
+ const order = new Map(ranked.map((guide, index) => [guide, index]));
365
+ const entries = [...selected.entries()].sort((left, right) => {
366
+ const leftRank = order.has(left[0]) ? order.get(left[0]) : ranked.length + [...selected.keys()].indexOf(left[0]);
367
+ const rightRank = order.has(right[0]) ? order.get(right[0]) : ranked.length + [...selected.keys()].indexOf(right[0]);
368
+ return leftRank - rightRank;
369
+ });
370
+ selected.clear();
371
+ for (const [guide, reasons] of entries) selected.set(guide, reasons);
372
+ }
373
+
176
374
  function normalizeArray(value, name, allowed) {
177
375
  if (value === undefined) return [];
178
376
  if (!Array.isArray(value)) throw new RouteInputError(`${name} must be an array`);
@@ -209,6 +407,14 @@ function normalizeProjectEvidence(value) {
209
407
  throw new RouteInputError(`Unsupported projectEvidence schema version: ${value.schemaVersion}`);
210
408
  }
211
409
  const frameworks = normalizeArray(value.frameworks, "frameworks", PROJECT_FRAMEWORKS);
410
+ const hasDotnet = frameworks.includes("dotnet");
411
+ const invalidOverlays = frameworks.filter((framework) => framework === "aspnetcore" || framework === "abp");
412
+ if (!hasDotnet && invalidOverlays.length > 0) {
413
+ const noun = invalidOverlays.length === 1 ? "framework" : "frameworks";
414
+ const verb = invalidOverlays.length === 1 ? "requires" : "require";
415
+ const labels = invalidOverlays.map((framework) => `"${framework}"`).join(", ");
416
+ throw new RouteInputError(`projectEvidence ${noun} ${labels} ${verb} "dotnet"`);
417
+ }
212
418
  const scope = value.scope ?? (frameworks.length > 0 ? "UNSCOPED" : "NONE");
213
419
  if (!PROJECT_EVIDENCE_SCOPES.includes(scope)) {
214
420
  throw new RouteInputError(`Unknown project evidence scope: ${scope}`);
@@ -264,6 +470,11 @@ export function evaluateRoute(input = {}, profileOptions = {}) {
264
470
 
265
471
  const projectEvidence = normalized.projectEvidence;
266
472
  addFlutterProjectGuides(normalized, add);
473
+ addDotNetProjectGuides(normalized, add);
474
+ addNodeJsProjectGuides(normalized, add);
475
+ addRustProjectGuides(normalized, add);
476
+ addLanguageProjectGuides(normalized, add);
477
+ addProjectBaselineGuides(normalized, add);
267
478
 
268
479
  const workReason = reasonForWorkType(normalized.workType);
269
480
  for (const guide of WORK_GUIDES[normalized.workType]) add(guide, workReason);
@@ -287,7 +498,7 @@ export function evaluateRoute(input = {}, profileOptions = {}) {
287
498
  }
288
499
 
289
500
  for (const risk of normalized.risks) {
290
- if (["untrusted-input", "personal-data", "secrets", "external-service", "publication"].includes(risk)) {
501
+ if (SECURITY_TRUST_BOUNDARY_RISKS.includes(risk)) {
291
502
  add("security", reasonForSignal("RISK", risk));
292
503
  }
293
504
  if (["critical-path", "performance"].includes(risk)) {
@@ -328,12 +539,19 @@ export function evaluateRoute(input = {}, profileOptions = {}) {
328
539
  excluded[guide] = [exclusionReasonForGuide(guide, projectEvidence, normalized.workType)];
329
540
  }
330
541
 
542
+ applySemanticGuideRecommendation(
543
+ selected,
544
+ excluded,
545
+ profileOptions.semanticRecommendation ?? null,
546
+ );
547
+
331
548
  const guides = [...selected.keys()];
332
549
  const result = {
333
550
  schemaVersion: ROUTING_SCHEMA_VERSION,
334
551
  protocolVersion: PROTOCOL_VERSION,
335
552
  input: normalized,
336
553
  primary: Object.prototype.hasOwnProperty.call(PRIMARY_GUIDES, normalized.workType)
554
+ && guides.includes(PRIMARY_GUIDES[normalized.workType])
337
555
  ? PRIMARY_GUIDES[normalized.workType]
338
556
  : guides[0] ?? null,
339
557
  guides,
@@ -346,6 +564,7 @@ export function evaluateRoute(input = {}, profileOptions = {}) {
346
564
  configuredProfile: profileOptions.configuredProfile ?? input.configuredProfile ?? "auto",
347
565
  requestedProfile: profileOptions.requestedProfile
348
566
  ?? (Object.prototype.hasOwnProperty.call(input, "executionProfile") ? input.executionProfile : null),
567
+ semanticRecommendation: profileOptions.semanticRecommendation ?? null,
349
568
  }),
350
569
  };
351
570
  return assertRouteInvariants(result);
@@ -4,8 +4,22 @@ import {
4
4
  normalizeVerificationExecutionPolicy,
5
5
  } from "./verification-execution.js";
6
6
  import { STRUCTURAL_QUALITY_PROVIDER_ID_PATTERN } from "./structural-quality/constants.js";
7
- import { E_ADVISORY_CONTEXT_PROVIDER_INVALID } from "./error-codes.js";
7
+ import {
8
+ E_ADVISORY_CONTEXT_PROVIDER_INVALID,
9
+ E_BROWSER_VERIFICATION_PROVIDER_INVALID,
10
+ E_SECURITY_REVIEW_PROVIDER_INVALID,
11
+ } from "./error-codes.js";
8
12
  import { assertAdvisoryContextProviderIdentity } from "./advisory-context/provider.js";
13
+ import {
14
+ BROWSER_VERIFICATION_PROVIDER_ID_PATTERN,
15
+ assertBrowserVerificationProvider,
16
+ assertBrowserVerificationProviderIdentity,
17
+ } from "./browser-verification/provider.js";
18
+ import {
19
+ SECURITY_REVIEW_PROVIDER_ID_PATTERN,
20
+ assertSecurityReviewProvider,
21
+ assertSecurityReviewProviderIdentity,
22
+ } from "./security-review/provider.js";
9
23
 
10
24
  export const AUTHORITY_TRUST_MODES = Object.freeze(["NONE", "HOST_ATTESTED"]);
11
25
 
@@ -83,6 +97,100 @@ export function resolveAuthorityContext(options = {}) {
83
97
  return hasDirectAuthority ? createAuthorityContext({ ...source, trustMode: "NONE" }) : createAuthorityContext();
84
98
  }
85
99
 
100
+ function isProviderObject(value) {
101
+ return value && typeof value === "object" && !Array.isArray(value);
102
+ }
103
+
104
+ function providerEntries(configured, label, code) {
105
+ if (!configured || typeof configured !== "object" || Array.isArray(configured)) {
106
+ const error = new Error(`${label} must be an object or Map`);
107
+ error.code = code;
108
+ throw error;
109
+ }
110
+ return configured instanceof Map ? [...configured.entries()] : Object.entries(configured);
111
+ }
112
+
113
+ function registerProviders(configured, { label, code, idError, validateId, providerError, validateProvider }) {
114
+ const providers = {};
115
+ for (const [id, provider] of providerEntries(configured, label, code)) {
116
+ if (!validateId(id)) {
117
+ const error = new Error(idError(id));
118
+ error.code = code;
119
+ throw error;
120
+ }
121
+ if (typeof provider !== "function" && !isProviderObject(provider)) {
122
+ const error = new Error(providerError(id));
123
+ error.code = code;
124
+ throw error;
125
+ }
126
+ if (typeof provider !== "function") validateProvider(provider, id);
127
+ providers[id] = provider;
128
+ }
129
+ return Object.freeze(providers);
130
+ }
131
+
132
+ function configureUsageProvider(context, provider) {
133
+ if (!provider
134
+ || typeof provider !== "object"
135
+ || Array.isArray(provider)
136
+ || typeof provider.getTaskUsage !== "function") {
137
+ const error = new Error("Usage provider must expose getTaskUsage({ projectPath, taskId })");
138
+ error.code = "E_USAGE_INVALID";
139
+ throw error;
140
+ }
141
+ context.usageProvider = provider;
142
+ }
143
+
144
+ function configureStructuralQualityProviders(context, configured) {
145
+ context.structuralQualityProviders = registerProviders(configured, {
146
+ label: "structuralQualityProviders",
147
+ code: "E_STRUCTURAL_QUALITY_PROVIDER_INVALID",
148
+ idError: id => `Invalid or reserved structural-quality provider ID: ${id}`,
149
+ validateId: id => STRUCTURAL_QUALITY_PROVIDER_ID_PATTERN.test(id) && id !== "sentrux",
150
+ providerError: id => `Structural-quality provider ${id} must be an object or factory`,
151
+ validateProvider: (provider, id) => {},
152
+ });
153
+ }
154
+
155
+ function configureAdvisoryContextProviders(context, configured) {
156
+ context.advisoryContextProviders = registerProviders(configured, {
157
+ label: "advisoryContextProviders",
158
+ code: E_ADVISORY_CONTEXT_PROVIDER_INVALID,
159
+ idError: id => `Invalid advisory-context provider ID: ${id}`,
160
+ validateId: id => /^[a-z0-9][a-z0-9_-]*$/.test(id),
161
+ providerError: id => `Advisory-context provider ${id} must be an object or factory`,
162
+ validateProvider: assertAdvisoryContextProviderIdentity,
163
+ });
164
+ }
165
+
166
+ function configureBrowserVerificationProviders(context, configured) {
167
+ context.browserVerificationProviders = registerProviders(configured, {
168
+ label: "browserVerificationProviders",
169
+ code: E_BROWSER_VERIFICATION_PROVIDER_INVALID,
170
+ idError: id => `Invalid browser-verification provider ID: ${id}`,
171
+ validateId: id => BROWSER_VERIFICATION_PROVIDER_ID_PATTERN.test(id),
172
+ providerError: id => `Browser-verification provider ${id} must be an object with verify() or a factory`,
173
+ validateProvider: (provider, id) => {
174
+ assertBrowserVerificationProviderIdentity(provider, { expectedId: id });
175
+ assertBrowserVerificationProvider(provider, { label: `browser-verification provider "${id}"` });
176
+ },
177
+ });
178
+ }
179
+
180
+ function configureSecurityReviewProviders(context, configured) {
181
+ context.securityReviewProviders = registerProviders(configured, {
182
+ label: "securityReviewProviders",
183
+ code: E_SECURITY_REVIEW_PROVIDER_INVALID,
184
+ idError: id => `Invalid security-review provider ID: ${id}`,
185
+ validateId: id => SECURITY_REVIEW_PROVIDER_ID_PATTERN.test(id),
186
+ providerError: id => `Security-review provider ${id} must be an object with review() or a factory`,
187
+ validateProvider: (provider, id) => {
188
+ assertSecurityReviewProviderIdentity(provider, { expectedId: id });
189
+ assertSecurityReviewProvider(provider, { label: `security-review provider "${id}"` });
190
+ },
191
+ });
192
+ }
193
+
86
194
  export function createForgeLoopContext(options = {}) {
87
195
  const authorityContext = createAuthorityContext(options);
88
196
  const context = { authorityContext };
@@ -98,70 +206,19 @@ export function createForgeLoopContext(options = {}) {
98
206
  context.verificationExecutionPolicy = normalizeVerificationExecutionPolicy(options.verificationExecutionPolicy);
99
207
  }
100
208
  if (options?.usageProvider !== undefined) {
101
- if (!options.usageProvider
102
- || typeof options.usageProvider !== "object"
103
- || Array.isArray(options.usageProvider)
104
- || typeof options.usageProvider.getTaskUsage !== "function") {
105
- const error = new Error("Usage provider must expose getTaskUsage({ projectPath, taskId })");
106
- error.code = "E_USAGE_INVALID";
107
- throw error;
108
- }
109
- context.usageProvider = options.usageProvider;
209
+ configureUsageProvider(context, options.usageProvider);
110
210
  }
111
211
  if (options?.structuralQualityProviders !== undefined) {
112
- const configured = options.structuralQualityProviders instanceof Map
113
- ? Object.fromEntries(options.structuralQualityProviders.entries())
114
- : options.structuralQualityProviders;
115
- if (!configured || typeof configured !== "object" || Array.isArray(configured)) {
116
- const error = new Error("structuralQualityProviders must be an object or Map");
117
- error.code = "E_STRUCTURAL_QUALITY_PROVIDER_INVALID";
118
- throw error;
119
- }
120
- const providers = {};
121
- for (const [id, provider] of Object.entries(configured)) {
122
- if (!STRUCTURAL_QUALITY_PROVIDER_ID_PATTERN.test(id) || id === "sentrux") {
123
- const error = new Error(`Invalid or reserved structural-quality provider ID: ${id}`);
124
- error.code = "E_STRUCTURAL_QUALITY_PROVIDER_INVALID";
125
- throw error;
126
- }
127
- if (typeof provider !== "function"
128
- && (!provider || typeof provider !== "object" || Array.isArray(provider))) {
129
- const error = new Error(`Structural-quality provider ${id} must be an object or factory`);
130
- error.code = "E_STRUCTURAL_QUALITY_PROVIDER_INVALID";
131
- throw error;
132
- }
133
- providers[id] = provider;
134
- }
135
- context.structuralQualityProviders = Object.freeze(providers);
212
+ configureStructuralQualityProviders(context, options.structuralQualityProviders);
136
213
  }
137
214
  if (options?.advisoryContextProviders !== undefined) {
138
- const configured = options.advisoryContextProviders instanceof Map
139
- ? Object.fromEntries(options.advisoryContextProviders.entries())
140
- : options.advisoryContextProviders;
141
- if (!configured || typeof configured !== "object" || Array.isArray(configured)) {
142
- const error = new Error("advisoryContextProviders must be an object or Map");
143
- error.code = E_ADVISORY_CONTEXT_PROVIDER_INVALID;
144
- throw error;
145
- }
146
- const providers = {};
147
- for (const [id, provider] of Object.entries(configured)) {
148
- if (!/^[a-z0-9][a-z0-9_-]*$/.test(id)) {
149
- const error = new Error(`Invalid advisory-context provider ID: ${id}`);
150
- error.code = E_ADVISORY_CONTEXT_PROVIDER_INVALID;
151
- throw error;
152
- }
153
- if (typeof provider !== "function"
154
- && (!provider || typeof provider !== "object" || Array.isArray(provider))) {
155
- const error = new Error(`Advisory-context provider ${id} must be an object or factory`);
156
- error.code = E_ADVISORY_CONTEXT_PROVIDER_INVALID;
157
- throw error;
158
- }
159
- if (typeof provider !== "function") {
160
- assertAdvisoryContextProviderIdentity(provider, id);
161
- }
162
- providers[id] = provider;
163
- }
164
- context.advisoryContextProviders = Object.freeze(providers);
215
+ configureAdvisoryContextProviders(context, options.advisoryContextProviders);
216
+ }
217
+ if (options?.browserVerificationProviders !== undefined) {
218
+ configureBrowserVerificationProviders(context, options.browserVerificationProviders);
219
+ }
220
+ if (options?.securityReviewProviders !== undefined) {
221
+ configureSecurityReviewProviders(context, options.securityReviewProviders);
165
222
  }
166
223
  return Object.freeze(context);
167
224
  }