@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.
- package/INSTALL.en.md +58 -12
- package/INSTALL.md +62 -33
- package/README.md +181 -54
- package/README.zh-CN.md +158 -51
- package/assets/brand/managed-review-loop.png +0 -0
- package/assets/manifest.json +10 -5
- 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 +287 -26
- package/assets/scripts/superflow-managed-work-guard.sh +6 -92
- package/assets/skills/superflow-clarify/SKILL.md +14 -0
- package/assets/skills/superflow-design/SKILL.md +38 -13
- package/assets/skills/superflow-docs/SKILL.md +93 -2
- package/assets/skills/superflow-pipeline/SKILL.md +167 -10
- 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 +9 -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 +123 -15
- 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 +7 -5
- package/assets/skills/superflow-pipeline/references/reviewer-checklist.md +7 -0
- package/assets/skills/superflow-pipeline/scripts/superflow-guard.sh +205 -9
- 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/superflow-clarify/SKILL.md +17 -0
- package/assets/skills-en/superflow-design/SKILL.md +31 -12
- package/assets/skills-en/superflow-docs/SKILL.md +99 -0
- package/assets/skills-en/superflow-pipeline/SKILL.md +163 -9
- 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 +10 -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 +160 -10
- package/assets/skills-en/superflow-pipeline/references/quality-gate.md +12 -0
- package/assets/skills-en/superflow-pipeline/references/reviewer-checklist.md +7 -0
- package/assets/skills-en/superflow-pipeline/scripts/superflow-guard.sh +205 -9
- 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 +64 -9
- 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 +12 -24
- package/dist/app/commands/init.js.map +1 -1
- package/dist/app/commands/mcp.js +167 -0
- package/dist/app/commands/mcp.js.map +1 -0
- package/dist/app/commands/pipeline.js +652 -18
- 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 +66 -3
- package/dist/app/commands/status.js.map +1 -1
- package/dist/app/commands/uninstall.js +3 -3
- package/dist/app/commands/uninstall.js.map +1 -1
- package/dist/app/commands/update.js +83 -9
- 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 +54 -12
- 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 +68 -7
- package/dist/domains/deps.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 +135 -4
- package/dist/domains/managed-work/contract.js.map +1 -1
- 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/input.js +29 -3
- package/dist/domains/managed-work/input.js.map +1 -1
- package/dist/domains/managed-work/journal.js +77 -3
- package/dist/domains/managed-work/journal.js.map +1 -1
- package/dist/domains/managed-work/lock.js +67 -1
- package/dist/domains/managed-work/lock.js.map +1 -1
- package/dist/domains/managed-work/notifications.js +0 -15
- package/dist/domains/managed-work/notifications.js.map +1 -1
- 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/prompts.js +257 -22
- package/dist/domains/managed-work/prompts.js.map +1 -1
- 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 +1967 -175
- package/dist/domains/managed-work/runner.js.map +1 -1
- package/dist/domains/managed-work/schemas.js +72 -2
- package/dist/domains/managed-work/schemas.js.map +1 -1
- package/dist/domains/managed-work/service.js +82 -13
- package/dist/domains/managed-work/service.js.map +1 -1
- package/dist/domains/managed-work/state.js +195 -5
- package/dist/domains/managed-work/state.js.map +1 -1
- package/dist/domains/managed-work/storage.js +66 -24
- package/dist/domains/managed-work/storage.js.map +1 -1
- package/dist/domains/managed-work/types.js +12 -9
- package/dist/domains/managed-work/types.js.map +1 -1
- 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 +21 -3
- package/dist/domains/managed-work/wait.js.map +1 -1
- 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 +499 -38
- package/dist/platform/agent-process.js.map +1 -1
- 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-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/package.json +17 -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/scripts/preinstall.js +57 -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,309 @@
|
|
|
1
|
+
#!/usr/bin/env python3
|
|
2
|
+
"""Pre-tool guard for Superflow managed Agent roles."""
|
|
3
|
+
|
|
4
|
+
from __future__ import annotations
|
|
5
|
+
|
|
6
|
+
import json
|
|
7
|
+
import os
|
|
8
|
+
import re
|
|
9
|
+
import shlex
|
|
10
|
+
import sys
|
|
11
|
+
from pathlib import Path
|
|
12
|
+
|
|
13
|
+
|
|
14
|
+
def task_language() -> str:
|
|
15
|
+
root = os.environ.get("SUPERFLOW_MANAGED_PROJECT_ROOT", "")
|
|
16
|
+
task_id = os.environ.get("SUPERFLOW_MANAGED_TASK_ID", "")
|
|
17
|
+
if not root or not task_id:
|
|
18
|
+
return "zh"
|
|
19
|
+
task_file = Path(root) / ".superflow" / "tasks" / task_id / "task.json"
|
|
20
|
+
try:
|
|
21
|
+
return json.loads(task_file.read_text(encoding="utf-8")).get(
|
|
22
|
+
"language", "zh"
|
|
23
|
+
)
|
|
24
|
+
except (OSError, ValueError):
|
|
25
|
+
return "zh"
|
|
26
|
+
|
|
27
|
+
|
|
28
|
+
def block(zh: str, en: str | None = None) -> None:
|
|
29
|
+
message = en if task_language() == "en" and en else zh
|
|
30
|
+
print(f"[Superflow managed-work guard] {message}", file=sys.stderr)
|
|
31
|
+
raise SystemExit(2)
|
|
32
|
+
|
|
33
|
+
|
|
34
|
+
def tool_field(payload: dict, *names: str) -> str:
|
|
35
|
+
tool_input = payload.get("tool_input") or {}
|
|
36
|
+
for name in names:
|
|
37
|
+
value = tool_input.get(name)
|
|
38
|
+
if isinstance(value, str) and value.strip():
|
|
39
|
+
return value.strip()
|
|
40
|
+
return ""
|
|
41
|
+
|
|
42
|
+
|
|
43
|
+
def canonical(value: str, root: Path) -> Path:
|
|
44
|
+
candidate = Path(value).expanduser()
|
|
45
|
+
if not candidate.is_absolute():
|
|
46
|
+
candidate = root / candidate
|
|
47
|
+
return candidate.resolve(strict=False)
|
|
48
|
+
|
|
49
|
+
|
|
50
|
+
def overlaps_protected(candidate: Path, protected: list[Path]) -> bool:
|
|
51
|
+
for item in protected:
|
|
52
|
+
if candidate == item or candidate in item.parents:
|
|
53
|
+
return True
|
|
54
|
+
return False
|
|
55
|
+
|
|
56
|
+
|
|
57
|
+
def protected_entries() -> tuple[list[Path], list[Path]]:
|
|
58
|
+
manifest_path = os.environ.get("SUPERFLOW_MANAGED_CONTEXT_MANIFEST", "")
|
|
59
|
+
if not manifest_path:
|
|
60
|
+
return [], []
|
|
61
|
+
try:
|
|
62
|
+
manifest = json.loads(Path(manifest_path).read_text(encoding="utf-8"))
|
|
63
|
+
except (OSError, ValueError) as error:
|
|
64
|
+
block(
|
|
65
|
+
f"无法读取受保护输入清单,按失败关闭:{error}",
|
|
66
|
+
f"Cannot read the protected-input manifest; failing closed: {error}",
|
|
67
|
+
)
|
|
68
|
+
immutable: list[Path] = []
|
|
69
|
+
retained: list[Path] = []
|
|
70
|
+
for entry in manifest.get("entries", []):
|
|
71
|
+
entry_path = entry.get("path")
|
|
72
|
+
if not isinstance(entry_path, str) or not entry_path:
|
|
73
|
+
continue
|
|
74
|
+
target = Path(entry_path).resolve(strict=False)
|
|
75
|
+
if entry.get("protection", "immutable") == "retain":
|
|
76
|
+
retained.append(target)
|
|
77
|
+
else:
|
|
78
|
+
immutable.append(target)
|
|
79
|
+
return immutable, retained
|
|
80
|
+
|
|
81
|
+
|
|
82
|
+
def shell_tokens(command: str) -> list[str]:
|
|
83
|
+
try:
|
|
84
|
+
return shlex.split(command, posix=os.name != "nt")
|
|
85
|
+
except ValueError:
|
|
86
|
+
return command.split()
|
|
87
|
+
|
|
88
|
+
|
|
89
|
+
def path_tokens(command: str, root: Path) -> list[Path]:
|
|
90
|
+
tokens = shell_tokens(command)
|
|
91
|
+
values: list[Path] = []
|
|
92
|
+
for token in tokens:
|
|
93
|
+
cleaned = token.strip("'\"")
|
|
94
|
+
if (
|
|
95
|
+
not cleaned
|
|
96
|
+
or cleaned.startswith("-")
|
|
97
|
+
or "$" in cleaned
|
|
98
|
+
or cleaned in {"rm", "mv", "cp", "truncate", "unlink", "rmdir"}
|
|
99
|
+
):
|
|
100
|
+
continue
|
|
101
|
+
values.append(canonical(cleaned, root))
|
|
102
|
+
return values
|
|
103
|
+
|
|
104
|
+
|
|
105
|
+
def mutates_files(command: str) -> bool:
|
|
106
|
+
return bool(
|
|
107
|
+
re.search(
|
|
108
|
+
r"(?:^|[;&|]\s*)(?:rm|mv|cp|truncate|unlink|rmdir)\s"
|
|
109
|
+
r"|\bsed\s+[^;&|]*-[A-Za-z]*i\b"
|
|
110
|
+
r"|\bperl\s+[^;&|]*-[A-Za-z]*i\b"
|
|
111
|
+
r"|(?:^|[^>])>{1,2}\s*[^&]",
|
|
112
|
+
command,
|
|
113
|
+
re.IGNORECASE,
|
|
114
|
+
)
|
|
115
|
+
)
|
|
116
|
+
|
|
117
|
+
|
|
118
|
+
def safe_workspace_rm(command: str, root: Path) -> bool:
|
|
119
|
+
allowed = {
|
|
120
|
+
"target",
|
|
121
|
+
"build",
|
|
122
|
+
"dist",
|
|
123
|
+
"coverage",
|
|
124
|
+
".gradle",
|
|
125
|
+
".cache",
|
|
126
|
+
".tmp",
|
|
127
|
+
"tmp",
|
|
128
|
+
}
|
|
129
|
+
matched = False
|
|
130
|
+
for segment in re.split(r"[;&|]", command):
|
|
131
|
+
if not re.search(r"(?:^|\s)rm\s+-[^\s]*r[^\s]*f", segment):
|
|
132
|
+
continue
|
|
133
|
+
matched = True
|
|
134
|
+
tokens = shell_tokens(segment)
|
|
135
|
+
try:
|
|
136
|
+
start = tokens.index("rm") + 1
|
|
137
|
+
except ValueError:
|
|
138
|
+
return False
|
|
139
|
+
targets = [token for token in tokens[start:] if not token.startswith("-")]
|
|
140
|
+
if not targets:
|
|
141
|
+
return False
|
|
142
|
+
for value in targets:
|
|
143
|
+
if "$" in value:
|
|
144
|
+
return False
|
|
145
|
+
target = canonical(value, root)
|
|
146
|
+
try:
|
|
147
|
+
relative = target.relative_to(root)
|
|
148
|
+
except ValueError:
|
|
149
|
+
return False
|
|
150
|
+
if not relative.parts or relative.parts[0] not in allowed:
|
|
151
|
+
return False
|
|
152
|
+
return matched
|
|
153
|
+
|
|
154
|
+
|
|
155
|
+
def guard_supervisor(tool_name: str, command: str) -> None:
|
|
156
|
+
if tool_name in {"Edit", "Write", "MultiEdit", "NotebookEdit", "apply_patch"}:
|
|
157
|
+
block(
|
|
158
|
+
"监督角色只允许检查,禁止修改目标文件。",
|
|
159
|
+
"The supervisor is read-only and cannot modify target files.",
|
|
160
|
+
)
|
|
161
|
+
if command and re.search(
|
|
162
|
+
r"(?:^|\s)(?:rm|mv|cp|mkdir|touch|chmod|chown)(?:\s|$)"
|
|
163
|
+
r"|git\s+(?:add|commit|push|tag|reset|checkout|restore)"
|
|
164
|
+
r"|npm\s+publish|apply_patch|sed\s+-i|(?:^|[^>])>{1,2}\s|tee\s",
|
|
165
|
+
command,
|
|
166
|
+
re.IGNORECASE,
|
|
167
|
+
):
|
|
168
|
+
block(
|
|
169
|
+
"监督角色的 Bash 仅允许只读检查,检测到可能写入的命令。",
|
|
170
|
+
"Supervisor shell access is read-only; a mutating command was detected.",
|
|
171
|
+
)
|
|
172
|
+
|
|
173
|
+
|
|
174
|
+
def guard_executor(
|
|
175
|
+
tool_name: str,
|
|
176
|
+
file_path: str,
|
|
177
|
+
command: str,
|
|
178
|
+
root: Path,
|
|
179
|
+
immutable: list[Path],
|
|
180
|
+
retained: list[Path],
|
|
181
|
+
) -> None:
|
|
182
|
+
write_tools = {"Edit", "Write", "MultiEdit", "NotebookEdit", "apply_patch"}
|
|
183
|
+
if tool_name in write_tools:
|
|
184
|
+
if not file_path:
|
|
185
|
+
block(
|
|
186
|
+
"写入工具缺少目标路径,按失败关闭。",
|
|
187
|
+
"The write tool has no target path; failing closed.",
|
|
188
|
+
)
|
|
189
|
+
target = canonical(file_path, root)
|
|
190
|
+
if ".superflow/tasks" in target.as_posix():
|
|
191
|
+
block(
|
|
192
|
+
"执行角色禁止修改托管状态、事件、Prompt 和过程报告。",
|
|
193
|
+
"The executor cannot edit managed state, events, prompts, or run reports.",
|
|
194
|
+
)
|
|
195
|
+
if target in immutable:
|
|
196
|
+
block(
|
|
197
|
+
f"冻结输入只读,禁止修改:{target}",
|
|
198
|
+
f"Frozen input is read-only: {target}",
|
|
199
|
+
)
|
|
200
|
+
|
|
201
|
+
if command and ".superflow/tasks/" in command.replace("\\", "/"):
|
|
202
|
+
block(
|
|
203
|
+
"执行角色的 Bash 禁止直接访问托管运行目录,请使用只读工具。",
|
|
204
|
+
"Executor shell commands cannot access the managed run directory; use read-only tools.",
|
|
205
|
+
)
|
|
206
|
+
if command and re.search(
|
|
207
|
+
r"git\s+(?:add|commit|push|tag)|npm\s+publish"
|
|
208
|
+
r"|dangerously-(?:bypass|skip)|kubectl\s+(?:apply|delete)"
|
|
209
|
+
r"|helm\s+(?:install|upgrade)|DROP\s+(?:DATABASE|TABLE)"
|
|
210
|
+
r"|TRUNCATE\s+TABLE",
|
|
211
|
+
command,
|
|
212
|
+
re.IGNORECASE,
|
|
213
|
+
):
|
|
214
|
+
block(
|
|
215
|
+
"执行角色禁止自动 Git、发布、部署、绕过沙箱或高破坏数据库操作。",
|
|
216
|
+
"The executor cannot perform Git delivery, publish, deploy, bypass the sandbox, or run destructive database operations.",
|
|
217
|
+
)
|
|
218
|
+
|
|
219
|
+
if command and mutates_files(command):
|
|
220
|
+
targets = path_tokens(command, root)
|
|
221
|
+
if any(overlaps_protected(target, immutable + retained) for target in targets):
|
|
222
|
+
block(
|
|
223
|
+
"命令试图删除、移动、截断或覆盖受保护合同/交付文档。",
|
|
224
|
+
"The command would delete, move, truncate, or overwrite a protected contract or retained delivery document.",
|
|
225
|
+
)
|
|
226
|
+
|
|
227
|
+
if not command:
|
|
228
|
+
return
|
|
229
|
+
if re.search(r"(?:^|[;&|]\s*)(?:pkill|killall)\b", command):
|
|
230
|
+
block(
|
|
231
|
+
"禁止按进程名清理;必须通过 owner helper 校验任务进程身份。",
|
|
232
|
+
"Process-name cleanup is forbidden; verify task ownership through the owner helper.",
|
|
233
|
+
)
|
|
234
|
+
if (
|
|
235
|
+
re.search(r"(?:^|[;&|]\s*)kill\s+(?!-0\b)", command)
|
|
236
|
+
and "superflow_signal_owner" not in command
|
|
237
|
+
):
|
|
238
|
+
block(
|
|
239
|
+
"禁止使用裸 kill;必须调用 superflow_signal_owner。",
|
|
240
|
+
"Bare kill is forbidden; call superflow_signal_owner.",
|
|
241
|
+
)
|
|
242
|
+
if (
|
|
243
|
+
re.search(
|
|
244
|
+
r"\bdocker\s+(?:(?:container|network|volume)\s+)?rm\b",
|
|
245
|
+
command,
|
|
246
|
+
re.IGNORECASE,
|
|
247
|
+
)
|
|
248
|
+
and "superflow_docker_remove_verified" not in command
|
|
249
|
+
):
|
|
250
|
+
block(
|
|
251
|
+
"禁止直接删除 Docker 资源;必须调用 owner helper 核验标签。",
|
|
252
|
+
"Direct Docker deletion is forbidden; verify labels through the owner helper.",
|
|
253
|
+
)
|
|
254
|
+
if re.search(r"(?:^|[;&|]\s*)rm\s+-[^;&|]*r[^;&|]*f", command):
|
|
255
|
+
if (
|
|
256
|
+
"superflow_runtime_remove_verified" not in command
|
|
257
|
+
and not safe_workspace_rm(command, root)
|
|
258
|
+
):
|
|
259
|
+
block(
|
|
260
|
+
"rm -rf 仅允许清理仓库构建缓存;运行目录必须通过 owner helper 删除。",
|
|
261
|
+
"rm -rf may only clean repository build caches; runtime directories require the owner helper.",
|
|
262
|
+
)
|
|
263
|
+
|
|
264
|
+
|
|
265
|
+
def main() -> None:
|
|
266
|
+
role = os.environ.get("SUPERFLOW_MANAGED_ROLE", "")
|
|
267
|
+
if not role:
|
|
268
|
+
return
|
|
269
|
+
if role not in {"supervisor", "executor"}:
|
|
270
|
+
block(
|
|
271
|
+
"检测到非法托管角色,按失败关闭。",
|
|
272
|
+
"Invalid managed role; failing closed.",
|
|
273
|
+
)
|
|
274
|
+
try:
|
|
275
|
+
payload = json.load(sys.stdin)
|
|
276
|
+
except (ValueError, TypeError):
|
|
277
|
+
block(
|
|
278
|
+
"Hook 输入不是有效 JSON,按失败关闭。",
|
|
279
|
+
"Hook input is not valid JSON; failing closed.",
|
|
280
|
+
)
|
|
281
|
+
if not isinstance(payload, dict):
|
|
282
|
+
block(
|
|
283
|
+
"Hook 输入不是 JSON 对象,按失败关闭。",
|
|
284
|
+
"Hook input is not a JSON object; failing closed.",
|
|
285
|
+
)
|
|
286
|
+
|
|
287
|
+
tool_name = str(payload.get("tool_name") or payload.get("tool") or "")
|
|
288
|
+
file_path = tool_field(payload, "file_path", "path", "file")
|
|
289
|
+
command = tool_field(payload, "command", "cmd").replace("\n", " ")
|
|
290
|
+
root = Path(
|
|
291
|
+
os.environ.get("SUPERFLOW_MANAGED_PROJECT_ROOT", os.getcwd())
|
|
292
|
+
).resolve(strict=False)
|
|
293
|
+
immutable, retained = protected_entries()
|
|
294
|
+
|
|
295
|
+
if role == "supervisor":
|
|
296
|
+
guard_supervisor(tool_name, command)
|
|
297
|
+
else:
|
|
298
|
+
guard_executor(
|
|
299
|
+
tool_name,
|
|
300
|
+
file_path,
|
|
301
|
+
command,
|
|
302
|
+
root,
|
|
303
|
+
immutable,
|
|
304
|
+
retained,
|
|
305
|
+
)
|
|
306
|
+
|
|
307
|
+
|
|
308
|
+
if __name__ == "__main__":
|
|
309
|
+
main()
|