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.
- package/dist/adapters/claude_code/tools.d.ts.map +1 -1
- package/dist/adapters/claude_code/tools.js +122 -3
- package/dist/adapters/claude_code/tools.js.map +1 -1
- package/dist/bin/soloforge.d.ts.map +1 -1
- package/dist/bin/soloforge.js +126 -62
- package/dist/bin/soloforge.js.map +1 -1
- package/dist/engine/asset_manifest.js +1 -1
- package/dist/engine/asset_manifest.js.map +1 -1
- package/dist/engine/consumable_asset_registry.d.ts.map +1 -1
- package/dist/engine/consumable_asset_registry.js +1 -0
- package/dist/engine/consumable_asset_registry.js.map +1 -1
- package/dist/engine/control_plane_contract.d.ts.map +1 -1
- package/dist/engine/control_plane_contract.js +1 -0
- package/dist/engine/control_plane_contract.js.map +1 -1
- package/dist/engine/decision_workshop.d.ts +8 -0
- package/dist/engine/decision_workshop.d.ts.map +1 -1
- package/dist/engine/decision_workshop.js +116 -6
- package/dist/engine/decision_workshop.js.map +1 -1
- package/dist/engine/design_artifact_pack.d.ts +4 -0
- package/dist/engine/design_artifact_pack.d.ts.map +1 -1
- package/dist/engine/design_artifact_pack.js +22 -0
- package/dist/engine/design_artifact_pack.js.map +1 -1
- package/dist/engine/diagnostic_registry.d.ts +4 -0
- package/dist/engine/diagnostic_registry.d.ts.map +1 -1
- package/dist/engine/diagnostic_registry.js +24 -0
- package/dist/engine/diagnostic_registry.js.map +1 -1
- package/dist/engine/explicit_asset_registry.d.ts.map +1 -1
- package/dist/engine/explicit_asset_registry.js +16 -0
- package/dist/engine/explicit_asset_registry.js.map +1 -1
- package/dist/engine/historical_issue_mechanization_matrix.js +8 -8
- package/dist/engine/historical_issue_mechanization_matrix.js.map +1 -1
- package/dist/engine/implementation_roadmap_registry.d.ts.map +1 -1
- package/dist/engine/implementation_roadmap_registry.js +16 -2
- package/dist/engine/implementation_roadmap_registry.js.map +1 -1
- package/dist/engine/next_action_planner.d.ts +9 -4
- package/dist/engine/next_action_planner.d.ts.map +1 -1
- package/dist/engine/next_action_planner.js +33 -0
- package/dist/engine/next_action_planner.js.map +1 -1
- package/dist/engine/release_issue_scenario_registry.d.ts.map +1 -1
- package/dist/engine/release_issue_scenario_registry.js +21 -7
- package/dist/engine/release_issue_scenario_registry.js.map +1 -1
- package/dist/engine/release_readiness_gate.d.ts.map +1 -1
- package/dist/engine/release_readiness_gate.js +86 -6
- package/dist/engine/release_readiness_gate.js.map +1 -1
- package/dist/engine/traceability.d.ts +42 -0
- package/dist/engine/traceability.d.ts.map +1 -1
- package/dist/engine/traceability.js +250 -0
- package/dist/engine/traceability.js.map +1 -1
- package/dist/types.d.ts +11 -0
- package/dist/types.d.ts.map +1 -1
- package/package.json +1 -1
- 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
- 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
- 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":";
|
|
1
|
+
{"version":3,"file":"soloforge.d.ts","sourceRoot":"","sources":["../../src/bin/soloforge.ts"],"names":[],"mappings":";AAqgCA,OAAO,EAAE,0BAA0B,EAAE,MAAM,gDAAgD,CAAC"}
|
package/dist/bin/soloforge.js
CHANGED
|
@@ -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
|
|
705
|
-
|
|
706
|
-
|
|
707
|
-
|
|
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
|
-
|
|
838
|
-
|
|
839
|
-
|
|
840
|
-
|
|
841
|
-
|
|
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("✅ 设计产物包复验通过,可作为后续实现输入");
|