soloforge 1.3.10 → 1.4.1

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 (32) hide show
  1. package/README.md +10 -0
  2. package/dist/adapters/claude_code/hooks.d.ts.map +1 -1
  3. package/dist/adapters/claude_code/hooks.js +9 -0
  4. package/dist/adapters/claude_code/hooks.js.map +1 -1
  5. package/dist/adapters/claude_code/tools.d.ts.map +1 -1
  6. package/dist/adapters/claude_code/tools.js +44 -3
  7. package/dist/adapters/claude_code/tools.js.map +1 -1
  8. package/dist/adapters/codex/codex_config.d.ts.map +1 -1
  9. package/dist/adapters/codex/codex_config.js +10 -0
  10. package/dist/adapters/codex/codex_config.js.map +1 -1
  11. package/dist/bin/soloforge.d.ts.map +1 -1
  12. package/dist/bin/soloforge.js +154 -0
  13. package/dist/bin/soloforge.js.map +1 -1
  14. package/dist/engine/control_plane_contract.d.ts +59 -0
  15. package/dist/engine/control_plane_contract.d.ts.map +1 -0
  16. package/dist/engine/control_plane_contract.js +246 -0
  17. package/dist/engine/control_plane_contract.js.map +1 -0
  18. package/dist/engine/historical_issue_mechanization_matrix.js +7 -7
  19. package/dist/engine/historical_issue_mechanization_matrix.js.map +1 -1
  20. package/dist/engine/implementation_roadmap_registry.d.ts.map +1 -1
  21. package/dist/engine/implementation_roadmap_registry.js +45 -1
  22. package/dist/engine/implementation_roadmap_registry.js.map +1 -1
  23. package/dist/engine/intent_expander.d.ts.map +1 -1
  24. package/dist/engine/intent_expander.js +18 -7
  25. package/dist/engine/intent_expander.js.map +1 -1
  26. package/dist/engine/release_readiness_gate.d.ts +1 -0
  27. package/dist/engine/release_readiness_gate.d.ts.map +1 -1
  28. package/dist/engine/release_readiness_gate.js +95 -4
  29. package/dist/engine/release_readiness_gate.js.map +1 -1
  30. package/dist/engine/tool_invocation_contract_registry.js +17 -17
  31. package/dist/engine/tool_invocation_contract_registry.js.map +1 -1
  32. package/package.json +1 -1
@@ -1 +1 @@
1
- {"version":3,"file":"codex_config.d.ts","sourceRoot":"","sources":["../../../src/adapters/codex/codex_config.ts"],"names":[],"mappings":"AAmBA;;;;GAIG;AACH,wBAAgB,sBAAsB,CAAC,WAAW,EAAE,MAAM,GAAG,MAAM,CAgBlE;AAED;;;GAGG;AACH,wBAAgB,wBAAwB,IAAI,MAAM,CA8BjD"}
1
+ {"version":3,"file":"codex_config.d.ts","sourceRoot":"","sources":["../../../src/adapters/codex/codex_config.ts"],"names":[],"mappings":"AAmBA;;;;GAIG;AACH,wBAAgB,sBAAsB,CAAC,WAAW,EAAE,MAAM,GAAG,MAAM,CAgBlE;AAED;;;GAGG;AACH,wBAAgB,wBAAwB,IAAI,MAAM,CAwCjD"}
@@ -53,6 +53,16 @@ export function generateCodexHooksConfig() {
53
53
  },
54
54
  ],
55
55
  },
56
+ {
57
+ matcher: "^Bash$",
58
+ hooks: [
59
+ {
60
+ type: "command",
61
+ command: hookCommand("check-bash"),
62
+ timeout: 15,
63
+ },
64
+ ],
65
+ },
56
66
  ],
57
67
  PostToolUse: [
58
68
  {
@@ -1 +1 @@
1
- {"version":3,"file":"codex_config.js","sourceRoot":"","sources":["../../../src/adapters/codex/codex_config.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,KAAK,EAAE,MAAM,wBAAwB,CAAC;AAE/C,SAAS,gBAAgB,CAAC,KAAa;IACrC,OAAO,KAAK;SACT,OAAO,CAAC,KAAK,EAAE,MAAM,CAAC;SACtB,OAAO,CAAC,IAAI,EAAE,KAAK,CAAC;SACpB,OAAO,CAAC,KAAK,EAAE,KAAK,CAAC;SACrB,OAAO,CAAC,KAAK,EAAE,KAAK,CAAC;SACrB,OAAO,CAAC,KAAK,EAAE,KAAK,CAAC,CAAC;AAC3B,CAAC;AAED,SAAS,WAAW,CAAC,KAAe;IAClC,MAAM,OAAO,GAAG,KAAK,CAAC,GAAG,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,IAAI,gBAAgB,CAAC,CAAC,CAAC,GAAG,CAAC,CAAC;IAC7D,OAAO,IAAI,OAAO,CAAC,IAAI,CAAC,IAAI,CAAC,GAAG,CAAC;AACnC,CAAC;AAED,MAAM,WAAW,GAAG,CAAC,UAAkB,EAAE,EAAE,CACzC,6EAA6E,UAAU,EAAE,CAAC;AAE5F;;;;GAIG;AACH,MAAM,UAAU,sBAAsB,CAAC,WAAmB;IACxD,KAAK,CAAC,SAAS,EAAE,aAAa,EAAE,WAAW,CAAC,CAAC;IAC7C,OAAO;;SAEA,WAAW,CAAC,CAAC,KAAK,CAAC,CAAC;;;uBAGN,gBAAgB,CAAC,WAAW,CAAC;;;;SAI3C,WAAW,CAAC,CAAC,wBAAwB,EAAE,YAAY,CAAC,CAAC;;;;CAI7D,CAAC;AACF,CAAC;AAED;;;GAGG;AACH,MAAM,UAAU,wBAAwB;IACtC,KAAK,CAAC,SAAS,EAAE,aAAa,CAAC,CAAC;IAChC,OAAO,IAAI,CAAC,SAAS,CAAC;QACpB,KAAK,EAAE;YACL,UAAU,EAAE;gBACV;oBACE,OAAO,EAAE,4BAA4B;oBACrC,KAAK,EAAE;wBACL;4BACE,IAAI,EAAE,SAAS;4BACf,OAAO,EAAE,WAAW,CAAC,aAAa,CAAC;4BACnC,OAAO,EAAE,EAAE;yBACZ;qBACF;iBACF;aACF;YACD,WAAW,EAAE;gBACX;oBACE,OAAO,EAAE,QAAQ;oBACjB,KAAK,EAAE;wBACL;4BACE,IAAI,EAAE,SAAS;4BACf,OAAO,EAAE,WAAW,CAAC,WAAW,CAAC;4BACjC,OAAO,EAAE,EAAE;yBACZ;qBACF;iBACF;aACF;SACF;KACF,EAAE,IAAI,EAAE,CAAC,CAAC,CAAC;AACd,CAAC"}
1
+ {"version":3,"file":"codex_config.js","sourceRoot":"","sources":["../../../src/adapters/codex/codex_config.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,KAAK,EAAE,MAAM,wBAAwB,CAAC;AAE/C,SAAS,gBAAgB,CAAC,KAAa;IACrC,OAAO,KAAK;SACT,OAAO,CAAC,KAAK,EAAE,MAAM,CAAC;SACtB,OAAO,CAAC,IAAI,EAAE,KAAK,CAAC;SACpB,OAAO,CAAC,KAAK,EAAE,KAAK,CAAC;SACrB,OAAO,CAAC,KAAK,EAAE,KAAK,CAAC;SACrB,OAAO,CAAC,KAAK,EAAE,KAAK,CAAC,CAAC;AAC3B,CAAC;AAED,SAAS,WAAW,CAAC,KAAe;IAClC,MAAM,OAAO,GAAG,KAAK,CAAC,GAAG,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,IAAI,gBAAgB,CAAC,CAAC,CAAC,GAAG,CAAC,CAAC;IAC7D,OAAO,IAAI,OAAO,CAAC,IAAI,CAAC,IAAI,CAAC,GAAG,CAAC;AACnC,CAAC;AAED,MAAM,WAAW,GAAG,CAAC,UAAkB,EAAE,EAAE,CACzC,6EAA6E,UAAU,EAAE,CAAC;AAE5F;;;;GAIG;AACH,MAAM,UAAU,sBAAsB,CAAC,WAAmB;IACxD,KAAK,CAAC,SAAS,EAAE,aAAa,EAAE,WAAW,CAAC,CAAC;IAC7C,OAAO;;SAEA,WAAW,CAAC,CAAC,KAAK,CAAC,CAAC;;;uBAGN,gBAAgB,CAAC,WAAW,CAAC;;;;SAI3C,WAAW,CAAC,CAAC,wBAAwB,EAAE,YAAY,CAAC,CAAC;;;;CAI7D,CAAC;AACF,CAAC;AAED;;;GAGG;AACH,MAAM,UAAU,wBAAwB;IACtC,KAAK,CAAC,SAAS,EAAE,aAAa,CAAC,CAAC;IAChC,OAAO,IAAI,CAAC,SAAS,CAAC;QACpB,KAAK,EAAE;YACL,UAAU,EAAE;gBACV;oBACE,OAAO,EAAE,4BAA4B;oBACrC,KAAK,EAAE;wBACL;4BACE,IAAI,EAAE,SAAS;4BACf,OAAO,EAAE,WAAW,CAAC,aAAa,CAAC;4BACnC,OAAO,EAAE,EAAE;yBACZ;qBACF;iBACF;gBACD;oBACE,OAAO,EAAE,QAAQ;oBACjB,KAAK,EAAE;wBACL;4BACE,IAAI,EAAE,SAAS;4BACf,OAAO,EAAE,WAAW,CAAC,YAAY,CAAC;4BAClC,OAAO,EAAE,EAAE;yBACZ;qBACF;iBACF;aACF;YACD,WAAW,EAAE;gBACX;oBACE,OAAO,EAAE,QAAQ;oBACjB,KAAK,EAAE;wBACL;4BACE,IAAI,EAAE,SAAS;4BACf,OAAO,EAAE,WAAW,CAAC,WAAW,CAAC;4BACjC,OAAO,EAAE,EAAE;yBACZ;qBACF;iBACF;aACF;SACF;KACF,EAAE,IAAI,EAAE,CAAC,CAAC,CAAC;AACd,CAAC"}
@@ -1 +1 @@
1
- {"version":3,"file":"soloforge.d.ts","sourceRoot":"","sources":["../../src/bin/soloforge.ts"],"names":[],"mappings":";AA+zBA,OAAO,EAAE,0BAA0B,EAAE,MAAM,gDAAgD,CAAC"}
1
+ {"version":3,"file":"soloforge.d.ts","sourceRoot":"","sources":["../../src/bin/soloforge.ts"],"names":[],"mappings":";AA87BA,OAAO,EAAE,0BAA0B,EAAE,MAAM,gDAAgD,CAAC"}
@@ -17,6 +17,7 @@ import { resolveCurrentProjectConfigReports, validateConfigPrecedence, } from ".
17
17
  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
+ import { auditControlPlaneProject, isSoloforgeStateBypassCommand } from "../engine/control_plane_contract.js";
20
21
  const command = process.argv[2];
21
22
  const args = process.argv.slice(3);
22
23
  async function main() {
@@ -45,6 +46,9 @@ async function main() {
45
46
  case "check-write":
46
47
  await cmdCheckWrite();
47
48
  break;
49
+ case "check-bash":
50
+ await cmdCheckBash();
51
+ break;
48
52
  case "post-bash":
49
53
  await cmdPostBash();
50
54
  break;
@@ -81,6 +85,9 @@ async function main() {
81
85
  case "validate-release":
82
86
  await cmdValidateReleaseGate();
83
87
  break;
88
+ case "doctor":
89
+ await cmdDoctor();
90
+ break;
84
91
  case "audit-template-visibility":
85
92
  await cmdAuditTemplateVisibility();
86
93
  break;
@@ -167,6 +174,8 @@ async function main() {
167
174
  generate-trae 生成 .trae/mcp.json 和 .trae/rules/project_rules.md
168
175
  generate-codex 生成 .codex/ 配置和 AGENTS.md
169
176
  check-write PreToolUse hook,用于范围检查(读取 stdin JSON)
177
+ check-bash PreToolUse hook,阻止 Bash 直接篡改 .soloforge/state
178
+ doctor 审计 MCP 控制面、适配器 hook、状态可信和恢复路径
170
179
  post-bash PostToolUse hook,用于跟踪 Bash 执行结果
171
180
  validate 验证项目配置和知识文件(config.yaml 可选,缺失时自动推断)
172
181
  validate-mechanisms 验证双层机制承载模型完整性(模板层 + 机制层)
@@ -194,6 +203,7 @@ async function main() {
194
203
  stage 显示当前项目阶段和任务阶段
195
204
  stage --explain 详细解释当前阶段(判断依据、必要条件、下一阶段)
196
205
  cleanup 清理过期任务和 evidence(默认 dry-run,--apply 执行实际清理)
206
+ cleanup --stale 检测陈旧 current-task 指针(--apply 时受控归档)
197
207
  `);
198
208
  }
199
209
  }
@@ -697,10 +707,41 @@ async function cmdCheckWrite() {
697
707
  if (!isDesignArtifactFile) {
698
708
  const { TaskContextManager } = await import("../engine/task_context.js");
699
709
  const stateDir = path.join(projectPath, ".soloforge", "state");
710
+ const implementationSource = isImplementationSourcePath(relativeFilePath);
700
711
  if (fss.existsSync(stateDir)) {
701
712
  const manager = new TaskContextManager(stateDir);
702
713
  const currentTask = await manager.getCurrentTask();
703
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
+ }
704
745
  if (designPack && designPack.status !== "implementation_ready") {
705
746
  process.stdout.write(JSON.stringify({
706
747
  hookSpecificOutput: {
@@ -742,12 +783,95 @@ function resolveProjectPath() {
742
783
  }
743
784
  return process.cwd();
744
785
  }
786
+ function isImplementationSourcePath(relativeFilePath) {
787
+ if (/^(?:docs|templates|\.soloforge|\.claude|\.codex|\.trae)\//.test(relativeFilePath))
788
+ return false;
789
+ if (/(^|\/)(?:package-lock|pnpm-lock|yarn.lock)$/.test(relativeFilePath))
790
+ return false;
791
+ 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
+ }
793
+ function hasDesignArtifacts(projectPath) {
794
+ 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
+ path.join(projectPath, "docs", "api", "openapi.yml"),
803
+ path.join(projectPath, "db", "migrations"),
804
+ path.join(projectPath, "db", "migration"),
805
+ ];
806
+ return candidates.some((candidate) => fss.existsSync(candidate));
807
+ }
745
808
  async function cmdPostBash() {
746
809
  debugLog("CLI: 执行 cmdPostBash");
747
810
  // Bash 的 PostToolUse hook — 目前为空操作占位符
748
811
  // 未来: 跟踪命令结果,检测构建/测试失败
749
812
  process.exit(0);
750
813
  }
814
+ function extractBashCommand(rawInput) {
815
+ const trimmed = rawInput.trim();
816
+ if (!trimmed)
817
+ return "";
818
+ try {
819
+ const parsed = JSON.parse(trimmed);
820
+ const input = parsed.tool_input || parsed.toolInput || parsed.input || parsed;
821
+ return String(input.command || input.cmd || parsed.command || "");
822
+ }
823
+ catch {
824
+ return trimmed;
825
+ }
826
+ }
827
+ async function cmdCheckBash() {
828
+ debugLog("CLI: 执行 cmdCheckBash");
829
+ try {
830
+ const chunks = [];
831
+ for await (const chunk of process.stdin) {
832
+ chunks.push(chunk);
833
+ }
834
+ const stdin = Buffer.concat(chunks).toString();
835
+ const commandText = extractBashCommand(stdin || args.join(" "));
836
+ 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
+ }));
844
+ }
845
+ process.exit(0);
846
+ }
847
+ catch (e) {
848
+ userError(`SoloForge Bash 检查错误: ${e instanceof Error ? e.message : String(e)}`);
849
+ process.exit(1);
850
+ }
851
+ }
852
+ async function cmdDoctor() {
853
+ debugLog("CLI: 执行 cmdDoctor");
854
+ const isJson = args.includes("--json");
855
+ const projectPath = process.cwd();
856
+ const report = auditControlPlaneProject(projectPath);
857
+ if (isJson) {
858
+ process.stdout.write(`${JSON.stringify(report, null, 2)}\n`);
859
+ return;
860
+ }
861
+ userInfo(`SoloForge 控制面诊断: ${projectPath}`);
862
+ userInfo(` 状态: ${report.passed ? "通过" : "需修复"}`);
863
+ userInfo(` 控制面矩阵: ${report.matrix_count} 类`);
864
+ userInfo(` 枝叶细节: ${report.branch_detail_count} 条`);
865
+ if (report.findings.length === 0) {
866
+ userInfo(" finding: 0");
867
+ return;
868
+ }
869
+ for (const finding of report.findings) {
870
+ const marker = finding.severity === "hard_fail" ? "❌" : finding.severity === "warning" ? "⚠️" : "ℹ️";
871
+ userInfo(` ${marker} [${finding.code}] ${finding.message}`);
872
+ userInfo(` 恢复: ${finding.recovery}`);
873
+ }
874
+ }
751
875
  export { buildHookAdditionalContext } from "../adapters/claude_code/pre_prompt_contract.js";
752
876
  async function cmdPrePrompt() {
753
877
  debugLog("CLI: 执行 cmdPrePrompt");
@@ -2458,11 +2582,41 @@ async function cmdStage() {
2458
2582
  async function cmdCleanup() {
2459
2583
  debug("CLI", "执行 cmdCleanup");
2460
2584
  const dryRun = !args.includes("--apply");
2585
+ const staleOnly = args.includes("--stale");
2461
2586
  const projectPath = process.cwd();
2462
2587
  const stateDir = path.join(projectPath, ".soloforge", "state");
2463
2588
  let hasError = false;
2464
2589
  userInfo(`SoloForge 清理: ${projectPath}`);
2465
2590
  userInfo(` 模式: ${dryRun ? "dry-run(仅评估)" : "⚠️ 实际清理"}`);
2591
+ if (staleOnly) {
2592
+ try {
2593
+ const { detectStaleCurrentTask, archiveStaleCurrentPointer } = await import("../engine/stale_current_task_detector.js");
2594
+ const stale = await detectStaleCurrentTask(stateDir);
2595
+ userInfo("");
2596
+ userInfo(" 陈旧 current-task 检测:");
2597
+ if (!stale.is_stale || !stale.task_id) {
2598
+ userInfo(" 未发现陈旧指针");
2599
+ return;
2600
+ }
2601
+ userInfo(` 陈旧任务: ${stale.task_id}`);
2602
+ userInfo(` 原因: ${stale.reason_zh}`);
2603
+ if (dryRun) {
2604
+ userInfo(" 处理: dry-run,仅提示;使用 --apply 归档指针");
2605
+ return;
2606
+ }
2607
+ const archived = await archiveStaleCurrentPointer(stateDir, stale.task_id);
2608
+ if (!archived) {
2609
+ userError(" ❌ 陈旧指针归档失败");
2610
+ process.exit(1);
2611
+ }
2612
+ userInfo(" 处理: 已归档 current-task 指针,任务文件未删除");
2613
+ return;
2614
+ }
2615
+ catch (e) {
2616
+ userError(` ❌ 陈旧 current-task 检测失败: ${e.message}`);
2617
+ process.exit(1);
2618
+ }
2619
+ }
2466
2620
  // 收集所有 evidence 引用来源:task-*.json 中的 evidence 文件名 / evidence id
2467
2621
  const referencedEvidenceIds = new Set();
2468
2622
  // 扫描 task context 文件中的 evidence 引用