soloforge 1.4.3 → 1.4.5

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 (110) hide show
  1. package/README.md +6 -5
  2. package/dist/adapters/claude_code/tools.d.ts.map +1 -1
  3. package/dist/adapters/claude_code/tools.js +56 -13
  4. package/dist/adapters/claude_code/tools.js.map +1 -1
  5. package/dist/adapters/shared/workflow_template.js +1 -1
  6. package/dist/bin/soloforge.d.ts.map +1 -1
  7. package/dist/bin/soloforge.js +186 -75
  8. package/dist/bin/soloforge.js.map +1 -1
  9. package/dist/engine/asset_manifest.js +1 -1
  10. package/dist/engine/asset_manifest.js.map +1 -1
  11. package/dist/engine/backend_implementation_contract.js +1 -1
  12. package/dist/engine/code_maintainability_observability_contract.js +7 -7
  13. package/dist/engine/code_maintainability_observability_contract.js.map +1 -1
  14. package/dist/engine/consumable_asset_registry.d.ts.map +1 -1
  15. package/dist/engine/consumable_asset_registry.js +1 -0
  16. package/dist/engine/consumable_asset_registry.js.map +1 -1
  17. package/dist/engine/consumption_trace_store.d.ts +9 -1
  18. package/dist/engine/consumption_trace_store.d.ts.map +1 -1
  19. package/dist/engine/consumption_trace_store.js +3 -0
  20. package/dist/engine/consumption_trace_store.js.map +1 -1
  21. package/dist/engine/control_plane_contract.d.ts.map +1 -1
  22. package/dist/engine/control_plane_contract.js +1 -0
  23. package/dist/engine/control_plane_contract.js.map +1 -1
  24. package/dist/engine/design_artifact_pack.d.ts +5 -0
  25. package/dist/engine/design_artifact_pack.d.ts.map +1 -1
  26. package/dist/engine/design_artifact_pack.js +23 -17
  27. package/dist/engine/design_artifact_pack.js.map +1 -1
  28. package/dist/engine/design_lifecycle_contract.d.ts +60 -0
  29. package/dist/engine/design_lifecycle_contract.d.ts.map +1 -0
  30. package/dist/engine/design_lifecycle_contract.js +497 -0
  31. package/dist/engine/design_lifecycle_contract.js.map +1 -0
  32. package/dist/engine/diagnostic_registry.js +2 -2
  33. package/dist/engine/diagnostic_registry.js.map +1 -1
  34. package/dist/engine/explicit_asset_registry.d.ts.map +1 -1
  35. package/dist/engine/explicit_asset_registry.js +16 -0
  36. package/dist/engine/explicit_asset_registry.js.map +1 -1
  37. package/dist/engine/foundation_scenario_registry.js +2 -2
  38. package/dist/engine/foundation_scenario_registry.js.map +1 -1
  39. package/dist/engine/foundation_scenario_runners.js +9 -9
  40. package/dist/engine/foundation_scenario_runners.js.map +1 -1
  41. package/dist/engine/historical_issue_mechanization_matrix.js +1 -1
  42. package/dist/engine/historical_issue_mechanization_matrix.js.map +1 -1
  43. package/dist/engine/implementation_roadmap_registry.js +2 -2
  44. package/dist/engine/implementation_roadmap_registry.js.map +1 -1
  45. package/dist/engine/input_material_extractor.js +1 -1
  46. package/dist/engine/input_material_extractor.js.map +1 -1
  47. package/dist/engine/intent_expander.d.ts.map +1 -1
  48. package/dist/engine/intent_expander.js +38 -15
  49. package/dist/engine/intent_expander.js.map +1 -1
  50. package/dist/engine/knowledge_asset_consumer.d.ts.map +1 -1
  51. package/dist/engine/knowledge_asset_consumer.js +21 -13
  52. package/dist/engine/knowledge_asset_consumer.js.map +1 -1
  53. package/dist/engine/knowledge_injection_boundary.d.ts +2 -2
  54. package/dist/engine/knowledge_injection_boundary.d.ts.map +1 -1
  55. package/dist/engine/knowledge_injection_boundary.js +19 -7
  56. package/dist/engine/knowledge_injection_boundary.js.map +1 -1
  57. package/dist/engine/lifecycle_knowledge_contract.d.ts +59 -0
  58. package/dist/engine/lifecycle_knowledge_contract.d.ts.map +1 -0
  59. package/dist/engine/lifecycle_knowledge_contract.js +203 -0
  60. package/dist/engine/lifecycle_knowledge_contract.js.map +1 -0
  61. package/dist/engine/next_action_planner.d.ts +9 -4
  62. package/dist/engine/next_action_planner.d.ts.map +1 -1
  63. package/dist/engine/next_action_planner.js +66 -23
  64. package/dist/engine/next_action_planner.js.map +1 -1
  65. package/dist/engine/observed_consumption.d.ts.map +1 -1
  66. package/dist/engine/observed_consumption.js +2 -1
  67. package/dist/engine/observed_consumption.js.map +1 -1
  68. package/dist/engine/project_knowledge_contract.d.ts +75 -1
  69. package/dist/engine/project_knowledge_contract.d.ts.map +1 -1
  70. package/dist/engine/project_knowledge_contract.js +267 -30
  71. package/dist/engine/project_knowledge_contract.js.map +1 -1
  72. package/dist/engine/regression_matrix.js +1 -1
  73. package/dist/engine/regression_matrix.js.map +1 -1
  74. package/dist/engine/release_issue_scenario_registry.d.ts.map +1 -1
  75. package/dist/engine/release_issue_scenario_registry.js +15 -12
  76. package/dist/engine/release_issue_scenario_registry.js.map +1 -1
  77. package/dist/engine/release_readiness_gate.d.ts.map +1 -1
  78. package/dist/engine/release_readiness_gate.js +211 -56
  79. package/dist/engine/release_readiness_gate.js.map +1 -1
  80. package/dist/engine/stale_current_task_detector.d.ts +1 -1
  81. package/dist/engine/stale_current_task_detector.js +4 -4
  82. package/dist/engine/stale_current_task_detector.js.map +1 -1
  83. package/dist/engine/task_context.d.ts.map +1 -1
  84. package/dist/engine/task_context.js +2 -0
  85. package/dist/engine/task_context.js.map +1 -1
  86. package/dist/engine/traceability.d.ts +7 -1
  87. package/dist/engine/traceability.d.ts.map +1 -1
  88. package/dist/engine/traceability.js +90 -15
  89. package/dist/engine/traceability.js.map +1 -1
  90. package/dist/engine/workflow_navigation_contract.d.ts +11 -0
  91. package/dist/engine/workflow_navigation_contract.d.ts.map +1 -1
  92. package/dist/engine/workspace_resumer.d.ts.map +1 -1
  93. package/dist/engine/workspace_resumer.js +2 -1
  94. package/dist/engine/workspace_resumer.js.map +1 -1
  95. package/dist/knowledge/conflict_detector.d.ts +1 -1
  96. package/dist/knowledge/conflict_detector.d.ts.map +1 -1
  97. package/dist/knowledge/conflict_detector.js +86 -2
  98. package/dist/knowledge/conflict_detector.js.map +1 -1
  99. package/dist/types.d.ts +14 -0
  100. package/dist/types.d.ts.map +1 -1
  101. package/package.json +1 -1
  102. package/templates/knowledge/acceptance_templates//344/273/243/347/240/201/346/263/250/351/207/212/344/270/216/346/227/245/345/277/227/351/252/214/346/224/266/346/250/241/346/235/277.md +1 -1
  103. package/templates/knowledge/acceptance_templates//345/216/237/345/236/213/350/257/264/346/230/216/346/250/241/347/211/210.md +6 -6
  104. package/templates/knowledge/acceptance_templates//351/234/200/346/261/202/345/216/237/345/236/213/350/256/276/350/256/241/345/256/236/347/216/260/350/277/275/350/270/252/347/237/251/351/230/265/346/250/241/347/211/210.md +103 -0
  105. package/templates/knowledge/procedures//345/205/250/347/224/237/345/221/275/345/221/250/346/234/237/345/267/245/344/275/234/346/265/201/345/257/274/350/210/252.md +13 -0
  106. package/templates/knowledge/procedures//346/272/220/347/240/201/345/216/237/345/236/213/344/272/244/344/273/230/346/265/201/347/250/213.md +8 -5
  107. package/templates/knowledge/procedures//350/257/246/347/273/206/350/256/276/350/256/241/346/265/201/347/250/213.md +19 -6
  108. package/templates/knowledge/rules//344/273/243/347/240/201/346/263/250/351/207/212/344/270/216/346/227/245/345/277/227/345/245/221/347/272/246/350/247/204/345/210/231.md +4 -4
  109. package/templates/knowledge/rules//345/267/245/344/275/234/346/265/201/345/257/274/350/210/252/345/245/221/347/272/246/350/247/204/345/210/231.md +12 -0
  110. 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 +16 -0
@@ -109,7 +109,7 @@ export function validateWorkflowRuleSources(rootDir) {
109
109
  };
110
110
  }
111
111
  /** 当前嵌入的权威规则校验和 — 由 `npm run update-workflow-checksum` 更新。 */
112
- const EMBEDDED_CHECKSUM = "3620b50bb23320b5";
112
+ const EMBEDDED_CHECKSUM = "1329a153ea580993";
113
113
  function getEmbeddedChecksum() {
114
114
  return EMBEDDED_CHECKSUM;
115
115
  }
@@ -1 +1 @@
1
- {"version":3,"file":"soloforge.d.ts","sourceRoot":"","sources":["../../src/bin/soloforge.ts"],"names":[],"mappings":";AA87BA,OAAO,EAAE,0BAA0B,EAAE,MAAM,gDAAgD,CAAC"}
1
+ {"version":3,"file":"soloforge.d.ts","sourceRoot":"","sources":["../../src/bin/soloforge.ts"],"names":[],"mappings":";AAshCA,OAAO,EAAE,0BAA0B,EAAE,MAAM,gDAAgD,CAAC"}
@@ -18,6 +18,7 @@ import { isReadForbidden } from "../engine/privacy_secret_contract.js";
18
18
  import { routeIntent } from "../engine/intent_router.js";
19
19
  import { debugLog, debug, userInfo, userWarn, userError, internalWarn, jsonSafeError, initLoggerFromEnv } from "../engine/logger.js";
20
20
  import { auditControlPlaneProject, isSoloforgeStateBypassCommand } from "../engine/control_plane_contract.js";
21
+ import { getDesignLifecycleArtifactPaths } from "../engine/design_lifecycle_contract.js";
21
22
  const command = process.argv[2];
22
23
  const args = process.argv.slice(3);
23
24
  async function main() {
@@ -174,7 +175,7 @@ async function main() {
174
175
  generate-trae 生成 .trae/mcp.json 和 .trae/rules/project_rules.md
175
176
  generate-codex 生成 .codex/ 配置和 AGENTS.md
176
177
  check-write PreToolUse hook,用于范围检查(读取 stdin JSON)
177
- check-bash PreToolUse hook,阻止 Bash 直接篡改 .soloforge/state
178
+ check-bash PreToolUse hook,阻止 Bash 直接篡改 .soloforge/state 或绕过受控任务写业务实现
178
179
  doctor 审计 MCP 控制面、适配器 hook、状态可信和恢复路径
179
180
  post-bash PostToolUse hook,用于跟踪 Bash 执行结果
180
181
  validate 验证项目配置和知识文件(config.yaml 可选,缺失时自动推断)
@@ -698,61 +699,11 @@ async function cmdCheckWrite() {
698
699
  if (result.severity === "warning") {
699
700
  jsonSafeError(JSON.stringify(result));
700
701
  }
701
- // 问题六十二: Claude Code 直接 Edit/Write 也必须遵守设计产物就绪门。
702
- // 设计资产本身允许继续修订;非设计文件在产物包复验通过前禁止写入。
703
702
  const relativeFilePath = path.relative(projectRealPath, targetFilePath).replaceAll(path.sep, "/");
704
- const isDesignArtifactFile = /^docs\/architecture\//.test(relativeFilePath) ||
705
- /^docs\/api\/openapi\.(?:ya?ml|json)$/.test(relativeFilePath) ||
706
- /^db\/(?:migrations|schema)\//.test(relativeFilePath);
707
- if (!isDesignArtifactFile) {
708
- const { TaskContextManager } = await import("../engine/task_context.js");
709
- const stateDir = path.join(projectPath, ".soloforge", "state");
710
- const implementationSource = isImplementationSourcePath(relativeFilePath);
711
- if (fss.existsSync(stateDir)) {
712
- const manager = new TaskContextManager(stateDir);
713
- const currentTask = await manager.getCurrentTask();
714
- const designPack = currentTask?.design_artifact_pack;
715
- if (implementationSource && currentTask && ["classifying", "clarifying", "expanding"].includes(currentTask.status)) {
716
- process.stdout.write(JSON.stringify({
717
- hookSpecificOutput: {
718
- hookEventName: "PreToolUse",
719
- permissionDecision: "deny",
720
- permissionDecisionReason: `当前 SoloForge 任务仍处于 ${currentTask.status},说明分类、澄清或意图膨胀未闭合。不得绕过 sf_expand 直接写业务实现;请先运行 soloforge next 或 sf_status action=archive_stale confirm=true 处理任务状态。`,
721
- },
722
- }));
723
- process.exit(0);
724
- }
725
- if (implementationSource && !currentTask && hasDesignArtifacts(projectPath)) {
726
- process.stdout.write(JSON.stringify({
727
- hookSpecificOutput: {
728
- hookEventName: "PreToolUse",
729
- permissionDecision: "deny",
730
- permissionDecisionReason: "检测到项目已有架构/详细/API/数据库设计产物,但当前没有受控 SoloForge 任务。不得绕过 sf_classify/sf_expand 直接写业务实现;请先运行 soloforge next 或通过 MCP 建立编码任务。",
731
- },
732
- }));
733
- process.exit(0);
734
- }
735
- if (implementationSource && currentTask && !designPack && hasDesignArtifacts(projectPath)) {
736
- process.stdout.write(JSON.stringify({
737
- hookSpecificOutput: {
738
- hookEventName: "PreToolUse",
739
- permissionDecision: "deny",
740
- permissionDecisionReason: "检测到项目已有设计产物,但当前任务缺少 design_artifact_pack 复验状态。不得直接写业务实现;请先运行 soloforge audit-design-artifacts,并通过 sf_expand/sf_verify 绑定设计产物包。",
741
- },
742
- }));
743
- process.exit(0);
744
- }
745
- if (designPack && designPack.status !== "implementation_ready") {
746
- process.stdout.write(JSON.stringify({
747
- hookSpecificOutput: {
748
- hookEventName: "PreToolUse",
749
- permissionDecision: "deny",
750
- permissionDecisionReason: `设计产物包状态为 ${designPack.status},请先完善 docs/architecture、docs/api/openapi 或 db/migrations|schema 并通过复验,再写入业务实现`,
751
- },
752
- }));
753
- process.exit(0);
754
- }
755
- }
703
+ const implementationGateReason = await evaluateImplementationWriteGate(projectPath, relativeFilePath);
704
+ if (implementationGateReason) {
705
+ denyPreToolUse(implementationGateReason);
706
+ process.exit(0);
756
707
  }
757
708
  process.exit(0); // 允许
758
709
  }
@@ -790,19 +741,76 @@ function isImplementationSourcePath(relativeFilePath) {
790
741
  return false;
791
742
  return /\.(?:java|kt|kts|scala|ts|tsx|js|jsx|mjs|cjs|py|go|rs|php|rb|cs|swift|vue|svelte|sql|xml|yml|yaml|properties)$/.test(relativeFilePath);
792
743
  }
744
+ function isDesignArtifactPath(relativeFilePath) {
745
+ const lifecyclePaths = getDesignLifecycleArtifactPaths();
746
+ if ([
747
+ lifecyclePaths.decision_record,
748
+ lifecyclePaths.architecture_document,
749
+ lifecyclePaths.database_document,
750
+ lifecyclePaths.api_document,
751
+ lifecyclePaths.optional_detail_design_document,
752
+ lifecyclePaths.slice_plan,
753
+ lifecyclePaths.consistency_report,
754
+ lifecyclePaths.traceability_matrix,
755
+ lifecyclePaths.openapi,
756
+ ].includes(relativeFilePath))
757
+ return true;
758
+ return /^docs\/architecture\//.test(relativeFilePath) ||
759
+ /^docs\/traceability\//.test(relativeFilePath) ||
760
+ /^docs\/api\/openapi\.(?:ya?ml|json)$/.test(relativeFilePath) ||
761
+ /^db\/(?:migrations?|schema|changelog)\//.test(relativeFilePath) ||
762
+ /(?:^|\/)(?:src\/main\/resources\/)?db\/(?:migrations?|schema|changelog)\//.test(relativeFilePath);
763
+ }
764
+ function denyPreToolUse(reason) {
765
+ process.stdout.write(JSON.stringify({
766
+ hookSpecificOutput: {
767
+ hookEventName: "PreToolUse",
768
+ permissionDecision: "deny",
769
+ permissionDecisionReason: reason,
770
+ },
771
+ }));
772
+ }
773
+ async function evaluateImplementationWriteGate(projectPath, relativeFilePath) {
774
+ // 问题六十二/七十三: Edit/Write/Bash 都必须遵守同一编码前门禁。
775
+ // 设计资产本身允许继续修订;非设计业务实现文件在产物包复验通过前禁止写入。
776
+ if (isDesignArtifactPath(relativeFilePath) || !isImplementationSourcePath(relativeFilePath))
777
+ return undefined;
778
+ const stateDir = path.join(projectPath, ".soloforge", "state");
779
+ if (!fss.existsSync(stateDir))
780
+ return undefined;
781
+ const { TaskContextManager } = await import("../engine/task_context.js");
782
+ const manager = new TaskContextManager(stateDir);
783
+ const currentTask = await manager.getCurrentTask();
784
+ const designPack = currentTask?.design_artifact_pack;
785
+ if (currentTask && ["classifying", "clarifying", "expanding"].includes(currentTask.status)) {
786
+ return `当前 SoloForge 任务仍处于 ${currentTask.status},说明分类、澄清或意图膨胀未闭合。不得绕过 sf_expand,不得取消/归档当前任务后改用 Bash/Edit/Write 直接写业务实现,也不得把 Bash 作为恢复路径。请先运行 soloforge next 查看阻断项;按返回的 auto_resolvable 或 recovery_command 补齐后重新 sf_expand。只有 soloforge next 明确判定 current-task 陈旧且用户确认时,才可运行 sf_status action=archive_stale confirm=true;归档后仍必须重新 sf_classify → sf_expand 建立受控编码任务。`;
787
+ }
788
+ if (!currentTask && hasDesignArtifacts(projectPath)) {
789
+ return "检测到项目已有架构/详细/API/数据库设计产物,但当前没有受控 SoloForge 任务。不得绕过 sf_classify/sf_expand 直接写业务实现,不得改用 Bash/Edit/Write 先写代码再补验证;请先运行 soloforge next 或通过 MCP 建立编码任务。";
790
+ }
791
+ if (currentTask && !designPack && hasDesignArtifacts(projectPath)) {
792
+ return "检测到项目已有设计产物,但当前任务缺少 design_artifact_pack 复验状态。不得取消任务后直接写业务实现,不得改用 Bash 绕过;请先运行 soloforge audit-design-artifacts,并通过 sf_expand/sf_verify 绑定设计产物包。";
793
+ }
794
+ if (designPack && designPack.status !== "implementation_ready") {
795
+ return `设计产物包状态为 ${designPack.status},不得改用 Bash/Edit/Write 绕过设计复验;请先完善 docs/architecture、docs/traceability、docs/api/openapi 或 db/migration|migrations|schema|changelog 并通过复验,再写入业务实现`;
796
+ }
797
+ return undefined;
798
+ }
793
799
  function hasDesignArtifacts(projectPath) {
800
+ const lifecyclePaths = getDesignLifecycleArtifactPaths();
794
801
  const candidates = [
795
- path.join(projectPath, "docs", "architecture", "00-架构决策记录.md"),
796
- path.join(projectPath, "docs", "architecture", "01-架构设计文档.md"),
797
- path.join(projectPath, "docs", "architecture", "02-数据库设计文档.md"),
798
- path.join(projectPath, "docs", "architecture", "03-API接口规格文档.md"),
799
- path.join(projectPath, "docs", "architecture", "04-详细设计文档.md"),
800
- path.join(projectPath, "docs", "architecture", "99-设计一致性验收报告.md"),
801
- path.join(projectPath, "docs", "api", "openapi.yaml"),
802
+ lifecyclePaths.decision_record,
803
+ lifecyclePaths.architecture_document,
804
+ lifecyclePaths.database_document,
805
+ lifecyclePaths.api_document,
806
+ lifecyclePaths.optional_detail_design_document,
807
+ lifecyclePaths.slice_plan,
808
+ lifecyclePaths.consistency_report,
809
+ lifecyclePaths.traceability_matrix,
810
+ lifecyclePaths.openapi,
802
811
  path.join(projectPath, "docs", "api", "openapi.yml"),
803
- path.join(projectPath, "db", "migrations"),
804
- path.join(projectPath, "db", "migration"),
805
- ];
812
+ ...lifecyclePaths.sql_roots,
813
+ ].map((candidate) => path.join(projectPath, candidate));
806
814
  return candidates.some((candidate) => fss.existsSync(candidate));
807
815
  }
808
816
  async function cmdPostBash() {
@@ -824,6 +832,68 @@ function extractBashCommand(rawInput) {
824
832
  return trimmed;
825
833
  }
826
834
  }
835
+ function unquoteShellToken(token) {
836
+ if (!token)
837
+ return undefined;
838
+ const trimmed = token.trim();
839
+ if ((trimmed.startsWith("\"") && trimmed.endsWith("\"")) || (trimmed.startsWith("'") && trimmed.endsWith("'"))) {
840
+ return trimmed.slice(1, -1);
841
+ }
842
+ return trimmed;
843
+ }
844
+ function addShellPathToken(targets, token) {
845
+ const value = unquoteShellToken(token);
846
+ if (!value || value === "-" || value.startsWith("$"))
847
+ return;
848
+ targets.add(value.replace(/[),;]+$/, ""));
849
+ }
850
+ function addSourcePathTokens(targets, commandText) {
851
+ const pathTokenPattern = /"([^"]+\.(?:java|kt|kts|scala|ts|tsx|js|jsx|mjs|cjs|py|go|rs|php|rb|cs|swift|vue|svelte|sql|xml|ya?ml|properties))"|'([^']+\.(?:java|kt|kts|scala|ts|tsx|js|jsx|mjs|cjs|py|go|rs|php|rb|cs|swift|vue|svelte|sql|xml|ya?ml|properties))'|([^\s'";&|()<>]+\.(?:java|kt|kts|scala|ts|tsx|js|jsx|mjs|cjs|py|go|rs|php|rb|cs|swift|vue|svelte|sql|xml|ya?ml|properties))/gi;
852
+ for (const match of commandText.matchAll(pathTokenPattern)) {
853
+ addShellPathToken(targets, match[1] || match[2] || match[3]);
854
+ }
855
+ }
856
+ function extractBashWriteTargets(commandText) {
857
+ const command = commandText.replace(/\s+/g, " ").trim();
858
+ const targets = new Set();
859
+ const redirectionPattern = /(?:^|[^<])>>?\s*(?:"([^"]+)"|'([^']+)'|([^\s;&|]+))/g;
860
+ for (const match of command.matchAll(redirectionPattern)) {
861
+ addShellPathToken(targets, match[1] || match[2] || match[3]);
862
+ }
863
+ const teePattern = /(?:^|[\s;&|()])tee\s+(?:-[A-Za-z]+\s+)*(?:"([^"]+)"|'([^']+)'|([^\s;&|]+))/g;
864
+ for (const match of command.matchAll(teePattern)) {
865
+ addShellPathToken(targets, match[1] || match[2] || match[3]);
866
+ }
867
+ const writeApiPattern = /(?:writeFileSync|writeFile|write_text)\s*\(\s*["']([^"']+)["']/g;
868
+ for (const match of command.matchAll(writeApiPattern)) {
869
+ addShellPathToken(targets, match[1]);
870
+ }
871
+ const pythonOpenWritePattern = /open\s*\(\s*["']([^"']+)["']\s*,\s*["'][^"']*[wa+][^"']*["']/g;
872
+ for (const match of command.matchAll(pythonOpenWritePattern)) {
873
+ addShellPathToken(targets, match[1]);
874
+ }
875
+ const pathlibWritePattern = /(?:Path|pathlib\.Path)\s*\(\s*["']([^"']+)["']\s*\)\.write_text/g;
876
+ for (const match of command.matchAll(pathlibWritePattern)) {
877
+ addShellPathToken(targets, match[1]);
878
+ }
879
+ const mutatingCommandPattern = /(?:^|[\s;&|()])(?:rm|mv|cp|touch|chmod|chown)\b/i;
880
+ const inPlaceEditPattern = /(?:^|[\s;&|()])(?:(?:g?sed)\s+[^;&|]*\s-i\b|perl\s+[^;&|]*\s-pi\b)/i;
881
+ if (mutatingCommandPattern.test(command) || inPlaceEditPattern.test(command)) {
882
+ addSourcePathTokens(targets, command);
883
+ }
884
+ return [...targets];
885
+ }
886
+ function resolveProjectRelativeTarget(projectPath, targetPath) {
887
+ const rawTargetFilePath = path.isAbsolute(targetPath) ? path.normalize(targetPath) : path.resolve(projectPath, targetPath);
888
+ const projectRealPath = fss.realpathSync(projectPath);
889
+ const targetFilePath = rawTargetFilePath === projectPath || rawTargetFilePath.startsWith(projectPath + path.sep)
890
+ ? path.join(projectRealPath, path.relative(projectPath, rawTargetFilePath))
891
+ : rawTargetFilePath;
892
+ const relativeFilePath = path.relative(projectRealPath, targetFilePath).replaceAll(path.sep, "/");
893
+ if (relativeFilePath === "" || relativeFilePath.startsWith("../") || path.isAbsolute(relativeFilePath))
894
+ return undefined;
895
+ return relativeFilePath;
896
+ }
827
897
  async function cmdCheckBash() {
828
898
  debugLog("CLI: 执行 cmdCheckBash");
829
899
  try {
@@ -834,13 +904,19 @@ async function cmdCheckBash() {
834
904
  const stdin = Buffer.concat(chunks).toString();
835
905
  const commandText = extractBashCommand(stdin || args.join(" "));
836
906
  if (isSoloforgeStateBypassCommand(commandText)) {
837
- process.stdout.write(JSON.stringify({
838
- hookSpecificOutput: {
839
- hookEventName: "PreToolUse",
840
- permissionDecision: "deny",
841
- permissionDecisionReason: "禁止通过 Bash 直接修改 .soloforge/state;请使用 SoloForge MCP 工具,例如 sf_status action=archive_stale confirm=true。",
842
- },
843
- }));
907
+ denyPreToolUse("禁止通过 Bash 直接修改 .soloforge/state;请使用 SoloForge MCP 工具,例如 sf_status action=archive_stale confirm=true。");
908
+ process.exit(0);
909
+ }
910
+ const projectPath = resolveProjectPath();
911
+ for (const target of extractBashWriteTargets(commandText)) {
912
+ const relativeTarget = resolveProjectRelativeTarget(projectPath, target);
913
+ if (!relativeTarget)
914
+ continue;
915
+ const implementationGateReason = await evaluateImplementationWriteGate(projectPath, relativeTarget);
916
+ if (implementationGateReason) {
917
+ denyPreToolUse(`禁止通过 Bash 直接写业务实现文件 ${relativeTarget};${implementationGateReason}`);
918
+ process.exit(0);
919
+ }
844
920
  }
845
921
  process.exit(0);
846
922
  }
@@ -1494,6 +1570,12 @@ async function cmdAuditDesignArtifacts() {
1494
1570
  userInfo(` hard_fail: ${result.findings.filter((finding) => finding.severity === "hard_fail").length}`);
1495
1571
  for (const finding of result.findings) {
1496
1572
  userInfo(` ${finding.severity === "hard_fail" ? "❌" : "⚠️"} [${finding.code}] ${finding.message_zh}`);
1573
+ if (finding.template_path)
1574
+ userInfo(` 模板: ${finding.template_path}`);
1575
+ if (finding.recovery_command)
1576
+ userInfo(` 命令: ${finding.recovery_command}`);
1577
+ if (finding.recovery_zh)
1578
+ userInfo(` 恢复: ${finding.recovery_zh}`);
1497
1579
  }
1498
1580
  if (result.passed)
1499
1581
  userInfo("✅ 设计产物包复验通过,可作为后续实现输入");
@@ -2434,7 +2516,20 @@ async function cmdNext() {
2434
2516
  if (plan.project_knowledge_context.selected.length > 0) {
2435
2517
  userInfo(" 下一步会优先参考:");
2436
2518
  for (const item of plan.project_knowledge_context.selected) {
2437
- userInfo(` - ${item.name} (${item.rel_path}, ${item.consumption})`);
2519
+ userInfo(` - ${item.name} (${item.rel_path}, ${item.enforcement ?? item.consumption})`);
2520
+ if (item.reason_zh)
2521
+ userInfo(` 原因: ${item.reason_zh}`);
2522
+ if (item.verification_command)
2523
+ userInfo(` 复验: ${item.verification_command}`);
2524
+ }
2525
+ }
2526
+ if (plan.project_knowledge_context.blocked && plan.project_knowledge_context.blocked.length > 0) {
2527
+ userInfo(" 必须先处理:");
2528
+ for (const item of plan.project_knowledge_context.blocked) {
2529
+ userInfo(` - ${item.name} (${item.rel_path})`);
2530
+ userInfo(` 原因: ${item.reason_zh}`);
2531
+ if (item.recovery)
2532
+ userInfo(` 恢复: ${item.recovery}`);
2438
2533
  }
2439
2534
  }
2440
2535
  }
@@ -2848,14 +2943,30 @@ async function cmdAuditProjectKnowledge() {
2848
2943
  userInfo(` hard_fail: ${report.hard_fail_count}`);
2849
2944
  userInfo(` warning: ${report.warning_count}`);
2850
2945
  for (const asset of report.assets) {
2851
- if (asset.findings.length === 0)
2852
- continue;
2853
2946
  userInfo(` - ${asset.rel_path} (${asset.identity}/${asset.consumption})`);
2947
+ userInfo(` 创建: ${asset.evidence.created_by};索引: ${asset.evidence.indexed_by}`);
2948
+ if (asset.lifecycle_stages.length > 0)
2949
+ userInfo(` 阶段: ${asset.lifecycle_stages.join(", ")}`);
2950
+ if (asset.path_patterns.length > 0)
2951
+ userInfo(` 路径: ${asset.path_patterns.join(", ")}`);
2952
+ if (asset.traceability_ids.length > 0)
2953
+ userInfo(` 追踪: ${asset.traceability_ids.join(", ")}`);
2954
+ if (asset.verification_commands.length > 0)
2955
+ userInfo(` 复验: ${asset.verification_commands.join(" / ")}`);
2956
+ if (asset.evidence.last_consumed_at) {
2957
+ userInfo(` 最近消费: ${asset.evidence.last_consumption_consumer}/${asset.evidence.last_consumption_action} ${asset.evidence.last_consumed_task} @ ${asset.evidence.last_consumed_at}`);
2958
+ }
2854
2959
  for (const finding of asset.findings) {
2855
2960
  const mark = finding.severity === "hard_fail" ? "❌" : finding.severity === "warning" ? "⚠️" : "ℹ️";
2856
2961
  userInfo(` ${mark} [${finding.code}] ${finding.message_zh}`);
2857
2962
  }
2858
2963
  }
2964
+ if (report.recent_consumption.length > 0) {
2965
+ userInfo(" 最近消费证据:");
2966
+ for (const item of report.recent_consumption.slice(0, 10)) {
2967
+ userInfo(` - ${item.rel_path ?? item.asset_id}: ${item.consumer}/${item.action ?? "unknown"} ${item.lifecycle_stage ?? "unknown_stage"} ${item.task_id_or_route} ${item.decision ?? ""}`);
2968
+ }
2969
+ }
2859
2970
  if (report.recommended_next_steps.length > 0) {
2860
2971
  userInfo(" 下一步:");
2861
2972
  for (const step of report.recommended_next_steps)