soloforge 1.3.0 → 1.3.2

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 (165) hide show
  1. package/README.md +15 -2
  2. package/dist/adapters/claude_code/claude_md.d.ts.map +1 -1
  3. package/dist/adapters/claude_code/claude_md.js +4 -0
  4. package/dist/adapters/claude_code/claude_md.js.map +1 -1
  5. package/dist/adapters/claude_code/tools.d.ts +36 -9
  6. package/dist/adapters/claude_code/tools.d.ts.map +1 -1
  7. package/dist/adapters/claude_code/tools.js +828 -19
  8. package/dist/adapters/claude_code/tools.js.map +1 -1
  9. package/dist/adapters/shared/workflow_template.d.ts +26 -0
  10. package/dist/adapters/shared/workflow_template.d.ts.map +1 -1
  11. package/dist/adapters/shared/workflow_template.js +139 -40
  12. package/dist/adapters/shared/workflow_template.js.map +1 -1
  13. package/dist/bin/soloforge.d.ts.map +1 -1
  14. package/dist/bin/soloforge.js +220 -45
  15. package/dist/bin/soloforge.js.map +1 -1
  16. package/dist/engine/architecture_decision_workshop.d.ts +58 -0
  17. package/dist/engine/architecture_decision_workshop.d.ts.map +1 -0
  18. package/dist/engine/architecture_decision_workshop.js +118 -0
  19. package/dist/engine/architecture_decision_workshop.js.map +1 -0
  20. package/dist/engine/asset_manifest.d.ts +7 -1
  21. package/dist/engine/asset_manifest.d.ts.map +1 -1
  22. package/dist/engine/asset_manifest.js +36 -15
  23. package/dist/engine/asset_manifest.js.map +1 -1
  24. package/dist/engine/consumable_asset_registry.d.ts.map +1 -1
  25. package/dist/engine/consumable_asset_registry.js +90 -1
  26. package/dist/engine/consumable_asset_registry.js.map +1 -1
  27. package/dist/engine/consumption_trace_store.d.ts +8 -8
  28. package/dist/engine/consumption_trace_store.d.ts.map +1 -1
  29. package/dist/engine/consumption_trace_store.js +11 -7
  30. package/dist/engine/consumption_trace_store.js.map +1 -1
  31. package/dist/engine/decision_workshop.d.ts +160 -0
  32. package/dist/engine/decision_workshop.d.ts.map +1 -0
  33. package/dist/engine/decision_workshop.js +279 -0
  34. package/dist/engine/decision_workshop.js.map +1 -0
  35. package/dist/engine/design_artifact_pack.d.ts +44 -0
  36. package/dist/engine/design_artifact_pack.d.ts.map +1 -0
  37. package/dist/engine/design_artifact_pack.js +167 -0
  38. package/dist/engine/design_artifact_pack.js.map +1 -0
  39. package/dist/engine/dual_layer_mechanism_registry.d.ts.map +1 -1
  40. package/dist/engine/dual_layer_mechanism_registry.js +294 -0
  41. package/dist/engine/dual_layer_mechanism_registry.js.map +1 -1
  42. package/dist/engine/evidence_grounding_contract.d.ts +137 -0
  43. package/dist/engine/evidence_grounding_contract.d.ts.map +1 -0
  44. package/dist/engine/evidence_grounding_contract.js +410 -0
  45. package/dist/engine/evidence_grounding_contract.js.map +1 -0
  46. package/dist/engine/explicit_asset_registry.d.ts +30 -0
  47. package/dist/engine/explicit_asset_registry.d.ts.map +1 -0
  48. package/dist/engine/explicit_asset_registry.js +3508 -0
  49. package/dist/engine/explicit_asset_registry.js.map +1 -0
  50. package/dist/engine/implementation_roadmap_registry.d.ts +2 -2
  51. package/dist/engine/implementation_roadmap_registry.d.ts.map +1 -1
  52. package/dist/engine/implementation_roadmap_registry.js +163 -6
  53. package/dist/engine/implementation_roadmap_registry.js.map +1 -1
  54. package/dist/engine/instruction_contract.d.ts +8 -1
  55. package/dist/engine/instruction_contract.d.ts.map +1 -1
  56. package/dist/engine/instruction_contract.js +63 -1
  57. package/dist/engine/instruction_contract.js.map +1 -1
  58. package/dist/engine/intent_expander.d.ts.map +1 -1
  59. package/dist/engine/intent_expander.js +60 -2
  60. package/dist/engine/intent_expander.js.map +1 -1
  61. package/dist/engine/intent_router.d.ts +1 -1
  62. package/dist/engine/intent_router.d.ts.map +1 -1
  63. package/dist/engine/intent_router.js +2 -1
  64. package/dist/engine/intent_router.js.map +1 -1
  65. package/dist/engine/knowledge_injection_boundary.d.ts +3 -0
  66. package/dist/engine/knowledge_injection_boundary.d.ts.map +1 -1
  67. package/dist/engine/knowledge_injection_boundary.js +48 -5
  68. package/dist/engine/knowledge_injection_boundary.js.map +1 -1
  69. package/dist/engine/mechanism_contract_registry.d.ts +1 -1
  70. package/dist/engine/mechanism_contract_registry.d.ts.map +1 -1
  71. package/dist/engine/mechanism_contract_registry.js +138 -0
  72. package/dist/engine/mechanism_contract_registry.js.map +1 -1
  73. package/dist/engine/observed_consumption.d.ts +54 -0
  74. package/dist/engine/observed_consumption.d.ts.map +1 -0
  75. package/dist/engine/observed_consumption.js +377 -0
  76. package/dist/engine/observed_consumption.js.map +1 -0
  77. package/dist/engine/platform_context.d.ts.map +1 -1
  78. package/dist/engine/platform_context.js +6 -2
  79. package/dist/engine/platform_context.js.map +1 -1
  80. package/dist/engine/release_gate_scenario_registry.d.ts +16 -1
  81. package/dist/engine/release_gate_scenario_registry.d.ts.map +1 -1
  82. package/dist/engine/release_gate_scenario_registry.js +205 -2
  83. package/dist/engine/release_gate_scenario_registry.js.map +1 -1
  84. package/dist/engine/release_issue_scenario_registry.d.ts +64 -0
  85. package/dist/engine/release_issue_scenario_registry.d.ts.map +1 -0
  86. package/dist/engine/release_issue_scenario_registry.js +1349 -0
  87. package/dist/engine/release_issue_scenario_registry.js.map +1 -0
  88. package/dist/engine/release_readiness_gate.d.ts +8 -0
  89. package/dist/engine/release_readiness_gate.d.ts.map +1 -1
  90. package/dist/engine/release_readiness_gate.js +1144 -6
  91. package/dist/engine/release_readiness_gate.js.map +1 -1
  92. package/dist/engine/release_tool_harness.d.ts +71 -0
  93. package/dist/engine/release_tool_harness.d.ts.map +1 -0
  94. package/dist/engine/release_tool_harness.js +161 -0
  95. package/dist/engine/release_tool_harness.js.map +1 -0
  96. package/dist/engine/scaffolder.d.ts.map +1 -1
  97. package/dist/engine/scaffolder.js +144 -7
  98. package/dist/engine/scaffolder.js.map +1 -1
  99. package/dist/engine/standard_asset_contract.d.ts +75 -0
  100. package/dist/engine/standard_asset_contract.d.ts.map +1 -0
  101. package/dist/engine/standard_asset_contract.js +388 -0
  102. package/dist/engine/standard_asset_contract.js.map +1 -0
  103. package/dist/engine/standard_asset_coverage.d.ts +45 -0
  104. package/dist/engine/standard_asset_coverage.d.ts.map +1 -0
  105. package/dist/engine/standard_asset_coverage.js +220 -0
  106. package/dist/engine/standard_asset_coverage.js.map +1 -0
  107. package/dist/engine/task_context.d.ts +9 -2
  108. package/dist/engine/task_context.d.ts.map +1 -1
  109. package/dist/engine/task_context.js +49 -12
  110. package/dist/engine/task_context.js.map +1 -1
  111. package/dist/engine/template_asset_contract_registry.d.ts +162 -0
  112. package/dist/engine/template_asset_contract_registry.d.ts.map +1 -0
  113. package/dist/engine/template_asset_contract_registry.js +598 -0
  114. package/dist/engine/template_asset_contract_registry.js.map +1 -0
  115. package/dist/engine/template_asset_visibility.d.ts +109 -0
  116. package/dist/engine/template_asset_visibility.d.ts.map +1 -0
  117. package/dist/engine/template_asset_visibility.js +321 -0
  118. package/dist/engine/template_asset_visibility.js.map +1 -0
  119. package/dist/engine/template_init_sync.d.ts +68 -0
  120. package/dist/engine/template_init_sync.d.ts.map +1 -0
  121. package/dist/engine/template_init_sync.js +218 -0
  122. package/dist/engine/template_init_sync.js.map +1 -0
  123. package/dist/engine/template_manifest_io.d.ts +10 -0
  124. package/dist/engine/template_manifest_io.d.ts.map +1 -1
  125. package/dist/engine/template_manifest_io.js +63 -30
  126. package/dist/engine/template_manifest_io.js.map +1 -1
  127. package/dist/engine/template_mechanism_auditor.d.ts +3 -1
  128. package/dist/engine/template_mechanism_auditor.d.ts.map +1 -1
  129. package/dist/engine/template_mechanism_auditor.js +27 -24
  130. package/dist/engine/template_mechanism_auditor.js.map +1 -1
  131. package/dist/engine/tool_invocation_contract_registry.d.ts.map +1 -1
  132. package/dist/engine/tool_invocation_contract_registry.js +11 -1
  133. package/dist/engine/tool_invocation_contract_registry.js.map +1 -1
  134. package/dist/engine/verifier.d.ts.map +1 -1
  135. package/dist/engine/verifier.js +3 -40
  136. package/dist/engine/verifier.js.map +1 -1
  137. package/dist/engine/workflow_contract_registry.d.ts.map +1 -1
  138. package/dist/engine/workflow_contract_registry.js +4 -3
  139. package/dist/engine/workflow_contract_registry.js.map +1 -1
  140. package/dist/knowledge/index_manager.d.ts +20 -0
  141. package/dist/knowledge/index_manager.d.ts.map +1 -1
  142. package/dist/knowledge/index_manager.js +234 -3
  143. package/dist/knowledge/index_manager.js.map +1 -1
  144. package/dist/types.d.ts +47 -0
  145. package/dist/types.d.ts.map +1 -1
  146. package/package.json +5 -4
  147. package/templates/knowledge/acceptance_templates/API/346/216/245/345/217/243/350/247/204/346/240/274/346/226/207/346/241/243/346/250/241/347/211/210.md +74 -0
  148. package/templates/knowledge/acceptance_templates//346/225/260/346/215/256/345/272/223/350/256/276/350/256/241/346/226/207/346/241/243/346/250/241/347/211/210.md +59 -0
  149. package/templates/knowledge/acceptance_templates//346/236/266/346/236/204/350/256/276/350/256/241/346/250/241/347/211/210.md +27 -7
  150. package/templates/knowledge/acceptance_templates//350/256/276/350/256/241/344/270/200/350/207/264/346/200/247/351/252/214/346/224/266/346/212/245/345/221/212/346/250/241/347/211/210.md +47 -0
  151. package/templates/knowledge/procedures//346/236/266/346/236/204/345/206/263/347/255/226/347/240/224/350/256/250/345/267/245/344/275/234/346/265/201.md +51 -0
  152. package/templates/knowledge/procedures//346/236/266/346/236/204/350/256/276/350/256/241/345/267/245/344/275/234/346/265/201.md +16 -7
  153. package/templates/knowledge/procedures//350/256/276/350/256/241/344/272/247/347/211/251/347/224/237/346/210/220/344/270/216/345/244/215/351/252/214/345/267/245/344/275/234/346/265/201.md +45 -0
  154. package/templates/knowledge/rules//345/267/245/344/275/234/346/265/201/346/250/241/346/235/277/345/214/205/350/247/204/345/210/231.md +10 -0
  155. package/templates/knowledge/rules//346/211/251/345/261/225/347/224/237/345/221/275/345/221/250/346/234/237/350/247/204/345/210/231.md +10 -0
  156. package/templates/knowledge/rules//346/226/275/345/267/245/346/214/207/344/273/244/345/245/221/347/272/246/350/247/204/345/210/231.md +33 -4
  157. package/templates/knowledge/rules//346/236/266/346/236/204/345/206/263/347/255/226/347/240/224/350/256/250/350/247/204/345/210/231.md +49 -0
  158. package/templates/knowledge/rules//346/240/207/345/207/206/350/265/204/344/272/247/350/246/206/347/233/226/350/247/204/345/210/231.md +29 -0
  159. package/templates/knowledge/rules//346/250/241/346/235/277/350/265/204/344/272/247/345/217/257/350/247/201/346/200/247/350/247/204/345/210/231.md +27 -0
  160. package/templates/knowledge/rules//347/224/250/346/210/267/345/217/215/351/246/210/345/245/221/347/272/246/350/247/204/345/210/231.md +62 -1
  161. package/templates/knowledge/rules//350/256/276/350/256/241/344/272/247/347/211/251/345/214/205/350/247/204/345/210/231.md +55 -0
  162. package/templates/knowledge/rules//350/257/201/346/215/256/351/251/261/345/212/250/344/270/216/345/217/215/345/271/273/350/247/211/350/247/204/345/210/231.md +75 -0
  163. package/templates/knowledge/rules//350/267/250/345/271/263/345/217/260/350/267/257/345/276/204/345/256/211/345/205/250/350/247/204/345/210/231.md +10 -0
  164. package/templates/knowledge/rules//351/200/232/347/224/250/345/206/263/347/255/226/347/240/224/350/256/250/350/247/204/345/210/231.md +30 -0
  165. package/templates/knowledge/rules//351/252/214/346/224/266/346/250/241/346/235/277/350/276/223/345/207/272/345/245/221/347/272/246/350/247/204/345/210/231.md +50 -0
@@ -13,8 +13,17 @@
13
13
  * 5. 旧 gate 误导标记
14
14
  * 6. 测试污染初筛
15
15
  * 7. Batch 问题文档格式一致性
16
+ * 8. 关键问题消费验证
17
+ * 9. 机制身份一致性
18
+ * 10. P0/P1 知识资产 schema
19
+ * 11. engine console.log 检查
20
+ * 12. 中文注释检查
21
+ * 13. 第五批一致性校验
22
+ * 14. 依赖漏洞扫描
23
+ * 15. 发布问题架构决策与设计产物真实消费
16
24
  */
17
25
  import fs from "node:fs";
26
+ import os from "node:os";
18
27
  import path from "node:path";
19
28
  // ── 辅助函数 ──
20
29
  function safeRead(filePath) {
@@ -218,7 +227,7 @@ const CONSUMER_ENTRY_FILES = {
218
227
  "CLI validate-knowledge": ["src/engine/knowledge_scenario_registry.ts"],
219
228
  "sf_verify": ["src/engine/verifier.ts"],
220
229
  "sf_deliver": ["src/adapters/claude_code/tools.ts", "src/engine/delivery_readiness.ts"],
221
- "sf_expand": ["src/engine/intent_expander.ts"],
230
+ "sf_expand": ["src/adapters/claude_code/tools.ts", "src/engine/intent_expander.ts"],
222
231
  "问题管理流程": [],
223
232
  };
224
233
  /** 模块名→文件路径映射 (用于模块间调用链如 "knowledge_evolution.validateCandidateTrust") */
@@ -435,7 +444,15 @@ function checkMainlineConsumption(rootDir, hardFail, _info) {
435
444
  continue;
436
445
  const staticImportPattern = new RegExp(`import\\s+\\{[^}]*\\b${ep}\\b[^}]*\\}\\s+from`);
437
446
  const dynamicImportPattern = new RegExp(`\\{[^}]*\\b${ep}\\b[^}]*\\}\\s*=\\s*await\\s+import\\(`);
438
- const hasImport = staticImportPattern.test(consumerContent) || dynamicImportPattern.test(consumerContent);
447
+ const hasLazyModuleImport = entry.owner_files.some((ownerFile) => {
448
+ const moduleName = path.basename(ownerFile, ".ts");
449
+ return consumerContent.includes(`import("../../engine/${moduleName}.js")`)
450
+ || consumerContent.includes(`import("../engine/${moduleName}.js")`)
451
+ || consumerContent.includes(`import("./${moduleName}.js")`);
452
+ });
453
+ const hasImport = staticImportPattern.test(consumerContent)
454
+ || dynamicImportPattern.test(consumerContent)
455
+ || hasLazyModuleImport;
439
456
  if (!hasImport)
440
457
  continue;
441
458
  const importLinePattern = new RegExp(`import\\s+\\{[^}]*\\b${ep}\\b|\\{[^}]*\\b${ep}\\b[^}]*\\}\\s*=\\s*await\\s+import`);
@@ -636,7 +653,7 @@ function checkTestPollution(rootDir, hardFail, _info) {
636
653
  }
637
654
  }
638
655
  // ── 8. 关键问题消费验证 (problem-17/38/57) ──
639
- function checkCriticalProblemConsumption(rootDir, hardFail, _info) {
656
+ async function checkCriticalProblemConsumption(rootDir, hardFail, _info) {
640
657
  const taskContextPath = path.join(rootDir, "src/engine/task_context.ts");
641
658
  const cliPath = path.join(rootDir, "src/bin/soloforge.ts");
642
659
  // problem-17: StateFact 分区 — save() 必须调用 validateStateFactPartition,hard_fail 必须 throw
@@ -765,6 +782,342 @@ function checkCriticalProblemConsumption(rootDir, hardFail, _info) {
765
782
  }
766
783
  }
767
784
  _info(" problem-17/38/57 消费验证完成");
785
+ // 问题六十:证据驱动与反幻觉契约 — 行为级检查
786
+ {
787
+ const evidenceGroundingPath = path.join(rootDir, "src/engine/evidence_grounding_contract.ts");
788
+ if (!fs.existsSync(evidenceGroundingPath)) {
789
+ hardFail("CRITICAL_PROBLEM_FILE_MISSING", "problem-60: evidence_grounding_contract.ts 不存在", ["src/engine/evidence_grounding_contract.ts"], "problem-60", "发布门禁不检查 problem-60 实现", "创建 evidence_grounding_contract.ts");
790
+ }
791
+ else {
792
+ // 行为级检查:动态导入并验证核心门禁行为
793
+ try {
794
+ const eg = await import(path.join(rootDir, "dist/engine/evidence_grounding_contract.js"));
795
+ const sys = eg.createEvidenceGroundingSystem();
796
+ // 检查 1:无证据高风险 claim 必须被 blocked
797
+ {
798
+ const noEvClaims = [{
799
+ id: "gate-test-no-ev", claim_text: "项目使用 React", category: "project_fact",
800
+ evidence_ids: [], is_uncertain: false, risk_level: "high",
801
+ }];
802
+ const noEvResult = sys.verifier.verify(noEvClaims, "high");
803
+ if (noEvResult.unsupported.length === 0) {
804
+ hardFail("CRITICAL_PROBLEM_BEHAVIOR", "problem-60: 无证据高风险 claim 未被 blocked", ["src/engine/evidence_grounding_contract.ts"], "problem-60", "FactClaimVerifier 未阻断无证据声明", "验证 classify 逻辑");
805
+ }
806
+ }
807
+ // 检查 2:generated_inference 单独支撑 project_fact 必须被 blocked
808
+ {
809
+ const infEv = sys.registry.register({
810
+ source_type: "generated_inference", authority: "weak", freshness: "unknown",
811
+ permission: "allowed", scope: "test", description: "推理",
812
+ });
813
+ const infClaims = [{
814
+ id: "gate-test-inference", claim_text: "项目使用 Vue", category: "tech_stack",
815
+ evidence_ids: [infEv.id], is_uncertain: false, risk_level: "high",
816
+ }];
817
+ const infResult = sys.verifier.verify(infClaims, "high");
818
+ if (infResult.unsupported.length === 0) {
819
+ hardFail("CRITICAL_PROBLEM_BEHAVIOR", "problem-60: generated_inference 单独支撑 project_fact 未被 blocked", ["src/engine/evidence_grounding_contract.ts"], "problem-60", "FactClaimVerifier 未阻断推理自证", "验证 isSelfAttestation 逻辑");
820
+ }
821
+ }
822
+ // 检查 3:validation_plan 冒充 validation_result 必须被 blocked
823
+ {
824
+ const planEv = sys.registry.register({
825
+ source_type: "generated_inference", authority: "weak", freshness: "unknown",
826
+ permission: "allowed", scope: "test", description: "验证计划",
827
+ });
828
+ const planClaims = [{
829
+ id: "gate-test-plan-as-result", claim_text: "验证已通过",
830
+ category: "validation_result",
831
+ evidence_ids: [planEv.id], is_uncertain: false, risk_level: "high",
832
+ }];
833
+ const planResult = sys.verifier.verify(planClaims, "high");
834
+ if (planResult.unsupported.length === 0) {
835
+ hardFail("CRITICAL_PROBLEM_BEHAVIOR", "problem-60: validation_plan 冒充 validation_result 未被 blocked", ["src/engine/evidence_grounding_contract.ts"], "problem-60", "FactClaimVerifier 未阻断验证计划冒充", "验证 validation_result 检查逻辑");
836
+ }
837
+ }
838
+ // 检查 4:forbidden source 必须被 blocked
839
+ {
840
+ const matrix = sys.builder.buildContext([], {
841
+ target_claims: [], source_types: [], keywords: [], max_results: 10,
842
+ });
843
+ matrix.evidence.push({
844
+ id: "ev-forbidden-test", source_type: "project_file",
845
+ authority: "authoritative", freshness: "current",
846
+ permission: "forbidden", scope: "sensitive",
847
+ description: "敏感数据", timestamp: new Date().toISOString(),
848
+ });
849
+ matrix.risk_summary = { low: 0, medium: 0, high: 0, critical: 1 };
850
+ const forbiddenResult = sys.gate.evaluate(matrix, "high");
851
+ if (forbiddenResult.allowed) {
852
+ hardFail("CRITICAL_PROBLEM_BEHAVIOR", "problem-60: forbidden source 未被 UnsupportedClaimGate 阻断", ["src/engine/evidence_grounding_contract.ts"], "problem-60", "UnsupportedClaimGate 允许 forbidden 证据通过", "验证 forbidden 检查逻辑");
853
+ }
854
+ }
855
+ // 检查 5:conflict unresolved 必须被 blocked(高风险)
856
+ {
857
+ const confMatrix = sys.builder.buildContext([], {
858
+ target_claims: [], source_types: [], keywords: [], max_results: 10,
859
+ });
860
+ confMatrix.conflicts.push({
861
+ claim_id: "claim-conflict-test",
862
+ conflicting_evidence_ids: ["ev-a", "ev-b"],
863
+ description: "矛盾",
864
+ resolution_status: "unresolved",
865
+ });
866
+ confMatrix.risk_summary = { low: 0, medium: 0, high: 1, critical: 0 };
867
+ const confResult = sys.gate.evaluate(confMatrix, "high");
868
+ if (confResult.allowed) {
869
+ hardFail("CRITICAL_PROBLEM_BEHAVIOR", "problem-60: unresolved conflict 未被 UnsupportedClaimGate 阻断", ["src/engine/evidence_grounding_contract.ts"], "problem-60", "UnsupportedClaimGate 允许未裁决冲突通过", "验证 conflict 检查逻辑");
870
+ }
871
+ }
872
+ // 检查 6:classification-only 证据不得支撑 architecture 声明
873
+ {
874
+ sys.registry.clear();
875
+ const clsOnlyEv = sys.registry.register({
876
+ source_type: "task_context", authority: "authoritative", freshness: "current",
877
+ permission: "allowed", scope: "classification", description: "任务分类结果",
878
+ });
879
+ const clsOnlyClaims = [{
880
+ id: "gate-test-cls-only", claim_text: "技术方案基于现有系统分析",
881
+ category: "architecture",
882
+ evidence_ids: [clsOnlyEv.id], is_uncertain: false, risk_level: "high",
883
+ }];
884
+ const clsOnlyResult = sys.verifier.verify(clsOnlyClaims, "high");
885
+ if (clsOnlyResult.unsupported.length === 0) {
886
+ hardFail("CRITICAL_PROBLEM_BEHAVIOR", "problem-60: classification-only 证据支撑 architecture 声明未被 blocked", ["src/engine/evidence_grounding_contract.ts"], "problem-60", "FactClaimVerifier 允许 task_context:classification 支撑项目事实声明", "验证 CATEGORY_REQUIRED_SOURCES 逻辑");
887
+ }
888
+ }
889
+ // 检查 7:project_file + project_source_file 证据可以支撑 architecture 声明
890
+ {
891
+ sys.registry.clear();
892
+ const pfEv = sys.registry.register({
893
+ source_type: "project_file", evidence_role: "project_source_file",
894
+ authority: "authoritative", freshness: "current",
895
+ permission: "allowed", scope: "src/", description: "项目源码文件",
896
+ });
897
+ const pfClaims = [{
898
+ id: "gate-test-pf-ok", claim_text: "技术方案基于现有系统分析",
899
+ category: "architecture",
900
+ evidence_ids: [pfEv.id], is_uncertain: false, risk_level: "high",
901
+ }];
902
+ const pfResult = sys.verifier.verify(pfClaims, "high");
903
+ if (pfResult.unsupported.length > 0) {
904
+ hardFail("CRITICAL_PROBLEM_BEHAVIOR", "problem-60: project_source_file 证据支撑 architecture 声明被错误 blocked", ["src/engine/evidence_grounding_contract.ts"], "problem-60", "FactClaimVerifier 误阻断了有效证据", "验证 CATEGORY_REQUIRED_ROLES 逻辑");
905
+ }
906
+ }
907
+ // 检查 8:template_guidance 证据不得支撑 architecture 声明
908
+ {
909
+ sys.registry.clear();
910
+ const tgEv = sys.registry.register({
911
+ source_type: "knowledge_asset", evidence_role: "template_guidance",
912
+ authority: "trusted", freshness: "current",
913
+ permission: "allowed", scope: "knowledge", description: "默认模板",
914
+ });
915
+ const tgClaims = [{
916
+ id: "gate-test-tg-block", claim_text: "技术方案基于现有系统分析",
917
+ category: "architecture",
918
+ evidence_ids: [tgEv.id], is_uncertain: false, risk_level: "high",
919
+ }];
920
+ const tgResult = sys.verifier.verify(tgClaims, "high");
921
+ if (tgResult.unsupported.length === 0) {
922
+ hardFail("CRITICAL_PROBLEM_BEHAVIOR", "problem-60: template_guidance 证据支撑 architecture 声明未被 blocked", ["src/engine/evidence_grounding_contract.ts"], "problem-60", "FactClaimVerifier 允许 template_guidance 支撑项目事实声明", "验证 CATEGORY_REQUIRED_ROLES 逻辑");
923
+ }
924
+ }
925
+ // 检查 9:project_source_file 证据角色可以支撑 architecture 声明
926
+ {
927
+ sys.registry.clear();
928
+ const psfEv = sys.registry.register({
929
+ source_type: "project_file", evidence_role: "project_source_file",
930
+ authority: "authoritative", freshness: "current",
931
+ permission: "allowed", scope: "src/", description: "项目源码文件",
932
+ });
933
+ const psfClaims = [{
934
+ id: "gate-test-psf-ok", claim_text: "技术方案基于现有系统分析",
935
+ category: "architecture",
936
+ evidence_ids: [psfEv.id], is_uncertain: false, risk_level: "high",
937
+ }];
938
+ const psfResult = sys.verifier.verify(psfClaims, "high");
939
+ if (psfResult.unsupported.length > 0) {
940
+ hardFail("CRITICAL_PROBLEM_BEHAVIOR", "problem-60: project_source_file 角色支撑 architecture 声明被错误 blocked", ["src/engine/evidence_grounding_contract.ts"], "problem-60", "FactClaimVerifier 误阻断了有效 project_source_file 证据", "验证 CATEGORY_REQUIRED_ROLES 逻辑");
941
+ }
942
+ }
943
+ // 检查 10:普通 code_change 不应因无 project_source_file 被 blocked(无 architecture claim 时)
944
+ {
945
+ sys.registry.clear();
946
+ const clsEv = sys.registry.register({
947
+ source_type: "task_context", evidence_role: "classification",
948
+ authority: "authoritative", freshness: "current",
949
+ permission: "allowed", scope: "classification", description: "任务分类结果",
950
+ });
951
+ // 普通 code_change 只有 user_confirmation claim(无 architecture claim)
952
+ const ccClaims = [{
953
+ id: "gate-test-cc-ok", claim_text: "任务路由和执行范围已确认",
954
+ category: "user_confirmation",
955
+ evidence_ids: [clsEv.id], is_uncertain: false, risk_level: "high",
956
+ }];
957
+ const ccResult = sys.verifier.verify(ccClaims, "high");
958
+ if (ccResult.unsupported.length > 0) {
959
+ hardFail("CRITICAL_PROBLEM_BEHAVIOR", "problem-60: 普通 code_change 的 user_confirmation claim 被 classification 证据错误 blocked", ["src/engine/evidence_grounding_contract.ts"], "problem-60", "FactClaimVerifier 误阻断了不需要项目事实的普通任务", "验证 claim 语义逻辑");
960
+ }
961
+ }
962
+ // 检查 11:architecture_design 无真实项目证据必须 blocked
963
+ {
964
+ sys.registry.clear();
965
+ const clsOnlyEv = sys.registry.register({
966
+ source_type: "task_context", evidence_role: "classification",
967
+ authority: "authoritative", freshness: "current",
968
+ permission: "allowed", scope: "classification", description: "任务分类结果",
969
+ });
970
+ const tgOnlyEv = sys.registry.register({
971
+ source_type: "knowledge_asset", evidence_role: "template_guidance",
972
+ authority: "trusted", freshness: "current",
973
+ permission: "allowed", scope: "knowledge", description: "默认模板",
974
+ });
975
+ const archClaims = [{
976
+ id: "gate-test-arch-block", claim_text: "技术方案基于现有系统分析",
977
+ category: "architecture",
978
+ evidence_ids: [clsOnlyEv.id, tgOnlyEv.id], is_uncertain: false, risk_level: "high",
979
+ }];
980
+ const archResult = sys.verifier.verify(archClaims, "high");
981
+ if (archResult.unsupported.length === 0) {
982
+ hardFail("CRITICAL_PROBLEM_BEHAVIOR", "problem-60: architecture_design 无真实项目证据未被 blocked(classification + template_guidance 不够)", ["src/engine/evidence_grounding_contract.ts"], "problem-60", "FactClaimVerifier 允许非项目证据支撑架构声明", "验证 CATEGORY_REQUIRED_ROLES + claim 语义逻辑");
983
+ }
984
+ }
985
+ }
986
+ catch (behaviorErr) {
987
+ hardFail("CRITICAL_PROBLEM_BEHAVIOR", `problem-60: 行为级检查执行失败: ${behaviorErr?.message ?? behaviorErr}`, ["src/engine/evidence_grounding_contract.ts"], "problem-60", "证据驱动机制行为检查不可执行", "修复 import 或机制代码");
988
+ }
989
+ }
990
+ // 静态检查:导出完整性
991
+ const egContent = safeRead(evidenceGroundingPath);
992
+ if (egContent) {
993
+ const requiredExports = ["createEvidenceGroundingSystem", "assessTaskRisk", "EvidenceSourceRegistry", "FactClaimVerifier", "UnsupportedClaimGate"];
994
+ for (const exp of requiredExports) {
995
+ if (!egContent.includes(exp)) {
996
+ hardFail("CRITICAL_PROBLEM_EXPORT_MISSING", `problem-60: evidence_grounding_contract.ts 缺少导出: ${exp}`, ["src/engine/evidence_grounding_contract.ts"], "problem-60", "证据驱动机制缺少核心导出", `添加 ${exp} 导出`);
997
+ }
998
+ }
999
+ }
1000
+ // 静态检查:InstructionContract schema 必须包含 evidence 字段
1001
+ const instrContractPath = path.join(rootDir, "src/engine/instruction_contract.ts");
1002
+ const instrContent = safeRead(instrContractPath);
1003
+ if (instrContent) {
1004
+ const evidenceFields = ["evidence_matrix", "unsupported_claim_policy", "uncertainty_policy", "conflict_resolution_policy"];
1005
+ for (const field of evidenceFields) {
1006
+ if (!instrContent.includes(field)) {
1007
+ hardFail("CRITICAL_PROBLEM_SCHEMA_INCOMPLETE", `problem-60: instruction_contract.ts schema 缺少证据字段: ${field}`, ["src/engine/instruction_contract.ts"], "problem-60", "施工指令契约缺少证据驱动字段", `添加 ${field} 字段`);
1008
+ }
1009
+ }
1010
+ }
1011
+ // 静态检查:主链路必须有 runtime 阻断(不能只是 advisory)
1012
+ const toolsPath = path.join(rootDir, "src/adapters/claude_code/tools.ts");
1013
+ const toolsContent = safeRead(toolsPath);
1014
+ if (!toolsContent?.includes("evidence_grounding_contract")) {
1015
+ hardFail("CRITICAL_PROBLEM_NOT_CONSUMED", "problem-60: tools.ts 未消费 evidence_grounding_contract", ["src/adapters/claude_code/tools.ts"], "problem-60", "证据驱动机制未接入主链路", "在 sf_expand/sf_verify/sf_deliver 中接入");
1016
+ }
1017
+ // sf_expand 必须有 blocked/manual_required 分支
1018
+ if (toolsContent && !toolsContent.includes("_evGate.allowed") && !toolsContent.includes("evidence_blocked")) {
1019
+ hardFail("CRITICAL_PROBLEM_ADVISORY_ONLY", "problem-60: sf_expand 证据检查只有 advisory,缺少 blocked 分支", ["src/adapters/claude_code/tools.ts"], "problem-60", "sf_expand 证据门禁不阻断", "添加 evidence gate blocked 分支");
1020
+ }
1021
+ // sf_verify: 验证计划描述行中不得包含 source_type: "validation_result"
1022
+ if (toolsContent) {
1023
+ // 找到 sf_verify handler 中问题六十部分
1024
+ const verifyStart = toolsContent.indexOf("问题六十:验证结论必须经过 FactClaimVerifier");
1025
+ const verifyEnd = toolsContent.indexOf("// 验证后核心体验评估", verifyStart);
1026
+ if (verifyStart >= 0 && verifyEnd >= 0) {
1027
+ const verifySection = toolsContent.substring(verifyStart, verifyEnd);
1028
+ // 检查验证计划注册调用中是否使用了 validation_result
1029
+ const planRegMatch = verifySection.match(/register\(\{[^}]*验证计划:[^}]*\}\)/s);
1030
+ if (planRegMatch && planRegMatch[0].includes("source_type: \"validation_result\"")) {
1031
+ hardFail("CRITICAL_PROBLEM_PLAN_AS_RESULT", "problem-60: sf_verify 把验证计划注册为 validation_result", ["src/adapters/claude_code/tools.ts"], "problem-60", "验证计划冒充验证结果", "验证计划应注册为 task_context 类型");
1032
+ }
1033
+ }
1034
+ }
1035
+ // 静态检查:负向测试
1036
+ const testPath = path.join(rootDir, "tests/engine/evidence_grounding_contract.test.ts");
1037
+ if (!fs.existsSync(testPath)) {
1038
+ hardFail("CRITICAL_PROBLEM_TEST_MISSING", "problem-60: 缺少负向测试文件", ["tests/engine/evidence_grounding_contract.test.ts"], "problem-60", "证据驱动机制缺少测试覆盖", "创建 evidence_grounding_contract.test.ts");
1039
+ }
1040
+ // 静态检查:知识规则
1041
+ const kaRulePath = path.join(rootDir, "templates/knowledge/rules/证据驱动与反幻觉规则.md");
1042
+ if (!fs.existsSync(kaRulePath)) {
1043
+ hardFail("CRITICAL_PROBLEM_ASSET_MISSING", "problem-60: 缺少知识规则文件 证据驱动与反幻觉规则.md", ["templates/knowledge/rules/证据驱动与反幻觉规则.md"], "problem-60", "证据驱动知识规则缺失", "创建知识规则文件");
1044
+ }
1045
+ // 静态检查:workflow_template
1046
+ const wfPath = path.join(rootDir, "src/adapters/shared/workflow_template.ts");
1047
+ const wfContent = safeRead(wfPath);
1048
+ if (!wfContent?.includes("wf-evidence-grounding")) {
1049
+ hardFail("CRITICAL_PROBLEM_TEMPLATE_MISSING", "problem-60: workflow_template.ts 缺少 wf-evidence-grounding 硬规则", ["src/adapters/shared/workflow_template.ts"], "problem-60", "工作流模板未包含证据驱动规则", "添加 wf-evidence-grounding 硬规则");
1050
+ }
1051
+ // 静态检查:TaskContext 类型必须有证据字段
1052
+ const typesPath = path.join(rootDir, "src/types.ts");
1053
+ const typesContent = safeRead(typesPath);
1054
+ if (typesContent && !typesContent.includes("evidence_matrix?:") && !typesContent.includes("evidence_gate_result?:")) {
1055
+ hardFail("CRITICAL_PROBLEM_TASKCTX_MISSING", "problem-60: TaskContext 缺少 evidence_matrix/evidence_gate_result 字段", ["src/types.ts"], "problem-60", "TaskContext 未保存证据结果", "添加 evidence_matrix 和 evidence_gate_result 字段");
1056
+ }
1057
+ // 静态检查:task_context.ts 必须有 setEvidenceGroundingResult 写入方法
1058
+ const tcPath = path.join(rootDir, "src/engine/task_context.ts");
1059
+ const tcContent = safeRead(tcPath);
1060
+ if (tcContent && !tcContent.includes("setEvidenceGroundingResult")) {
1061
+ hardFail("CRITICAL_PROBLEM_NO_WRITEBACK", "problem-60: task_context.ts 缺少 setEvidenceGroundingResult 写入方法", ["src/engine/task_context.ts"], "problem-60", "TaskContext 只有类型字段没有写入方法", "添加 setEvidenceGroundingResult 方法");
1062
+ }
1063
+ // 静态检查:tools.ts 主链路必须调用 setEvidenceGroundingResult
1064
+ if (toolsContent && !toolsContent.includes("setEvidenceGroundingResult")) {
1065
+ hardFail("CRITICAL_PROBLEM_NO_RUNTIME_WRITEBACK", "problem-60: tools.ts 未调用 setEvidenceGroundingResult 写回 TaskContext", ["src/adapters/claude_code/tools.ts"], "problem-60", "主链路不写回证据结果到 TaskContext", "在 sf_expand/sf_verify/sf_deliver 中调用 setEvidenceGroundingResult");
1066
+ }
1067
+ // 静态检查:tools.ts 不得出现 registry.query({}).map() 无差别证据绑定
1068
+ if (toolsContent) {
1069
+ const toolsLines = toolsContent.split("\n");
1070
+ for (let i = 0; i < toolsLines.length; i++) {
1071
+ if (/registry\.query\(\{\}\)\.map/.test(toolsLines[i])) {
1072
+ hardFail("CRITICAL_PROBLEM_INDISCRIMINATE_BINDING", `problem-60: tools.ts:${i + 1} 使用 registry.query({}).map() 无差别绑定证据到所有 claim`, ["src/adapters/claude_code/tools.ts"], "problem-60", "所有 registry 证据被无差别绑定到所有 claim,不区分语义", "按 claim 语义筛选 evidence_ids");
1073
+ }
1074
+ }
1075
+ }
1076
+ // 行为检查:setEvidenceGroundingResult 写入后 load 能读回
1077
+ try {
1078
+ const tcModule = await import(path.join(rootDir, "dist/engine/task_context.js"));
1079
+ const tmpStateDir = path.join(rootDir, ".soloforge", "state");
1080
+ const fsModule = await import("node:fs");
1081
+ if (!fsModule.existsSync(tmpStateDir)) {
1082
+ fsModule.mkdirSync(tmpStateDir, { recursive: true });
1083
+ }
1084
+ const tmpMgr = new tcModule.TaskContextManager(tmpStateDir);
1085
+ const tmpCtx = await tmpMgr.create("gate-evidence-writeback-test", "test");
1086
+ const testMatrix = {
1087
+ task_id: tmpCtx.task_id, claims: [], evidence: [],
1088
+ conflicts: [], unsupported_claims: ["test-claim"],
1089
+ risk_summary: { low: 0, medium: 0, high: 1, critical: 0 },
1090
+ created_at: new Date().toISOString(),
1091
+ };
1092
+ const testGate = {
1093
+ allowed: false, reason_zh: "测试", diagnostic_code: "SF-EVIDENCE-TEST",
1094
+ unsupported_claims: ["test-claim"], unresolved_conflicts: [],
1095
+ missing_evidence: [], risk_level: "high",
1096
+ requires_user_confirmation: [],
1097
+ };
1098
+ await tmpMgr.setEvidenceGroundingResult(tmpCtx.task_id, {
1099
+ evidence_matrix: testMatrix,
1100
+ evidence_gate_result: testGate,
1101
+ unsupported_claims: ["test-claim"],
1102
+ });
1103
+ const loaded = await tmpMgr.load(tmpCtx.task_id);
1104
+ if (!loaded?.evidence_matrix || !loaded?.evidence_gate_result) {
1105
+ hardFail("CRITICAL_PROBLEM_WRITEBACK_FAILED", "problem-60: setEvidenceGroundingResult 写入后 load 无法读回", ["src/engine/task_context.ts"], "problem-60", "TaskContext 写入方法不工作", "修复 setEvidenceGroundingResult 或 save/load");
1106
+ }
1107
+ if (loaded.evidence_gate_result?.allowed !== false) {
1108
+ hardFail("CRITICAL_PROBLEM_WRITEBACK_CORRUPT", "problem-60: setEvidenceGroundingResult 写入的 gate result 数据损坏", ["src/engine/task_context.ts"], "problem-60", "写入的 allowed=false 被读回为 true", "检查序列化逻辑");
1109
+ }
1110
+ // 清理测试任务
1111
+ try {
1112
+ fsModule.rmSync(path.join(tmpStateDir, `${tmpCtx.task_id}.json`), { force: true });
1113
+ }
1114
+ catch { }
1115
+ }
1116
+ catch (wbErr) {
1117
+ hardFail("CRITICAL_PROBLEM_WRITEBACK_ERROR", `problem-60: setEvidenceGroundingResult 行为检查失败: ${wbErr?.message ?? wbErr}`, ["src/engine/task_context.ts"], "problem-60", "TaskContext 写入方法不可用", "修复 import 或方法实现");
1118
+ }
1119
+ }
1120
+ _info(" problem-60 证据驱动消费验证完成");
768
1121
  }
769
1122
  // ── 7. Batch 问题文档格式一致性 ──
770
1123
  async function checkBatchIssueFormatConsistency(rootDir, hardFail, _info) {
@@ -783,13 +1136,13 @@ async function checkBatchIssueFormatConsistency(rootDir, hardFail, _info) {
783
1136
  hardFail("BATCH_ISSUE_FORMAT_INCONSISTENT", "loadBatchIssueDetails 导出未找到", ["scripts/batch_issue_details.mjs"], "构建系统", "共享解析脚本导出不正确", "修复脚本");
784
1137
  return;
785
1138
  }
786
- const expectedCounts = { 1: 19, 2: 12, 3: 9, 4: 13, 5: 5 };
1139
+ const expectedCounts = { 1: 19, 2: 12, 3: 9, 4: 13, 5: 6, 6: 4 };
787
1140
  const requiredPerProblemSections = [
788
1141
  "问题背景", "用户反馈 / 触发场景", "根因分析", "解决方案", "方案细节",
789
1142
  "硬规则", "非目标", "影响范围", "落地文件", "验收标准", "回归风险",
790
1143
  "与其他问题的关联", "发布门禁要求",
791
1144
  ];
792
- for (let batch = 1; batch <= 5; batch++) {
1145
+ for (let batch = 1; batch <= 6; batch++) {
793
1146
  const details = loadBatchIssueDetails(batch, rootDir);
794
1147
  if (!details.loaded) {
795
1148
  hardFail("BATCH_ISSUE_FORMAT_INCONSISTENT", `Batch${batch} 问题集未加载: ${details.error}`, [`docs/SoloForge-Batch${batch}问题集.md`], `Batch${batch}`, "旧 gate 只检查文档存在,不验证是否可解析", "第 3 步统一格式");
@@ -965,6 +1318,767 @@ function findFilesRecursive(dir, ext) {
965
1318
  }
966
1319
  return results;
967
1320
  }
1321
+ // ── Phase 10: P0/P1 知识资产 schema 完整性 ──
1322
+ async function checkKnowledgeAssetSchema(rootDir, hardFail, _info) {
1323
+ const matter = (await import("gray-matter")).default;
1324
+ const { validateKnowledgeAssetFrontmatter } = await import("./knowledge_asset_schema.js");
1325
+ const knowledgeDir = path.join(rootDir, "templates", "knowledge");
1326
+ if (!fs.existsSync(knowledgeDir))
1327
+ return;
1328
+ const mdFiles = findFilesRecursive(knowledgeDir, ".md");
1329
+ for (const filePath of mdFiles) {
1330
+ const raw = fs.readFileSync(filePath, "utf-8");
1331
+ const { data: frontmatter } = matter(raw);
1332
+ if (!frontmatter || Object.keys(frontmatter).length === 0)
1333
+ continue;
1334
+ if (!frontmatter.asset_kind)
1335
+ continue;
1336
+ const findings = validateKnowledgeAssetFrontmatter(frontmatter, filePath);
1337
+ for (const f of findings) {
1338
+ if (f.severity === "hard_fail") {
1339
+ hardFail(`KA_SCHEMA_${f.kind}`, f.message_zh, [filePath], f.asset_id, "旧 validate-release 不独立校验知识资产 schema", "补齐缺失的 frontmatter 字段");
1340
+ }
1341
+ }
1342
+ }
1343
+ }
1344
+ // ── Phase 11: engine console.log 检查 ──
1345
+ function checkEngineConsoleLog(rootDir, hardFail) {
1346
+ // 扫描全 src/ 目录(不仅 engine)
1347
+ const srcDir = path.join(rootDir, "src");
1348
+ if (!fs.existsSync(srcDir))
1349
+ return;
1350
+ const files = findFilesRecursive(srcDir, ".ts");
1351
+ for (const file of files) {
1352
+ const content = fs.readFileSync(file, "utf-8");
1353
+ const lines = content.split("\n");
1354
+ for (let i = 0; i < lines.length; i++) {
1355
+ const line = lines[i];
1356
+ if (/^\s*\/\//.test(line))
1357
+ continue;
1358
+ if (/^\s*\*|^\s*\/\*/.test(line))
1359
+ continue;
1360
+ if (/"console\.log"|`console\.log`|'console\.log'|console\\.log/.test(line))
1361
+ continue;
1362
+ // 日志模块内部 stdout 封装允许 console.log
1363
+ if (/logger\.ts$/.test(file) && /^\s*console\.log\(/.test(line))
1364
+ continue;
1365
+ if (/console\.log\s*\(/.test(line)) {
1366
+ const rel = path.relative(rootDir, file);
1367
+ hardFail("ENGINE_CONSOLE_LOG", `${rel}:${i + 1} 使用 console.log(应使用 logger 或 console.error 写 stderr)`, [rel], "发布门禁 console 检查", "旧 gate 不检查 src/ 全目录 console.log", "替换为 logger 模块调用");
1368
+ }
1369
+ }
1370
+ }
1371
+ }
1372
+ // ── Phase 12: 中文注释检查 ──
1373
+ function checkChineseComments(rootDir, hardFail) {
1374
+ const srcDir = path.join(rootDir, "src");
1375
+ if (!fs.existsSync(srcDir))
1376
+ return;
1377
+ const tsFiles = findFilesRecursive(srcDir, ".ts");
1378
+ for (const file of tsFiles) {
1379
+ const content = fs.readFileSync(file, "utf-8");
1380
+ const lines = content.split("\n");
1381
+ for (let i = 0; i < lines.length; i++) {
1382
+ const line = lines[i];
1383
+ if (!/^\s*\/\//.test(line))
1384
+ continue;
1385
+ if (/[一-龥]/.test(line))
1386
+ continue;
1387
+ // 跳过装饰分隔线
1388
+ if (/^\s*\/\/\s*[─═\-~=*#]+\s*$/.test(line))
1389
+ continue;
1390
+ // 跳过工具指令
1391
+ if (/@ts-|eslint|TODO|FIXME|NOTE|prettier|istanbul|c8/.test(line))
1392
+ continue;
1393
+ // 跳过机制 ID 标签
1394
+ if (/\/\/\s*──\s*\d+\./.test(line))
1395
+ continue;
1396
+ if (/\/\/\s*mc-[a-z]/.test(line))
1397
+ continue;
1398
+ // 跳过纯代码标识符
1399
+ if (/^\s*\/\/\s*[a-z][a-z0-9_.\/-]+\s*$/.test(line))
1400
+ continue;
1401
+ // 跳过大小标注
1402
+ if (/^\s*\/\/\s*\d+\s*(MB|KB|GB|ms|min|分钟|秒)/.test(line))
1403
+ continue;
1404
+ // 跳过技术术语注释
1405
+ if (/^\s*\/\/\s*[a-z_]+\.[a-z_]+/.test(line))
1406
+ continue;
1407
+ // 检查连续 4+ 字母
1408
+ if (/[a-zA-Z]{4,}/.test(line)) {
1409
+ const rel = path.relative(rootDir, file);
1410
+ hardFail("ENGLISH_COMMENT", `${rel}:${i + 1}: ${line.trim()}(应使用中文注释)`, [rel], "中文注释检查", "旧 gate 不检查中文注释", "将英文注释改为中文");
1411
+ }
1412
+ }
1413
+ }
1414
+ }
1415
+ // ── 13. 第五批文档↔roadmap↔scenario 一致性 ──
1416
+ async function checkLastBatchConsistency(rootDir, hardFail, _info) {
1417
+ // 动态导入避免循环依赖
1418
+ const roadmapPath = path.join(rootDir, "dist", "engine", "implementation_roadmap_registry.js");
1419
+ const scenarioPath = path.join(rootDir, "dist", "engine", "release_gate_scenario_registry.js");
1420
+ let listAllProblems;
1421
+ let listBatches;
1422
+ let RELEASE_GATE_SCENARIOS;
1423
+ let runAllReleaseGateScenarios;
1424
+ try {
1425
+ const roadmap = await import(roadmapPath);
1426
+ listAllProblems = roadmap.listAllProblems;
1427
+ listBatches = roadmap.listBatches;
1428
+ }
1429
+ catch {
1430
+ hardFail("LAST_BATCH_CONSISTENCY", "无法导入 implementation_roadmap_registry", ["src/engine/implementation_roadmap_registry.ts"], "末批一致性", "旧 gate 不检查 roadmap↔scenario 一致性", "先 build");
1431
+ return;
1432
+ }
1433
+ try {
1434
+ const scenario = await import(scenarioPath);
1435
+ RELEASE_GATE_SCENARIOS = scenario.RELEASE_GATE_SCENARIOS;
1436
+ runAllReleaseGateScenarios = scenario.runAllReleaseGateScenarios;
1437
+ }
1438
+ catch {
1439
+ hardFail("LAST_BATCH_CONSISTENCY", "无法导入 release_gate_scenario_registry", ["src/engine/release_gate_scenario_registry.ts"], "末批一致性", "旧 gate 不检查 roadmap↔scenario 一致性", "先 build");
1440
+ return;
1441
+ }
1442
+ // 1. 末批必须存在且包含 problem-55 到 problem-60
1443
+ const batches = listBatches();
1444
+ const lastBatch = batches.find((b) => b.batch_id === "batch-5");
1445
+ if (!lastBatch) {
1446
+ hardFail("LAST_BATCH_CONSISTENCY", "implementation_roadmap_registry 缺少 batch-5", ["src/engine/implementation_roadmap_registry.ts"], "末批一致性", "旧 gate 不检查末批是否存在于 roadmap", "新增 batch-5 并登记 problem-55 到 problem-60");
1447
+ return;
1448
+ }
1449
+ const expectedLastProblems = ["problem-55", "problem-56", "problem-57", "problem-58", "problem-59", "problem-60"];
1450
+ const missing = expectedLastProblems.filter((pid) => !lastBatch.included_problem_ids.includes(pid));
1451
+ if (missing.length > 0) {
1452
+ hardFail("LAST_BATCH_CONSISTENCY", `batch-5 缺少问题: ${missing.join(", ")}`, ["src/engine/implementation_roadmap_registry.ts"], "末批一致性", "旧 gate 不检查末批问题归属完整性", `将 ${missing.join(", ")} 加入 batch-5`);
1453
+ }
1454
+ // 2. problem-58 和 problem-60 不得在 batch-4
1455
+ const prevBatch = batches.find((b) => b.batch_id === "batch-4");
1456
+ if (prevBatch) {
1457
+ const misattrib = ["problem-58", "problem-60"].filter((pid) => prevBatch.included_problem_ids.includes(pid));
1458
+ if (misattrib.length > 0) {
1459
+ hardFail("LAST_BATCH_CONSISTENCY", `problem-58/60 错误归属到 batch-4: ${misattrib.join(", ")}`, ["src/engine/implementation_roadmap_registry.ts"], "末批一致性", "旧 gate 不检查问题是否被错误归入其他批次", "将 problem-58/60 从 batch-4 迁到 batch-5");
1460
+ }
1461
+ }
1462
+ // 3. 场景矩阵必须覆盖问题五十五到问题六十
1463
+ const scenarios = RELEASE_GATE_SCENARIOS;
1464
+ const allCoveredIssues = scenarios.flatMap((s) => s.covered_issues);
1465
+ const expectedIssueLabels = ["问题五十五", "问题五十六", "问题五十七", "问题五十八", "问题五十九", "问题六十"];
1466
+ const uncovered = expectedIssueLabels.filter((label) => !allCoveredIssues.includes(label));
1467
+ if (uncovered.length > 0) {
1468
+ hardFail("LAST_BATCH_CONSISTENCY", `场景矩阵未覆盖: ${uncovered.join(", ")}`, ["src/engine/release_gate_scenario_registry.ts"], "末批一致性", "旧 gate 不检查场景矩阵是否覆盖所有末批问题", `为 ${uncovered.join(", ")} 新增场景`);
1469
+ }
1470
+ // 4. 场景矩阵必须全部通过
1471
+ const results = runAllReleaseGateScenarios();
1472
+ const failedScenarios = results.filter((r) => !r.passed);
1473
+ if (failedScenarios.length > 0) {
1474
+ hardFail("LAST_BATCH_CONSISTENCY", `场景矩阵未全部通过: ${failedScenarios.map((r) => `${r.scenario_id}: ${r.failures.join("; ")}`).join(", ")}`, ["src/engine/release_gate_scenario_registry.ts"], "末批一致性", "旧 gate 不运行场景矩阵验证", "修复失败场景");
1475
+ }
1476
+ _info(" 第五批一致性校验通过");
1477
+ }
1478
+ // ── 14. 依赖漏洞扫描 ──
1479
+ function checkDependencyAudit(rootDir, hardFail, _info) {
1480
+ const reportPath = path.join(rootDir, ".soloforge", "advisory-report.json");
1481
+ if (!fs.existsSync(reportPath)) {
1482
+ hardFail("DEPENDENCY_AUDIT", "未找到依赖漏洞扫描报告 (.soloforge/advisory-report.json)", ["package.json"], "依赖健康", "旧 gate 不检查依赖漏洞", "运行 npm run audit-deps");
1483
+ return;
1484
+ }
1485
+ try {
1486
+ const report = JSON.parse(fs.readFileSync(reportPath, "utf-8"));
1487
+ if (!report.timestamp) {
1488
+ hardFail("DEPENDENCY_AUDIT", "漏洞扫描报告缺少时间戳", ["package.json"], "依赖健康", "旧 gate 不验证报告完整性", "重新运行 npm run audit-deps");
1489
+ return;
1490
+ }
1491
+ // 报告过期检查: 超过 24 小时视为过期
1492
+ const reportAge = Date.now() - new Date(report.timestamp).getTime();
1493
+ const maxAge = 24 * 60 * 60 * 1000;
1494
+ if (reportAge > maxAge) {
1495
+ hardFail("DEPENDENCY_AUDIT", `漏洞扫描报告已过期 (生成于 ${report.timestamp})`, ["package.json"], "依赖健康", "旧 gate 不检查报告时效", "重新运行 npm run audit-deps");
1496
+ return;
1497
+ }
1498
+ const blocked = report.blocked || [];
1499
+ if (blocked.length > 0) {
1500
+ const details = blocked.map((b) => `[${b.severity}] ${b.package}: ${b.title}`).join("; ");
1501
+ hardFail("DEPENDENCY_AUDIT", `发现 ${blocked.length} 个阻断级漏洞: ${details}`, ["package.json"], "依赖健康", "旧 gate 不扫描依赖漏洞", "npm audit fix 或升级依赖");
1502
+ return;
1503
+ }
1504
+ const total = report.vulnerability_summary?.total || 0;
1505
+ _info(` 依赖漏洞扫描通过: ${total} 个已知漏洞(无阻断级)`);
1506
+ }
1507
+ catch {
1508
+ hardFail("DEPENDENCY_AUDIT", "漏洞扫描报告格式无效", ["package.json"], "依赖健康", "旧 gate 不验证报告格式", "删除 .soloforge/advisory-report.json 并重新运行 npm run audit-deps");
1509
+ }
1510
+ }
1511
+ // ── 15. 发布问题全局合同 ──
1512
+ async function checkReleaseIssueDesignPath(rootDir, hardFail, _info) {
1513
+ const toolsText = safeRead(path.join(rootDir, "src", "adapters", "claude_code", "tools.ts")) ?? "";
1514
+ const cliText = safeRead(path.join(rootDir, "src", "bin", "soloforge.ts")) ?? "";
1515
+ const workflowText = safeRead(path.join(rootDir, "src", "adapters", "shared", "workflow_template.ts")) ?? "";
1516
+ // ══ 前置检查: roadmap 状态与发布文档一致性 ══
1517
+ const roadmapText = safeRead(path.join(rootDir, "src", "engine", "implementation_roadmap_registry.ts")) ?? "";
1518
+ const gateDocText = safeRead(path.join(rootDir, "docs", "正式发布门槛.md")) ?? "";
1519
+ if (/status:\s*"in_repair"/.test(roadmapText)) {
1520
+ hardFail("RELEASE_ISSUE_ROADMAP_IN_REPAIR", "发布问题仍有 in_repair 状态,不得通过发布门禁", ["src/engine/implementation_roadmap_registry.ts"], "release-issues", "roadmap 状态为 in_repair 时不能声称完成", "完成所有返修后改为 implemented_verified");
1521
+ }
1522
+ // 发布问题场景矩阵: 结构化场景注册表验证测试文件存在 + 执行证据
1523
+ {
1524
+ const { validateReleaseIssueScenarios, executeReleaseIssueScenarios } = await import("./release_issue_scenario_registry.js");
1525
+ const scenarioResult = validateReleaseIssueScenarios(rootDir);
1526
+ _info(` 发布问题场景: ${scenarioResult.scenario_count} 条,覆盖 ${scenarioResult.covered_problems.join(", ")}`);
1527
+ if (scenarioResult.missing_runners.length > 0) {
1528
+ hardFail("RELEASE_ISSUE_SCENARIO_NO_RUNNER", `发布问题场景缺少 runner: ${scenarioResult.missing_runners.join("; ")}`, ["src/engine/release_issue_scenario_registry.ts"], "release-issues", "所有场景必须有真实生产入口 runner", "为缺失场景添加 runner");
1529
+ }
1530
+ if (scenarioResult.missing_tests.length > 0) {
1531
+ hardFail("RELEASE_ISSUE_SCENARIO_TESTS_MISSING", `发布问题场景引用的测试文件缺失: ${scenarioResult.missing_tests.join("; ")}`, scenarioResult.missing_tests.map(m => m.split(": ")[1] ?? "unknown"), "release-issues", "场景测试文件必须存在", "创建缺失的测试文件");
1532
+ }
1533
+ // 运行所有场景收集执行证据(全部必须有 runner)
1534
+ const execResults = await executeReleaseIssueScenarios();
1535
+ const failed = execResults.filter(r => r.status === "fail");
1536
+ _info(` 发布问题场景执行: ${execResults.length} 条已执行,${failed.length} 条失败`);
1537
+ if (failed.length > 0) {
1538
+ hardFail("RELEASE_ISSUE_SCENARIO_EXECUTION_FAILED", `发布问题场景执行失败: ${failed.map(f => `${f.scenario_id}: ${f.error ?? "unknown"}`).join("; ")}`, ["src/engine/release_issue_scenario_registry.ts"], "release-issues", "所有场景执行必须通过", "修复失败场景的 runner 逻辑");
1539
+ }
1540
+ // R10: 所有场景必须有 production_trace 且 tool_entrypoint 引用真实工具
1541
+ const noTrace = execResults.filter(r => !r.production_trace);
1542
+ if (noTrace.length > 0) {
1543
+ 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");
1544
+ }
1545
+ // R12: production_trace 的 diagnostic_codes 必须非空(来自工具返回值或函数调用结果)
1546
+ const noDiagCodes = execResults.filter(r => r.production_trace && (!r.production_trace.diagnostic_codes || r.production_trace.diagnostic_codes.length === 0));
1547
+ if (noDiagCodes.length > 0) {
1548
+ 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");
1549
+ }
1550
+ // R10: 每个 problem 必须至少有一个场景通过真实 MCP 工具入口执行
1551
+ const realToolNames = ["sf_classify", "sf_expand", "sf_verify", "sf_deliver", "sf_learn", "sf_accept", "sf_record_verification_execution", "sf_scaffold"];
1552
+ // 用 scenario_id 前缀识别 problem 归属
1553
+ const problemPrefixes = {
1554
+ "problem-61": ["release-scenario-architecture-workshop-", "release-scenario-decision-workshop-"],
1555
+ "problem-62": ["release-scenario-design-pack-"],
1556
+ "problem-63": ["release-scenario-template-contract-"],
1557
+ "problem-64": ["release-scenario-template-visibility-"],
1558
+ };
1559
+ for (const [problem, prefixes] of Object.entries(problemPrefixes)) {
1560
+ const problemScenarios = execResults.filter(r => prefixes.some(p => r.scenario_id.startsWith(p)));
1561
+ const withRealTools = problemScenarios.filter(r => r.production_trace && realToolNames.some(t => r.production_trace.tool_entrypoint.includes(t)));
1562
+ if (withRealTools.length === 0 && problemScenarios.length > 0) {
1563
+ hardFail("RELEASE_ISSUE_SCENARIO_NO_PRODUCTION_TRACE", `${problem} 无场景通过真实 MCP 工具入口执行`, ["src/engine/release_issue_scenario_registry.ts"], "release-issues", `R10 ${problem} 必须有通过 sf_classify/sf_expand 等真实 MCP 工具执行的场景`, "用 createToolHarness + callTool 重写 runner");
1564
+ }
1565
+ }
1566
+ // R13: problem-61 必须证明 awaiting_confirmation 阻断 + 确认放行双向
1567
+ {
1568
+ const p61 = execResults.filter(r => r.scenario_id.startsWith("release-scenario-decision-workshop-"));
1569
+ // 阻断证据: evidence 包含 "awaiting" 或 diagnostic_codes 包含决策研讨阻断码
1570
+ const hasAwaiting = p61.some(r => r.evidence?.includes("awaiting") ||
1571
+ r.production_trace?.diagnostic_codes?.some(c => c.includes("决策研讨") || c.includes("awaiting_confirmation")));
1572
+ // 放行证据: 至少一个 decision_workshop 场景通过两次 sf_expand(tool_entrypoint 含两个 sf_expand)
1573
+ const hasConfirmedPass = p61.some(r => r.status === "pass" && r.production_trace?.tool_entrypoint &&
1574
+ (r.production_trace.tool_entrypoint.match(/sf_expand/g) ?? []).length >= 2);
1575
+ if (!hasAwaiting || !hasConfirmedPass) {
1576
+ hardFail("RELEASE_ISSUE_SCENARIO_NO_PRODUCTION_TRACE", `problem-61 缺少 awaiting_confirmation 阻断+放行双向证据 (awaiting=${hasAwaiting}, confirmed=${hasConfirmedPass})`, ["src/engine/release_issue_scenario_registry.ts"], "release-issues", "problem-61 必须有场景证明 decision_workshop 阻断后确认放行", "确保 data-migration/security-policy 等场景含 awaiting 阻断和确认放行双向证据");
1577
+ }
1578
+ }
1579
+ // problem-63 必须在同一正式产物任务上证明阻断、清零、接受与交付,不能由描述文字自证。
1580
+ {
1581
+ const p63 = execResults.filter(r => r.scenario_id.startsWith("release-scenario-template-contract-"));
1582
+ const lifecycleProof = p63.find(r => r.scenario_id === "release-scenario-template-contract-repair-directive")?.behavioral_proof;
1583
+ const sameTask = lifecycleProof?.task_id
1584
+ && lifecycleProof.blocked_task_id === lifecycleProof.task_id
1585
+ && lifecycleProof.delivered_task_id === lifecycleProof.task_id;
1586
+ const completed = lifecycleProof?.contract_blocked === true
1587
+ && lifecycleProof?.repair_directive_cleared === true
1588
+ && lifecycleProof?.artifact_status === "accepted"
1589
+ && !!lifecycleProof?.acceptance_confirmation_ref
1590
+ && lifecycleProof?.delivery_succeeded === true;
1591
+ if (!sameTask || !completed) {
1592
+ hardFail("RELEASE_ISSUE_SCENARIO_NO_PRODUCTION_TRACE", `problem-63 缺少同任务正式产物生命周期证据 (same_task=${!!sameTask}, completed=${completed})`, ["src/engine/release_issue_scenario_registry.ts"], "release-issues", "problem-63 必须从同一 task 的 artifact_output 证明阻断、重验、显式接受和成功交付", "修复 repair-directive 场景的 behavioral_proof 与真实工具链");
1593
+ }
1594
+ }
1595
+ }
1596
+ // R10: index_only 泄漏行为门禁 — 通过真实 sf_expand 验证 index_only 不进入 prompt
1597
+ {
1598
+ const { createToolHarness } = await import("./release_tool_harness.js");
1599
+ const { clearInjectionContractCache } = await import("./knowledge_injection_boundary.js");
1600
+ clearInjectionContractCache();
1601
+ try {
1602
+ const harness = await createToolHarness({ copyKnowledge: true });
1603
+ try {
1604
+ const all = harness.knowledgeIndex.getAllEntries();
1605
+ // 找到 index_only 条目
1606
+ const { buildTemplateAssetContracts } = await import("./template_asset_contract_registry.js");
1607
+ const contracts = buildTemplateAssetContracts(rootDir);
1608
+ const indexOnlyIds = new Set(contracts.filter(c => c.runtime_visibility === "index_only").map(c => c.asset_id));
1609
+ // R12: 前置条件缺失 = hardFail,不得静默跳过
1610
+ if (all.project.length === 0) {
1611
+ hardFail("RELEASE_ISSUE_INDEX_ONLY_PROMPT_LEAK", "copyKnowledge 后无项目资产被索引,前置条件不满足", ["src/engine/knowledge_injection_boundary.ts"], "release-issues", "index_only 行为检查必须有同步+索引的资产", "检查 copyKnowledgeToProject 和 KIM build");
1612
+ }
1613
+ if (indexOnlyIds.size === 0) {
1614
+ hardFail("RELEASE_ISSUE_INDEX_ONLY_PROMPT_LEAK", "模板合同中无 index_only 资产,前置条件不满足", ["src/engine/template_asset_contract_registry.ts"], "release-issues", "index_only 行为检查必须存在 index_only 合同", "检查 buildTemplateAssetContracts 是否注册了 index_only 资产");
1615
+ }
1616
+ const clsRaw = await harness.callTool("sf_classify", { intent: "添加用户管理功能" });
1617
+ const cls = harness.parseResult(clsRaw);
1618
+ if (!cls.task_id) {
1619
+ hardFail("RELEASE_ISSUE_INDEX_ONLY_PROMPT_LEAK", "sf_classify 未返回 task_id,前置条件不满足", ["src/engine/classifier.ts"], "release-issues", "index_only 行为检查必须 sf_classify 返回有效 task_id", "检查分类器是否正常返回");
1620
+ }
1621
+ const expRaw = await harness.callTool("sf_expand", { task_id: cls.task_id });
1622
+ const exp = harness.parseResult(expRaw);
1623
+ // index_only 资产不得进入 matched_knowledge
1624
+ const leaked = (exp.matched_knowledge ?? []).filter((k) => indexOnlyIds.has(k.id));
1625
+ if (leaked.length > 0) {
1626
+ hardFail("RELEASE_ISSUE_INDEX_ONLY_PROMPT_LEAK", `index_only 资产泄漏到 expand 结果: ${leaked.map((k) => k.id).join(", ")}`, ["src/engine/knowledge_injection_boundary.ts"], "release-issues", "index_only 资产不得进入 matched_knowledge 或 prompt", "修复注入边界逻辑");
1627
+ }
1628
+ // prompt 中也不得包含 index_only 资产内容
1629
+ const promptStr = exp.prompt ?? "";
1630
+ const leakedInPrompt = all.project.filter((e) => {
1631
+ const contract = contracts.find(c => c.asset_id === e.id || c.path.endsWith(e.name + ".md"));
1632
+ return contract?.runtime_visibility === "index_only" && e.body && promptStr.includes(e.body.slice(0, 50));
1633
+ });
1634
+ if (leakedInPrompt.length > 0) {
1635
+ hardFail("RELEASE_ISSUE_INDEX_ONLY_PROMPT_LEAK", `index_only 资产内容泄漏到 prompt: ${leakedInPrompt.map((e) => e.id).join(", ")}`, ["src/engine/knowledge_injection_boundary.ts"], "release-issues", "index_only 资产内容不得出现在 prompt 中", "修复注入边界逻辑");
1636
+ }
1637
+ }
1638
+ finally {
1639
+ harness.cleanup();
1640
+ }
1641
+ }
1642
+ catch (err) {
1643
+ // R11: fail-closed — 缺少前置条件或异常 = hard_fail
1644
+ hardFail("RELEASE_ISSUE_INDEX_ONLY_PROMPT_LEAK", `index_only 行为检查失败: ${err.message}`, ["src/engine/knowledge_injection_boundary.ts"], "release-issues", "index_only 行为检查不得跳过", "修复 index_only 检查前置条件");
1645
+ }
1646
+ }
1647
+ // R10: 权威来源 fail-closed 主动负向测试 — 已注册但文件缺失必须被阻断
1648
+ {
1649
+ const { getConsumptionTraces, clearConsumptionTraces } = await import("./consumption_trace_store.js");
1650
+ // 先检查已有 traces
1651
+ const traces = getConsumptionTraces();
1652
+ const authorityLeaks = traces.filter((t) => t.matched_reason?.includes("权威来源文件缺失") && t.contract_decision === "allowed");
1653
+ if (authorityLeaks.length > 0) {
1654
+ hardFail("RELEASE_ISSUE_AUTHORITIVE_FAIL_OPEN", `权威来源缺失的资产被 allowed: ${authorityLeaks.map((t) => t.asset_id).join(", ")}`, ["src/knowledge/index_manager.ts"], "release-issues", "已注册资产权威来源缺失时必须 fail-closed", "检查 filterByContractGate 逻辑");
1655
+ }
1656
+ // 主动负向测试: 创建临时项目,manifest 指向不存在的权威文件
1657
+ try {
1658
+ const { copyKnowledgeToProject } = await import("./template_init_sync.js");
1659
+ const { KnowledgeIndexManager } = await import("../knowledge/index_manager.js");
1660
+ const tmpDir = fs.mkdtempSync(path.join(os.tmpdir(), "soloforge-r10-auth-"));
1661
+ try {
1662
+ await copyKnowledgeToProject(rootDir, tmpDir);
1663
+ const manifestPath = path.join(tmpDir, ".soloforge", "sync-manifest.json");
1664
+ const manifest = JSON.parse(fs.readFileSync(manifestPath, "utf-8"));
1665
+ const domainKey = Object.keys(manifest.entries).find(k => k.startsWith("domain/"));
1666
+ // R12: 找不到 domain 条目 = hardFail,不得静默通过
1667
+ if (!domainKey) {
1668
+ hardFail("RELEASE_ISSUE_AUTHORITIVE_FAIL_OPEN", "sync-manifest 中无 domain/ 条目可做篡改测试,前置条件不满足", ["src/engine/template_init_sync.ts"], "release-issues", "权威 fail-closed 测试必须找到可篡改的 domain 合同", "检查 copyKnowledgeToProject 是否同步了 domain 资产");
1669
+ }
1670
+ manifest.entries[domainKey].source_contract_path = "templates/knowledge/domain/不存在的权威文件.md";
1671
+ fs.writeFileSync(manifestPath, JSON.stringify(manifest, null, 2), "utf-8");
1672
+ clearConsumptionTraces();
1673
+ const kim = new KnowledgeIndexManager({
1674
+ tech_stack: { backend: { lang: "java", framework: "spring-boot" }, frontend: { lang: "typescript", framework: "react" } },
1675
+ _projectPath: tmpDir,
1676
+ }, { watch: false });
1677
+ await kim.build();
1678
+ const domainEntries = kim.getAllEntries().project.filter((e) => e.file_path?.replace(/\\/g, "/").includes(domainKey));
1679
+ await kim.close();
1680
+ // 权威来源缺失 → 不得被索引
1681
+ if (domainEntries.length > 0) {
1682
+ hardFail("RELEASE_ISSUE_AUTHORITIVE_FAIL_OPEN", `权威缺失的资产被索引: ${domainKey}`, ["src/knowledge/index_manager.ts"], "release-issues", "已注册资产权威来源缺失时必须 fail-closed", "检查 filterByContractGate 逻辑");
1683
+ }
1684
+ }
1685
+ finally {
1686
+ fs.rmSync(tmpDir, { recursive: true, force: true });
1687
+ }
1688
+ }
1689
+ catch (err) {
1690
+ // R11: fail-closed — 找不到篡改目标或执行异常 = hard_fail
1691
+ hardFail("RELEASE_ISSUE_AUTHORITIVE_FAIL_OPEN", `权威 fail-closed 主动测试失败: ${err.message}`, ["src/knowledge/index_manager.ts"], "release-issues", "权威来源缺失测试不得跳过", "检查 filterByContractGate 和模板合同配置");
1692
+ }
1693
+ }
1694
+ // 工作流硬规则正文单真源检查
1695
+ {
1696
+ const { WORKFLOW_TEMPLATE_HARD_RULES } = await import("./asset_manifest.js");
1697
+ const { generateWorkflowRules } = await import("../adapters/shared/workflow_template.js");
1698
+ const emptyRules = WORKFLOW_TEMPLATE_HARD_RULES.filter(r => r.enforcement_status === "enforced" && (!r.user_instruction || r.user_instruction.trim() === ""));
1699
+ if (emptyRules.length > 0) {
1700
+ hardFail("WORKFLOW_RULE_EMPTY_INSTRUCTION", `enforced 规则 user_instruction 为空: ${emptyRules.map(r => r.rule_id).join(", ")}`, ["src/engine/asset_manifest.ts"], "release-issues", "enforced 规则必须有非空 user_instruction", "为空规则补充用户可见指令文本");
1701
+ }
1702
+ const output = generateWorkflowRules({});
1703
+ for (const rule of WORKFLOW_TEMPLATE_HARD_RULES) {
1704
+ if (rule.user_instruction) {
1705
+ const lines = rule.user_instruction.split("\n").filter(l => l.trim());
1706
+ for (const line of lines) {
1707
+ if (!output.includes(line)) {
1708
+ hardFail("WORKFLOW_RULE_TEXT_MISSING_IN_OUTPUT", `规则 ${rule.rule_id} 正文未出现在生成输出: ${line}`, ["src/engine/asset_manifest.ts", "src/adapters/shared/workflow_template.ts"], "release-issues", "每条规则正文必须出现在 generateWorkflowRules 输出", "检查 WORKFLOW_STEPS 中是否遗漏该规则");
1709
+ }
1710
+ }
1711
+ }
1712
+ if (!output.includes(`id=${rule.rule_id}`)) {
1713
+ hardFail("WORKFLOW_RULE_MARKER_MISSING", `规则 ${rule.rule_id} marker 未出现在生成输出`, ["src/adapters/shared/workflow_template.ts"], "release-issues", "每条规则 marker 必须出现在 generateWorkflowRules 输出", "将规则添加到 WORKFLOW_STEPS");
1714
+ }
1715
+ }
1716
+ }
1717
+ if (/发布问题.*100%|100%.*发布问题/.test(gateDocText)) {
1718
+ hardFail("RELEASE_ISSUE_DOC_PREMATURE_100", "发布门槛文档提前声称发布问题 100% 完成", ["docs/正式发布门槛.md"], "release-issues", "文档不得在返修未完成前声称 100%", "完成所有返修后更新文档");
1719
+ }
1720
+ // ══ 问题六十一: 通用决策研讨机制 ══
1721
+ // TaskContext 必须有强类型通用决策研讨字段
1722
+ const typesText = safeRead(path.join(rootDir, "src", "types.ts")) ?? "";
1723
+ if (!typesText.includes("decision_workshop?: import")) {
1724
+ hardFail("RELEASE_ISSUE_TASKCONTEXT_WEAK_TYPE", "TaskContext 缺少强类型 decision_workshop 字段", ["src/types.ts"], "problem-61", "使用 (ctx as any).decision_workshop 是弱类型", "在 TaskContext 添加 decision_workshop 强类型字段");
1725
+ }
1726
+ // tools.ts 不得使用 (ctx as any).decision_workshop
1727
+ if (toolsText.includes("(ctx as any).decision_workshop")) {
1728
+ hardFail("RELEASE_ISSUE_TOOLS_ANY_CAST", "tools.ts 仍使用 (ctx as any).decision_workshop 弱类型", ["src/adapters/claude_code/tools.ts"], "problem-61", "必须使用 TaskContext 强类型字段", "将 (ctx as any).decision_workshop 改为 ctx.decision_workshop");
1729
+ }
1730
+ // ExpandSchema 必须支持 decision_workshop 输入
1731
+ if (!toolsText.includes("decision_workshop: z.unknown()")) {
1732
+ hardFail("RELEASE_ISSUE_EXPAND_NO_WORKSHOP_INPUT", "ExpandSchema 不支持通用 decision_workshop 输入", ["src/adapters/claude_code/tools.ts"], "problem-61", "用户无法回传确认后的决策合同", "在 ExpandSchema 添加 decision_workshop 输入参数");
1733
+ }
1734
+ // 架构决策研讨子包必须接入 sf_expand
1735
+ if (!toolsText.includes("checkArchitectureDecisionWorkshopGate({") ||
1736
+ !toolsText.includes("projectContext: detectedArchitectureContext")) {
1737
+ hardFail("RELEASE_ISSUE_MAINLINE_MISSING", "问题六十一架构研讨子包未接入 sf_expand", ["src/adapters/claude_code/tools.ts"], "problem-61", "仅有合同或模板不能证明主链路消费", "在 sf_expand 真实路由调用架构研讨门");
1738
+ }
1739
+ // 通用决策研讨必须接入 sf_expand(可组合决策包)
1740
+ if (!toolsText.includes("lazyDecisionWorkshop") || !toolsText.includes("matchDecisionPacks") || !toolsText.includes("nonArchPacks")) {
1741
+ hardFail("RELEASE_ISSUE_DECISION_WORKSHOP_MISSING", "问题六十一通用决策研讨未接入 sf_expand 可组合决策包", ["src/adapters/claude_code/tools.ts"], "problem-61", "非架构高影响任务也必须门禁,且必须支持组合多个决策包", "在 sf_expand 接入 matchDecisionPacks 门禁");
1742
+ }
1743
+ // DecisionPackRegistry 必须有 business_process 和 delivery_validation 独立包
1744
+ {
1745
+ const dwText = safeRead(path.join(rootDir, "src", "engine", "decision_workshop.ts")) ?? "";
1746
+ if (!dwText.includes("business_process") || !dwText.includes("delivery_validation")) {
1747
+ hardFail("RELEASE_ISSUE_DECISION_PACK_MISSING", "问题六十一 DecisionPackRegistry 缺少 business_process 或 delivery_validation 独立包", ["src/engine/decision_workshop.ts"], "problem-61", "业务流程和交付验证必须可独立触发", "在 DECISION_PACKS 添加 business_process 和 delivery_validation");
1748
+ }
1749
+ }
1750
+ try {
1751
+ const dw = await import(path.join(rootDir, "dist", "engine", "decision_workshop.js"));
1752
+ // must-fail: 未确认的技术选型必须阻断
1753
+ const techContract = dw.createDecisionWorkshop("gate-test", "technology_selection");
1754
+ const techGate = dw.evaluateDecisionWorkshop(techContract);
1755
+ if (techGate.allowed) {
1756
+ hardFail("RELEASE_ISSUE_DECISION_FALSE_PASS", "问题六十一未确认技术选型草稿错误放行", ["src/engine/decision_workshop.ts"], "problem-61", "技术选型未确认应阻断", "修复 evaluateDecisionWorkshop");
1757
+ }
1758
+ // must-fail: 低风险任务不需要研讨
1759
+ const lowRisk = dw.requiresDecisionWorkshop({ intent: "修复按钮颜色" });
1760
+ if (lowRisk.required) {
1761
+ hardFail("RELEASE_ISSUE_DECISION_LOW_RISK_BLOCKED", "问题六十一低风险任务错误触发决策研讨", ["src/engine/decision_workshop.ts"], "problem-61", "低风险任务不应触发研讨", "修复 requiresDecisionWorkshop 触发条件");
1762
+ }
1763
+ // must-pass: 确认的技术选型放行
1764
+ const confirmed = dw.createDecisionWorkshop("gate-test-2", "technology_selection");
1765
+ confirmed.extended_domains.technology_selection.status = "confirmed";
1766
+ confirmed.extended_domains.technology_selection.user_confirmation_ref = "user-ok";
1767
+ if (!dw.evaluateDecisionWorkshop(confirmed).allowed) {
1768
+ hardFail("RELEASE_ISSUE_DECISION_CONFIRMED_BLOCKED", "问题六十一已确认决策仍被阻断", ["src/engine/decision_workshop.ts"], "problem-61", "已确认决策应放行", "修复 evaluateDecisionWorkshop 确认检查");
1769
+ }
1770
+ }
1771
+ catch (error) {
1772
+ hardFail("RELEASE_ISSUE_DECISION_LOAD_FAILED", `无法执行问题六十一行为复验: ${error?.message ?? error}`, ["src/engine/decision_workshop.ts"], "problem-61", "模块不可执行", "修复构建或导出");
1773
+ }
1774
+ // ══ 问题六十二: 标准资产覆盖机制 ══
1775
+ if (!toolsText.includes("verifyDesignArtifactPack(projectPath, designPack)") ||
1776
+ !toolsText.includes("ctx.design_artifact_pack.status !== \"implementation_ready\"") ||
1777
+ !toolsText.includes("checkDesignArtifactWriteGate({ ctx, toolName: name, sideEffects: effectiveSideEffects })")) {
1778
+ hardFail("RELEASE_ISSUE_MAINLINE_MISSING", "问题六十二设计产物包子机制未接入真实消费路径", ["src/adapters/claude_code/tools.ts"], "problem-62", "仅有文档校验函数不能阻断用户编码/交付", "在验证、写入与交付路径消费设计产物包状态");
1779
+ }
1780
+ if (!cliText.includes("cmdAuditDesignArtifacts") ||
1781
+ !cliText.includes("cmdUpgradeDesignArtifacts") ||
1782
+ !cliText.includes("currentTask?.design_artifact_pack")) {
1783
+ hardFail("RELEASE_ISSUE_USER_CLI_MISSING", "问题六十二缺少用户 CLI 审计/升级或 Claude Code 写入 hook 阻断路径", ["src/bin/soloforge.ts"], "problem-62", "MCP 内部调用不能覆盖用户现有文档升级与直接 Edit/Write 路径", "接入 audit/upgrade CLI 与 check-write 设计产物门");
1784
+ }
1785
+ // 标准资产覆盖机制必须使用真实注册表审计
1786
+ try {
1787
+ const cov = await import(path.join(rootDir, "dist", "engine", "standard_asset_coverage.js"));
1788
+ const car = await import(path.join(rootDir, "dist", "engine", "consumable_asset_registry.js"));
1789
+ const manifest = await import(path.join(rootDir, "dist", "engine", "asset_manifest.js"));
1790
+ // 从真实注册表构建消费证据
1791
+ const registeredAssets = car.listBuiltinConsumableAssets();
1792
+ const consumablePaths = new Set(registeredAssets.map((a) => a.path));
1793
+ const ownerMap = new Map(registeredAssets.map((a) => [a.path, a.owner_mechanism_id ?? ""]));
1794
+ const manifestEntries = manifest.ASSET_MANIFEST;
1795
+ const consumerMap = new Map(manifestEntries.map((e) => [e.path, []]));
1796
+ const visibilityMap = new Map(manifestEntries.map((e) => [e.path, e.consumption_mode ?? "unknown"]));
1797
+ const report = cov.auditStandardAssetCoverage(rootDir, consumablePaths, ownerMap, consumerMap, visibilityMap);
1798
+ // 必须能审计并返回结构化报告
1799
+ if (typeof report.total_assets !== "number" || typeof report.covered_assets !== "number") {
1800
+ hardFail("RELEASE_ISSUE_COVERAGE_REPORT_INVALID", "问题六十二覆盖审计报告结构不完整", ["src/engine/standard_asset_coverage.ts"], "problem-62", "审计报告缺少必须字段", "修复 auditStandardAssetCoverage 返回结构");
1801
+ }
1802
+ // 不得传空 Set/空 Map 通过覆盖检查 — 必须使用真实数据
1803
+ if (report.total_assets > 0 && report.covered_assets === report.total_assets && consumablePaths.size === 0) {
1804
+ hardFail("RELEASE_ISSUE_COVERAGE_EMPTY_REGISTRY_PASS", "问题六十二空注册表不应全部覆盖", ["src/engine/standard_asset_coverage.ts"], "problem-62", "空注册表时所有资产必须 uncovered", "修复覆盖检查使用真实注册表");
1805
+ }
1806
+ }
1807
+ catch (error) {
1808
+ hardFail("RELEASE_ISSUE_COVERAGE_LOAD_FAILED", `无法执行问题六十二覆盖审计行为复验: ${error?.message ?? error}`, ["src/engine/standard_asset_coverage.ts"], "problem-62", "模块不可执行", "修复构建或导出");
1809
+ }
1810
+ try {
1811
+ const workshop = await import(path.join(rootDir, "dist", "engine", "architecture_decision_workshop.js"));
1812
+ const designPack = await import(path.join(rootDir, "dist", "engine", "design_artifact_pack.js"));
1813
+ const draft = workshop.createArchitectureDecisionWorkshop("release-issue", "new_system");
1814
+ if (workshop.evaluateArchitectureDecisionWorkshop(draft).allowed) {
1815
+ hardFail("RELEASE_ISSUE_BEHAVIOR_FALSE_PASS", "问题六十一未确认六域的草稿错误放行", ["src/engine/architecture_decision_workshop.ts"], "problem-61", "必须有负向行为证据", "修复研讨生成门");
1816
+ }
1817
+ const missingPackResult = designPack.verifyDesignArtifactPack(rootDir, designPack.createDesignArtifactPack("release-issue"));
1818
+ if (missingPackResult.passed) {
1819
+ hardFail("RELEASE_ISSUE_BEHAVIOR_FALSE_PASS", "问题六十二缺少用户项目设计产物时错误放行", ["src/engine/design_artifact_pack.ts"], "problem-62", "必须读取真实资产并阻断缺失", "修复设计产物复验门");
1820
+ }
1821
+ }
1822
+ catch (error) {
1823
+ hardFail("RELEASE_ISSUE_MODULE_LOAD_FAILED", `无法执行 发布问题行为复验: ${error?.message ?? error}`, ["src/engine/architecture_decision_workshop.ts", "src/engine/design_artifact_pack.ts"], "发布问题", "模块不可执行", "修复构建或导出");
1824
+ }
1825
+ if (!workflowText.includes("wf-architecture-workshop-first") || !workflowText.includes("wf-design-artifact-pack")) {
1826
+ hardFail("RELEASE_ISSUE_USER_INJECTION_MISSING", "用户项目工作流模板未注入 发布问题硬协议", ["src/adapters/shared/workflow_template.ts"], "problem-61/problem-62", "运行时有门但用户不可预期会造成体验断裂", "向 workflow template 注入硬规则");
1827
+ }
1828
+ // workflow_template @sf-hard-rule 机制 ID 必须能解析到权威机制注册条目且资产文件存在
1829
+ try {
1830
+ const { validateWorkflowRuleSources } = await import("../adapters/shared/workflow_template.js");
1831
+ const wfValidation = validateWorkflowRuleSources(rootDir);
1832
+ if (!wfValidation.valid) {
1833
+ if (wfValidation.missing_mechanisms.length > 0) {
1834
+ hardFail("RELEASE_ISSUE_WF_RULE_MISSING_MECHANISM", `工作流模板引用了未注册的机制: ${wfValidation.missing_mechanisms.join(", ")}`, ["src/adapters/shared/workflow_template.ts"], "发布问题", "硬编码规则与权威机制注册表漂移", "在 dual_layer_mechanism_registry 中注册缺失机制或修正 workflow_template 引用");
1835
+ }
1836
+ if (wfValidation.missing_assets.length > 0) {
1837
+ hardFail("RELEASE_ISSUE_WF_RULE_MISSING_ASSET", `工作流模板规则的权威资产文件缺失: ${wfValidation.missing_assets.join(", ")}`, ["src/adapters/shared/workflow_template.ts"], "发布问题", "权威规则资产文件不存在", "恢复缺失的模板文件或修正注册表路径");
1838
+ }
1839
+ if (wfValidation.checksum_mismatch) {
1840
+ hardFail("RELEASE_ISSUE_WF_RULE_CONTENT_DRIFT", `工作流模板权威规则内容校验和不匹配: 嵌入=${wfValidation.embedded_checksum}, 实际=${wfValidation.computed_checksum}`, ["src/adapters/shared/workflow_template.ts"], "发布问题", "权威规则资产内容已变化但工作流模板未同步", "运行 updateEmbeddedChecksum 或重新生成工作流模板");
1841
+ }
1842
+ }
1843
+ }
1844
+ catch (error) {
1845
+ hardFail("RELEASE_ISSUE_WF_RULE_VALIDATION_FAILED", `工作流规则来源校验失败: ${error?.message ?? error}`, ["src/adapters/shared/workflow_template.ts"], "发布问题", "校验函数不可执行", "修复 validateWorkflowRuleSources 导出");
1846
+ }
1847
+ // ══ 问题六十三: 标准资产契约 ══
1848
+ const sacText = safeRead(path.join(rootDir, "src", "engine", "standard_asset_contract.ts")) ?? "";
1849
+ if (!sacText.includes("matchTemplateContract") || !sacText.includes("verifyOutputAgainstContract") || !sacText.includes("createRepairReverifyDirective")) {
1850
+ hardFail("RELEASE_ISSUE_CONTRACT_MISSING", "问题六十三缺少模板契约匹配、产物验证或修复重验函数", ["src/engine/standard_asset_contract.ts"], "problem-63", "缺少核心函数不能实现契约验证", "实现 matchTemplateContract / verifyOutputAgainstContract / createRepairReverifyDirective");
1851
+ }
1852
+ // sf_verify 必须按文件逐一匹配契约(不得使用 changed_files[0] 的契约校验全部文件)
1853
+ if (!toolsText.includes("lazyStandardAssetContract") || !toolsText.includes("SF-CONTRACT-0003") || !toolsText.includes("repair_reverify_directive")) {
1854
+ hardFail("RELEASE_ISSUE_CONTRACT_MAINLINE_MISSING", "问题六十三未接入 sf_verify 模板契约验证和 sf_deliver 修复重验阻断", ["src/adapters/claude_code/tools.ts"], "problem-63", "仅有契约定义不能阻断交付", "在 sf_verify 消费模板契约验证、sf_deliver 消费修复重验阻断");
1855
+ }
1856
+ // 必须检查 per-file 匹配(不得 changed_files[0] 统一匹配)
1857
+ if (toolsText.includes("file_path: args.changed_files?.[0]") && !toolsText.includes("file_path: changedFile")) {
1858
+ hardFail("RELEASE_ISSUE_CONTRACT_SINGLE_FILE_MATCH", "问题六十三 sf_verify 仍使用 changed_files[0] 单一匹配", ["src/adapters/claude_code/tools.ts"], "problem-63", "一个契约不能校验所有文件", "改为按文件逐一匹配 matchTemplateContract");
1859
+ }
1860
+ // 草稿必须阻断(不得 continue 跳过)
1861
+ if (toolsText.includes("SF-CONTRACT-DRAFT") === false) {
1862
+ hardFail("RELEASE_ISSUE_CONTRACT_DRAFT_SKIP", "问题六十三草稿文件未阻断下游消费", ["src/adapters/claude_code/tools.ts"], "problem-63", "草稿不得静默跳过", "草稿必须写入 blocked 状态并阻断");
1863
+ }
1864
+ try {
1865
+ const sac = await import(path.join(rootDir, "dist", "engine", "standard_asset_contract.js"));
1866
+ if (sac.matchTemplateContract({ file_path: "docs/architecture/01-架构设计文档.md" }) === null) {
1867
+ hardFail("RELEASE_ISSUE_CONTRACT_MATCH_FAILURE", "问题六十三模板匹配失败:架构设计文档路径应匹配模板契约", ["src/engine/standard_asset_contract.ts"], "problem-63", "精确路径应匹配 P0 模板", "修复 matchTemplateContract 路径匹配逻辑");
1868
+ }
1869
+ }
1870
+ catch (error) {
1871
+ hardFail("RELEASE_ISSUE_CONTRACT_LOAD_FAILED", `无法执行问题六十三行为复验: ${error?.message ?? error}`, ["src/engine/standard_asset_contract.ts"], "problem-63", "模块不可执行", "修复构建或导出");
1872
+ }
1873
+ // ══ 问题六十四: 模板资产可见性 ══
1874
+ const visText = safeRead(path.join(rootDir, "src", "engine", "template_asset_visibility.ts")) ?? "";
1875
+ if (!visText.includes("isInherentlyInternal") || !visText.includes("inferVisibilityFromPath") || !visText.includes("filterSyncAssets")) {
1876
+ hardFail("RELEASE_ISSUE_VISIBILITY_MISSING", "问题六十四缺少内部资产识别、可见性推断或同步过滤函数", ["src/engine/template_asset_visibility.ts"], "problem-64", "缺少核心函数不能实现可见性门禁", "实现 isInherentlyInternal / inferVisibilityFromPath / filterSyncAssets");
1877
+ }
1878
+ const syncText = safeRead(path.join(rootDir, "src", "engine", "template_init_sync.ts")) ?? "";
1879
+ if (!cliText.includes("getContractDecision") && !syncText.includes("getContractDecision")) {
1880
+ hardFail("RELEASE_ISSUE_VISIBILITY_SYNC_MISSING", "问题六十四 copyKnowledgeTemplates 未接入可见性门禁", ["src/engine/template_init_sync.ts"], "problem-64", "init/sync 不加门禁会泄漏内部资产到用户项目", "在 template_init_sync 中消费可见性门禁");
1881
+ }
1882
+ // copyKnowledgeTemplates 不得使用 subDirs 硬编码列表
1883
+ if (cliText.includes("const subDirs = [")) {
1884
+ hardFail("RELEASE_ISSUE_VISIBILITY_SUBDIRS_HARDCODE", "问题六十四 copyKnowledgeTemplates 仍使用 subDirs 硬编码列表", ["src/bin/soloforge.ts"], "problem-64", "硬编码 subDirs 无法覆盖新增目录", "改为递归发现并统一走可见性门禁");
1885
+ }
1886
+ // scaffolder 不得使用 process.cwd() 作为内置路径 fallback
1887
+ const scaffolderText = safeRead(path.join(rootDir, "src", "engine", "scaffolder.ts")) ?? "";
1888
+ if (scaffolderText.includes("process.cwd()")) {
1889
+ hardFail("RELEASE_ISSUE_SCAFFOLDER_CWD_FALLBACK", "问题六十四 scaffolder 仍使用 process.cwd() 作为内置路径", ["src/engine/scaffolder.ts"], "problem-64", "process.cwd() 在 npm 安装场景解析错误", "改为 import.meta.dirname 解析包内路径");
1890
+ }
1891
+ try {
1892
+ const vis = await import(path.join(rootDir, "dist", "engine", "template_asset_visibility.js"));
1893
+ // 内部资产必须被识别
1894
+ if (!vis.isInherentlyInternal("templates/knowledge/rules/演进回归规则.md")) {
1895
+ hardFail("RELEASE_ISSUE_VISIBILITY_INTERNAL_MISS", "问题六十四内部资产识别失败:演进回归规则应被识别为内部资产", ["src/engine/template_asset_visibility.ts"], "problem-64", "denylist 不生效", "修复 isInherentlyInternal 逻辑");
1896
+ }
1897
+ // 用户可见资产不应被识别为内部
1898
+ if (vis.isInherentlyInternal("templates/knowledge/acceptance_templates/架构设计模版.md")) {
1899
+ hardFail("RELEASE_ISSUE_VISIBILITY_USER_MISS", "问题六十四用户项目资产误判为内部资产", ["src/engine/template_asset_visibility.ts"], "problem-64", "用户可见资产不应被过滤", "修复 isInherentlyInternal 逻辑");
1900
+ }
1901
+ }
1902
+ catch (error) {
1903
+ hardFail("RELEASE_ISSUE_VISIBILITY_LOAD_FAILED", `无法执行问题六十四行为复验: ${error?.message ?? error}`, ["src/engine/template_asset_visibility.ts"], "problem-64", "模块不可执行", "修复构建或导出");
1904
+ }
1905
+ // ══ 发布问题返修新增门禁 ══
1906
+ // 问题六十一: sf_expand 不得使用不存在的 args.intent
1907
+ {
1908
+ const expandFnMatch = toolsText.match(/\/\/ 问题六十一[\s\S]*?matchDecisionPacks\(\{[^}]*\}\)/);
1909
+ if (expandFnMatch && expandFnMatch[0].includes("args.intent")) {
1910
+ hardFail("RELEASE_ISSUE_DECISION_ARGS_INTENT", "问题六十一 sf_expand 使用不存在的 args.intent 调用 matchDecisionPacks", ["src/adapters/claude_code/tools.ts"], "problem-61", "ExpandSchema 无 intent 字段,args.intent 永远 undefined", "改用 ctx.intent / ctx.classification / ctx.route_decision");
1911
+ }
1912
+ }
1913
+ // 问题六十二: standard_asset_coverage 不得以注册存在替代复验路径
1914
+ {
1915
+ const sacCovText = safeRead(path.join(rootDir, "src", "engine", "standard_asset_coverage.ts")) ?? "";
1916
+ if (sacCovText.includes("hasRevalidation = isRegistered") || sacCovText.includes("has_revalidation_path: isRegistered")) {
1917
+ hardFail("RELEASE_ISSUE_COVERAGE_FAKE_REVALIDATION", "问题六十二 hasRevalidation=isRegistered 将注册等同于复验路径", ["src/engine/standard_asset_coverage.ts"], "problem-62", "注册存在不等于有复验入口", "必须从真实消费链路验证复验路径");
1918
+ }
1919
+ // 覆盖审计不得从 ASSET_MANIFEST 创建空 consumerMap
1920
+ if (cliText.includes("ASSET_MANIFEST") && cliText.includes("[] as string[]") && cliText.includes("consumerMap")) {
1921
+ hardFail("RELEASE_ISSUE_COVERAGE_EMPTY_CONSUMER", "问题六十二 audit-template-visibility 从 ASSET_MANIFEST 创建空 consumerMap", ["src/bin/soloforge.ts"], "problem-62", "空 consumerMap 导致所有资产 missing_consumer=0 假通过", "从权威合同获取真实消费者");
1922
+ }
1923
+ // 覆盖报告中有 uncovered/missing_consumer 时必须 hard_fail
1924
+ // 消费者证据从逐资产运行时观察结果获取,不从合同声明字段复制
1925
+ try {
1926
+ const cov = await import(path.join(rootDir, "dist", "engine", "standard_asset_coverage.js"));
1927
+ const car = await import(path.join(rootDir, "dist", "engine", "consumable_asset_registry.js"));
1928
+ const contractReg = await import(path.join(rootDir, "dist", "engine", "template_asset_contract_registry.js"));
1929
+ const obsMod = await import(path.join(rootDir, "dist", "engine", "observed_consumption.js"));
1930
+ const registeredAssets = car.listBuiltinConsumableAssets();
1931
+ const contracts = contractReg.listTemplateAssetContracts();
1932
+ const consumablePaths = new Set(registeredAssets.map((a) => a.path));
1933
+ const ownerMap = new Map(contracts.map((c) => [c.path, c.owner_mechanism_id]));
1934
+ // 运行真实 scenario 生成消费 trace,然后构建消费者映射
1935
+ const obsReport = await obsMod.observeAllConsumption(rootDir);
1936
+ const consumerMap = new Map();
1937
+ for (const obs of obsReport.observations) {
1938
+ const consumers = consumerMap.get(obs.asset_path) ?? [];
1939
+ // entry_point 即为真实消费入口
1940
+ if (obs.entry_point && !consumers.includes(obs.entry_point)) {
1941
+ consumers.push(obs.entry_point);
1942
+ }
1943
+ consumerMap.set(obs.asset_path, consumers);
1944
+ }
1945
+ // 补充复验入口标识(与消费者证据不同维度,复验入口是审计/验证角色标识)
1946
+ for (const c of contracts) {
1947
+ const consumers = consumerMap.get(c.path) ?? [];
1948
+ if (c.validation_entrypoint && !consumers.includes(c.validation_entrypoint)) {
1949
+ consumers.push(c.validation_entrypoint);
1950
+ }
1951
+ consumerMap.set(c.path, consumers);
1952
+ }
1953
+ const visibilityMap = new Map(contracts.map((c) => [c.path, c.asset_visibility ?? "unknown"]));
1954
+ const report = cov.auditStandardAssetCoverage(rootDir, consumablePaths, ownerMap, consumerMap, visibilityMap);
1955
+ if (report.uncovered_assets > 0) {
1956
+ hardFail("RELEASE_ISSUE_COVERAGE_UNCOVERED", `问题六十二覆盖审计发现 ${report.uncovered_assets} 个未覆盖资产`, ["src/engine/standard_asset_coverage.ts", "src/engine/template_asset_contract_registry.ts"], "problem-62", "未覆盖资产不得通过发布门禁", "为每个资产建立真实消费证据");
1957
+ }
1958
+ if (report.missing_consumer > 0) {
1959
+ hardFail("RELEASE_ISSUE_COVERAGE_MISSING_CONSUMER", `问题六十二覆盖审计发现 ${report.missing_consumer} 个资产缺少主链路消费者`, ["src/engine/standard_asset_coverage.ts", "src/engine/template_asset_contract_registry.ts"], "problem-62", "无消费者证据不得通过发布门禁", "为每个资产建立真实主链路消费者");
1960
+ }
1961
+ if (report.missing_revalidation > 0) {
1962
+ hardFail("RELEASE_ISSUE_COVERAGE_MISSING_REVALIDATION", `问题六十二覆盖审计发现 ${report.missing_revalidation} 个资产缺少复验入口`, ["src/engine/standard_asset_coverage.ts", "src/engine/template_asset_contract_registry.ts"], "problem-62", "无复验入口不得通过发布门禁", "为每个资产建立真实复验入口");
1963
+ }
1964
+ }
1965
+ catch (error) {
1966
+ // template_asset_contract_registry 尚不存在 — 这是预期失败
1967
+ hardFail("RELEASE_ISSUE_CONTRACT_REGISTRY_MISSING", `问题六十三权威合同注册表不存在: ${error?.message ?? error}`, ["src/engine/template_asset_contract_registry.ts"], "problem-63", "214 资产必须有权威合同", "创建 template_asset_contract_registry.ts");
1968
+ }
1969
+ }
1970
+ // 问题六十三: templates 总数 = 权威合同覆盖数
1971
+ {
1972
+ const contractRegText = safeRead(path.join(rootDir, "src", "engine", "template_asset_contract_registry.ts")) ?? "";
1973
+ if (!contractRegText.includes("TemplateAssetContract") || !contractRegText.includes("listTemplateAssetContracts")) {
1974
+ hardFail("RELEASE_ISSUE_CONTRACT_REGISTRY_MISSING", "问题六十三缺少权威合同注册表(TemplateAssetContract / listTemplateAssetContracts)", ["src/engine/template_asset_contract_registry.ts"], "problem-63", "214 资产必须有单一权威元数据体系", "创建 template_asset_contract_registry.ts");
1975
+ }
1976
+ }
1977
+ // 问题六十四: 可见性不得使用 denylist 猜测
1978
+ {
1979
+ const visText2 = safeRead(path.join(rootDir, "src", "engine", "template_asset_visibility.ts")) ?? "";
1980
+ if (visText2.includes("INTERNAL_ASSET_PATTERNS")) {
1981
+ hardFail("RELEASE_ISSUE_VISIBILITY_DENYLIST", "问题六十四 template_asset_visibility.ts 仍使用 INTERNAL_ASSET_PATTERNS denylist", ["src/engine/template_asset_visibility.ts"], "problem-64", "denylist 猜测不是权威分类", "改用权威合同注册表的 asset_visibility 字段");
1982
+ }
1983
+ }
1984
+ // 问题六十四: 必须存在并执行真实技术栈受限资产,禁止以跳过或内存假资产代替。
1985
+ {
1986
+ const { buildTemplateAssetContracts } = await import("./template_asset_contract_registry.js");
1987
+ const restrictedScaffolds = buildTemplateAssetContracts(rootDir).filter((contract) => contract.consume_category === "scaffold_only" && contract.applicable_tech_stack.length > 0);
1988
+ if (restrictedScaffolds.length === 0) {
1989
+ hardFail("RELEASE_ISSUE_TECH_STACK_ASSET_MISSING", "问题六十四不存在真实 applicable_tech_stack 脚手架资产", ["src/engine/explicit_asset_registry.ts"], "problem-64", "没有真实受限资产不能证明技术栈隔离", "为内置脚手架声明权威 applicable_tech_stack 并走 sf_scaffold 验证");
1990
+ }
1991
+ const scenarioTestText = safeRead(path.join(rootDir, "tests", "engine", "release_issue_scenario_matrix.test.ts")) ?? "";
1992
+ const failClosedTestText = safeRead(path.join(rootDir, "tests", "engine", "contract_fail_closed.test.ts")) ?? "";
1993
+ if (/it\.skip\([\s\S]{0,80}技术栈|test-java-rule/.test(`${scenarioTestText}\n${failClosedTestText}`)) {
1994
+ hardFail("RELEASE_ISSUE_TECH_STACK_FAKE_TEST", "问题六十四仍以跳过测试或内存假资产代替真实技术栈复验", ["tests/engine/release_issue_scenario_matrix.test.ts", "tests/engine/contract_fail_closed.test.ts"], "problem-64", "假资产/skip 不证明用户实际链路", "仅使用 explicit_asset_registry 已登记资产执行真实 scaffold 场景");
1995
+ }
1996
+ }
1997
+ // sf_accept 必须为需要显式授权的验收入口,且处理器不得直接篡改产物状态。
1998
+ {
1999
+ const contractText = safeRead(path.join(rootDir, "src", "engine", "tool_invocation_contract_registry.ts")) ?? "";
2000
+ const acceptStart = toolsText.indexOf('registerSafeTool(\n "sf_accept"');
2001
+ const deliverStart = toolsText.indexOf('registerSafeTool(\n "sf_deliver"', acceptStart + 1);
2002
+ const acceptBody = toolsText.substring(acceptStart, deliverStart > 0 ? deliverStart : toolsText.length);
2003
+ const contractStrict = /tool_name:\s*"sf_accept"[\s\S]{0,120}category:\s*"strict_controlled"[\s\S]{0,120}requires_authorization:\s*true/.test(contractText);
2004
+ const confirmsEvidence = acceptBody.includes("confirmation_ref") && acceptBody.includes("confirmed_by") && acceptBody.includes("updateArtifactStatus");
2005
+ const directStatusWrite = /artifact_output\.status\s*=\s*"accepted"/.test(acceptBody);
2006
+ if (!contractStrict || !confirmsEvidence || directStatusWrite) {
2007
+ hardFail("RELEASE_ISSUE_ACCEPTANCE_SELF_ATTESTATION", "sf_accept 仍可缺少显式确认或绕过 artifact 生命周期", ["src/adapters/claude_code/tools.ts", "src/engine/tool_invocation_contract_registry.ts"], "problem-63", "自动生成的验收证据不得放行正式产物", "要求显式确认引用并只通过 updateArtifactStatus 接受产物");
2008
+ }
2009
+ }
2010
+ // 问题五十九: 新增 CLI 命令不得使用 console.log
2011
+ {
2012
+ const auditFnStart = cliText.indexOf("async function cmdAuditTemplateVisibility");
2013
+ const nextFnStart = cliText.indexOf("\nasync function ", auditFnStart + 1);
2014
+ const fnBody = cliText.substring(auditFnStart, nextFnStart > 0 ? nextFnStart : cliText.length);
2015
+ if (fnBody.includes("console.log")) {
2016
+ hardFail("RELEASE_ISSUE_LOGGER_VIOLATION", "问题五十九 cmdAuditTemplateVisibility 使用 console.log 违反日志治理", ["src/bin/soloforge.ts"], "problem-59", "新增代码必须使用统一 logger", "替换 console.log 为 userInfo/userJson");
2017
+ }
2018
+ }
2019
+ // 问题六十二/六十三: 逐资产运行时消费证据 — 不得接受"consumer file exists"或"export name exists"作为真实消费证明
2020
+ {
2021
+ const { observeAllConsumption } = await import("./observed_consumption.js");
2022
+ const report = await observeAllConsumption(rootDir);
2023
+ if (report.fail > 0) {
2024
+ hardFail("RELEASE_ISSUE_CONSUMPTION_EVIDENCE_FAIL", `问题六十二逐资产消费证据验证失败(${report.fail} failures): ${report.failures.join("; ")}`, ["src/engine/observed_consumption.ts", "src/engine/template_asset_contract_registry.ts"], "problem-62", "逐资产运行时消费证据不完整", "检查 observeAllConsumption 中每个资产的观察结果");
2025
+ }
2026
+ if (report.total < 215) {
2027
+ hardFail("RELEASE_ISSUE_CONSUMPTION_EVIDENCE_INCOMPLETE", `问题六十二消费证据仅覆盖 ${report.total} 个资产,预期至少 215`, ["src/engine/observed_consumption.ts"], "problem-62", "消费证据覆盖不全", "确保所有注册资产都有消费观察记录");
2028
+ }
2029
+ // trace 必须来自真实入口(init/sync_templates),不得仅来自 contract_gate 兜底
2030
+ const traceCounts = report.trace_counts;
2031
+ if ((traceCounts.init ?? 0) === 0 && (traceCounts.sync_templates ?? 0) === 0) {
2032
+ hardFail("RELEASE_ISSUE_CONSUMPTION_EVIDENCE_NO_REAL_TRACE", "问题六十二消费证据无真实入口 trace(init/sync_templates 均为 0)", ["src/engine/observed_consumption.ts"], "problem-62", "必须运行真实入口 scenario 产生 trace", "检查 observeAllConsumption 中的 scenario runner");
2033
+ }
2034
+ // 硬检查: mainline trace 的 declared_consumer 必须与合同 mainline_consumer 精确匹配
2035
+ for (const obs of report.observations) {
2036
+ if (obs.consume_category === "mainline" && obs.validation_result === "fail" && obs.failure_reason?.includes("无精确消费 trace")) {
2037
+ hardFail("RELEASE_ISSUE_MAINLINE_TRACE_MISMATCH", `mainline 资产消费 trace 不匹配: ${obs.failure_reason}`, ["src/engine/observed_consumption.ts", obs.asset_path], "problem-62", "mainline trace declared_consumer 必须与合同 mainline_consumer 精确对应", "确保生产函数记录 declared_consumer");
2038
+ }
2039
+ }
2040
+ }
2041
+ // 负向测试: scaffold_only 资产必须有 scaffold trace,删除 scaffold scenario 后必须 fail
2042
+ {
2043
+ const { buildTemplateAssetContracts } = await import("./template_asset_contract_registry.js");
2044
+ const { getConsumptionTraces, clearConsumptionTraces, recordConsumptionTrace } = await import("./consumption_trace_store.js");
2045
+ clearConsumptionTraces();
2046
+ // 只运行 init+sync(不运行 scaffold),scaffold_only 资产应无合法 trace
2047
+ const { copyKnowledgeToProject, copyPatternsToGlobal } = await import("./template_init_sync.js");
2048
+ const tmpNegDir = fs.mkdtempSync(path.join(os.tmpdir(), "soloforge-neg-"));
2049
+ try {
2050
+ await copyKnowledgeToProject(rootDir, tmpNegDir);
2051
+ const globalDir = path.join(tmpNegDir, ".soloforge", "patterns");
2052
+ await copyPatternsToGlobal(rootDir, globalDir);
2053
+ }
2054
+ finally {
2055
+ fs.rmSync(tmpNegDir, { recursive: true, force: true });
2056
+ }
2057
+ // 收集 scaffold_only 合同,验证它们都没有 scaffold trace
2058
+ const contracts = buildTemplateAssetContracts(rootDir);
2059
+ const scaffoldContracts = contracts.filter(c => c.consume_category === "scaffold_only");
2060
+ const traces = getConsumptionTraces();
2061
+ const scaffoldTracedPaths = new Set(traces.filter(t => t.consumer === "scaffold").map(t => t.asset_path));
2062
+ const scaffoldWithTrace = scaffoldContracts.filter(c => scaffoldTracedPaths.has(c.path));
2063
+ if (scaffoldWithTrace.length > 0) {
2064
+ hardFail("RELEASE_ISSUE_NEGATIVE_SCAFFOLD_LEAK", `负向测试失败: 未运行 scaffold scenario 但 ${scaffoldWithTrace.length} 个 scaffold_only 资产有 scaffold trace`, ["src/engine/observed_consumption.ts"], "problem-62", "scaffold trace 不应从其他 scenario 泄露", "检查 scaffold scenario 是否独立");
2065
+ }
2066
+ clearConsumptionTraces();
2067
+ }
2068
+ // 问题六十四: copyKnowledgeTemplates 必须对无合同资产 fail-closed
2069
+ {
2070
+ const copyFnSource = syncText || cliText;
2071
+ const copyFnStart = copyFnSource.indexOf("async function copyKnowledge") !== -1
2072
+ ? copyFnSource.indexOf("async function copyKnowledge")
2073
+ : copyFnSource.indexOf("export async function copyKnowledgeToProject");
2074
+ const copyFnEnd = copyFnSource.indexOf("\nasync function ", copyFnStart + 1);
2075
+ const copyFnBody = copyFnSource.substring(copyFnStart, copyFnEnd > 0 ? copyFnEnd : copyFnSource.length);
2076
+ // 必须有 "skipped_no_contract" 或 "unregistered" — 证明无合同时会跳过
2077
+ if (!copyFnBody.includes("skipped_no_contract") && !copyFnBody.includes("unregistered")) {
2078
+ hardFail("RELEASE_ISSUE_COPY_NO_CONTRACT_FAIL_OPEN", "问题六十四 copyKnowledgeTemplates 未对无合同资产 fail-closed", ["src/engine/template_init_sync.ts"], "problem-64", "无合同资产不应被复制", "添加 skipped_no_contract 计数并在无合同时 continue");
2079
+ }
2080
+ }
2081
+ }
968
2082
  // ── 主入口 ──
969
2083
  export async function runReleaseReadinessGate(rootDir) {
970
2084
  const hardFails = [];
@@ -1025,12 +2139,36 @@ export async function runReleaseReadinessGate(rootDir) {
1025
2139
  endPhase("Batch 问题文档格式一致性");
1026
2140
  // 8
1027
2141
  beginPhase("关键问题消费验证");
1028
- checkCriticalProblemConsumption(rootDir, hardFail, _info);
2142
+ await checkCriticalProblemConsumption(rootDir, hardFail, _info);
1029
2143
  endPhase("关键问题消费验证");
1030
2144
  // 9
1031
2145
  beginPhase("机制身份一致性");
1032
2146
  await checkMechanismIdentityConsistency(rootDir, hardFail, _info);
1033
2147
  endPhase("机制身份一致性");
2148
+ // 10: P0/P1 知识资产 schema 完整性
2149
+ beginPhase("P0/P1 知识资产 schema");
2150
+ await checkKnowledgeAssetSchema(rootDir, hardFail, _info);
2151
+ endPhase("P0/P1 知识资产 schema");
2152
+ // 11: console.log 检查
2153
+ beginPhase("engine console.log 检查");
2154
+ checkEngineConsoleLog(rootDir, hardFail);
2155
+ endPhase("engine console.log 检查");
2156
+ // 12: 中文注释检查
2157
+ beginPhase("中文注释检查");
2158
+ checkChineseComments(rootDir, hardFail);
2159
+ endPhase("中文注释检查");
2160
+ // 13: 第五批文档↔roadmap↔scenario 一致性
2161
+ beginPhase("第五批一致性校验");
2162
+ await checkLastBatchConsistency(rootDir, hardFail, _info);
2163
+ endPhase("第五批一致性校验");
2164
+ // 14: 依赖漏洞扫描(在 console.error 恢复前执行以避免干扰)
2165
+ beginPhase("依赖漏洞扫描");
2166
+ checkDependencyAudit(rootDir, hardFail, _info);
2167
+ endPhase("依赖漏洞扫描");
2168
+ // 15: 发布问题全局合同(问题六十一至六十四)
2169
+ beginPhase("发布问题全局合同");
2170
+ await checkReleaseIssueDesignPath(rootDir, hardFail, _info);
2171
+ endPhase("发布问题全局合同");
1034
2172
  // 恢复 console.error 和日志器
1035
2173
  console.error = origConsoleError;
1036
2174
  resetLogger();