@chenmk/superflow 0.3.0 → 0.5.0
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/INSTALL.en.md +54 -12
- package/INSTALL.md +59 -33
- package/README.md +192 -49
- package/README.zh-CN.md +166 -46
- package/assets/brand/managed-review-loop.png +0 -0
- package/assets/manifest.json +15 -9
- package/assets/prompts/superflow-design.md +3 -0
- package/assets/prompts/superflow-docs.md +4 -1
- package/assets/scripts/superflow-coding-ready.mjs +135 -0
- package/assets/scripts/superflow-dependency-update-hook.sh +81 -8
- package/assets/scripts/superflow-document-audit.mjs +240 -0
- package/assets/scripts/superflow-environment-preflight.mjs +398 -0
- package/assets/scripts/superflow-hook-guard.sh +40 -1
- package/assets/scripts/superflow-managed-command-guard.py +309 -0
- package/assets/scripts/superflow-managed-executor-preflight.mjs +421 -0
- package/assets/scripts/superflow-managed-owner-verification.sh +117 -0
- package/assets/scripts/superflow-managed-work-check.mjs +455 -0
- package/assets/scripts/superflow-managed-work-guard.sh +17 -0
- package/assets/skills/openspec-propose/SKILL.md +11 -0
- package/assets/skills/superflow-clarify/SKILL.md +14 -0
- package/assets/skills/superflow-design/SKILL.md +46 -12
- package/assets/skills/superflow-docs/SKILL.md +109 -2
- package/assets/skills/superflow-pipeline/SKILL.md +205 -2
- package/assets/skills/superflow-pipeline/references/batch-prompt-template.md +17 -9
- package/assets/skills/superflow-pipeline/references/document-readiness.md +157 -0
- package/assets/skills/superflow-pipeline/references/document-templates.md +17 -5
- package/assets/skills/superflow-pipeline/references/feature-gated-workflow.md +14 -0
- package/assets/skills/superflow-pipeline/references/implementation-prompt-template.md +20 -11
- package/assets/skills/superflow-pipeline/references/managed-work.md +166 -0
- package/assets/skills/superflow-pipeline/references/orchestration.md +2 -2
- package/assets/skills/superflow-pipeline/references/p0-baseline-template.md +3 -2
- package/assets/skills/superflow-pipeline/references/quality-gate.md +8 -5
- package/assets/skills/superflow-pipeline/references/reviewer-checklist.md +7 -0
- package/assets/skills/superflow-pipeline/references/superpower-technical-design-template.md +10 -0
- package/assets/skills/superflow-pipeline/scripts/superflow-archive.sh +1 -1
- package/assets/skills/superflow-pipeline/scripts/superflow-guard.sh +282 -16
- package/assets/skills/superflow-pipeline/scripts/superflow-handoff.sh +7 -4
- package/assets/skills/superflow-requirement-review/SKILL.md +178 -0
- package/assets/skills/superflow-requirement-review/agents/openai.yaml +4 -0
- package/assets/skills-en/openspec-propose/SKILL.md +14 -0
- package/assets/skills-en/superflow-clarify/SKILL.md +17 -0
- package/assets/skills-en/superflow-design/SKILL.md +40 -11
- package/assets/skills-en/superflow-docs/SKILL.md +115 -0
- package/assets/skills-en/superflow-pipeline/SKILL.md +202 -0
- package/assets/skills-en/superflow-pipeline/references/batch-prompt-template.md +4 -0
- package/assets/skills-en/superflow-pipeline/references/document-readiness.md +125 -0
- package/assets/skills-en/superflow-pipeline/references/document-templates.md +15 -0
- package/assets/skills-en/superflow-pipeline/references/feature-gated-workflow.md +7 -0
- package/assets/skills-en/superflow-pipeline/references/implementation-prompt-template.md +7 -0
- package/assets/skills-en/superflow-pipeline/references/managed-work.md +184 -0
- package/assets/skills-en/superflow-pipeline/references/quality-gate.md +20 -0
- package/assets/skills-en/superflow-pipeline/references/reviewer-checklist.md +7 -0
- package/assets/skills-en/superflow-pipeline/references/superpower-technical-design-template.md +11 -0
- package/assets/skills-en/superflow-pipeline/scripts/superflow-archive.sh +1 -1
- package/assets/skills-en/superflow-pipeline/scripts/superflow-guard.sh +279 -14
- package/assets/skills-en/superflow-pipeline/scripts/superflow-handoff.sh +7 -4
- package/assets/skills-en/superflow-requirement-review/SKILL.md +99 -0
- package/assets/skills-en/superflow-requirement-review/agents/openai.yaml +4 -0
- package/dist/app/cli.js +176 -103
- package/dist/app/cli.js.map +1 -1
- package/dist/app/commands/change-guard.js +19 -2
- package/dist/app/commands/change-guard.js.map +1 -1
- package/dist/app/commands/check.js +107 -45
- package/dist/app/commands/check.js.map +1 -1
- package/dist/app/commands/docs.js +7 -3
- package/dist/app/commands/docs.js.map +1 -1
- package/dist/app/commands/doctor.js +174 -198
- package/dist/app/commands/doctor.js.map +1 -1
- package/dist/app/commands/eval.js +64 -0
- package/dist/app/commands/eval.js.map +1 -0
- package/dist/app/commands/implement.js +7 -3
- package/dist/app/commands/implement.js.map +1 -1
- package/dist/app/commands/init.js +16 -24
- package/dist/app/commands/init.js.map +1 -1
- package/dist/app/commands/managed-service.js +5 -0
- package/dist/app/commands/managed-service.js.map +1 -0
- package/dist/app/commands/mcp.js +167 -0
- package/dist/app/commands/mcp.js.map +1 -0
- package/dist/app/commands/pipeline.js +757 -3
- package/dist/app/commands/pipeline.js.map +1 -1
- package/dist/app/commands/requirement-review.js +6 -0
- package/dist/app/commands/requirement-review.js.map +1 -0
- package/dist/app/commands/status.js +262 -77
- package/dist/app/commands/status.js.map +1 -1
- package/dist/app/commands/uninstall.js +8 -5
- package/dist/app/commands/uninstall.js.map +1 -1
- package/dist/app/commands/update.js +114 -15
- package/dist/app/commands/update.js.map +1 -1
- package/dist/domains/agent.js +3 -11
- package/dist/domains/agent.js.map +1 -1
- package/dist/domains/config/cli-help.js +145 -73
- package/dist/domains/config/cli-help.js.map +1 -1
- package/dist/domains/config/i18n.js +6 -8
- package/dist/domains/config/i18n.js.map +1 -1
- package/dist/domains/config/manifest.js +0 -3
- package/dist/domains/config/manifest.js.map +1 -1
- package/dist/domains/deps.js +70 -8
- package/dist/domains/deps.js.map +1 -1
- package/dist/domains/hook.js +71 -38
- package/dist/domains/hook.js.map +1 -1
- package/dist/domains/managed-work/completion-policy.js +174 -0
- package/dist/domains/managed-work/completion-policy.js.map +1 -0
- package/dist/domains/managed-work/context-manifest.js +208 -0
- package/dist/domains/managed-work/context-manifest.js.map +1 -0
- package/dist/domains/managed-work/contract.js +349 -0
- package/dist/domains/managed-work/contract.js.map +1 -0
- package/dist/domains/managed-work/control-signals.js +34 -0
- package/dist/domains/managed-work/control-signals.js.map +1 -0
- package/dist/domains/managed-work/control.js +628 -0
- package/dist/domains/managed-work/control.js.map +1 -0
- package/dist/domains/managed-work/evaluation.js +497 -0
- package/dist/domains/managed-work/evaluation.js.map +1 -0
- package/dist/domains/managed-work/evidence-path.js +14 -0
- package/dist/domains/managed-work/evidence-path.js.map +1 -0
- package/dist/domains/managed-work/execution-contract.js +167 -0
- package/dist/domains/managed-work/execution-contract.js.map +1 -0
- package/dist/domains/managed-work/executor-policy.js +65 -0
- package/dist/domains/managed-work/executor-policy.js.map +1 -0
- package/dist/domains/managed-work/failure.js +65 -0
- package/dist/domains/managed-work/failure.js.map +1 -0
- package/dist/domains/managed-work/host-review.js +77 -0
- package/dist/domains/managed-work/host-review.js.map +1 -0
- package/dist/domains/managed-work/human-messages.js +31 -0
- package/dist/domains/managed-work/human-messages.js.map +1 -0
- package/dist/domains/managed-work/i18n.js +12 -0
- package/dist/domains/managed-work/i18n.js.map +1 -0
- package/dist/domains/managed-work/input.js +137 -0
- package/dist/domains/managed-work/input.js.map +1 -0
- package/dist/domains/managed-work/journal.js +196 -0
- package/dist/domains/managed-work/journal.js.map +1 -0
- package/dist/domains/managed-work/lock.js +106 -0
- package/dist/domains/managed-work/lock.js.map +1 -0
- package/dist/domains/managed-work/notifications.js +38 -0
- package/dist/domains/managed-work/notifications.js.map +1 -0
- package/dist/domains/managed-work/pair-admission.js +108 -0
- package/dist/domains/managed-work/pair-admission.js.map +1 -0
- package/dist/domains/managed-work/paths.js +18 -0
- package/dist/domains/managed-work/paths.js.map +1 -0
- package/dist/domains/managed-work/prompts.js +453 -0
- package/dist/domains/managed-work/prompts.js.map +1 -0
- package/dist/domains/managed-work/redaction.js +90 -0
- package/dist/domains/managed-work/redaction.js.map +1 -0
- package/dist/domains/managed-work/review-convergence.js +41 -0
- package/dist/domains/managed-work/review-convergence.js.map +1 -0
- package/dist/domains/managed-work/review-facts.js +181 -0
- package/dist/domains/managed-work/review-facts.js.map +1 -0
- package/dist/domains/managed-work/rule-selection.js +84 -0
- package/dist/domains/managed-work/rule-selection.js.map +1 -0
- package/dist/domains/managed-work/runner.js +2418 -0
- package/dist/domains/managed-work/runner.js.map +1 -0
- package/dist/domains/managed-work/schemas.js +155 -0
- package/dist/domains/managed-work/schemas.js.map +1 -0
- package/dist/domains/managed-work/service.js +190 -0
- package/dist/domains/managed-work/service.js.map +1 -0
- package/dist/domains/managed-work/state.js +280 -0
- package/dist/domains/managed-work/state.js.map +1 -0
- package/dist/domains/managed-work/storage.js +223 -0
- package/dist/domains/managed-work/storage.js.map +1 -0
- package/dist/domains/managed-work/types.js +19 -0
- package/dist/domains/managed-work/types.js.map +1 -0
- package/dist/domains/managed-work/verification-categories.js +39 -0
- package/dist/domains/managed-work/verification-categories.js.map +1 -0
- package/dist/domains/managed-work/verification-policy.js +116 -0
- package/dist/domains/managed-work/verification-policy.js.map +1 -0
- package/dist/domains/managed-work/wait.js +58 -0
- package/dist/domains/managed-work/wait.js.map +1 -0
- package/dist/domains/managed-work/workspace-binding.js +133 -0
- package/dist/domains/managed-work/workspace-binding.js.map +1 -0
- package/dist/domains/sdd-readiness.js +44 -0
- package/dist/domains/sdd-readiness.js.map +1 -0
- package/dist/domains/skill/deploy.js +0 -6
- package/dist/domains/skill/deploy.js.map +1 -1
- package/dist/domains/skill/scripts.js +0 -8
- package/dist/domains/skill/scripts.js.map +1 -1
- package/dist/domains/state.js +1 -2
- package/dist/domains/state.js.map +1 -1
- package/dist/mcp/runtime-identity.js +21 -0
- package/dist/mcp/runtime-identity.js.map +1 -0
- package/dist/mcp/server.js +369 -0
- package/dist/mcp/server.js.map +1 -0
- package/dist/platform/agent-process.js +769 -0
- package/dist/platform/agent-process.js.map +1 -0
- package/dist/platform/executable.js +28 -0
- package/dist/platform/executable.js.map +1 -0
- package/dist/platform/paths.js +1 -17
- package/dist/platform/paths.js.map +1 -1
- package/dist/platform/process-liveness.js +10 -0
- package/dist/platform/process-liveness.js.map +1 -0
- package/dist/platform/process-tree.js +44 -0
- package/dist/platform/process-tree.js.map +1 -0
- package/dist/platform/process.js +1 -1
- package/dist/platform/process.js.map +1 -1
- package/dist/platform/version-check.js +30 -9
- package/dist/platform/version-check.js.map +1 -1
- package/package.json +16 -7
- package/scripts/check-design-principles.mjs +107 -0
- package/scripts/check-managed-work-design.mjs +507 -0
- package/scripts/clean-dist.js +9 -0
- package/scripts/ensure-bin-modes.js +11 -0
- package/dist/cli/index.js +0 -193
- package/dist/cli/index.js.map +0 -1
- package/dist/commands/archive.js +0 -6
- package/dist/commands/archive.js.map +0 -1
- package/dist/commands/clarify.js +0 -6
- package/dist/commands/clarify.js.map +0 -1
- package/dist/commands/design.js +0 -28
- package/dist/commands/design.js.map +0 -1
- package/dist/commands/docs.js +0 -6
- package/dist/commands/docs.js.map +0 -1
- package/dist/commands/doctor.js +0 -494
- package/dist/commands/doctor.js.map +0 -1
- package/dist/commands/implement.js +0 -6
- package/dist/commands/implement.js.map +0 -1
- package/dist/commands/init.js +0 -520
- package/dist/commands/init.js.map +0 -1
- package/dist/commands/pipeline.js +0 -6
- package/dist/commands/pipeline.js.map +0 -1
- package/dist/commands/scan.js +0 -63
- package/dist/commands/scan.js.map +0 -1
- package/dist/commands/status.js +0 -173
- package/dist/commands/status.js.map +0 -1
- package/dist/commands/uninstall.js +0 -214
- package/dist/commands/uninstall.js.map +0 -1
- package/dist/commands/update.js +0 -193
- package/dist/commands/update.js.map +0 -1
- package/dist/commands/verify.js +0 -6
- package/dist/commands/verify.js.map +0 -1
- package/dist/core/assets.js +0 -27
- package/dist/core/assets.js.map +0 -1
- package/dist/core/cli-help.js +0 -98
- package/dist/core/cli-help.js.map +0 -1
- package/dist/core/context.js +0 -123
- package/dist/core/context.js.map +0 -1
- package/dist/core/dependencies.js +0 -146
- package/dist/core/dependencies.js.map +0 -1
- package/dist/core/detect.js +0 -64
- package/dist/core/detect.js.map +0 -1
- package/dist/core/i18n.js +0 -105
- package/dist/core/i18n.js.map +0 -1
- package/dist/core/integrity.js +0 -46
- package/dist/core/integrity.js.map +0 -1
- package/dist/core/manifest.js +0 -21
- package/dist/core/manifest.js.map +0 -1
- package/dist/core/prompts.js +0 -20
- package/dist/core/prompts.js.map +0 -1
- package/dist/core/registry.js +0 -134
- package/dist/core/registry.js.map +0 -1
- package/dist/core/rules.js +0 -17
- package/dist/core/rules.js.map +0 -1
- package/dist/core/scripts.js +0 -48
- package/dist/core/scripts.js.map +0 -1
- package/dist/core/skill-check.js +0 -32
- package/dist/core/skill-check.js.map +0 -1
- package/dist/core/skills.js +0 -62
- package/dist/core/skills.js.map +0 -1
- package/dist/core/state.js +0 -54
- package/dist/core/state.js.map +0 -1
- package/dist/utils/path.js +0 -11
- package/dist/utils/path.js.map +0 -1
- package/dist/utils/shell.js +0 -29
- package/dist/utils/shell.js.map +0 -1
|
@@ -0,0 +1,421 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
import { execFileSync } from "node:child_process";
|
|
3
|
+
import { createHash } from "node:crypto";
|
|
4
|
+
import fs from "node:fs";
|
|
5
|
+
import path from "node:path";
|
|
6
|
+
|
|
7
|
+
const [projectArg = ".", taskId] = process.argv.slice(2);
|
|
8
|
+
if (!taskId) {
|
|
9
|
+
fail(
|
|
10
|
+
"用法: superflow-managed-executor-preflight.mjs <project-root> <task-id>",
|
|
11
|
+
);
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
const root = path.resolve(projectArg);
|
|
15
|
+
const taskFile = path.join(root, ".superflow", "tasks", taskId, "task.json");
|
|
16
|
+
if (!fs.existsSync(taskFile)) fail(`缺少托管任务合同: ${taskFile}`);
|
|
17
|
+
const task = JSON.parse(fs.readFileSync(taskFile, "utf8"));
|
|
18
|
+
const english = task.language === "en";
|
|
19
|
+
const protectedInputErrors = validateProtectedInputs(root, taskId);
|
|
20
|
+
const enforceEightyColumns = requiresEightyColumns(task, root);
|
|
21
|
+
const enforcePortableAcceptance = requiresPortableAcceptance(task);
|
|
22
|
+
const enforceOwnedCleanup = requiresOwnedCleanup(task);
|
|
23
|
+
const changed = changedFiles(root).filter(
|
|
24
|
+
(file) =>
|
|
25
|
+
!file.startsWith(".superflow/") &&
|
|
26
|
+
!file.startsWith("target/") &&
|
|
27
|
+
!file.startsWith("node_modules/") &&
|
|
28
|
+
!file.includes("/node_modules/"),
|
|
29
|
+
);
|
|
30
|
+
if (changed.length === 0) fail("没有检测到需求产物变更");
|
|
31
|
+
|
|
32
|
+
const errors = [...protectedInputErrors];
|
|
33
|
+
const warnings = [];
|
|
34
|
+
for (const relative of changed) {
|
|
35
|
+
const absolute = path.join(root, relative);
|
|
36
|
+
if (!fs.existsSync(absolute) || fs.statSync(absolute).isDirectory()) continue;
|
|
37
|
+
if (!isTextFile(relative)) continue;
|
|
38
|
+
const content = fs.readFileSync(absolute, "utf8");
|
|
39
|
+
if (enforceEightyColumns && /\.(?:java|xml)$/i.test(relative)) {
|
|
40
|
+
content.split(/\r?\n/).forEach((line, index) => {
|
|
41
|
+
if ([...line].length > 80) {
|
|
42
|
+
errors.push(`${relative}:${index + 1} 超过 80 字符`);
|
|
43
|
+
}
|
|
44
|
+
});
|
|
45
|
+
}
|
|
46
|
+
content.split(/\r?\n/).forEach((line, index) => {
|
|
47
|
+
if (/[ \t]+$/.test(line)) {
|
|
48
|
+
errors.push(`${relative}:${index + 1} 存在行尾空白`);
|
|
49
|
+
}
|
|
50
|
+
});
|
|
51
|
+
if (containsPlaintextCredential(content)) {
|
|
52
|
+
if (isEvidencePath(relative)) {
|
|
53
|
+
errors.push(`${relative} 的交付证据包含明文凭据`);
|
|
54
|
+
} else {
|
|
55
|
+
warnings.push(`${relative} 命中疑似明文凭据,交由 Host 语义复核`);
|
|
56
|
+
}
|
|
57
|
+
} else if (isEvidencePath(relative) && containsEvidenceCredential(content)) {
|
|
58
|
+
errors.push(`${relative} 的交付证据包含明文凭据`);
|
|
59
|
+
}
|
|
60
|
+
if (
|
|
61
|
+
enforcePortableAcceptance &&
|
|
62
|
+
isPortableExecutable(relative) &&
|
|
63
|
+
containsPersonalAbsolutePath(content)
|
|
64
|
+
) {
|
|
65
|
+
errors.push(
|
|
66
|
+
`${relative} 的验收脚本包含个人绝对路径,必须改为 PATH、项目相对路径或运行时发现`,
|
|
67
|
+
);
|
|
68
|
+
}
|
|
69
|
+
if (enforceOwnedCleanup && isPortableExecutable(relative)) {
|
|
70
|
+
errors.push(...unsafeCleanupErrors(relative, content));
|
|
71
|
+
}
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
const changeDir = findChangeDir(task, root);
|
|
75
|
+
if (changeDir) {
|
|
76
|
+
const tasksFile = path.join(changeDir, "tasks.md");
|
|
77
|
+
const reportFile = path.join(changeDir, "test-report.md");
|
|
78
|
+
if (!fs.existsSync(tasksFile)) {
|
|
79
|
+
errors.push(`缺少 tasks.md: ${tasksFile}`);
|
|
80
|
+
} else {
|
|
81
|
+
const taskContent = fs.readFileSync(tasksFile, "utf8");
|
|
82
|
+
const pending = taskContent
|
|
83
|
+
.split(/\r?\n/)
|
|
84
|
+
.filter((line) => /^\s*- \[ \]/.test(line))
|
|
85
|
+
.filter(
|
|
86
|
+
(line) => !/\[(?:environment_required|release_required)\]/.test(line),
|
|
87
|
+
);
|
|
88
|
+
if (pending.length > 0) {
|
|
89
|
+
errors.push(`仍有 ${pending.length} 个 local_required 任务未勾选`);
|
|
90
|
+
}
|
|
91
|
+
const frozenPrompt = task.taskPrompt?.snapshotPath;
|
|
92
|
+
if (
|
|
93
|
+
frozenPrompt &&
|
|
94
|
+
fs.existsSync(frozenPrompt) &&
|
|
95
|
+
frozenPromptRequiresLocalOnly(fs.readFileSync(frozenPrompt, "utf8")) &&
|
|
96
|
+
/\[(?:environment_required|release_required)\]/i.test(taskContent)
|
|
97
|
+
) {
|
|
98
|
+
errors.push(
|
|
99
|
+
"冻结 Prompt 规定 tasks.md 仅使用 local_required,禁止把任务降级为 environment_required 或 release_required",
|
|
100
|
+
);
|
|
101
|
+
}
|
|
102
|
+
}
|
|
103
|
+
if (!fs.existsSync(reportFile))
|
|
104
|
+
errors.push(`缺少 test-report.md: ${reportFile}`);
|
|
105
|
+
}
|
|
106
|
+
|
|
107
|
+
if (errors.length > 0) {
|
|
108
|
+
console.error("Executor 交付前确定性门禁失败:");
|
|
109
|
+
errors.forEach((error) => console.error(`- ${error}`));
|
|
110
|
+
process.exit(1);
|
|
111
|
+
}
|
|
112
|
+
|
|
113
|
+
warnings.forEach((warning) => console.warn(`WARN ${warning}`));
|
|
114
|
+
|
|
115
|
+
console.log(`OK Executor 交付前确定性门禁通过: ${changed.length} 个需求文件`);
|
|
116
|
+
|
|
117
|
+
function changedFiles(projectRoot) {
|
|
118
|
+
try {
|
|
119
|
+
const output = execFileSync(
|
|
120
|
+
"git",
|
|
121
|
+
[
|
|
122
|
+
"status",
|
|
123
|
+
"--porcelain=v1",
|
|
124
|
+
"-z",
|
|
125
|
+
"--untracked-files=all",
|
|
126
|
+
"--no-renames",
|
|
127
|
+
],
|
|
128
|
+
{
|
|
129
|
+
cwd: projectRoot,
|
|
130
|
+
encoding: "utf8",
|
|
131
|
+
stdio: ["ignore", "pipe", "ignore"],
|
|
132
|
+
},
|
|
133
|
+
);
|
|
134
|
+
return output
|
|
135
|
+
.split("\0")
|
|
136
|
+
.filter(Boolean)
|
|
137
|
+
.map((entry) => entry.slice(3).replaceAll("\\", "/"));
|
|
138
|
+
} catch {
|
|
139
|
+
return walkWorkspaceFiles(projectRoot);
|
|
140
|
+
}
|
|
141
|
+
}
|
|
142
|
+
|
|
143
|
+
function walkWorkspaceFiles(projectRoot) {
|
|
144
|
+
const files = [];
|
|
145
|
+
const visit = (directory) => {
|
|
146
|
+
for (const entry of fs.readdirSync(directory, { withFileTypes: true })) {
|
|
147
|
+
if ([".git", ".superflow"].includes(entry.name)) continue;
|
|
148
|
+
const absolute = path.join(directory, entry.name);
|
|
149
|
+
if (entry.isDirectory()) {
|
|
150
|
+
visit(absolute);
|
|
151
|
+
} else if (entry.isFile()) {
|
|
152
|
+
files.push(path.relative(projectRoot, absolute).replaceAll("\\", "/"));
|
|
153
|
+
}
|
|
154
|
+
}
|
|
155
|
+
};
|
|
156
|
+
visit(projectRoot);
|
|
157
|
+
return files;
|
|
158
|
+
}
|
|
159
|
+
|
|
160
|
+
function findChangeDir(contract, projectRoot) {
|
|
161
|
+
const original = contract.taskPrompt?.originalPath;
|
|
162
|
+
if (!original) return null;
|
|
163
|
+
let current = path.dirname(path.resolve(original));
|
|
164
|
+
while (current.startsWith(projectRoot)) {
|
|
165
|
+
if (fs.existsSync(path.join(current, "tasks.md"))) return current;
|
|
166
|
+
const parent = path.dirname(current);
|
|
167
|
+
if (parent === current) break;
|
|
168
|
+
current = parent;
|
|
169
|
+
}
|
|
170
|
+
return null;
|
|
171
|
+
}
|
|
172
|
+
|
|
173
|
+
function isTextFile(file) {
|
|
174
|
+
return /\.(?:java|xml|ts|tsx|js|jsx|vue|json|ya?ml|md|sql|properties|log|out|txt|sh|bash|zsh|ps1)$/i.test(
|
|
175
|
+
file,
|
|
176
|
+
);
|
|
177
|
+
}
|
|
178
|
+
|
|
179
|
+
function isEvidencePath(file) {
|
|
180
|
+
return /(?:^|\/)evidence(?:\/|$)/i.test(file);
|
|
181
|
+
}
|
|
182
|
+
|
|
183
|
+
function containsEvidenceCredential(value) {
|
|
184
|
+
const assignment =
|
|
185
|
+
/\b(?:[a-z0-9]+_)*(?:password|secret|api[_-]?key|access[_-]?token|auth[_-]?token)\b\s*[:=]\s*["']?([^\s,"'}\]]+)/gi;
|
|
186
|
+
for (const match of value.matchAll(assignment)) {
|
|
187
|
+
const candidate = match[1].trim().toLowerCase();
|
|
188
|
+
if (
|
|
189
|
+
candidate &&
|
|
190
|
+
!candidate.includes("<redacted>") &&
|
|
191
|
+
!candidate.includes("<masked>") &&
|
|
192
|
+
!candidate.startsWith("$")
|
|
193
|
+
)
|
|
194
|
+
return true;
|
|
195
|
+
}
|
|
196
|
+
return (
|
|
197
|
+
/authorization\s*[:=]\s*(?!<redacted>|<masked>)[^\s"']+/i.test(value) ||
|
|
198
|
+
/jdbc:[^\s]+:\/\/[^\s/:]+:[^\s/@]+@/i.test(value)
|
|
199
|
+
);
|
|
200
|
+
}
|
|
201
|
+
|
|
202
|
+
function requiresEightyColumns(contract, projectRoot) {
|
|
203
|
+
const files = [
|
|
204
|
+
contract.taskPrompt?.snapshotPath,
|
|
205
|
+
path.join(projectRoot, "AGENTS.md"),
|
|
206
|
+
path.join(projectRoot, "CLAUDE.md"),
|
|
207
|
+
...ruleFiles(path.join(projectRoot, ".claude", "rules")),
|
|
208
|
+
].filter((file) => file && fs.existsSync(file));
|
|
209
|
+
const text = [
|
|
210
|
+
...(contract.mandatoryEngineeringRules ?? []),
|
|
211
|
+
...files.map((file) => fs.readFileSync(file, "utf8")),
|
|
212
|
+
].join("\n");
|
|
213
|
+
return /(?:每行|行|line).{0,24}(?:不超过|不得超过|<=|maximum|max).{0,12}80|80.{0,12}(?:字符|columns?)/i.test(
|
|
214
|
+
text,
|
|
215
|
+
);
|
|
216
|
+
}
|
|
217
|
+
|
|
218
|
+
function ruleFiles(root) {
|
|
219
|
+
if (!fs.existsSync(root)) return [];
|
|
220
|
+
return fs.readdirSync(root, { withFileTypes: true }).flatMap((entry) => {
|
|
221
|
+
const target = path.join(root, entry.name);
|
|
222
|
+
if (entry.isDirectory()) return ruleFiles(target);
|
|
223
|
+
return entry.isFile() && entry.name.endsWith(".md") ? [target] : [];
|
|
224
|
+
});
|
|
225
|
+
}
|
|
226
|
+
|
|
227
|
+
function containsPlaintextCredential(value) {
|
|
228
|
+
const literal =
|
|
229
|
+
/\b(?:[a-z0-9]+_)*(?:password|secret|api[_-]?key|access[_-]?token|auth[_-]?token)\b\s*[:=]\s*["']([^"']+)["']/gi;
|
|
230
|
+
for (const match of value.matchAll(literal)) {
|
|
231
|
+
if (isSecret(match[1])) return true;
|
|
232
|
+
}
|
|
233
|
+
const mysql =
|
|
234
|
+
/(?:\s-p(?:'([^']*)'|"([^"]*)"|([^\s'"-][^\s]*))|--password=(?:'([^']*)'|"([^"]*)"|([^\s]+)))/g;
|
|
235
|
+
for (const match of value.matchAll(mysql)) {
|
|
236
|
+
const secret = match.slice(1).find((item) => item !== undefined) ?? "";
|
|
237
|
+
if (isSecret(secret)) return true;
|
|
238
|
+
}
|
|
239
|
+
return /authorization\s*[:=]\s*bearer\s+(?!<redacted>)[^\s"']+/i.test(value);
|
|
240
|
+
}
|
|
241
|
+
|
|
242
|
+
function isSecret(value) {
|
|
243
|
+
const normalized = value.trim().toLowerCase();
|
|
244
|
+
return (
|
|
245
|
+
normalized.length >= 6 &&
|
|
246
|
+
!normalized.includes("<redacted>") &&
|
|
247
|
+
!isDocumentationSecretPlaceholder(normalized) &&
|
|
248
|
+
!normalized.startsWith("$") &&
|
|
249
|
+
!isNonceDerivedTemporarySecret(normalized)
|
|
250
|
+
);
|
|
251
|
+
}
|
|
252
|
+
|
|
253
|
+
function isDocumentationSecretPlaceholder(value) {
|
|
254
|
+
return /^<(?:pass(?:word)?|secret|api[_-]?key|access[_-]?token|auth[_-]?token)>$/i.test(
|
|
255
|
+
value,
|
|
256
|
+
);
|
|
257
|
+
}
|
|
258
|
+
|
|
259
|
+
function isNonceDerivedTemporarySecret(value) {
|
|
260
|
+
return /^[a-z0-9_-]*\$(?:\{[a-z0-9_]*nonce\}|[a-z0-9_]*nonce)$/.test(value);
|
|
261
|
+
}
|
|
262
|
+
|
|
263
|
+
function frozenPromptRequiresLocalOnly(content) {
|
|
264
|
+
return (
|
|
265
|
+
/tasks\.md[\s\S]{0,160}(?:仅使用|只能使用)[\s\S]{0,80}\[local_required\]/i.test(
|
|
266
|
+
content,
|
|
267
|
+
) ||
|
|
268
|
+
/tasks\.md[\s\S]{0,160}(?:only|must)[\s\S]{0,40}(?:use|contain)[\s\S]{0,80}\[local_required\]/i.test(
|
|
269
|
+
content,
|
|
270
|
+
)
|
|
271
|
+
);
|
|
272
|
+
}
|
|
273
|
+
|
|
274
|
+
function requiresPortableAcceptance(contract) {
|
|
275
|
+
const prompt = contract.taskPrompt?.snapshotPath;
|
|
276
|
+
if (!prompt || !fs.existsSync(prompt)) return false;
|
|
277
|
+
const content = fs.readFileSync(prompt, "utf8");
|
|
278
|
+
return /(?:可移植|个人绝对路径|portable|personal absolute path)/i.test(
|
|
279
|
+
content,
|
|
280
|
+
);
|
|
281
|
+
}
|
|
282
|
+
|
|
283
|
+
function requiresOwnedCleanup(contract) {
|
|
284
|
+
const content = [contract.request, contract.objective]
|
|
285
|
+
.filter(Boolean)
|
|
286
|
+
.join("\n");
|
|
287
|
+
return /(?:\bapi\b|\bhttp\b|\be2e\b|\bsql\b|\bmysql\b|\bdatabase\b|\bdocker\b|\bbrowser\b|\bserver\b|\bservice\b|接口|启动|数据库|浏览器|服务|容器|端到端|集成测试)/i.test(
|
|
288
|
+
content,
|
|
289
|
+
);
|
|
290
|
+
}
|
|
291
|
+
|
|
292
|
+
function validateProtectedInputs(projectRoot, currentTaskId) {
|
|
293
|
+
const manifestFile = path.join(
|
|
294
|
+
projectRoot,
|
|
295
|
+
".superflow",
|
|
296
|
+
"tasks",
|
|
297
|
+
currentTaskId,
|
|
298
|
+
"context-manifest.json",
|
|
299
|
+
);
|
|
300
|
+
if (!fs.existsSync(manifestFile)) return [];
|
|
301
|
+
const manifest = JSON.parse(fs.readFileSync(manifestFile, "utf8"));
|
|
302
|
+
const issues = [];
|
|
303
|
+
for (const entry of manifest.entries ?? []) {
|
|
304
|
+
const protection = entry.protection ?? "immutable";
|
|
305
|
+
if (!fs.existsSync(entry.path)) {
|
|
306
|
+
issues.push(
|
|
307
|
+
managedMessage(
|
|
308
|
+
`受保护输入已被删除:${entry.path}`,
|
|
309
|
+
`Protected input was deleted: ${entry.path}`,
|
|
310
|
+
),
|
|
311
|
+
);
|
|
312
|
+
continue;
|
|
313
|
+
}
|
|
314
|
+
if (protection === "immutable" && sha256File(entry.path) !== entry.sha256) {
|
|
315
|
+
issues.push(
|
|
316
|
+
managedMessage(
|
|
317
|
+
`冻结输入已被修改:${entry.path}`,
|
|
318
|
+
`Immutable input was modified: ${entry.path}`,
|
|
319
|
+
),
|
|
320
|
+
);
|
|
321
|
+
}
|
|
322
|
+
}
|
|
323
|
+
return issues;
|
|
324
|
+
}
|
|
325
|
+
|
|
326
|
+
function unsafeCleanupErrors(relative, content) {
|
|
327
|
+
const issues = [];
|
|
328
|
+
if (/(?:^|[;&|]\s*)(?:pkill|killall)\b/m.test(content)) {
|
|
329
|
+
issues.push(
|
|
330
|
+
managedMessage(
|
|
331
|
+
`${relative} 使用按进程名清理,必须改用 owner helper`,
|
|
332
|
+
`${relative} uses process-name cleanup; use the owner helper`,
|
|
333
|
+
),
|
|
334
|
+
);
|
|
335
|
+
}
|
|
336
|
+
if (
|
|
337
|
+
/(?:^|[;&|]\s*)kill\s+(?!-0\b)/m.test(content) &&
|
|
338
|
+
!/superflow_signal_owner/.test(content)
|
|
339
|
+
) {
|
|
340
|
+
issues.push(
|
|
341
|
+
managedMessage(
|
|
342
|
+
`${relative} 使用裸 kill,必须调用 superflow_signal_owner`,
|
|
343
|
+
`${relative} uses bare kill; call superflow_signal_owner`,
|
|
344
|
+
),
|
|
345
|
+
);
|
|
346
|
+
}
|
|
347
|
+
if (
|
|
348
|
+
/\bdocker\s+(?:(?:container|network|volume)\s+)?rm\b/i.test(content) &&
|
|
349
|
+
!/superflow_docker_remove_verified/.test(content)
|
|
350
|
+
) {
|
|
351
|
+
issues.push(
|
|
352
|
+
managedMessage(
|
|
353
|
+
`${relative} 直接删除 Docker 资源,必须调用 owner helper`,
|
|
354
|
+
`${relative} deletes Docker resources directly; use the owner helper`,
|
|
355
|
+
),
|
|
356
|
+
);
|
|
357
|
+
}
|
|
358
|
+
if (
|
|
359
|
+
/(?:^|[;&|]\s*)rm\s+-[^\n;&|]*r[^\n;&|]*f/m.test(content) &&
|
|
360
|
+
!/superflow_runtime_remove_verified/.test(content) &&
|
|
361
|
+
!safeBuildDirectoryCleanup(content)
|
|
362
|
+
) {
|
|
363
|
+
issues.push(
|
|
364
|
+
managedMessage(
|
|
365
|
+
`${relative} 使用未校验的 rm -rf,运行目录必须由 owner helper 删除`,
|
|
366
|
+
`${relative} uses unverified rm -rf; runtime directories require the owner helper`,
|
|
367
|
+
),
|
|
368
|
+
);
|
|
369
|
+
}
|
|
370
|
+
return issues;
|
|
371
|
+
}
|
|
372
|
+
|
|
373
|
+
function safeBuildDirectoryCleanup(content) {
|
|
374
|
+
const matches = content.matchAll(
|
|
375
|
+
/(?:^|[;&|]\s*)rm\s+-[^\n;&|]*r[^\n;&|]*f\s+([^\n;&|]+)/gm,
|
|
376
|
+
);
|
|
377
|
+
let found = false;
|
|
378
|
+
for (const match of matches) {
|
|
379
|
+
found = true;
|
|
380
|
+
const targets = match[1]
|
|
381
|
+
.trim()
|
|
382
|
+
.split(/\s+/)
|
|
383
|
+
.filter((value) => value && !value.startsWith("-"));
|
|
384
|
+
if (
|
|
385
|
+
targets.length === 0 ||
|
|
386
|
+
targets.some(
|
|
387
|
+
(target) =>
|
|
388
|
+
/[$*?]/.test(target) ||
|
|
389
|
+
!/^(?:\.\/)?(?:target|build|dist|coverage|\.gradle|\.cache|\.tmp|tmp)(?:\/|$)/.test(
|
|
390
|
+
target.replace(/^['"]|['"]$/g, ""),
|
|
391
|
+
),
|
|
392
|
+
)
|
|
393
|
+
) {
|
|
394
|
+
return false;
|
|
395
|
+
}
|
|
396
|
+
}
|
|
397
|
+
return found;
|
|
398
|
+
}
|
|
399
|
+
|
|
400
|
+
function sha256File(file) {
|
|
401
|
+
return createHash("sha256").update(fs.readFileSync(file)).digest("hex");
|
|
402
|
+
}
|
|
403
|
+
|
|
404
|
+
function managedMessage(zh, en) {
|
|
405
|
+
return english ? en : zh;
|
|
406
|
+
}
|
|
407
|
+
|
|
408
|
+
function isPortableExecutable(file) {
|
|
409
|
+
return /\.(?:sh|bash|zsh|ps1)$/i.test(file);
|
|
410
|
+
}
|
|
411
|
+
|
|
412
|
+
function containsPersonalAbsolutePath(content) {
|
|
413
|
+
return /(?:^|[\s"'=])(?:\/Users|\/home)\/[A-Za-z0-9._-]+(?:\/|$)|(?:^|[\s"'=])[A-Za-z]:\\Users\\[A-Za-z0-9._-]+(?:\\|$)/m.test(
|
|
414
|
+
content,
|
|
415
|
+
);
|
|
416
|
+
}
|
|
417
|
+
|
|
418
|
+
function fail(message) {
|
|
419
|
+
console.error(message);
|
|
420
|
+
process.exit(2);
|
|
421
|
+
}
|
|
@@ -0,0 +1,117 @@
|
|
|
1
|
+
#!/usr/bin/env bash
|
|
2
|
+
# 由任务验收入口通过可配置路径 source 的 owner 校验公共 helper,禁止复制后分叉维护。
|
|
3
|
+
# 模板只提供确定事实:PID、lstart、nonce、真实进程签名、真实监听端口、
|
|
4
|
+
# Docker task/nonce/name 标签与 runtime 目录 marker。
|
|
5
|
+
# 业务 key/表能否删除仍由任务验收入口与 Host 评审决定。
|
|
6
|
+
|
|
7
|
+
superflow_lstart_of() {
|
|
8
|
+
ps -o lstart= -p "$1" 2>/dev/null | sed 's/^ *//;s/ */ /g'
|
|
9
|
+
}
|
|
10
|
+
|
|
11
|
+
superflow_pid_listens() {
|
|
12
|
+
lsof -nP -a -p "$1" -iTCP:"$2" -sTCP:LISTEN 2>/dev/null \
|
|
13
|
+
| grep -q LISTEN
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
superflow_owner_matches() {
|
|
17
|
+
local pidfile="$1" fingerprint="$2" nonce="$3" port="$4" signature="$5"
|
|
18
|
+
[ -f "$pidfile" ] && [ -f "$fingerprint" ] || return 1
|
|
19
|
+
local pid recorded_pid recorded_nonce recorded_port recorded_lstart live args
|
|
20
|
+
pid="$(tr -dc '0-9' < "$pidfile")"
|
|
21
|
+
recorded_pid="$(sed -n 's/^PID=//p' "$fingerprint")"
|
|
22
|
+
recorded_nonce="$(sed -n 's/^NONCE=//p' "$fingerprint")"
|
|
23
|
+
recorded_port="$(sed -n 's/^PORT=//p' "$fingerprint")"
|
|
24
|
+
recorded_lstart="$(sed -n 's/^LSTART=//p' "$fingerprint")"
|
|
25
|
+
[ -n "$pid" ] && [ "$pid" = "$recorded_pid" ] || return 1
|
|
26
|
+
[ "$recorded_nonce" = "$nonce" ] || return 1
|
|
27
|
+
[ "$recorded_port" = "$port" ] || return 1
|
|
28
|
+
kill -0 "$pid" 2>/dev/null || return 1
|
|
29
|
+
live="$(superflow_lstart_of "$pid")"
|
|
30
|
+
[ -n "$recorded_lstart" ] && [ "$recorded_lstart" = "$live" ] || return 1
|
|
31
|
+
args="$(ps -o args= -p "$pid" 2>/dev/null || true)"
|
|
32
|
+
printf '%s' "$args" | grep -Fq -- "$signature" || return 1
|
|
33
|
+
superflow_pid_listens "$pid" "$port"
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
superflow_write_fingerprint() {
|
|
37
|
+
local fingerprint="$1" pid="$2" nonce="$3" port="$4" signature="$5"
|
|
38
|
+
local lstart
|
|
39
|
+
lstart="$(superflow_lstart_of "$pid")"
|
|
40
|
+
[ -n "$lstart" ] || return 1
|
|
41
|
+
cat >"$fingerprint" <<EOF
|
|
42
|
+
PID=$pid
|
|
43
|
+
NONCE=$nonce
|
|
44
|
+
PORT=$port
|
|
45
|
+
LSTART=$lstart
|
|
46
|
+
SIGNATURE=$signature
|
|
47
|
+
EOF
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
# 等待后若需升级信号,必须再次调用本函数,禁止复用首次校验后的裸 PID。
|
|
51
|
+
superflow_signal_owner() {
|
|
52
|
+
local signal="$1" pidfile="$2" fingerprint="$3" nonce="$4" port="$5"
|
|
53
|
+
local signature="$6" pid
|
|
54
|
+
superflow_owner_matches \
|
|
55
|
+
"$pidfile" "$fingerprint" "$nonce" "$port" "$signature" || return 1
|
|
56
|
+
pid="$(tr -dc '0-9' <"$pidfile")"
|
|
57
|
+
kill "-$signal" "$pid"
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
superflow_docker_exists() {
|
|
61
|
+
local docker_bin="$1" kind="$2" resource="$3"
|
|
62
|
+
case "$kind" in
|
|
63
|
+
container) "$docker_bin" inspect --type container "$resource" >/dev/null 2>&1 ;;
|
|
64
|
+
network|volume) "$docker_bin" "$kind" inspect "$resource" >/dev/null 2>&1 ;;
|
|
65
|
+
*) return 1 ;;
|
|
66
|
+
esac
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
superflow_docker_labels_equal() {
|
|
70
|
+
local docker_bin="$1" kind="$2" resource="$3"
|
|
71
|
+
local task="$4" nonce="$5" expected_name="$6" actual
|
|
72
|
+
case "$kind" in
|
|
73
|
+
container)
|
|
74
|
+
actual="$("$docker_bin" inspect --type container \
|
|
75
|
+
-f '{{index .Config.Labels "superflow.task"}}|{{index .Config.Labels "superflow.nonce"}}|{{.Name}}' \
|
|
76
|
+
"$resource" 2>/dev/null || true)"
|
|
77
|
+
expected_name="/$expected_name"
|
|
78
|
+
;;
|
|
79
|
+
network|volume)
|
|
80
|
+
actual="$("$docker_bin" "$kind" inspect \
|
|
81
|
+
-f '{{index .Labels "superflow.task"}}|{{index .Labels "superflow.nonce"}}|{{.Name}}' \
|
|
82
|
+
"$resource" 2>/dev/null || true)"
|
|
83
|
+
;;
|
|
84
|
+
*) return 1 ;;
|
|
85
|
+
esac
|
|
86
|
+
[ "$actual" = "$task|$nonce|$expected_name" ]
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
superflow_docker_remove_verified() {
|
|
90
|
+
local docker_bin="$1" kind="$2" resource="$3"
|
|
91
|
+
local task="$4" nonce="$5" expected_name="$6"
|
|
92
|
+
superflow_docker_exists "$docker_bin" "$kind" "$resource" || return 0
|
|
93
|
+
superflow_docker_labels_equal \
|
|
94
|
+
"$docker_bin" "$kind" "$resource" "$task" "$nonce" "$expected_name" \
|
|
95
|
+
|| return 1
|
|
96
|
+
case "$kind" in
|
|
97
|
+
container) "$docker_bin" rm -f -v "$resource" ;;
|
|
98
|
+
network) "$docker_bin" network rm "$resource" ;;
|
|
99
|
+
volume) "$docker_bin" volume rm "$resource" ;;
|
|
100
|
+
*) return 1 ;;
|
|
101
|
+
esac
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
superflow_runtime_remove_verified() {
|
|
105
|
+
local runtime_root="$1" dir="$2" task="$3" nonce="$4"
|
|
106
|
+
[ -d "$dir" ] || return 0
|
|
107
|
+
local real_root real_dir marker
|
|
108
|
+
real_root="$(cd "$runtime_root" 2>/dev/null && pwd -P)" || return 1
|
|
109
|
+
real_dir="$(cd "$dir" 2>/dev/null && pwd -P)" || return 1
|
|
110
|
+
[ "$(dirname "$real_dir")" = "$real_root" ] || return 1
|
|
111
|
+
[ "$(basename "$real_dir")" = "$nonce" ] || return 1
|
|
112
|
+
marker="$real_dir/owner.marker"
|
|
113
|
+
[ -f "$marker" ] || return 1
|
|
114
|
+
grep -qx "task=$task" "$marker" || return 1
|
|
115
|
+
grep -qx "nonce=$nonce" "$marker" || return 1
|
|
116
|
+
rm -rf "$real_dir"
|
|
117
|
+
}
|