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.
- package/README.md +6 -5
- package/dist/adapters/claude_code/tools.d.ts.map +1 -1
- package/dist/adapters/claude_code/tools.js +56 -13
- package/dist/adapters/claude_code/tools.js.map +1 -1
- package/dist/adapters/shared/workflow_template.js +1 -1
- package/dist/bin/soloforge.d.ts.map +1 -1
- package/dist/bin/soloforge.js +186 -75
- 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/backend_implementation_contract.js +1 -1
- package/dist/engine/code_maintainability_observability_contract.js +7 -7
- package/dist/engine/code_maintainability_observability_contract.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/consumption_trace_store.d.ts +9 -1
- package/dist/engine/consumption_trace_store.d.ts.map +1 -1
- package/dist/engine/consumption_trace_store.js +3 -0
- package/dist/engine/consumption_trace_store.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/design_artifact_pack.d.ts +5 -0
- package/dist/engine/design_artifact_pack.d.ts.map +1 -1
- package/dist/engine/design_artifact_pack.js +23 -17
- package/dist/engine/design_artifact_pack.js.map +1 -1
- package/dist/engine/design_lifecycle_contract.d.ts +60 -0
- package/dist/engine/design_lifecycle_contract.d.ts.map +1 -0
- package/dist/engine/design_lifecycle_contract.js +497 -0
- package/dist/engine/design_lifecycle_contract.js.map +1 -0
- package/dist/engine/diagnostic_registry.js +2 -2
- 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/foundation_scenario_registry.js +2 -2
- package/dist/engine/foundation_scenario_registry.js.map +1 -1
- package/dist/engine/foundation_scenario_runners.js +9 -9
- package/dist/engine/foundation_scenario_runners.js.map +1 -1
- package/dist/engine/historical_issue_mechanization_matrix.js +1 -1
- package/dist/engine/historical_issue_mechanization_matrix.js.map +1 -1
- package/dist/engine/implementation_roadmap_registry.js +2 -2
- package/dist/engine/implementation_roadmap_registry.js.map +1 -1
- package/dist/engine/input_material_extractor.js +1 -1
- package/dist/engine/input_material_extractor.js.map +1 -1
- package/dist/engine/intent_expander.d.ts.map +1 -1
- package/dist/engine/intent_expander.js +38 -15
- package/dist/engine/intent_expander.js.map +1 -1
- package/dist/engine/knowledge_asset_consumer.d.ts.map +1 -1
- package/dist/engine/knowledge_asset_consumer.js +21 -13
- package/dist/engine/knowledge_asset_consumer.js.map +1 -1
- package/dist/engine/knowledge_injection_boundary.d.ts +2 -2
- package/dist/engine/knowledge_injection_boundary.d.ts.map +1 -1
- package/dist/engine/knowledge_injection_boundary.js +19 -7
- package/dist/engine/knowledge_injection_boundary.js.map +1 -1
- package/dist/engine/lifecycle_knowledge_contract.d.ts +59 -0
- package/dist/engine/lifecycle_knowledge_contract.d.ts.map +1 -0
- package/dist/engine/lifecycle_knowledge_contract.js +203 -0
- package/dist/engine/lifecycle_knowledge_contract.js.map +1 -0
- 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 +66 -23
- package/dist/engine/next_action_planner.js.map +1 -1
- package/dist/engine/observed_consumption.d.ts.map +1 -1
- package/dist/engine/observed_consumption.js +2 -1
- package/dist/engine/observed_consumption.js.map +1 -1
- package/dist/engine/project_knowledge_contract.d.ts +75 -1
- package/dist/engine/project_knowledge_contract.d.ts.map +1 -1
- package/dist/engine/project_knowledge_contract.js +267 -30
- package/dist/engine/project_knowledge_contract.js.map +1 -1
- package/dist/engine/regression_matrix.js +1 -1
- package/dist/engine/regression_matrix.js.map +1 -1
- package/dist/engine/release_issue_scenario_registry.d.ts.map +1 -1
- package/dist/engine/release_issue_scenario_registry.js +15 -12
- 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 +211 -56
- package/dist/engine/release_readiness_gate.js.map +1 -1
- package/dist/engine/stale_current_task_detector.d.ts +1 -1
- package/dist/engine/stale_current_task_detector.js +4 -4
- package/dist/engine/stale_current_task_detector.js.map +1 -1
- package/dist/engine/task_context.d.ts.map +1 -1
- package/dist/engine/task_context.js +2 -0
- package/dist/engine/task_context.js.map +1 -1
- package/dist/engine/traceability.d.ts +7 -1
- package/dist/engine/traceability.d.ts.map +1 -1
- package/dist/engine/traceability.js +90 -15
- package/dist/engine/traceability.js.map +1 -1
- package/dist/engine/workflow_navigation_contract.d.ts +11 -0
- package/dist/engine/workflow_navigation_contract.d.ts.map +1 -1
- package/dist/engine/workspace_resumer.d.ts.map +1 -1
- package/dist/engine/workspace_resumer.js +2 -1
- package/dist/engine/workspace_resumer.js.map +1 -1
- package/dist/knowledge/conflict_detector.d.ts +1 -1
- package/dist/knowledge/conflict_detector.d.ts.map +1 -1
- package/dist/knowledge/conflict_detector.js +86 -2
- package/dist/knowledge/conflict_detector.js.map +1 -1
- package/dist/types.d.ts +14 -0
- package/dist/types.d.ts.map +1 -1
- package/package.json +1 -1
- 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
- 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
- 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
- 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/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
- 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
- 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
- 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
- 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 = "
|
|
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":";
|
|
1
|
+
{"version":3,"file":"soloforge.d.ts","sourceRoot":"","sources":["../../src/bin/soloforge.ts"],"names":[],"mappings":";AAshCA,OAAO,EAAE,0BAA0B,EAAE,MAAM,gDAAgD,CAAC"}
|
package/dist/bin/soloforge.js
CHANGED
|
@@ -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
|
|
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
|
-
}
|
|
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
|
-
|
|
796
|
-
|
|
797
|
-
|
|
798
|
-
|
|
799
|
-
|
|
800
|
-
|
|
801
|
-
|
|
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
|
-
|
|
804
|
-
|
|
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
|
-
|
|
838
|
-
|
|
839
|
-
|
|
840
|
-
|
|
841
|
-
|
|
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)
|