soloforge 1.3.4 → 1.3.6

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 (96) hide show
  1. package/README.md +12 -5
  2. package/dist/adapters/claude_code/tools.d.ts.map +1 -1
  3. package/dist/adapters/claude_code/tools.js +80 -44
  4. package/dist/adapters/claude_code/tools.js.map +1 -1
  5. package/dist/adapters/shared/workflow_template.js +1 -1
  6. package/dist/bin/soloforge.d.ts.map +1 -1
  7. package/dist/bin/soloforge.js +83 -1
  8. package/dist/bin/soloforge.js.map +1 -1
  9. package/dist/engine/backend_implementation_contract.d.ts +1 -1
  10. package/dist/engine/backend_implementation_contract.d.ts.map +1 -1
  11. package/dist/engine/backend_implementation_contract.js +22 -0
  12. package/dist/engine/backend_implementation_contract.js.map +1 -1
  13. package/dist/engine/brainstorm_contract.d.ts +1 -1
  14. package/dist/engine/brainstorm_contract.js +1 -1
  15. package/dist/engine/code_maintainability_observability_contract.d.ts +1 -1
  16. package/dist/engine/code_maintainability_observability_contract.d.ts.map +1 -1
  17. package/dist/engine/code_maintainability_observability_contract.js +241 -3
  18. package/dist/engine/code_maintainability_observability_contract.js.map +1 -1
  19. package/dist/engine/contract_registry.js +1 -1
  20. package/dist/engine/diagnostic_registry.d.ts +1 -0
  21. package/dist/engine/diagnostic_registry.d.ts.map +1 -1
  22. package/dist/engine/diagnostic_registry.js +6 -0
  23. package/dist/engine/diagnostic_registry.js.map +1 -1
  24. package/dist/engine/dual_layer_mechanism_registry.js +1 -1
  25. package/dist/engine/extension_scenario_registry.js +4 -4
  26. package/dist/engine/extension_scenario_registry.js.map +1 -1
  27. package/dist/engine/foundation_scenario_runners.d.ts.map +1 -1
  28. package/dist/engine/foundation_scenario_runners.js +4 -2
  29. package/dist/engine/foundation_scenario_runners.js.map +1 -1
  30. package/dist/engine/historical_issue_mechanization_matrix.d.ts +28 -0
  31. package/dist/engine/historical_issue_mechanization_matrix.d.ts.map +1 -0
  32. package/dist/engine/historical_issue_mechanization_matrix.js +134 -0
  33. package/dist/engine/historical_issue_mechanization_matrix.js.map +1 -0
  34. package/dist/engine/implementation_roadmap_registry.d.ts.map +1 -1
  35. package/dist/engine/implementation_roadmap_registry.js +70 -13
  36. package/dist/engine/implementation_roadmap_registry.js.map +1 -1
  37. package/dist/engine/intent_expander.d.ts.map +1 -1
  38. package/dist/engine/intent_expander.js +151 -1
  39. package/dist/engine/intent_expander.js.map +1 -1
  40. package/dist/engine/next_action_planner.d.ts.map +1 -1
  41. package/dist/engine/next_action_planner.js +72 -4
  42. package/dist/engine/next_action_planner.js.map +1 -1
  43. package/dist/engine/project_knowledge_contract.d.ts +58 -0
  44. package/dist/engine/project_knowledge_contract.d.ts.map +1 -0
  45. package/dist/engine/project_knowledge_contract.js +298 -0
  46. package/dist/engine/project_knowledge_contract.js.map +1 -0
  47. package/dist/engine/project_knowledge_system_regression_matrix.d.ts +27 -0
  48. package/dist/engine/project_knowledge_system_regression_matrix.d.ts.map +1 -0
  49. package/dist/engine/project_knowledge_system_regression_matrix.js +295 -0
  50. package/dist/engine/project_knowledge_system_regression_matrix.js.map +1 -0
  51. package/dist/engine/release_issue_scenario_registry.d.ts.map +1 -1
  52. package/dist/engine/release_issue_scenario_registry.js +297 -102
  53. package/dist/engine/release_issue_scenario_registry.js.map +1 -1
  54. package/dist/engine/release_readiness_gate.d.ts +1 -0
  55. package/dist/engine/release_readiness_gate.d.ts.map +1 -1
  56. package/dist/engine/release_readiness_gate.js +384 -5
  57. package/dist/engine/release_readiness_gate.js.map +1 -1
  58. package/dist/engine/technology_decision.js +5 -5
  59. package/dist/engine/technology_decision.js.map +1 -1
  60. package/dist/engine/template_asset_contract_registry.d.ts.map +1 -1
  61. package/dist/engine/template_asset_contract_registry.js +6 -5
  62. package/dist/engine/template_asset_contract_registry.js.map +1 -1
  63. package/dist/engine/verifier.js +1 -1
  64. package/dist/engine/verifier.js.map +1 -1
  65. package/dist/engine/workflow_navigation_contract.d.ts +10 -0
  66. package/dist/engine/workflow_navigation_contract.d.ts.map +1 -1
  67. package/dist/knowledge/loader.d.ts +3 -1
  68. package/dist/knowledge/loader.d.ts.map +1 -1
  69. package/dist/knowledge/loader.js +2 -2
  70. package/dist/knowledge/loader.js.map +1 -1
  71. package/dist/types.d.ts +19 -0
  72. package/dist/types.d.ts.map +1 -1
  73. package/package.json +1 -1
  74. package/templates/knowledge/acceptance_templates//351/200/232/347/224/250/350/264/250/351/207/217/351/252/214/346/224/266/346/270/205/345/215/225.md +1 -1
  75. package/templates/knowledge/acceptance_templates//351/207/215/346/236/204/346/226/271/346/241/210/346/250/241/347/211/210.md +1 -1
  76. package/templates/knowledge/domain//346/224/257/344/273/230/350/247/204/345/210/231.md +1 -1
  77. package/templates/knowledge/procedures//346/225/260/346/215/256/345/272/223/350/277/201/347/247/273/346/265/201/347/250/213.md +1 -1
  78. package/templates/knowledge/procedures//351/203/250/347/275/262/345/217/221/345/270/203/346/265/201/347/250/213.md +1 -1
  79. package/templates/knowledge/procedures//351/207/215/346/236/204/346/265/201/346/260/264/347/272/277.md +1 -1
  80. package/templates/knowledge/review_rules//344/272/244/344/273/230/345/256/214/345/244/207/346/200/247/345/256/241/346/237/245/350/247/204/345/210/231.md +1 -1
  81. package/templates/knowledge/review_rules//350/264/250/351/207/217/345/256/241/346/237/245/350/247/204/345/210/231.md +3 -3
  82. package/templates/knowledge/rules//344/273/243/347/240/201/346/263/250/351/207/212/344/270/216/346/227/245/345/277/227/345/245/221/347/272/246/350/247/204/345/210/231.md +32 -3
  83. package/templates/knowledge/rules//346/240/270/345/277/203/344/275/223/351/252/214/345/216/237/345/210/231.md +1 -1
  84. package/templates/knowledge/rules//346/240/270/345/277/203/345/267/245/347/250/213/346/211/247/350/241/214/345/216/237/345/210/231.md +2 -2
  85. package/templates/knowledge/rules//346/274/224/350/277/233/345/233/236/345/275/222/351/227/250/346/216/247/350/247/204/345/210/231.md +1 -1
  86. package/templates/patterns/Git/346/223/215/344/275/234/350/247/204/350/214/203.md +1 -1
  87. package/templates/scaffolds/react/Form.tsx.hbs +11 -3
  88. package/templates/scaffolds/react/List.tsx.hbs +11 -3
  89. package/templates/scaffolds/react/Page.tsx.hbs +1 -1
  90. package/templates/scaffolds/react/types.ts.hbs +4 -1
  91. package/templates/scaffolds/spring-boot/Controller.java.hbs +18 -4
  92. package/templates/scaffolds/spring-boot/DTO.java.hbs +4 -1
  93. package/templates/scaffolds/spring-boot/Entity.java.hbs +8 -3
  94. package/templates/scaffolds/spring-boot/Mapper.java.hbs +4 -1
  95. package/templates/scaffolds/spring-boot/ServiceImpl.java.hbs +34 -10
  96. package/templates/scaffolds/spring-boot/ServiceTest.java.hbs +0 -1
@@ -24,6 +24,7 @@
24
24
  * 18. 代码可维护性与可观测性契约行为验证
25
25
  * 19. 历史问题长期机制化行为验证
26
26
  * 20. 诊断码集中治理检查
27
+ * 21. 模板卫生与项目知识消费验证
27
28
  */
28
29
  import fs from "node:fs";
29
30
  import os from "node:os";
@@ -622,7 +623,12 @@ function checkTestPollution(rootDir, hardFail, _info) {
622
623
  const checksImplementedVerified = content.includes("implemented_verified");
623
624
  const checksFileExists = /existsSync/.test(content) && /required.*file/i.test(content);
624
625
  const checksScenarioSelfAttest = /fixture_status.*ready|scenario.*status.*PASS/.test(content);
625
- if (isScenarioMatrix) {
626
+ const hasPlaceholderTest = /场景注册占位/.test(content) || /expect\s*\(\s*true\s*\)\s*\.\s*toBe\s*\(\s*true\s*\)/.test(content) || /registered\s+only/.test(content);
627
+ const hasTodoPlaceholder = /\bit\s*\.\s*todo\s*\(/.test(content) || /\bdescribe\s*\.\s*todo\s*\(/.test(content);
628
+ if (hasPlaceholderTest || hasTodoPlaceholder) {
629
+ categories.suspicious_test_pollution.push(rel);
630
+ }
631
+ else if (isScenarioMatrix) {
626
632
  categories.batch_construction_test.push(rel);
627
633
  }
628
634
  else if (usefulContractExemptions.has(rel)) {
@@ -1127,19 +1133,22 @@ async function checkBatchIssueFormatConsistency(rootDir, hardFail, _info) {
1127
1133
  // 复用 scripts/batch_issue_details.mjs 的共享解析逻辑,不维护独立的问题识别规则
1128
1134
  const scriptPath = path.join(rootDir, "scripts", "batch_issue_details.mjs");
1129
1135
  let loadBatchIssueDetails;
1136
+ let validateCanonicalIssueLedger;
1130
1137
  try {
1131
1138
  const mod = await import(scriptPath);
1132
1139
  loadBatchIssueDetails = mod.loadBatchIssueDetails;
1140
+ validateCanonicalIssueLedger = mod.validateCanonicalIssueLedger;
1133
1141
  }
1134
1142
  catch (e) {
1135
1143
  hardFail("BATCH_ISSUE_FORMAT_INCONSISTENT", `无法加载共享解析脚本: ${e.message}`, ["scripts/batch_issue_details.mjs"], "构建系统", "共享解析脚本不可用", "修复脚本");
1136
1144
  return;
1137
1145
  }
1138
- if (!loadBatchIssueDetails) {
1139
- hardFail("BATCH_ISSUE_FORMAT_INCONSISTENT", "loadBatchIssueDetails 导出未找到", ["scripts/batch_issue_details.mjs"], "构建系统", "共享解析脚本导出不正确", "修复脚本");
1146
+ if (!loadBatchIssueDetails || !validateCanonicalIssueLedger) {
1147
+ hardFail("BATCH_ISSUE_FORMAT_INCONSISTENT", "共享解析脚本缺少 Batch 加载或 canonical ledger 校验导出", ["scripts/batch_issue_details.mjs"], "构建系统", "共享解析脚本导出不正确", "修复脚本");
1140
1148
  return;
1141
1149
  }
1142
- const expectedCounts = { 1: 19, 2: 12, 3: 9, 4: 13, 5: 6, 6: 5, 7: 2, 8: 1 };
1150
+ const expectedCounts = { 1: 19, 2: 12, 3: 9, 4: 14, 5: 6, 6: 5, 7: 2, 8: 4 };
1151
+ const parsedBatches = [];
1143
1152
  const requiredPerProblemSections = [
1144
1153
  "问题背景", "用户反馈 / 触发场景", "根因分析", "解决方案", "方案细节",
1145
1154
  "硬规则", "非目标", "影响范围", "落地文件", "验收标准", "回归风险",
@@ -1147,6 +1156,7 @@ async function checkBatchIssueFormatConsistency(rootDir, hardFail, _info) {
1147
1156
  ];
1148
1157
  for (let batch = 1; batch <= 8; batch++) {
1149
1158
  const details = loadBatchIssueDetails(batch, rootDir);
1159
+ parsedBatches.push(details);
1150
1160
  if (!details.loaded) {
1151
1161
  hardFail("BATCH_ISSUE_FORMAT_INCONSISTENT", `Batch${batch} 问题集未加载: ${details.error}`, [`docs/SoloForge-Batch${batch}问题集.md`], `Batch${batch}`, "旧 gate 只检查文档存在,不验证是否可解析", "第 3 步统一格式");
1152
1162
  continue;
@@ -1190,7 +1200,27 @@ async function checkBatchIssueFormatConsistency(rootDir, hardFail, _info) {
1190
1200
  }
1191
1201
  _info(` Batch${batch}: 已检查 (${details.issue_count} 个问题)`);
1192
1202
  }
1193
- _info(" 说明: 本检查不修改文档。格式不一致问题将在第 3 步统一处理。");
1203
+ try {
1204
+ const roadmapPath = path.join(rootDir, "dist", "engine", "implementation_roadmap_registry.js");
1205
+ const roadmap = await import(roadmapPath);
1206
+ const roadmapProblems = roadmap.listAllProblems();
1207
+ const ledgerFindings = validateCanonicalIssueLedger(parsedBatches, roadmapProblems, 71);
1208
+ for (const finding of ledgerFindings) {
1209
+ hardFail("CANONICAL_ISSUE_LEDGER_INCONSISTENT", finding.message, ["scripts/batch_issue_details.mjs", "src/engine/implementation_roadmap_registry.ts"], finding.issue_label ?? finding.problem_id ?? "canonical-ledger", "旧 gate 只校验单份文档格式与数量,不校验 Batch 文档和 roadmap 的唯一身份映射", "修正 canonical issue ID、文档归属或 roadmap 登记");
1210
+ }
1211
+ const roadmapFindings = roadmap.validateImplementationRoadmap()
1212
+ .filter((finding) => finding.severity === "hard_fail");
1213
+ for (const finding of roadmapFindings) {
1214
+ hardFail("CANONICAL_ISSUE_LEDGER_INCONSISTENT", finding.message, ["src/engine/implementation_roadmap_registry.ts"], finding.subject_id ?? "roadmap", "旧 gate 不执行 roadmap 内部双向引用校验", "修正 ProblemEntry 与 ImplementationBatch 的双向归属");
1215
+ }
1216
+ if (ledgerFindings.length === 0 && roadmapFindings.length === 0) {
1217
+ _info(" canonical ledger: 问题文档与 roadmap 的 71 个问题身份、归属一致");
1218
+ }
1219
+ }
1220
+ catch (e) {
1221
+ hardFail("CANONICAL_ISSUE_LEDGER_INCONSISTENT", `无法执行 canonical 问题账本对账: ${e.message}`, ["scripts/batch_issue_details.mjs", "src/engine/implementation_roadmap_registry.ts"], "canonical-ledger", "旧 gate 不加载 roadmap 执行文档双向对账", "先完成 build 并修复账本校验入口");
1222
+ }
1223
+ _info(" 说明: 本检查不修改文档;格式或 canonical 身份不一致会阻断发布。");
1194
1224
  }
1195
1225
  // ── 机制身份一致性 ──
1196
1226
  async function checkMechanismIdentityConsistency(rootDir, hardFail, _info) {
@@ -1633,6 +1663,14 @@ async function checkImplementationContractBehavior(rootDir, hardFail, _info) {
1633
1663
  if (!backend.hasBlockingBackendFindings(alignmentFindings)) {
1634
1664
  hardFail("BACKEND_API_OPENAPI_DRIFT_FALSE_PASS", "problem-67: Markdown API 与 OpenAPI endpoint 漂移未阻断", ["src/engine/backend_implementation_contract.ts"], "problem-67", "契约对齐必须由行为检查证明", "修复 API/OpenAPI 漂移检测");
1635
1665
  }
1666
+ const lombokFindings = backend.reviewBackendImplementationFiles({
1667
+ "src/main/java/com/smartcare/staff/entity/Staff.java": "import jakarta.persistence.Entity; import lombok.Data; @Entity @Data public class Staff { private Long id; }",
1668
+ "src/main/java/com/smartcare/staff/dto/StaffCreateRequest.java": "import lombok.Getter; import lombok.Setter; @Getter @Setter public class StaffCreateRequest { private String name; }",
1669
+ });
1670
+ const modelContractFindings = lombokFindings.filter((item) => item.category === "model_lombok_contract" && item.severity === "hard_fail");
1671
+ if (modelContractFindings.length < 2) {
1672
+ hardFail("BACKEND_MODEL_LOMBOK_CONTRACT_FALSE_PASS", "problem-71: Java Entity/DTO/VO/Request Lombok 分层契约未阻断错误注解", ["src/engine/backend_implementation_contract.ts", "templates/scaffolds/spring-boot/Entity.java.hbs", "templates/scaffolds/spring-boot/DTO.java.hbs"], "problem-71", "后端工程契约不能只治理 Controller/DTO 命名,还必须约束数据模型注解语义", "添加 Entity @Getter/@Setter、DTO/VO/Request @Data 的行为检查和脚手架默认值");
1673
+ }
1636
1674
  }
1637
1675
  catch (e) {
1638
1676
  hardFail("IMPLEMENTATION_CONTRACT_EXECUTION_ERROR", `problem-66/67: 实现工程行为检查执行失败: ${e.message}`, ["src/engine/ood_solid_contract.ts", "src/engine/backend_implementation_contract.ts"], "problem-66/problem-67", "契约模块必须可运行并返回确定结果", "修复运行时异常");
@@ -1684,11 +1722,43 @@ async function checkReleaseIssueDesignPath(rootDir, hardFail, _info) {
1684
1722
  if (noTrace.length > 0) {
1685
1723
  hardFail("RELEASE_ISSUE_SCENARIO_NO_PRODUCTION_TRACE", `场景缺少 production_trace: ${noTrace.map(r => r.scenario_id).join("; ")}`, ["src/engine/release_issue_scenario_registry.ts"], "release-issues", "所有场景必须有 production_trace 记录真实入口", "为缺失场景添加 production_trace");
1686
1724
  }
1725
+ // R10.1: 每个场景(非 engine_contract 声明)的 tool_entrypoint 必须包含真实工具名
1726
+ const allRealToolNames = ["sf_classify", "sf_expand", "sf_verify", "sf_deliver", "sf_learn", "sf_accept", "sf_record_verification_execution", "sf_scaffold", "sf_navigation", "soloforge next", "checkArchitectureDecisionWorkshopGate", "planNextAction"];
1727
+ const fakeTraceScenarios = execResults.filter(r => r.production_trace &&
1728
+ !allRealToolNames.some(t => r.production_trace.tool_entrypoint.includes(t)) &&
1729
+ !r.production_trace.gates_consumed?.includes("engine_contract"));
1730
+ if (fakeTraceScenarios.length > 0) {
1731
+ hardFail("RELEASE_ISSUE_SCENARIO_NO_PRODUCTION_TRACE", `场景 production_trace 不引用真实工具入口: ${fakeTraceScenarios.map(r => `${r.scenario_id}(entrypoint=${r.production_trace.tool_entrypoint})`).join("; ")}`, ["src/engine/release_issue_scenario_registry.ts"], "release-issues", "所有场景的 production_trace.tool_entrypoint 必须引用真实工具(sf_classify/sf_expand/adapter gate 等)", "用 createToolHarness 重写 runner");
1732
+ }
1687
1733
  // R12: production_trace 的 diagnostic_codes 必须非空(来自工具返回值或函数调用结果)
1688
1734
  const noDiagCodes = execResults.filter(r => r.production_trace && (!r.production_trace.diagnostic_codes || r.production_trace.diagnostic_codes.length === 0));
1689
1735
  if (noDiagCodes.length > 0) {
1690
1736
  hardFail("RELEASE_ISSUE_SCENARIO_NO_PRODUCTION_TRACE", `场景 production_trace 缺少 diagnostic_codes: ${noDiagCodes.map(r => r.scenario_id).join("; ")}`, ["src/engine/release_issue_scenario_registry.ts"], "release-issues", "所有 production_trace 必须包含来自工具/函数返回值的 diagnostic_codes", "为场景添加真实 diagnostic_codes");
1691
1737
  }
1738
+ // R14: architecture-workshop 非 low-risk 场景必须真正触发 workshop(不得假绿)
1739
+ {
1740
+ const archNonLowRisk = execResults.filter(r => r.scenario_id.startsWith("release-scenario-architecture-workshop-") &&
1741
+ !r.scenario_id.includes("low-risk-skip"));
1742
+ const fakeGreenPatterns = ["workshop=false", "domains=0", "no workshop", "blocked by earlier gate"];
1743
+ const fakeGreens = archNonLowRisk.filter(r => r.status === "pass" && fakeGreenPatterns.some(p => r.evidence?.includes(p)));
1744
+ if (fakeGreens.length > 0) {
1745
+ hardFail("RELEASE_ISSUE_SCENARIO_NO_PRODUCTION_TRACE", `architecture-workshop 场景假绿: ${fakeGreens.map(r => `${r.scenario_id}(evidence=${r.evidence?.slice(0, 120)})`).join("; ")}`, ["src/engine/release_issue_scenario_registry.ts"], "release-issues", "非 low-risk 架构研讨场景必须真正触发 architecture_decision_workshop,不得把证据门阻断当作 pass", "确保场景提供足够证据通过 evidence grounding gate,使 workshop gate 能实际触发");
1746
+ }
1747
+ // 非 low-risk 场景必须同时有 sf_classify 和 sf_expand 入口
1748
+ const missingEntrypoint = archNonLowRisk.filter(r => r.production_trace &&
1749
+ (!r.production_trace.tool_entrypoint.includes("sf_classify") || !r.production_trace.tool_entrypoint.includes("sf_expand")));
1750
+ if (missingEntrypoint.length > 0) {
1751
+ hardFail("RELEASE_ISSUE_SCENARIO_NO_PRODUCTION_TRACE", `architecture-workshop 场景缺少 sf_classify 或 sf_expand 入口: ${missingEntrypoint.map(r => `${r.scenario_id}(entrypoint=${r.production_trace.tool_entrypoint})`).join("; ")}`, ["src/engine/release_issue_scenario_registry.ts"], "release-issues", "architecture-workshop 非 low-risk 场景必须同时通过 sf_classify 和 sf_expand 真实 MCP 工具执行", "使用 createToolHarness + callTool('sf_classify') + callTool('sf_expand') 重写 runner");
1752
+ }
1753
+ }
1754
+ // R15: production_trace.diagnostic_codes 不得包含运行时异常
1755
+ {
1756
+ const runtimeErrorPattern = /Cannot read|TypeError|ReferenceError|^undefined$|uncaught exception|\.error\b/i;
1757
+ const runtimeDiagCodes = execResults.filter(r => r.production_trace?.diagnostic_codes?.some(c => runtimeErrorPattern.test(c)));
1758
+ if (runtimeDiagCodes.length > 0) {
1759
+ hardFail("RELEASE_ISSUE_SCENARIO_DIAGNOSTIC_SWALLOWED_ERROR", `场景 production_trace.diagnostic_codes 包含运行时异常(不是业务/门禁诊断码): ${runtimeDiagCodes.map(r => `${r.scenario_id}(codes=${r.production_trace.diagnostic_codes.filter(c => runtimeErrorPattern.test(c)).join("; ")})`).join("; ")}`, ["src/engine/release_issue_scenario_registry.ts"], "release-issues", "diagnostic_codes 必须来自真实业务/门禁诊断,不得把异常信息当作有效诊断码", "定位异常根因并修复,或确保场景提供完整输入使工具不抛异常");
1760
+ }
1761
+ }
1692
1762
  // R10: 每个 problem 必须至少有一个场景通过真实 MCP 工具入口执行
1693
1763
  const realToolNames = ["sf_classify", "sf_expand", "sf_verify", "sf_deliver", "sf_learn", "sf_accept", "sf_record_verification_execution", "sf_scaffold"];
1694
1764
  // 用 scenario_id 前缀识别 problem 归属
@@ -2301,6 +2371,80 @@ async function checkCodeObservabilityBehavior(rootDir, hardFail, _info) {
2301
2371
  if (!obs.hasBlockingObservabilityFindings(migrationFindings)) {
2302
2372
  hardFail("CODE_OBSERVABILITY_MIGRATION_FALSE_PASS", "problem-68: 迁移脚本无审计日志未被检测到", ["src/engine/code_maintainability_observability_contract.ts"], "problem-68", "数据修复脚本必须有审计日志", "修复 reviewMissingLogs 迁移脚本检测逻辑");
2303
2373
  }
2374
+ // 行为验证: 普通 Spring 后端 Slice 的 CRUD/事务双写无日志无注释也必须被检测。
2375
+ const smartCareSliceFindings = obs.verifyChangedFilesObservability({
2376
+ changed_files: ["src/main/java/com/smartcare/staff/service/impl/StaffServiceImpl.java"],
2377
+ file_contents: {
2378
+ "src/main/java/com/smartcare/staff/service/impl/StaffServiceImpl.java": `@Service
2379
+ public class StaffServiceImpl implements StaffService {
2380
+ @Transactional
2381
+ public StaffVO create(StaffCreateRequest request) {
2382
+ Staff staff = convert(request);
2383
+ staffMapper.insert(staff);
2384
+ SysUser user = buildUser(request);
2385
+ sysUserMapper.insert(user);
2386
+ return toVO(staff);
2387
+ }
2388
+ @Transactional
2389
+ public void resign(Long staffId) {
2390
+ staffMapper.updateStatus(staffId, StaffStatus.RESIGNED);
2391
+ sysUserMapper.updateAccountStatus(staffId, 0);
2392
+ }
2393
+ }`,
2394
+ },
2395
+ intent: "完成 Slice 2 机构设施与员工管理后端实现",
2396
+ });
2397
+ const smartCareBlocked = smartCareSliceFindings.some((f) => f.category === "missing_log_business_write" && f.severity === "hard_fail")
2398
+ && smartCareSliceFindings.some((f) => f.category === "missing_comment_complex");
2399
+ if (!smartCareBlocked) {
2400
+ hardFail("CODE_OBSERVABILITY_BACKEND_SLICE_FALSE_PASS", "problem-68: 普通后端 Slice 的 Controller/Service 事务双写和级联规则无日志无注释未被阻断", ["src/engine/code_maintainability_observability_contract.ts"], "problem-68", "旧门禁只覆盖支付/安全/迁移,真实 CRUD 后端实现仍可无日志无注释通过", "扩展后端实现文件检测,覆盖 Controller/Service/DTO/Entity");
2401
+ }
2402
+ const javaDocAndChineseFindings = obs.verifyChangedFilesObservability({
2403
+ changed_files: ["src/main/java/com/smartcare/staff/service/impl/StaffServiceImpl.java"],
2404
+ file_contents: {
2405
+ "src/main/java/com/smartcare/staff/service/impl/StaffServiceImpl.java": `@Service
2406
+ public class StaffServiceImpl implements StaffService {
2407
+ public StaffVO create(StaffCreateRequest request) {
2408
+ Staff staff = convert(request);
2409
+ staffMapper.insert(staff);
2410
+ logger.info("staff created staffId=" + staff.getId());
2411
+ return toVO(staff);
2412
+ }
2413
+ }`,
2414
+ },
2415
+ intent: "完成 Slice 2 员工管理后端实现",
2416
+ });
2417
+ const javaDocAndChineseBlocked = ["missing_class_doc", "missing_method_doc", "missing_important_line_comment", "non_chinese_log"].every((category) => javaDocAndChineseFindings.some((f) => f.category === category && f.severity === "hard_fail"));
2418
+ if (!javaDocAndChineseBlocked) {
2419
+ hardFail("CODE_OBSERVABILITY_CHINESE_DOC_CONTRACT_FALSE_PASS", "problem-68: 后端类/方法中文 Javadoc、关键行中文注释或中文日志缺失未被阻断", ["src/engine/code_maintainability_observability_contract.ts"], "problem-68", "只检查是否有日志/注释会放过英文日志、无类说明、无方法入参出参说明和关键行业务意图缺失", "扩展 reviewCommentQuality/reviewMissingLogs,强制中文日志、中文注释、类 Javadoc、方法 Javadoc 和关键行注释");
2420
+ }
2421
+ const crossStackFindings = obs.verifyChangedFilesObservability({
2422
+ changed_files: ["src/routes/staff.route.ts", "app/api/staff.py", "src/pages/StaffForm.tsx"],
2423
+ file_contents: {
2424
+ "src/routes/staff.route.ts": `router.post('/staff', async (req, res) => {
2425
+ const staff = await staffRepo.create(req.body);
2426
+ await userRepo.create({ staffId: staff.id });
2427
+ res.json(staff);
2428
+ });`,
2429
+ "app/api/staff.py": `@router.post("/staff")
2430
+ async def create_staff(payload: StaffCreate):
2431
+ staff = await staff_repo.create(payload)
2432
+ await user_repo.create(staff_id=staff.id)
2433
+ return staff`,
2434
+ "src/pages/StaffForm.tsx": `export function StaffForm() {
2435
+ async function handleSubmit(values) {
2436
+ await apiClient.post('/api/institution/staff', values);
2437
+ setStatus('created');
2438
+ }
2439
+ return <form onSubmit={handleSubmit}>...</form>;
2440
+ }`,
2441
+ },
2442
+ intent: "实现员工新增前后端接口与表单",
2443
+ });
2444
+ const crossStackBlocked = crossStackFindings.filter((f) => f.category === "missing_log_business_write" && f.severity === "hard_fail").length >= 3;
2445
+ if (!crossStackBlocked) {
2446
+ hardFail("CODE_OBSERVABILITY_CROSS_STACK_FALSE_PASS", "problem-68: 非 Spring 后端或前端业务写操作无日志/埋点未被阻断", ["src/engine/code_maintainability_observability_contract.ts"], "problem-68", "问题六十八不能只覆盖 Spring 后端文件", "扩展检测到 Nest/Express/FastAPI/Go/React/Vue 等通用实现形态");
2447
+ }
2304
2448
  }
2305
2449
  catch (e) {
2306
2450
  hardFail("CODE_OBSERVABILITY_EXECUTION_ERROR", `problem-68: 可观测性行为检查执行失败: ${e.message}`, ["src/engine/code_maintainability_observability_contract.ts"], "problem-68", "契约模块必须可运行并返回确定结果", "修复运行时异常");
@@ -2393,7 +2537,238 @@ async function refund(order) { order.save(); logger.info("退款成功 orderId="
2393
2537
  }
2394
2538
  _info(" problem-68 代码可维护性与可观测性行为验证完成");
2395
2539
  }
2540
+ // ── 21. 模板卫生与用户项目知识消费验证 ──
2541
+ async function checkTemplateAndProjectKnowledgeHygiene(rootDir, hardFail, _info) {
2542
+ try {
2543
+ const matrix = await import(path.join(rootDir, "dist", "engine", "project_knowledge_system_regression_matrix.js"));
2544
+ const report = matrix.validateProjectKnowledgeSystemMatrix?.();
2545
+ if (!report?.passed) {
2546
+ const messages = (report?.findings ?? []).map((finding) => `${finding.code}: ${finding.message}`).join("; ");
2547
+ hardFail("PROJECT_KNOWLEDGE_SYSTEM_MATRIX_INCOMPLETE", `problem-69: 知识资产与项目规则消费体系 20 项防回归矩阵不完整: ${messages || "未知缺口"}`, ["src/engine/project_knowledge_system_regression_matrix.ts", "docs/SoloForge-Batch8问题集.md"], "problem-69", "旧门禁只检查部分项目知识行为,未覆盖用户讨论的 20 项完整细节", "补齐 20 项矩阵、生产入口、must-fail/must-pass 与文档引用");
2548
+ }
2549
+ }
2550
+ catch (e) {
2551
+ hardFail("PROJECT_KNOWLEDGE_SYSTEM_MATRIX_EXCEPTION", `problem-69: 知识资产与项目规则消费体系矩阵执行失败: ${e.message}`, ["src/engine/project_knowledge_system_regression_matrix.ts"], "problem-69", "项目知识体系矩阵必须可执行", "修复矩阵模块或编译产物");
2552
+ }
2553
+ const scaffoldDir = path.join(rootDir, "templates", "scaffolds");
2554
+ const scaffoldFindings = [];
2555
+ function scanScaffolds(dir) {
2556
+ if (!fs.existsSync(dir))
2557
+ return;
2558
+ for (const entry of fs.readdirSync(dir, { withFileTypes: true })) {
2559
+ const full = path.join(dir, entry.name);
2560
+ if (entry.isDirectory()) {
2561
+ scanScaffolds(full);
2562
+ continue;
2563
+ }
2564
+ if (!entry.isFile())
2565
+ continue;
2566
+ const rel = path.relative(rootDir, full).replace(/\\/g, "/");
2567
+ const lines = safeReadLines(full);
2568
+ for (let i = 0; i < lines.length; i++) {
2569
+ if (/\b(?:TODO|FIXME|HACK|XXX)\b/.test(lines[i])) {
2570
+ scaffoldFindings.push(`${rel}:${i + 1}`);
2571
+ }
2572
+ }
2573
+ }
2574
+ }
2575
+ scanScaffolds(scaffoldDir);
2576
+ for (const finding of scaffoldFindings) {
2577
+ hardFail("TEMPLATE_SCAFFOLD_PLACEHOLDER_LEAK", `脚手架模板仍包含未落地占位标记: ${finding}`, [finding.split(":")[0]], "template-hygiene", "历史门禁只检查模板是否注册,不检查用户生成代码是否带 TODO/FIXME", "清理脚手架占位标记并补行为测试");
2578
+ }
2579
+ try {
2580
+ const contractMod = await import(path.join(rootDir, "dist", "engine", "template_asset_contract_registry.js"));
2581
+ const contracts = contractMod.listTemplateAssetContracts?.() ?? [];
2582
+ const forbidden = /\b(?:Batch[1-8]|validate-batch|ReleaseFix|problem-\d+)\b/i;
2583
+ for (const contract of contracts) {
2584
+ if (!contract.user_visible)
2585
+ continue;
2586
+ if (!contract.path || !/\.(md|ya?ml|hbs|json)$/i.test(contract.path))
2587
+ continue;
2588
+ const abs = path.join(rootDir, contract.path);
2589
+ const content = safeRead(abs);
2590
+ if (!content || !forbidden.test(content))
2591
+ continue;
2592
+ hardFail("USER_VISIBLE_TEMPLATE_INTERNAL_TRACE", `用户可见模板泄漏内部施工语义: ${contract.path}`, [contract.path], "template-hygiene", "旧门禁只检查内置资产可见性,不检查用户可见模板内容是否混入内部批次/问题编号", "清理用户可见模板内部语义");
2593
+ }
2594
+ }
2595
+ catch (e) {
2596
+ hardFail("TEMPLATE_CONTRACT_AUDIT_FAILED", `无法执行用户可见模板内容审计: ${e.message}`, ["src/engine/template_asset_contract_registry.ts"], "template-hygiene", "模板内容卫生必须可运行审计", "修复模板合同注册表或编译产物");
2597
+ }
2598
+ try {
2599
+ const obs = await import(path.join(rootDir, "dist", "engine", "code_maintainability_observability_contract.js"));
2600
+ const ordinaryApiImplementationTriggers = obs.requiresCodeObservabilityContract?.("实现机构端入住列表接口", "code_change") === true;
2601
+ if (!ordinaryApiImplementationTriggers) {
2602
+ hardFail("CODE_OBSERVABILITY_BUSINESS_API_NOT_TRIGGERED", "problem-68: 普通业务接口实现未触发代码注释与日志契约,真实项目会绕过问题六十八", ["src/engine/code_maintainability_observability_contract.ts", "src/adapters/claude_code/tools.ts"], "problem-68", "旧行为只覆盖支付/安全等关键词,未覆盖普通 Controller/API/Service 编码", "扩展触发条件并在 sf_expand 聚合返回工作包");
2603
+ }
2604
+ }
2605
+ catch (e) {
2606
+ hardFail("CODE_OBSERVABILITY_TRIGGER_AUDIT_FAILED", `问题六十八触发审计执行失败: ${e.message}`, ["src/engine/code_maintainability_observability_contract.ts"], "problem-68", "触发判断必须可运行", "修复编译或导出");
2607
+ }
2608
+ const tmp = fs.mkdtempSync(path.join(os.tmpdir(), "soloforge-project-knowledge-"));
2609
+ try {
2610
+ const goodDir = path.join(tmp, ".soloforge", "knowledge", "rules");
2611
+ fs.mkdirSync(goodDir, { recursive: true });
2612
+ fs.writeFileSync(path.join(goodDir, "API响应规则.md"), `---
2613
+ id: smartcare-api-response-rule
2614
+ asset_kind: hard_rule
2615
+ routes:
2616
+ - code_change
2617
+ - artifact_generation
2618
+ primary_triggers:
2619
+ - API
2620
+ - 接口
2621
+ - 响应
2622
+ tech_stack:
2623
+ - spring
2624
+ enforcement:
2625
+ level: hard_rule
2626
+ ---
2627
+ # API 响应规则
2628
+
2629
+ 必须使用项目统一响应结构,错误码、字段表和 OpenAPI 保持一致。
2630
+ `, "utf-8");
2631
+ const projectKnowledge = await import(path.join(rootDir, "dist", "engine", "project_knowledge_contract.js"));
2632
+ const report = projectKnowledge.auditProjectKnowledge(tmp);
2633
+ if (report.hard_fail_count !== 0 || report.injectable < 1) {
2634
+ hardFail("PROJECT_KNOWLEDGE_RULE_NOT_CONSUMABLE", "用户项目 .soloforge/knowledge hard rule 未被识别为可消费规则", ["src/engine/project_knowledge_contract.ts"], "project-knowledge-consumption", "旧链路只同步/索引内置模板,未证明用户项目自定义规则可被承载消费", "修复项目知识合同审计与选择逻辑");
2635
+ }
2636
+ const selected = projectKnowledge.selectProjectKnowledgeForTask(report, {
2637
+ intent: "实现机构端 API 接口统一响应",
2638
+ route: "code_change",
2639
+ tech_stack: ["spring"],
2640
+ });
2641
+ if (!selected.some((asset) => asset.id === "smartcare-api-response-rule")) {
2642
+ hardFail("PROJECT_KNOWLEDGE_RULE_NOT_SELECTED", "用户项目自定义 hard rule 未按意图/路由/技术栈被选中", ["src/engine/project_knowledge_contract.ts"], "project-knowledge-consumption", "索引存在不等于任务会消费", "补齐 selectProjectKnowledgeForTask 行为和门禁");
2643
+ }
2644
+ const expander = await import(path.join(rootDir, "dist", "engine", "intent_expander.js"));
2645
+ const expandResult = await expander.expand({
2646
+ intent: "实现机构端 API 接口统一响应",
2647
+ classification: {
2648
+ task_type: "feature",
2649
+ risk: "low",
2650
+ complexity: "low",
2651
+ ambiguity: "low",
2652
+ affected_repos: ["backend"],
2653
+ mode: "autonomous",
2654
+ strategy: "full_pipeline",
2655
+ },
2656
+ projectPath: tmp,
2657
+ config: {
2658
+ name: "release-gate-project-knowledge",
2659
+ tech_stack: {
2660
+ backend: { lang: "java", framework: "spring-boot", version: "3" },
2661
+ frontend: { lang: "typescript", framework: "react", version: "18" },
2662
+ },
2663
+ product_profile: "saas",
2664
+ repos: [{ name: "backend", path: "backend", lang: "java", framework: "spring-boot", scope: ["backend/src/"] }],
2665
+ build_commands: {
2666
+ backend: { build: "mvn compile", test: "mvn test", full: "mvn verify" },
2667
+ frontend: { build: "npm run build", test: "npm test", full: "npm test" },
2668
+ },
2669
+ scope: { backend: ["backend/src/"], frontend: ["src/"] },
2670
+ _projectPath: tmp,
2671
+ },
2672
+ knowledgeIndex: { query: () => [], markUsed: () => { } },
2673
+ route_decision: {
2674
+ route: "code_change",
2675
+ execution_shape: "code_execution",
2676
+ mutation_allowed: true,
2677
+ input_materials: [],
2678
+ missing_required_inputs: [],
2679
+ scope: { read_only: false },
2680
+ constraints: [],
2681
+ language_policy: { primary: "zh" },
2682
+ confidence: 0.9,
2683
+ evidence: [],
2684
+ rejected_routes: [],
2685
+ conflicts: [],
2686
+ decision_version: 1,
2687
+ },
2688
+ });
2689
+ if (!expandResult.project_knowledge?.selected?.some((asset) => asset.id === "smartcare-api-response-rule")
2690
+ || !String(expandResult.prompt).includes("## 用户项目规则")) {
2691
+ hardFail("PROJECT_KNOWLEDGE_NOT_IN_EXPAND_MAINPATH", "用户项目规则未进入 sf_expand 真实 prompt 和结构化结果", ["src/engine/intent_expander.ts", "src/engine/project_knowledge_contract.ts"], "project-knowledge-consumption", "审计/选择通过不等于 MCP 主链路会消费", "将项目规则接入 sf_expand 并补主链路测试");
2692
+ }
2693
+ const nextPlanner = await import(path.join(rootDir, "dist", "engine", "next_action_planner.js"));
2694
+ const nextPlan = await nextPlanner.planNextAction(tmp);
2695
+ if (!nextPlan.project_knowledge_context || nextPlan.project_knowledge_context.total < 1) {
2696
+ hardFail("PROJECT_KNOWLEDGE_NOT_IN_NEXT", "soloforge next 未携带项目知识上下文,用户无法知道下一步会应用哪些项目规则", ["src/engine/next_action_planner.ts", "src/bin/soloforge.ts"], "project-knowledge-consumption", "旧导航只看阶段,不看项目规则", "将项目知识上下文接入 next JSON 和人类输出");
2697
+ }
2698
+ const badTmp = fs.mkdtempSync(path.join(os.tmpdir(), "soloforge-project-knowledge-bad-"));
2699
+ try {
2700
+ const badDir = path.join(badTmp, ".soloforge", "knowledge", "rules");
2701
+ fs.mkdirSync(badDir, { recursive: true });
2702
+ fs.writeFileSync(path.join(badDir, "未路由强规则.md"), "# 未路由强规则\n\n必须使用统一响应。", "utf-8");
2703
+ const badReport = projectKnowledge.auditProjectKnowledge(badTmp);
2704
+ if (badReport.hard_fail_count === 0) {
2705
+ hardFail("PROJECT_KNOWLEDGE_UNROUTED_RULE_FALSE_PASS", "用户项目 hard rule 缺少 routes/primary_triggers 时错误放行", ["src/engine/project_knowledge_contract.ts"], "project-knowledge-consumption", "规则文件存在不等于可稳定消费", "对未路由 hard rule fail-closed");
2706
+ }
2707
+ }
2708
+ finally {
2709
+ fs.rmSync(badTmp, { recursive: true, force: true });
2710
+ }
2711
+ const conflictTmp = fs.mkdtempSync(path.join(os.tmpdir(), "soloforge-project-knowledge-conflict-"));
2712
+ try {
2713
+ const conflictDir = path.join(conflictTmp, ".soloforge", "knowledge", "rules");
2714
+ fs.mkdirSync(conflictDir, { recursive: true });
2715
+ fs.writeFileSync(path.join(conflictDir, "响应A.md"), `---
2716
+ id: response-a
2717
+ asset_kind: hard_rule
2718
+ routes: [code_change]
2719
+ primary_triggers: [API]
2720
+ conflict_group: api_response_format
2721
+ rule_value: ApiResponse
2722
+ enforcement:
2723
+ level: hard_rule
2724
+ ---
2725
+ # 响应 A
2726
+
2727
+ 必须使用 ApiResponse。
2728
+ `, "utf-8");
2729
+ fs.writeFileSync(path.join(conflictDir, "响应B.md"), `---
2730
+ id: response-b
2731
+ asset_kind: hard_rule
2732
+ routes: [code_change]
2733
+ primary_triggers: [API]
2734
+ conflict_group: api_response_format
2735
+ rule_value: Result
2736
+ enforcement:
2737
+ level: hard_rule
2738
+ ---
2739
+ # 响应 B
2740
+
2741
+ 必须使用 Result。
2742
+ `, "utf-8");
2743
+ const conflictReport = projectKnowledge.auditProjectKnowledge(conflictTmp);
2744
+ if (!conflictReport.assets.some((asset) => asset.findings.some((finding) => finding.code === "PROJECT_KNOWLEDGE_RULE_CONFLICT"))) {
2745
+ hardFail("PROJECT_KNOWLEDGE_CONFLICT_FALSE_PASS", "项目 API 响应等规则冲突未进入用户确认", ["src/engine/project_knowledge_contract.ts"], "project-knowledge-consumption", "旧项目知识审计只看是否可选中,不看项目规则之间互斥", "补冲突组/显式 conflicts_with 检测");
2746
+ }
2747
+ }
2748
+ finally {
2749
+ fs.rmSync(conflictTmp, { recursive: true, force: true });
2750
+ }
2751
+ }
2752
+ catch (e) {
2753
+ hardFail("PROJECT_KNOWLEDGE_AUDIT_FAILED", `用户项目知识消费行为审计执行失败: ${e.message}`, ["src/engine/project_knowledge_contract.ts"], "project-knowledge-consumption", "项目知识消费必须有可执行行为门禁", "修复项目知识合同模块和编译产物");
2754
+ }
2755
+ finally {
2756
+ fs.rmSync(tmp, { recursive: true, force: true });
2757
+ }
2758
+ _info(" 模板卫生与项目知识消费验证完成");
2759
+ }
2396
2760
  async function checkLongTermMechanization(rootDir, hardFail, _info) {
2761
+ try {
2762
+ const matrix = await import("./historical_issue_mechanization_matrix.js");
2763
+ const report = matrix.validateHistoricalIssueMechanizationMatrix?.();
2764
+ if (!report?.passed) {
2765
+ const messages = (report?.findings ?? []).map((finding) => `${finding.code}: ${finding.message}`).join("; ");
2766
+ hardFail("HISTORICAL_FIRST_PRINCIPLES_MATRIX_INCOMPLETE", `历史问题第一性原理机制化矩阵不完整: ${messages || "未知缺口"}`, ["src/engine/historical_issue_mechanization_matrix.ts", "docs/SoloForge-Batch8问题集.md"], "problem-70", "旧复审只统计 mechanized/partial/not,不强制每个历史问题归入长期机制族", "补齐历史问题机制族、生产入口、must-fail/must-pass 和发布门禁");
2767
+ }
2768
+ }
2769
+ catch (e) {
2770
+ hardFail("HISTORICAL_FIRST_PRINCIPLES_MATRIX_EXCEPTION", `历史问题第一性原理矩阵检查异常: ${e.message}`, ["src/engine/historical_issue_mechanization_matrix.ts"], "problem-70", "旧 gate 不执行历史问题矩阵", "修复历史问题机制化矩阵模块");
2771
+ }
2397
2772
  try {
2398
2773
  const health = await import("./mechanism_health_check.js");
2399
2774
  const report = health.runFullHealthCheck(rootDir);
@@ -2567,6 +2942,10 @@ export async function runReleaseReadinessGate(rootDir) {
2567
2942
  beginPhase("诊断码集中治理检查");
2568
2943
  await checkDiagnosticCentralization(rootDir, hardFail);
2569
2944
  endPhase("诊断码集中治理检查");
2945
+ // 21: 模板卫生与用户项目知识消费验证
2946
+ beginPhase("模板卫生与项目知识消费验证");
2947
+ await checkTemplateAndProjectKnowledgeHygiene(rootDir, hardFail, _info);
2948
+ endPhase("模板卫生与项目知识消费验证");
2570
2949
  // 恢复 console.error 和日志器
2571
2950
  console.error = origConsoleError;
2572
2951
  resetLogger();