soloforge 1.4.2 → 1.4.4

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 (54) hide show
  1. package/dist/adapters/claude_code/tools.d.ts.map +1 -1
  2. package/dist/adapters/claude_code/tools.js +122 -3
  3. package/dist/adapters/claude_code/tools.js.map +1 -1
  4. package/dist/bin/soloforge.d.ts.map +1 -1
  5. package/dist/bin/soloforge.js +126 -62
  6. package/dist/bin/soloforge.js.map +1 -1
  7. package/dist/engine/asset_manifest.js +1 -1
  8. package/dist/engine/asset_manifest.js.map +1 -1
  9. package/dist/engine/consumable_asset_registry.d.ts.map +1 -1
  10. package/dist/engine/consumable_asset_registry.js +1 -0
  11. package/dist/engine/consumable_asset_registry.js.map +1 -1
  12. package/dist/engine/control_plane_contract.d.ts.map +1 -1
  13. package/dist/engine/control_plane_contract.js +1 -0
  14. package/dist/engine/control_plane_contract.js.map +1 -1
  15. package/dist/engine/decision_workshop.d.ts +8 -0
  16. package/dist/engine/decision_workshop.d.ts.map +1 -1
  17. package/dist/engine/decision_workshop.js +116 -6
  18. package/dist/engine/decision_workshop.js.map +1 -1
  19. package/dist/engine/design_artifact_pack.d.ts +4 -0
  20. package/dist/engine/design_artifact_pack.d.ts.map +1 -1
  21. package/dist/engine/design_artifact_pack.js +22 -0
  22. package/dist/engine/design_artifact_pack.js.map +1 -1
  23. package/dist/engine/diagnostic_registry.d.ts +4 -0
  24. package/dist/engine/diagnostic_registry.d.ts.map +1 -1
  25. package/dist/engine/diagnostic_registry.js +24 -0
  26. package/dist/engine/diagnostic_registry.js.map +1 -1
  27. package/dist/engine/explicit_asset_registry.d.ts.map +1 -1
  28. package/dist/engine/explicit_asset_registry.js +16 -0
  29. package/dist/engine/explicit_asset_registry.js.map +1 -1
  30. package/dist/engine/historical_issue_mechanization_matrix.js +8 -8
  31. package/dist/engine/historical_issue_mechanization_matrix.js.map +1 -1
  32. package/dist/engine/implementation_roadmap_registry.d.ts.map +1 -1
  33. package/dist/engine/implementation_roadmap_registry.js +16 -2
  34. package/dist/engine/implementation_roadmap_registry.js.map +1 -1
  35. package/dist/engine/next_action_planner.d.ts +9 -4
  36. package/dist/engine/next_action_planner.d.ts.map +1 -1
  37. package/dist/engine/next_action_planner.js +33 -0
  38. package/dist/engine/next_action_planner.js.map +1 -1
  39. package/dist/engine/release_issue_scenario_registry.d.ts.map +1 -1
  40. package/dist/engine/release_issue_scenario_registry.js +21 -7
  41. package/dist/engine/release_issue_scenario_registry.js.map +1 -1
  42. package/dist/engine/release_readiness_gate.d.ts.map +1 -1
  43. package/dist/engine/release_readiness_gate.js +86 -6
  44. package/dist/engine/release_readiness_gate.js.map +1 -1
  45. package/dist/engine/traceability.d.ts +42 -0
  46. package/dist/engine/traceability.d.ts.map +1 -1
  47. package/dist/engine/traceability.js +250 -0
  48. package/dist/engine/traceability.js.map +1 -1
  49. package/dist/types.d.ts +11 -0
  50. package/dist/types.d.ts.map +1 -1
  51. package/package.json +1 -1
  52. 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 +98 -0
  53. 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
  54. 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
@@ -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":";AAqgCA,OAAO,EAAE,0BAA0B,EAAE,MAAM,gDAAgD,CAAC"}
@@ -174,7 +174,7 @@ async function main() {
174
174
  generate-trae 生成 .trae/mcp.json 和 .trae/rules/project_rules.md
175
175
  generate-codex 生成 .codex/ 配置和 AGENTS.md
176
176
  check-write PreToolUse hook,用于范围检查(读取 stdin JSON)
177
- check-bash PreToolUse hook,阻止 Bash 直接篡改 .soloforge/state
177
+ check-bash PreToolUse hook,阻止 Bash 直接篡改 .soloforge/state 或绕过受控任务写业务实现
178
178
  doctor 审计 MCP 控制面、适配器 hook、状态可信和恢复路径
179
179
  post-bash PostToolUse hook,用于跟踪 Bash 执行结果
180
180
  validate 验证项目配置和知识文件(config.yaml 可选,缺失时自动推断)
@@ -698,61 +698,11 @@ async function cmdCheckWrite() {
698
698
  if (result.severity === "warning") {
699
699
  jsonSafeError(JSON.stringify(result));
700
700
  }
701
- // 问题六十二: Claude Code 直接 Edit/Write 也必须遵守设计产物就绪门。
702
- // 设计资产本身允许继续修订;非设计文件在产物包复验通过前禁止写入。
703
701
  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
- }
702
+ const implementationGateReason = await evaluateImplementationWriteGate(projectPath, relativeFilePath);
703
+ if (implementationGateReason) {
704
+ denyPreToolUse(implementationGateReason);
705
+ process.exit(0);
756
706
  }
757
707
  process.exit(0); // 允许
758
708
  }
@@ -790,6 +740,46 @@ function isImplementationSourcePath(relativeFilePath) {
790
740
  return false;
791
741
  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
742
  }
743
+ function isDesignArtifactPath(relativeFilePath) {
744
+ return /^docs\/architecture\//.test(relativeFilePath) ||
745
+ /^docs\/api\/openapi\.(?:ya?ml|json)$/.test(relativeFilePath) ||
746
+ /^db\/(?:migrations|schema)\//.test(relativeFilePath);
747
+ }
748
+ function denyPreToolUse(reason) {
749
+ process.stdout.write(JSON.stringify({
750
+ hookSpecificOutput: {
751
+ hookEventName: "PreToolUse",
752
+ permissionDecision: "deny",
753
+ permissionDecisionReason: reason,
754
+ },
755
+ }));
756
+ }
757
+ async function evaluateImplementationWriteGate(projectPath, relativeFilePath) {
758
+ // 问题六十二/七十三: Edit/Write/Bash 都必须遵守同一编码前门禁。
759
+ // 设计资产本身允许继续修订;非设计业务实现文件在产物包复验通过前禁止写入。
760
+ if (isDesignArtifactPath(relativeFilePath) || !isImplementationSourcePath(relativeFilePath))
761
+ return undefined;
762
+ const stateDir = path.join(projectPath, ".soloforge", "state");
763
+ if (!fss.existsSync(stateDir))
764
+ return undefined;
765
+ const { TaskContextManager } = await import("../engine/task_context.js");
766
+ const manager = new TaskContextManager(stateDir);
767
+ const currentTask = await manager.getCurrentTask();
768
+ const designPack = currentTask?.design_artifact_pack;
769
+ if (currentTask && ["classifying", "clarifying", "expanding"].includes(currentTask.status)) {
770
+ 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 建立受控编码任务。`;
771
+ }
772
+ if (!currentTask && hasDesignArtifacts(projectPath)) {
773
+ return "检测到项目已有架构/详细/API/数据库设计产物,但当前没有受控 SoloForge 任务。不得绕过 sf_classify/sf_expand 直接写业务实现,不得改用 Bash/Edit/Write 先写代码再补验证;请先运行 soloforge next 或通过 MCP 建立编码任务。";
774
+ }
775
+ if (currentTask && !designPack && hasDesignArtifacts(projectPath)) {
776
+ return "检测到项目已有设计产物,但当前任务缺少 design_artifact_pack 复验状态。不得取消任务后直接写业务实现,不得改用 Bash 绕过;请先运行 soloforge audit-design-artifacts,并通过 sf_expand/sf_verify 绑定设计产物包。";
777
+ }
778
+ if (designPack && designPack.status !== "implementation_ready") {
779
+ return `设计产物包状态为 ${designPack.status},不得改用 Bash/Edit/Write 绕过设计复验;请先完善 docs/architecture、docs/api/openapi 或 db/migrations|schema 并通过复验,再写入业务实现`;
780
+ }
781
+ return undefined;
782
+ }
793
783
  function hasDesignArtifacts(projectPath) {
794
784
  const candidates = [
795
785
  path.join(projectPath, "docs", "architecture", "00-架构决策记录.md"),
@@ -824,6 +814,68 @@ function extractBashCommand(rawInput) {
824
814
  return trimmed;
825
815
  }
826
816
  }
817
+ function unquoteShellToken(token) {
818
+ if (!token)
819
+ return undefined;
820
+ const trimmed = token.trim();
821
+ if ((trimmed.startsWith("\"") && trimmed.endsWith("\"")) || (trimmed.startsWith("'") && trimmed.endsWith("'"))) {
822
+ return trimmed.slice(1, -1);
823
+ }
824
+ return trimmed;
825
+ }
826
+ function addShellPathToken(targets, token) {
827
+ const value = unquoteShellToken(token);
828
+ if (!value || value === "-" || value.startsWith("$"))
829
+ return;
830
+ targets.add(value.replace(/[),;]+$/, ""));
831
+ }
832
+ function addSourcePathTokens(targets, commandText) {
833
+ 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;
834
+ for (const match of commandText.matchAll(pathTokenPattern)) {
835
+ addShellPathToken(targets, match[1] || match[2] || match[3]);
836
+ }
837
+ }
838
+ function extractBashWriteTargets(commandText) {
839
+ const command = commandText.replace(/\s+/g, " ").trim();
840
+ const targets = new Set();
841
+ const redirectionPattern = /(?:^|[^<])>>?\s*(?:"([^"]+)"|'([^']+)'|([^\s;&|]+))/g;
842
+ for (const match of command.matchAll(redirectionPattern)) {
843
+ addShellPathToken(targets, match[1] || match[2] || match[3]);
844
+ }
845
+ const teePattern = /(?:^|[\s;&|()])tee\s+(?:-[A-Za-z]+\s+)*(?:"([^"]+)"|'([^']+)'|([^\s;&|]+))/g;
846
+ for (const match of command.matchAll(teePattern)) {
847
+ addShellPathToken(targets, match[1] || match[2] || match[3]);
848
+ }
849
+ const writeApiPattern = /(?:writeFileSync|writeFile|write_text)\s*\(\s*["']([^"']+)["']/g;
850
+ for (const match of command.matchAll(writeApiPattern)) {
851
+ addShellPathToken(targets, match[1]);
852
+ }
853
+ const pythonOpenWritePattern = /open\s*\(\s*["']([^"']+)["']\s*,\s*["'][^"']*[wa+][^"']*["']/g;
854
+ for (const match of command.matchAll(pythonOpenWritePattern)) {
855
+ addShellPathToken(targets, match[1]);
856
+ }
857
+ const pathlibWritePattern = /(?:Path|pathlib\.Path)\s*\(\s*["']([^"']+)["']\s*\)\.write_text/g;
858
+ for (const match of command.matchAll(pathlibWritePattern)) {
859
+ addShellPathToken(targets, match[1]);
860
+ }
861
+ const mutatingCommandPattern = /(?:^|[\s;&|()])(?:rm|mv|cp|touch|chmod|chown)\b/i;
862
+ const inPlaceEditPattern = /(?:^|[\s;&|()])(?:(?:g?sed)\s+[^;&|]*\s-i\b|perl\s+[^;&|]*\s-pi\b)/i;
863
+ if (mutatingCommandPattern.test(command) || inPlaceEditPattern.test(command)) {
864
+ addSourcePathTokens(targets, command);
865
+ }
866
+ return [...targets];
867
+ }
868
+ function resolveProjectRelativeTarget(projectPath, targetPath) {
869
+ const rawTargetFilePath = path.isAbsolute(targetPath) ? path.normalize(targetPath) : path.resolve(projectPath, targetPath);
870
+ const projectRealPath = fss.realpathSync(projectPath);
871
+ const targetFilePath = rawTargetFilePath === projectPath || rawTargetFilePath.startsWith(projectPath + path.sep)
872
+ ? path.join(projectRealPath, path.relative(projectPath, rawTargetFilePath))
873
+ : rawTargetFilePath;
874
+ const relativeFilePath = path.relative(projectRealPath, targetFilePath).replaceAll(path.sep, "/");
875
+ if (relativeFilePath === "" || relativeFilePath.startsWith("../") || path.isAbsolute(relativeFilePath))
876
+ return undefined;
877
+ return relativeFilePath;
878
+ }
827
879
  async function cmdCheckBash() {
828
880
  debugLog("CLI: 执行 cmdCheckBash");
829
881
  try {
@@ -834,13 +886,19 @@ async function cmdCheckBash() {
834
886
  const stdin = Buffer.concat(chunks).toString();
835
887
  const commandText = extractBashCommand(stdin || args.join(" "));
836
888
  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
- }));
889
+ denyPreToolUse("禁止通过 Bash 直接修改 .soloforge/state;请使用 SoloForge MCP 工具,例如 sf_status action=archive_stale confirm=true。");
890
+ process.exit(0);
891
+ }
892
+ const projectPath = resolveProjectPath();
893
+ for (const target of extractBashWriteTargets(commandText)) {
894
+ const relativeTarget = resolveProjectRelativeTarget(projectPath, target);
895
+ if (!relativeTarget)
896
+ continue;
897
+ const implementationGateReason = await evaluateImplementationWriteGate(projectPath, relativeTarget);
898
+ if (implementationGateReason) {
899
+ denyPreToolUse(`禁止通过 Bash 直接写业务实现文件 ${relativeTarget};${implementationGateReason}`);
900
+ process.exit(0);
901
+ }
844
902
  }
845
903
  process.exit(0);
846
904
  }
@@ -1494,6 +1552,12 @@ async function cmdAuditDesignArtifacts() {
1494
1552
  userInfo(` hard_fail: ${result.findings.filter((finding) => finding.severity === "hard_fail").length}`);
1495
1553
  for (const finding of result.findings) {
1496
1554
  userInfo(` ${finding.severity === "hard_fail" ? "❌" : "⚠️"} [${finding.code}] ${finding.message_zh}`);
1555
+ if (finding.template_path)
1556
+ userInfo(` 模板: ${finding.template_path}`);
1557
+ if (finding.recovery_command)
1558
+ userInfo(` 命令: ${finding.recovery_command}`);
1559
+ if (finding.recovery_zh)
1560
+ userInfo(` 恢复: ${finding.recovery_zh}`);
1497
1561
  }
1498
1562
  if (result.passed)
1499
1563
  userInfo("✅ 设计产物包复验通过,可作为后续实现输入");