@chenmk/superflow 0.4.0 → 0.5.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 (242) hide show
  1. package/INSTALL.en.md +58 -12
  2. package/INSTALL.md +62 -33
  3. package/README.md +181 -54
  4. package/README.zh-CN.md +158 -51
  5. package/assets/brand/managed-review-loop.png +0 -0
  6. package/assets/manifest.json +10 -5
  7. package/assets/scripts/superflow-coding-ready.mjs +135 -0
  8. package/assets/scripts/superflow-dependency-update-hook.sh +81 -8
  9. package/assets/scripts/superflow-document-audit.mjs +240 -0
  10. package/assets/scripts/superflow-environment-preflight.mjs +398 -0
  11. package/assets/scripts/superflow-hook-guard.sh +40 -1
  12. package/assets/scripts/superflow-managed-command-guard.py +309 -0
  13. package/assets/scripts/superflow-managed-executor-preflight.mjs +421 -0
  14. package/assets/scripts/superflow-managed-owner-verification.sh +117 -0
  15. package/assets/scripts/superflow-managed-work-check.mjs +287 -26
  16. package/assets/scripts/superflow-managed-work-guard.sh +6 -92
  17. package/assets/skills/superflow-clarify/SKILL.md +14 -0
  18. package/assets/skills/superflow-design/SKILL.md +38 -13
  19. package/assets/skills/superflow-docs/SKILL.md +93 -2
  20. package/assets/skills/superflow-pipeline/SKILL.md +167 -10
  21. package/assets/skills/superflow-pipeline/references/batch-prompt-template.md +17 -9
  22. package/assets/skills/superflow-pipeline/references/document-readiness.md +157 -0
  23. package/assets/skills/superflow-pipeline/references/document-templates.md +9 -5
  24. package/assets/skills/superflow-pipeline/references/feature-gated-workflow.md +14 -0
  25. package/assets/skills/superflow-pipeline/references/implementation-prompt-template.md +20 -11
  26. package/assets/skills/superflow-pipeline/references/managed-work.md +123 -15
  27. package/assets/skills/superflow-pipeline/references/orchestration.md +2 -2
  28. package/assets/skills/superflow-pipeline/references/p0-baseline-template.md +3 -2
  29. package/assets/skills/superflow-pipeline/references/quality-gate.md +7 -5
  30. package/assets/skills/superflow-pipeline/references/reviewer-checklist.md +7 -0
  31. package/assets/skills/superflow-pipeline/scripts/superflow-guard.sh +205 -9
  32. package/assets/skills/superflow-pipeline/scripts/superflow-handoff.sh +7 -4
  33. package/assets/skills/superflow-requirement-review/SKILL.md +178 -0
  34. package/assets/skills/superflow-requirement-review/agents/openai.yaml +4 -0
  35. package/assets/skills-en/superflow-clarify/SKILL.md +17 -0
  36. package/assets/skills-en/superflow-design/SKILL.md +31 -12
  37. package/assets/skills-en/superflow-docs/SKILL.md +99 -0
  38. package/assets/skills-en/superflow-pipeline/SKILL.md +163 -9
  39. package/assets/skills-en/superflow-pipeline/references/batch-prompt-template.md +4 -0
  40. package/assets/skills-en/superflow-pipeline/references/document-readiness.md +125 -0
  41. package/assets/skills-en/superflow-pipeline/references/document-templates.md +10 -0
  42. package/assets/skills-en/superflow-pipeline/references/feature-gated-workflow.md +7 -0
  43. package/assets/skills-en/superflow-pipeline/references/implementation-prompt-template.md +7 -0
  44. package/assets/skills-en/superflow-pipeline/references/managed-work.md +160 -10
  45. package/assets/skills-en/superflow-pipeline/references/quality-gate.md +12 -0
  46. package/assets/skills-en/superflow-pipeline/references/reviewer-checklist.md +7 -0
  47. package/assets/skills-en/superflow-pipeline/scripts/superflow-guard.sh +205 -9
  48. package/assets/skills-en/superflow-pipeline/scripts/superflow-handoff.sh +7 -4
  49. package/assets/skills-en/superflow-requirement-review/SKILL.md +99 -0
  50. package/assets/skills-en/superflow-requirement-review/agents/openai.yaml +4 -0
  51. package/dist/app/cli.js +64 -9
  52. package/dist/app/cli.js.map +1 -1
  53. package/dist/app/commands/change-guard.js +19 -2
  54. package/dist/app/commands/change-guard.js.map +1 -1
  55. package/dist/app/commands/check.js +107 -45
  56. package/dist/app/commands/check.js.map +1 -1
  57. package/dist/app/commands/docs.js +7 -3
  58. package/dist/app/commands/docs.js.map +1 -1
  59. package/dist/app/commands/doctor.js +174 -198
  60. package/dist/app/commands/doctor.js.map +1 -1
  61. package/dist/app/commands/eval.js +64 -0
  62. package/dist/app/commands/eval.js.map +1 -0
  63. package/dist/app/commands/implement.js +7 -3
  64. package/dist/app/commands/implement.js.map +1 -1
  65. package/dist/app/commands/init.js +12 -24
  66. package/dist/app/commands/init.js.map +1 -1
  67. package/dist/app/commands/mcp.js +167 -0
  68. package/dist/app/commands/mcp.js.map +1 -0
  69. package/dist/app/commands/pipeline.js +652 -18
  70. package/dist/app/commands/pipeline.js.map +1 -1
  71. package/dist/app/commands/requirement-review.js +6 -0
  72. package/dist/app/commands/requirement-review.js.map +1 -0
  73. package/dist/app/commands/status.js +66 -3
  74. package/dist/app/commands/status.js.map +1 -1
  75. package/dist/app/commands/uninstall.js +3 -3
  76. package/dist/app/commands/uninstall.js.map +1 -1
  77. package/dist/app/commands/update.js +83 -9
  78. package/dist/app/commands/update.js.map +1 -1
  79. package/dist/domains/agent.js +3 -11
  80. package/dist/domains/agent.js.map +1 -1
  81. package/dist/domains/config/cli-help.js +54 -12
  82. package/dist/domains/config/cli-help.js.map +1 -1
  83. package/dist/domains/config/i18n.js +6 -8
  84. package/dist/domains/config/i18n.js.map +1 -1
  85. package/dist/domains/config/manifest.js +0 -3
  86. package/dist/domains/config/manifest.js.map +1 -1
  87. package/dist/domains/deps.js +68 -7
  88. package/dist/domains/deps.js.map +1 -1
  89. package/dist/domains/managed-work/completion-policy.js +174 -0
  90. package/dist/domains/managed-work/completion-policy.js.map +1 -0
  91. package/dist/domains/managed-work/context-manifest.js +208 -0
  92. package/dist/domains/managed-work/context-manifest.js.map +1 -0
  93. package/dist/domains/managed-work/contract.js +135 -4
  94. package/dist/domains/managed-work/contract.js.map +1 -1
  95. package/dist/domains/managed-work/control-signals.js +34 -0
  96. package/dist/domains/managed-work/control-signals.js.map +1 -0
  97. package/dist/domains/managed-work/control.js +628 -0
  98. package/dist/domains/managed-work/control.js.map +1 -0
  99. package/dist/domains/managed-work/evaluation.js +497 -0
  100. package/dist/domains/managed-work/evaluation.js.map +1 -0
  101. package/dist/domains/managed-work/evidence-path.js +14 -0
  102. package/dist/domains/managed-work/evidence-path.js.map +1 -0
  103. package/dist/domains/managed-work/execution-contract.js +167 -0
  104. package/dist/domains/managed-work/execution-contract.js.map +1 -0
  105. package/dist/domains/managed-work/executor-policy.js +65 -0
  106. package/dist/domains/managed-work/executor-policy.js.map +1 -0
  107. package/dist/domains/managed-work/failure.js +65 -0
  108. package/dist/domains/managed-work/failure.js.map +1 -0
  109. package/dist/domains/managed-work/host-review.js +77 -0
  110. package/dist/domains/managed-work/host-review.js.map +1 -0
  111. package/dist/domains/managed-work/human-messages.js +31 -0
  112. package/dist/domains/managed-work/human-messages.js.map +1 -0
  113. package/dist/domains/managed-work/input.js +29 -3
  114. package/dist/domains/managed-work/input.js.map +1 -1
  115. package/dist/domains/managed-work/journal.js +77 -3
  116. package/dist/domains/managed-work/journal.js.map +1 -1
  117. package/dist/domains/managed-work/lock.js +67 -1
  118. package/dist/domains/managed-work/lock.js.map +1 -1
  119. package/dist/domains/managed-work/notifications.js +0 -15
  120. package/dist/domains/managed-work/notifications.js.map +1 -1
  121. package/dist/domains/managed-work/pair-admission.js +108 -0
  122. package/dist/domains/managed-work/pair-admission.js.map +1 -0
  123. package/dist/domains/managed-work/prompts.js +257 -22
  124. package/dist/domains/managed-work/prompts.js.map +1 -1
  125. package/dist/domains/managed-work/redaction.js +90 -0
  126. package/dist/domains/managed-work/redaction.js.map +1 -0
  127. package/dist/domains/managed-work/review-convergence.js +41 -0
  128. package/dist/domains/managed-work/review-convergence.js.map +1 -0
  129. package/dist/domains/managed-work/review-facts.js +181 -0
  130. package/dist/domains/managed-work/review-facts.js.map +1 -0
  131. package/dist/domains/managed-work/rule-selection.js +84 -0
  132. package/dist/domains/managed-work/rule-selection.js.map +1 -0
  133. package/dist/domains/managed-work/runner.js +1967 -175
  134. package/dist/domains/managed-work/runner.js.map +1 -1
  135. package/dist/domains/managed-work/schemas.js +72 -2
  136. package/dist/domains/managed-work/schemas.js.map +1 -1
  137. package/dist/domains/managed-work/service.js +82 -13
  138. package/dist/domains/managed-work/service.js.map +1 -1
  139. package/dist/domains/managed-work/state.js +195 -5
  140. package/dist/domains/managed-work/state.js.map +1 -1
  141. package/dist/domains/managed-work/storage.js +66 -24
  142. package/dist/domains/managed-work/storage.js.map +1 -1
  143. package/dist/domains/managed-work/types.js +12 -9
  144. package/dist/domains/managed-work/types.js.map +1 -1
  145. package/dist/domains/managed-work/verification-categories.js +39 -0
  146. package/dist/domains/managed-work/verification-categories.js.map +1 -0
  147. package/dist/domains/managed-work/verification-policy.js +116 -0
  148. package/dist/domains/managed-work/verification-policy.js.map +1 -0
  149. package/dist/domains/managed-work/wait.js +21 -3
  150. package/dist/domains/managed-work/wait.js.map +1 -1
  151. package/dist/domains/managed-work/workspace-binding.js +133 -0
  152. package/dist/domains/managed-work/workspace-binding.js.map +1 -0
  153. package/dist/domains/sdd-readiness.js +44 -0
  154. package/dist/domains/sdd-readiness.js.map +1 -0
  155. package/dist/domains/skill/deploy.js +0 -6
  156. package/dist/domains/skill/deploy.js.map +1 -1
  157. package/dist/domains/skill/scripts.js +0 -8
  158. package/dist/domains/skill/scripts.js.map +1 -1
  159. package/dist/domains/state.js +1 -2
  160. package/dist/domains/state.js.map +1 -1
  161. package/dist/mcp/runtime-identity.js +21 -0
  162. package/dist/mcp/runtime-identity.js.map +1 -0
  163. package/dist/mcp/server.js +369 -0
  164. package/dist/mcp/server.js.map +1 -0
  165. package/dist/platform/agent-process.js +499 -38
  166. package/dist/platform/agent-process.js.map +1 -1
  167. package/dist/platform/executable.js +28 -0
  168. package/dist/platform/executable.js.map +1 -0
  169. package/dist/platform/paths.js +1 -17
  170. package/dist/platform/paths.js.map +1 -1
  171. package/dist/platform/process-tree.js +44 -0
  172. package/dist/platform/process-tree.js.map +1 -0
  173. package/dist/platform/process.js +1 -1
  174. package/dist/platform/process.js.map +1 -1
  175. package/package.json +17 -7
  176. package/scripts/check-design-principles.mjs +107 -0
  177. package/scripts/check-managed-work-design.mjs +507 -0
  178. package/scripts/clean-dist.js +9 -0
  179. package/scripts/ensure-bin-modes.js +11 -0
  180. package/scripts/preinstall.js +57 -0
  181. package/dist/cli/index.js +0 -193
  182. package/dist/cli/index.js.map +0 -1
  183. package/dist/commands/archive.js +0 -6
  184. package/dist/commands/archive.js.map +0 -1
  185. package/dist/commands/clarify.js +0 -6
  186. package/dist/commands/clarify.js.map +0 -1
  187. package/dist/commands/design.js +0 -28
  188. package/dist/commands/design.js.map +0 -1
  189. package/dist/commands/docs.js +0 -6
  190. package/dist/commands/docs.js.map +0 -1
  191. package/dist/commands/doctor.js +0 -494
  192. package/dist/commands/doctor.js.map +0 -1
  193. package/dist/commands/implement.js +0 -6
  194. package/dist/commands/implement.js.map +0 -1
  195. package/dist/commands/init.js +0 -520
  196. package/dist/commands/init.js.map +0 -1
  197. package/dist/commands/pipeline.js +0 -6
  198. package/dist/commands/pipeline.js.map +0 -1
  199. package/dist/commands/scan.js +0 -63
  200. package/dist/commands/scan.js.map +0 -1
  201. package/dist/commands/status.js +0 -173
  202. package/dist/commands/status.js.map +0 -1
  203. package/dist/commands/uninstall.js +0 -214
  204. package/dist/commands/uninstall.js.map +0 -1
  205. package/dist/commands/update.js +0 -193
  206. package/dist/commands/update.js.map +0 -1
  207. package/dist/commands/verify.js +0 -6
  208. package/dist/commands/verify.js.map +0 -1
  209. package/dist/core/assets.js +0 -27
  210. package/dist/core/assets.js.map +0 -1
  211. package/dist/core/cli-help.js +0 -98
  212. package/dist/core/cli-help.js.map +0 -1
  213. package/dist/core/context.js +0 -123
  214. package/dist/core/context.js.map +0 -1
  215. package/dist/core/dependencies.js +0 -146
  216. package/dist/core/dependencies.js.map +0 -1
  217. package/dist/core/detect.js +0 -64
  218. package/dist/core/detect.js.map +0 -1
  219. package/dist/core/i18n.js +0 -105
  220. package/dist/core/i18n.js.map +0 -1
  221. package/dist/core/integrity.js +0 -46
  222. package/dist/core/integrity.js.map +0 -1
  223. package/dist/core/manifest.js +0 -21
  224. package/dist/core/manifest.js.map +0 -1
  225. package/dist/core/prompts.js +0 -20
  226. package/dist/core/prompts.js.map +0 -1
  227. package/dist/core/registry.js +0 -134
  228. package/dist/core/registry.js.map +0 -1
  229. package/dist/core/rules.js +0 -17
  230. package/dist/core/rules.js.map +0 -1
  231. package/dist/core/scripts.js +0 -48
  232. package/dist/core/scripts.js.map +0 -1
  233. package/dist/core/skill-check.js +0 -32
  234. package/dist/core/skill-check.js.map +0 -1
  235. package/dist/core/skills.js +0 -62
  236. package/dist/core/skills.js.map +0 -1
  237. package/dist/core/state.js +0 -54
  238. package/dist/core/state.js.map +0 -1
  239. package/dist/utils/path.js +0 -11
  240. package/dist/utils/path.js.map +0 -1
  241. package/dist/utils/shell.js +0 -29
  242. 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
+ }