soloforge 1.3.1 → 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 (118) hide show
  1. package/README.md +6 -3
  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 +10 -10
  6. package/dist/adapters/claude_code/tools.d.ts.map +1 -1
  7. package/dist/adapters/claude_code/tools.js +317 -18
  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 -46
  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 +85 -53
  15. package/dist/bin/soloforge.js.map +1 -1
  16. package/dist/engine/asset_manifest.d.ts +7 -1
  17. package/dist/engine/asset_manifest.d.ts.map +1 -1
  18. package/dist/engine/asset_manifest.js +28 -18
  19. package/dist/engine/asset_manifest.js.map +1 -1
  20. package/dist/engine/consumable_asset_registry.d.ts.map +1 -1
  21. package/dist/engine/consumable_asset_registry.js +26 -0
  22. package/dist/engine/consumable_asset_registry.js.map +1 -1
  23. package/dist/engine/consumption_trace_store.d.ts +8 -8
  24. package/dist/engine/consumption_trace_store.d.ts.map +1 -1
  25. package/dist/engine/consumption_trace_store.js +11 -7
  26. package/dist/engine/consumption_trace_store.js.map +1 -1
  27. package/dist/engine/decision_workshop.d.ts +160 -0
  28. package/dist/engine/decision_workshop.d.ts.map +1 -0
  29. package/dist/engine/decision_workshop.js +279 -0
  30. package/dist/engine/decision_workshop.js.map +1 -0
  31. package/dist/engine/dual_layer_mechanism_registry.d.ts.map +1 -1
  32. package/dist/engine/dual_layer_mechanism_registry.js +176 -2
  33. package/dist/engine/dual_layer_mechanism_registry.js.map +1 -1
  34. package/dist/engine/explicit_asset_registry.d.ts +30 -0
  35. package/dist/engine/explicit_asset_registry.d.ts.map +1 -0
  36. package/dist/engine/explicit_asset_registry.js +3508 -0
  37. package/dist/engine/explicit_asset_registry.js.map +1 -0
  38. package/dist/engine/implementation_roadmap_registry.d.ts +2 -2
  39. package/dist/engine/implementation_roadmap_registry.d.ts.map +1 -1
  40. package/dist/engine/implementation_roadmap_registry.js +44 -16
  41. package/dist/engine/implementation_roadmap_registry.js.map +1 -1
  42. package/dist/engine/intent_expander.d.ts.map +1 -1
  43. package/dist/engine/intent_expander.js +46 -2
  44. package/dist/engine/intent_expander.js.map +1 -1
  45. package/dist/engine/intent_router.d.ts +1 -1
  46. package/dist/engine/intent_router.d.ts.map +1 -1
  47. package/dist/engine/intent_router.js +2 -1
  48. package/dist/engine/intent_router.js.map +1 -1
  49. package/dist/engine/knowledge_injection_boundary.d.ts +3 -0
  50. package/dist/engine/knowledge_injection_boundary.d.ts.map +1 -1
  51. package/dist/engine/knowledge_injection_boundary.js +48 -5
  52. package/dist/engine/knowledge_injection_boundary.js.map +1 -1
  53. package/dist/engine/mechanism_contract_registry.d.ts +1 -1
  54. package/dist/engine/mechanism_contract_registry.d.ts.map +1 -1
  55. package/dist/engine/mechanism_contract_registry.js +74 -2
  56. package/dist/engine/mechanism_contract_registry.js.map +1 -1
  57. package/dist/engine/observed_consumption.d.ts +54 -0
  58. package/dist/engine/observed_consumption.d.ts.map +1 -0
  59. package/dist/engine/observed_consumption.js +377 -0
  60. package/dist/engine/observed_consumption.js.map +1 -0
  61. package/dist/engine/release_issue_scenario_registry.d.ts +64 -0
  62. package/dist/engine/release_issue_scenario_registry.d.ts.map +1 -0
  63. package/dist/engine/release_issue_scenario_registry.js +1349 -0
  64. package/dist/engine/release_issue_scenario_registry.js.map +1 -0
  65. package/dist/engine/release_readiness_gate.d.ts +1 -1
  66. package/dist/engine/release_readiness_gate.d.ts.map +1 -1
  67. package/dist/engine/release_readiness_gate.js +574 -46
  68. package/dist/engine/release_readiness_gate.js.map +1 -1
  69. package/dist/engine/release_tool_harness.d.ts +71 -0
  70. package/dist/engine/release_tool_harness.d.ts.map +1 -0
  71. package/dist/engine/release_tool_harness.js +161 -0
  72. package/dist/engine/release_tool_harness.js.map +1 -0
  73. package/dist/engine/scaffolder.d.ts.map +1 -1
  74. package/dist/engine/scaffolder.js +144 -7
  75. package/dist/engine/scaffolder.js.map +1 -1
  76. package/dist/engine/standard_asset_contract.d.ts +75 -0
  77. package/dist/engine/standard_asset_contract.d.ts.map +1 -0
  78. package/dist/engine/standard_asset_contract.js +388 -0
  79. package/dist/engine/standard_asset_contract.js.map +1 -0
  80. package/dist/engine/standard_asset_coverage.d.ts +45 -0
  81. package/dist/engine/standard_asset_coverage.d.ts.map +1 -0
  82. package/dist/engine/standard_asset_coverage.js +220 -0
  83. package/dist/engine/standard_asset_coverage.js.map +1 -0
  84. package/dist/engine/template_asset_contract_registry.d.ts +162 -0
  85. package/dist/engine/template_asset_contract_registry.d.ts.map +1 -0
  86. package/dist/engine/template_asset_contract_registry.js +598 -0
  87. package/dist/engine/template_asset_contract_registry.js.map +1 -0
  88. package/dist/engine/template_asset_visibility.d.ts +109 -0
  89. package/dist/engine/template_asset_visibility.d.ts.map +1 -0
  90. package/dist/engine/template_asset_visibility.js +321 -0
  91. package/dist/engine/template_asset_visibility.js.map +1 -0
  92. package/dist/engine/template_init_sync.d.ts +68 -0
  93. package/dist/engine/template_init_sync.d.ts.map +1 -0
  94. package/dist/engine/template_init_sync.js +218 -0
  95. package/dist/engine/template_init_sync.js.map +1 -0
  96. package/dist/engine/template_manifest_io.d.ts +10 -0
  97. package/dist/engine/template_manifest_io.d.ts.map +1 -1
  98. package/dist/engine/template_manifest_io.js +63 -30
  99. package/dist/engine/template_manifest_io.js.map +1 -1
  100. package/dist/engine/template_mechanism_auditor.d.ts +3 -1
  101. package/dist/engine/template_mechanism_auditor.d.ts.map +1 -1
  102. package/dist/engine/template_mechanism_auditor.js +27 -24
  103. package/dist/engine/template_mechanism_auditor.js.map +1 -1
  104. package/dist/engine/tool_invocation_contract_registry.d.ts.map +1 -1
  105. package/dist/engine/tool_invocation_contract_registry.js +11 -1
  106. package/dist/engine/tool_invocation_contract_registry.js.map +1 -1
  107. package/dist/knowledge/index_manager.d.ts +20 -0
  108. package/dist/knowledge/index_manager.d.ts.map +1 -1
  109. package/dist/knowledge/index_manager.js +234 -3
  110. package/dist/knowledge/index_manager.js.map +1 -1
  111. package/dist/types.d.ts +36 -1
  112. package/dist/types.d.ts.map +1 -1
  113. package/package.json +2 -2
  114. 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
  115. 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
  116. 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
  117. 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
  118. 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
@@ -20,9 +20,10 @@
20
20
  * 12. 中文注释检查
21
21
  * 13. 第五批一致性校验
22
22
  * 14. 依赖漏洞扫描
23
- * 15. Batch6 架构决策与设计产物真实消费
23
+ * 15. 发布问题架构决策与设计产物真实消费
24
24
  */
25
25
  import fs from "node:fs";
26
+ import os from "node:os";
26
27
  import path from "node:path";
27
28
  // ── 辅助函数 ──
28
29
  function safeRead(filePath) {
@@ -1135,7 +1136,7 @@ async function checkBatchIssueFormatConsistency(rootDir, hardFail, _info) {
1135
1136
  hardFail("BATCH_ISSUE_FORMAT_INCONSISTENT", "loadBatchIssueDetails 导出未找到", ["scripts/batch_issue_details.mjs"], "构建系统", "共享解析脚本导出不正确", "修复脚本");
1136
1137
  return;
1137
1138
  }
1138
- const expectedCounts = { 1: 19, 2: 12, 3: 9, 4: 13, 5: 6, 6: 2 };
1139
+ const expectedCounts = { 1: 19, 2: 12, 3: 9, 4: 13, 5: 6, 6: 4 };
1139
1140
  const requiredPerProblemSections = [
1140
1141
  "问题背景", "用户反馈 / 触发场景", "根因分析", "解决方案", "方案细节",
1141
1142
  "硬规则", "非目标", "影响范围", "落地文件", "验收标准", "回归风险",
@@ -1342,33 +1343,28 @@ async function checkKnowledgeAssetSchema(rootDir, hardFail, _info) {
1342
1343
  }
1343
1344
  // ── Phase 11: engine console.log 检查 ──
1344
1345
  function checkEngineConsoleLog(rootDir, hardFail) {
1345
- const targets = ["src/engine/", "src/index.ts"];
1346
- for (const target of targets) {
1347
- const fullTarget = path.join(rootDir, target);
1348
- if (!fs.existsSync(fullTarget))
1349
- continue;
1350
- const isDir = fs.statSync(fullTarget).isDirectory();
1351
- const files = isDir
1352
- ? findFilesRecursive(fullTarget, ".ts")
1353
- : [fullTarget];
1354
- for (const file of files) {
1355
- const content = fs.readFileSync(file, "utf-8");
1356
- const lines = content.split("\n");
1357
- for (let i = 0; i < lines.length; i++) {
1358
- const line = lines[i];
1359
- if (/^\s*\/\//.test(line))
1360
- continue;
1361
- if (/^\s*\*|^\s*\/\*/.test(line))
1362
- continue;
1363
- if (/"console\.log"|`console\.log`|'console\.log'|console\\.log/.test(line))
1364
- continue;
1365
- // 日志模块内部 stdout 封装允许 console.log
1366
- if (/logger\.ts$/.test(file) && /^\s*console\.log\(/.test(line))
1367
- continue;
1368
- if (/console\.log\s*\(/.test(line)) {
1369
- const rel = path.relative(rootDir, file);
1370
- hardFail("ENGINE_CONSOLE_LOG", `${rel}:${i + 1} 使用 console.log(应使用 logger 或 console.error 写 stderr)`, [rel], "发布门禁 console 检查", "旧 gate 不检查 engine console.log", "替换为 logger 模块调用");
1371
- }
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 模块调用");
1372
1368
  }
1373
1369
  }
1374
1370
  }
@@ -1512,44 +1508,576 @@ function checkDependencyAudit(rootDir, hardFail, _info) {
1512
1508
  hardFail("DEPENDENCY_AUDIT", "漏洞扫描报告格式无效", ["package.json"], "依赖健康", "旧 gate 不验证报告格式", "删除 .soloforge/advisory-report.json 并重新运行 npm run audit-deps");
1513
1509
  }
1514
1510
  }
1515
- // ── 15. Batch6 架构决策与设计产物真实消费 ──
1516
- async function checkBatch6DesignPath(rootDir, hardFail, _info) {
1511
+ // ── 15. 发布问题全局合同 ──
1512
+ async function checkReleaseIssueDesignPath(rootDir, hardFail, _info) {
1517
1513
  const toolsText = safeRead(path.join(rootDir, "src", "adapters", "claude_code", "tools.ts")) ?? "";
1518
1514
  const cliText = safeRead(path.join(rootDir, "src", "bin", "soloforge.ts")) ?? "";
1519
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
1520
1735
  if (!toolsText.includes("checkArchitectureDecisionWorkshopGate({") ||
1521
1736
  !toolsText.includes("projectContext: detectedArchitectureContext")) {
1522
- hardFail("BATCH6_MAINLINE_MISSING", "问题六十一未接入 sf_expand 架构研讨阻断路径", ["src/adapters/claude_code/tools.ts"], "problem-61", "仅有合同或模板不能证明主链路消费", "在 sf_expand 真实路由调用架构研讨门");
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
+ }
1523
1770
  }
1771
+ catch (error) {
1772
+ hardFail("RELEASE_ISSUE_DECISION_LOAD_FAILED", `无法执行问题六十一行为复验: ${error?.message ?? error}`, ["src/engine/decision_workshop.ts"], "problem-61", "模块不可执行", "修复构建或导出");
1773
+ }
1774
+ // ══ 问题六十二: 标准资产覆盖机制 ══
1524
1775
  if (!toolsText.includes("verifyDesignArtifactPack(projectPath, designPack)") ||
1525
1776
  !toolsText.includes("ctx.design_artifact_pack.status !== \"implementation_ready\"") ||
1526
1777
  !toolsText.includes("checkDesignArtifactWriteGate({ ctx, toolName: name, sideEffects: effectiveSideEffects })")) {
1527
- hardFail("BATCH6_MAINLINE_MISSING", "问题六十二未同时接入真实 sf_verify、实现写入与 sf_deliver 就绪阻断", ["src/adapters/claude_code/tools.ts"], "problem-62", "仅有文档校验函数不能阻断用户编码/交付", "在验证、写入与交付路径消费设计产物包状态");
1778
+ hardFail("RELEASE_ISSUE_MAINLINE_MISSING", "问题六十二设计产物包子机制未接入真实消费路径", ["src/adapters/claude_code/tools.ts"], "problem-62", "仅有文档校验函数不能阻断用户编码/交付", "在验证、写入与交付路径消费设计产物包状态");
1528
1779
  }
1529
1780
  if (!cliText.includes("cmdAuditDesignArtifacts") ||
1530
1781
  !cliText.includes("cmdUpgradeDesignArtifacts") ||
1531
1782
  !cliText.includes("currentTask?.design_artifact_pack")) {
1532
- hardFail("BATCH6_USER_CLI_MISSING", "问题六十二缺少用户 CLI 审计/升级或 Claude Code 写入 hook 阻断路径", ["src/bin/soloforge.ts"], "problem-62", "MCP 内部调用不能覆盖用户现有文档升级与直接 Edit/Write 路径", "接入 audit/upgrade CLI 与 check-write 设计产物门");
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 设计产物门");
1533
1784
  }
1534
- if (!workflowText.includes("wf-architecture-workshop-first") || !workflowText.includes("wf-design-artifact-pack")) {
1535
- hardFail("BATCH6_USER_INJECTION_MISSING", "用户项目工作流模板未注入 Batch6 薄硬协议", ["src/adapters/shared/workflow_template.ts"], "problem-61/problem-62", "运行时有门但用户不可预期会造成体验断裂", "向 workflow template 注入硬规则");
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", "模块不可执行", "修复构建或导出");
1536
1809
  }
1537
1810
  try {
1538
1811
  const workshop = await import(path.join(rootDir, "dist", "engine", "architecture_decision_workshop.js"));
1539
1812
  const designPack = await import(path.join(rootDir, "dist", "engine", "design_artifact_pack.js"));
1540
- const draft = workshop.createArchitectureDecisionWorkshop("release-batch6", "new_system");
1813
+ const draft = workshop.createArchitectureDecisionWorkshop("release-issue", "new_system");
1541
1814
  if (workshop.evaluateArchitectureDecisionWorkshop(draft).allowed) {
1542
- hardFail("BATCH6_BEHAVIOR_FALSE_PASS", "问题六十一未确认六域的草稿错误放行", ["src/engine/architecture_decision_workshop.ts"], "problem-61", "必须有负向行为证据", "修复研讨生成门");
1815
+ hardFail("RELEASE_ISSUE_BEHAVIOR_FALSE_PASS", "问题六十一未确认六域的草稿错误放行", ["src/engine/architecture_decision_workshop.ts"], "problem-61", "必须有负向行为证据", "修复研讨生成门");
1543
1816
  }
1544
- const missingPackResult = designPack.verifyDesignArtifactPack(rootDir, designPack.createDesignArtifactPack("release-batch6"));
1817
+ const missingPackResult = designPack.verifyDesignArtifactPack(rootDir, designPack.createDesignArtifactPack("release-issue"));
1545
1818
  if (missingPackResult.passed) {
1546
- hardFail("BATCH6_BEHAVIOR_FALSE_PASS", "问题六十二缺少用户项目设计产物时错误放行", ["src/engine/design_artifact_pack.ts"], "problem-62", "必须读取真实资产并阻断缺失", "修复设计产物复验门");
1819
+ hardFail("RELEASE_ISSUE_BEHAVIOR_FALSE_PASS", "问题六十二缺少用户项目设计产物时错误放行", ["src/engine/design_artifact_pack.ts"], "problem-62", "必须读取真实资产并阻断缺失", "修复设计产物复验门");
1547
1820
  }
1548
1821
  }
1549
1822
  catch (error) {
1550
- hardFail("BATCH6_MODULE_LOAD_FAILED", `无法执行 Batch6 行为复验: ${error?.message ?? error}`, ["src/engine/architecture_decision_workshop.ts", "src/engine/design_artifact_pack.ts"], "Batch6", "模块不可执行", "修复构建或导出");
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
+ }
1551
2080
  }
1552
- _info(" Batch6 架构决策与设计产物真实消费复验完成");
1553
2081
  }
1554
2082
  // ── 主入口 ──
1555
2083
  export async function runReleaseReadinessGate(rootDir) {
@@ -1637,10 +2165,10 @@ export async function runReleaseReadinessGate(rootDir) {
1637
2165
  beginPhase("依赖漏洞扫描");
1638
2166
  checkDependencyAudit(rootDir, hardFail, _info);
1639
2167
  endPhase("依赖漏洞扫描");
1640
- // 15: Batch6 新增真实用户路径
1641
- beginPhase("Batch6 架构决策与设计产物真实消费");
1642
- await checkBatch6DesignPath(rootDir, hardFail, _info);
1643
- endPhase("Batch6 架构决策与设计产物真实消费");
2168
+ // 15: 发布问题全局合同(问题六十一至六十四)
2169
+ beginPhase("发布问题全局合同");
2170
+ await checkReleaseIssueDesignPath(rootDir, hardFail, _info);
2171
+ endPhase("发布问题全局合同");
1644
2172
  // 恢复 console.error 和日志器
1645
2173
  console.error = origConsoleError;
1646
2174
  resetLogger();