@chenmk/superflow 0.5.11 → 0.5.13

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 (101) hide show
  1. package/README.md +26 -0
  2. package/README.zh-CN.md +8 -0
  3. package/assets/manifest.json +33 -7
  4. package/assets/review-rules.json +24 -0
  5. package/assets/scripts/superflow-archive-command-hook.sh +4 -0
  6. package/assets/scripts/superflow-contract-hooks.sh +4 -0
  7. package/assets/scripts/superflow-delivery-check.sh +4 -0
  8. package/assets/scripts/superflow-dependency-update-hook.sh +29 -0
  9. package/assets/scripts/superflow-doc-placement-hook.py +38 -0
  10. package/assets/scripts/superflow-enforce-hook.sh +4 -0
  11. package/assets/scripts/superflow-hook-guard.sh +4 -0
  12. package/assets/scripts/superflow-integration-evidence-hook.sh +4 -0
  13. package/assets/scripts/superflow-java-build-hook.py +178 -0
  14. package/assets/scripts/superflow-java-stop-hook.sh +3 -0
  15. package/assets/scripts/superflow-loop-end-hook.sh +3 -0
  16. package/assets/scripts/superflow-loop-failure-hook.sh +3 -0
  17. package/assets/scripts/superflow-loop-observer.py +149 -0
  18. package/assets/scripts/superflow-managed-work-guard.sh +4 -0
  19. package/assets/scripts/superflow-sql-sync-hook.py +151 -3
  20. package/assets/skill-registry.json +229 -0
  21. package/assets/skills/openspec-propose/SKILL.md +14 -0
  22. package/assets/skills/superflow-clarify/SKILL.md +23 -1
  23. package/assets/skills/superflow-design/SKILL.md +6 -1
  24. package/assets/skills/superflow-docs/SKILL.md +12 -0
  25. package/assets/skills/superflow-implement/SKILL.md +8 -0
  26. package/assets/skills/superflow-pipeline/SKILL.md +28 -1
  27. package/assets/skills/superflow-pipeline/references/superpower-technical-design-template.md +17 -0
  28. package/assets/skills/superflow-quick-dev/SKILL.md +44 -0
  29. package/assets/skills/superflow-verify/SKILL.md +41 -0
  30. package/assets/skills-en/openspec-propose/SKILL.md +14 -0
  31. package/assets/skills-en/superflow-clarify/SKILL.md +23 -1
  32. package/assets/skills-en/superflow-design/SKILL.md +6 -1
  33. package/assets/skills-en/superflow-docs/SKILL.md +12 -0
  34. package/assets/skills-en/superflow-implement/SKILL.md +9 -0
  35. package/assets/skills-en/superflow-pipeline/SKILL.md +30 -0
  36. package/assets/skills-en/superflow-quick-dev/SKILL.md +43 -0
  37. package/assets/skills-en/superflow-verify/SKILL.md +46 -0
  38. package/dist/app/cli.js +98 -0
  39. package/dist/app/cli.js.map +1 -1
  40. package/dist/app/commands/check.js +7 -3
  41. package/dist/app/commands/check.js.map +1 -1
  42. package/dist/app/commands/cleanup-audit.js +13 -0
  43. package/dist/app/commands/cleanup-audit.js.map +1 -0
  44. package/dist/app/commands/context-audit.js +43 -0
  45. package/dist/app/commands/context-audit.js.map +1 -0
  46. package/dist/app/commands/docs-audit.js +11 -0
  47. package/dist/app/commands/docs-audit.js.map +1 -0
  48. package/dist/app/commands/doctor.js +10 -0
  49. package/dist/app/commands/doctor.js.map +1 -1
  50. package/dist/app/commands/hook-migrate.js +25 -0
  51. package/dist/app/commands/hook-migrate.js.map +1 -0
  52. package/dist/app/commands/init.js +25 -18
  53. package/dist/app/commands/init.js.map +1 -1
  54. package/dist/app/commands/map-audit.js +10 -0
  55. package/dist/app/commands/map-audit.js.map +1 -0
  56. package/dist/app/commands/pipeline.js +13 -1
  57. package/dist/app/commands/pipeline.js.map +1 -1
  58. package/dist/app/commands/quick.js +136 -0
  59. package/dist/app/commands/quick.js.map +1 -0
  60. package/dist/app/commands/review-coverage.js +84 -0
  61. package/dist/app/commands/review-coverage.js.map +1 -0
  62. package/dist/app/commands/skill-audit.js +36 -0
  63. package/dist/app/commands/skill-audit.js.map +1 -0
  64. package/dist/app/commands/status.js +108 -7
  65. package/dist/app/commands/status.js.map +1 -1
  66. package/dist/app/commands/uninstall.js +119 -25
  67. package/dist/app/commands/uninstall.js.map +1 -1
  68. package/dist/app/commands/update.js +62 -25
  69. package/dist/app/commands/update.js.map +1 -1
  70. package/dist/domains/cleanup-audit.js +38 -0
  71. package/dist/domains/cleanup-audit.js.map +1 -0
  72. package/dist/domains/config/cli-help.js +14 -0
  73. package/dist/domains/config/cli-help.js.map +1 -1
  74. package/dist/domains/config/context.js +13 -10
  75. package/dist/domains/config/context.js.map +1 -1
  76. package/dist/domains/context-audit.js +39 -0
  77. package/dist/domains/context-audit.js.map +1 -0
  78. package/dist/domains/deps.js +18 -8
  79. package/dist/domains/deps.js.map +1 -1
  80. package/dist/domains/docs-audit.js +34 -0
  81. package/dist/domains/docs-audit.js.map +1 -0
  82. package/dist/domains/hook-migration.js +127 -0
  83. package/dist/domains/hook-migration.js.map +1 -0
  84. package/dist/domains/hook.js +76 -2
  85. package/dist/domains/hook.js.map +1 -1
  86. package/dist/domains/managed-work/control.js +56 -2
  87. package/dist/domains/managed-work/control.js.map +1 -1
  88. package/dist/domains/map-audit.js +23 -0
  89. package/dist/domains/map-audit.js.map +1 -0
  90. package/dist/domains/quick-dev/gate.js +91 -0
  91. package/dist/domains/quick-dev/gate.js.map +1 -0
  92. package/dist/domains/review/coverage.js +119 -0
  93. package/dist/domains/review/coverage.js.map +1 -0
  94. package/dist/domains/review/report.js +62 -0
  95. package/dist/domains/review/report.js.map +1 -0
  96. package/dist/domains/skill/registry.js +87 -0
  97. package/dist/domains/skill/registry.js.map +1 -0
  98. package/dist/domains/state.js +64 -0
  99. package/dist/domains/state.js.map +1 -1
  100. package/package.json +1 -1
  101. package/scripts/check-design-principles.mjs +4 -0
package/README.md CHANGED
@@ -46,6 +46,31 @@ real verified delivery. SuperBridge Flow makes that gap explicit:
46
46
  The CLI installs skills, hooks or command aliases, rules, scripts, handoff
47
47
  state, and dependency guards for Claude Code and Codex.
48
48
 
49
+ In OpenSpec/SDD Java repositories, hooks also block unsafe MyBatis `${}`
50
+ interpolation, Git hook bypasses, and staged secrets. Java/build edits are
51
+ accumulated and compiled once at Stop for affected Maven/Gradle modules. Missing
52
+ build tools or compilation failures are reported as failed verification.
53
+ Native session observation hooks warn once on the sixth edit to one file in
54
+ ten minutes or the third consecutive failure of one tool in five minutes. A
55
+ successful call resets that tool's failure count, and SessionEnd clears the
56
+ observation state. These warnings do not block tools or alter managed budgets.
57
+ `superflow context-audit` provides a read-only check for `CONTEXT.md`
58
+ terminology and required ADR fields. A missing ADR is advisory and does not add
59
+ an approval gate.
60
+ `superflow docs-audit` reports Markdown age, dead code anchors, and inbound
61
+ links. New Markdown placement reminders always exit successfully and never move,
62
+ merge, or delete files.
63
+ `superflow map-audit` compares full, task Top-K, and directory/symbol skeleton
64
+ contexts from the existing Understand Anything graph without creating another
65
+ persistent map.
66
+ `superflow cleanup-audit` reports duplicate names, complexity clues, and TODO
67
+ hotspots with evidence and a route, without modifying source files.
68
+ `superflow hook-migrate --path <project> --json` audits project Hooks read-only;
69
+ `--apply` backs up the config and removes only known legacy ake Hooks with
70
+ provenance or managed Superflow/SDD entries. Custom Hooks remain. Project init
71
+ and update use the same migration; the global prompt Hook also attempts one
72
+ safe migration per project session and asks for a restart if definitions changed.
73
+
49
74
  ## Highlights
50
75
 
51
76
  0.5.10 strengthens document delivery: requirement × real-entry coverage (including
@@ -330,6 +355,7 @@ best-effort and does not block initialization.
330
355
  | `superflow --language en --help` | Show English CLI help |
331
356
  | `superflow scan --language en` | Regenerate project context templates in English |
332
357
  | `superflow pipeline` | Check pipeline skill deployment |
358
+ | `superflow quick "<task>" --path <file> --seam <existing-file>` | Gate a small code task before Quick Spec approval and quick managed execution |
333
359
  | `superflow pipeline "<task>" --managed --project <path>` | Run short-session execution with direct review by the current host agent |
334
360
  | `--acceptance-contract <file>` | Required frozen acceptance JSON for prompt/SDD starts |
335
361
  | `superflow docs [change]` | Run the docs gate and check the phase skill |
package/README.zh-CN.md CHANGED
@@ -34,6 +34,13 @@ Superpowers 编排成一个有状态的研发流程:前者负责需求、合
34
34
  后者负责源码级设计、TDD 顺序、实现分工、review 和真实验证。
35
35
 
36
36
  CLI 会把 SuperBridge Flow 技能、配套 hook/command 脚本整合为单一 npm 包,
37
+ 启用 OpenSpec/SDD 的 Java 仓库还会在写码时拦截危险 Mapper `${}` 插值、Git Hook 绕过和暂存密钥;Java/构建文件编辑只记录路径,在 Stop 时按受影响 Maven/Gradle 模块批量编译一次。缺少构建工具或编译失败会明确报告,不能当作验证通过。
38
+ 原生会话的观察 Hook 会在同文件十分钟内第六次编辑、同工具五分钟内第三次连续失败时各提醒一次;成功调用重置该工具失败计数,SessionEnd 清理观察状态,提醒不阻断工具或改变托管预算。
39
+ `superflow context-audit` 可只读检查 `CONTEXT.md` 术语和 ADR 必备字段:术语只写业务语言,ADR 只用于难逆转且有备选方案的决定;缺少 ADR 仅提醒,不增加审批门禁。
40
+ `superflow docs-audit` 可只读盘点 Markdown 的年龄、失效代码锚点和入链数;新建 Markdown 的落位提醒恒退 0,不自动移动、合并或删除。
41
+ `superflow map-audit` 只读比较 Understand Anything 的完整图谱、任务 Top-K 和目录/符号骨架,不创建第二套持久地图。
42
+ `superflow cleanup-audit` 只读输出重复文件名、复杂度线索和 TODO 热点,带证据与执行路由,不修改源码。
43
+ 项目旧 Hook 可用 `superflow hook-migrate --path <项目> --json` 只读审计,追加 `--apply` 后只迁移有 ake 来源标记的已知旧 Hook 或 Superflow/SDD 管理项;先备份,保留自定义 Hook。项目级 init/update 自动执行同一迁移;全局会话 Hook 首次使用项目时也会尝试一次安全迁移,若配置已改变须重启当前会话。
37
44
  自动部署到:
38
45
 
39
46
  - Claude Code:`~/.claude/skills/`、`~/.claude/scripts/`,并注册 `~/.claude/settings.json` hook
@@ -271,6 +278,7 @@ MCP 是主 Agent 与本地托管状态机的通信通道,不会把 Superflow
271
278
  | `superflow design [change]` | 校验 SuperBridge Flow design 阶段技能部署 |
272
279
  | `superflow implement [change]` | 生成当前 Coding Ready 凭证后进入实现阶段 |
273
280
  | `superflow pipeline` | 校验 SuperBridge Flow pipeline 阶段技能部署 |
281
+ | `superflow quick "<任务>" --path <文件> --seam <既有文件>` | 对小型代码任务做窄门判断;批准 Quick Spec 后复用 quick 托管档位 |
274
282
  | `superflow pipeline "<任务>" --managed --project <目录>` | 执行 Agent 短会话开发,当前 Host Agent 直接评审 |
275
283
  | `--acceptance-contract <文件>` | Prompt/SDD 启动必填的冻结验收合同 JSON |
276
284
  | `superflow check <change> --level files\|docs\|coding-ready` | 分级检查文件、文档交付或可直接编码就绪度 |
@@ -1,6 +1,9 @@
1
1
  {
2
2
  "version": "0.1.2",
3
- "agents": ["claude", "codex"],
3
+ "agents": [
4
+ "claude",
5
+ "codex"
6
+ ],
4
7
  "skills": [
5
8
  "openspec-apply-change",
6
9
  "openspec-archive-change",
@@ -16,13 +19,22 @@
16
19
  "superflow-requirement-review",
17
20
  "superflow-table-impact-analysis",
18
21
  "superflow-tweak",
19
- "superflow-verify"
22
+ "superflow-verify",
23
+ "superflow-quick-dev"
24
+ ],
25
+ "rules": [
26
+ "superflow-phase-guard.md"
20
27
  ],
21
- "rules": ["superflow-phase-guard.md"],
22
28
  "scripts": [
23
29
  "superflow-verify-integration.sh",
24
30
  "superflow-delivery-check.sh",
25
31
  "superflow-sql-sync-hook.py",
32
+ "superflow-java-build-hook.py",
33
+ "superflow-java-stop-hook.sh",
34
+ "superflow-loop-observer.py",
35
+ "superflow-loop-failure-hook.sh",
36
+ "superflow-loop-end-hook.sh",
37
+ "superflow-doc-placement-hook.py",
26
38
  "superflow-contract-hooks.sh",
27
39
  "superflow-enforce-hook.sh",
28
40
  "superflow-hook-guard.sh",
@@ -43,8 +55,12 @@
43
55
  "sync-settings-json.py"
44
56
  ],
45
57
  "agentScripts": {
46
- "claude": ["claude-auto-backup-hook.sh"],
47
- "codex": ["codex-auto-backup-hook.sh"]
58
+ "claude": [
59
+ "claude-auto-backup-hook.sh"
60
+ ],
61
+ "codex": [
62
+ "codex-auto-backup-hook.sh"
63
+ ]
48
64
  },
49
65
  "hooks": [
50
66
  "superflow-enforce-hook.sh",
@@ -52,13 +68,23 @@
52
68
  "superflow-contract-hooks.sh",
53
69
  "superflow-dependency-update-hook.sh",
54
70
  "superflow-sql-sync-hook.py",
71
+ "superflow-java-build-hook.py",
72
+ "superflow-java-stop-hook.sh",
73
+ "superflow-loop-observer.py",
74
+ "superflow-loop-failure-hook.sh",
75
+ "superflow-loop-end-hook.sh",
76
+ "superflow-doc-placement-hook.py",
55
77
  "superflow-delivery-check.sh",
56
78
  "superflow-integration-evidence-hook.sh",
57
79
  "superflow-archive-command-hook.sh",
58
80
  "superflow-managed-work-guard.sh"
59
81
  ],
60
82
  "agentHooks": {
61
- "claude": ["claude-auto-backup-hook.sh"],
62
- "codex": ["codex-auto-backup-hook.sh"]
83
+ "claude": [
84
+ "claude-auto-backup-hook.sh"
85
+ ],
86
+ "codex": [
87
+ "codex-auto-backup-hook.sh"
88
+ ]
63
89
  }
64
90
  }
@@ -0,0 +1,24 @@
1
+ {
2
+ "rules": [
3
+ {
4
+ "path": "**/*Mapper.xml",
5
+ "rule": "Verify parameterized MyBatis bindings and dynamic identifier allowlists",
6
+ "source": "system"
7
+ },
8
+ {
9
+ "path": "**/*Controller.java",
10
+ "rule": "Review authentication, validation, and controller-to-service boundaries",
11
+ "source": "system"
12
+ },
13
+ {
14
+ "path": "**/*Service*.java",
15
+ "rule": "Review transactions, batch access, N+1 queries, and external-call boundaries",
16
+ "source": "system"
17
+ },
18
+ {
19
+ "path": "**/*.java",
20
+ "rule": "Review correctness, exceptions, sensitive logging, and concurrency",
21
+ "source": "system"
22
+ }
23
+ ]
24
+ }
@@ -1,6 +1,10 @@
1
1
  #!/bin/bash
2
2
  # Blocks direct OpenSpec/Superflow archive commands that bypass lifecycle gates.
3
3
 
4
+ # Defensive short-circuit: run SDD gates only in SDD projects
5
+ # (openspec/, .sdd/, .sdd-enforced); zero-cost exit elsewhere.
6
+ [ -d openspec ] || [ -d .sdd ] || [ -f .sdd-enforced ] || exit 0
7
+
4
8
  set -u
5
9
 
6
10
  INPUT=$(cat)
@@ -9,6 +9,10 @@
9
9
  # 环境变量:
10
10
  # SDD_CONTRACT_HOOK_DEBUG=1 开启调试输出
11
11
 
12
+ # Defensive short-circuit: run SDD gates only in SDD projects
13
+ # (openspec/, .sdd/, .sdd-enforced); zero-cost exit elsewhere.
14
+ [ -d openspec ] || [ -d .sdd ] || [ -f .sdd-enforced ] || exit 0
15
+
12
16
  INPUT=$(cat)
13
17
 
14
18
  # 提取 file_path
@@ -3,6 +3,10 @@
3
3
  # Blocks implementation commits that forget to update the matching PXX
4
4
  # delivery documents and test evidence.
5
5
 
6
+ # Defensive short-circuit: run SDD gates only in SDD projects
7
+ # (openspec/, .sdd/, .sdd-enforced); zero-cost exit elsewhere.
8
+ [ -d openspec ] || [ -d .sdd ] || [ -f .sdd-enforced ] || exit 0
9
+
6
10
  set -u
7
11
 
8
12
  MODE="${1:-}"
@@ -68,6 +68,35 @@ GLOBAL_STAMP="$STATE_DIR/dependency-update-last.stamp"
68
68
  LOG_FILE="$STATE_DIR/dependency-update.log"
69
69
  LOCK_DIR="$STATE_DIR/dependency-update.lock"
70
70
 
71
+ # One safe project migration per session. The CLI only removes known managed
72
+ # legacy entries after a provenance check, backs up the project config, and
73
+ # preserves custom hooks. A running Host may need a restart to reload hooks.
74
+ PROJECT_STAMP="$STATE_DIR/project-hooks-$SESSION_HASH.stamp"
75
+ if [ ! -f "$PROJECT_STAMP" ] && command -v superflow >/dev/null 2>&1; then
76
+ HOST_AGENT=""
77
+ case "$0" in
78
+ */.codex/*) HOST_AGENT="codex" ;;
79
+ */.claude/*) HOST_AGENT="claude" ;;
80
+ esac
81
+ if [ -n "$HOST_AGENT" ]; then
82
+ MIGRATION_JSON="$(superflow hook-migrate --path "$PWD" --agent "$HOST_AGENT" --apply --json 2>/dev/null || true)"
83
+ if [ -n "$MIGRATION_JSON" ]; then
84
+ MIGRATED="$(printf '%s' "$MIGRATION_JSON" | python3 -c '
85
+ import json, sys
86
+ try:
87
+ report = json.load(sys.stdin)
88
+ print("1" if any(item.get("changed") for item in report.get("hosts", [])) else "0")
89
+ except Exception:
90
+ print("0")
91
+ ' 2>/dev/null)"
92
+ if [ "$MIGRATED" = "1" ]; then
93
+ printf '[Superflow] 已备份并迁移当前项目旧 Hook;请重启本会话以加载新配置。\n' >&2
94
+ fi
95
+ printf '%s\n' "$(date +%s)" > "$PROJECT_STAMP" 2>/dev/null || true
96
+ fi
97
+ fi
98
+ fi
99
+
71
100
  now_epoch() {
72
101
  date +%s
73
102
  }
@@ -0,0 +1,38 @@
1
+ #!/usr/bin/env python3
2
+ """Advisory Markdown placement reminder; always exits successfully."""
3
+ import json
4
+ import os
5
+ import sys
6
+ from pathlib import Path
7
+
8
+ ROOT = {"README.md", "AGENTS.md", "CLAUDE.md", "CONTEXT.md", "CHANGELOG.md"}
9
+
10
+ def main():
11
+ try:
12
+ data = json.loads(sys.stdin.read() or "{}")
13
+ tool = data.get("tool_input") or {}
14
+ raw = tool.get("file_path") or ""
15
+ if not raw.lower().endswith(".md"):
16
+ return 0
17
+ cwd = Path(data.get("cwd") or os.getcwd()).resolve()
18
+ path = Path(raw).expanduser()
19
+ path = (path if path.is_absolute() else cwd / path).resolve()
20
+ if path.exists():
21
+ return 0
22
+ try:
23
+ relative = path.relative_to(cwd).as_posix()
24
+ except ValueError:
25
+ return 0
26
+ parts = relative.split("/")
27
+ if len(parts) == 1 and relative not in ROOT:
28
+ print(f"[Superflow 文档落位] 新建 {relative} 不在仓根白名单;"
29
+ "任务产物建议放入 .ake/tasks/<日期>-<slug>/。", file=sys.stderr)
30
+ elif len(parts) == 2 and parts[0].lower() == "docs":
31
+ print(f"[Superflow 文档落位] 新建 {relative} 位于 docs/ 顶层;"
32
+ "请放入 docs/adr/、docs/howto/ 等主题子目录。", file=sys.stderr)
33
+ except (OSError, ValueError, TypeError, json.JSONDecodeError):
34
+ pass
35
+ return 0
36
+
37
+ if __name__ == "__main__":
38
+ sys.exit(main())
@@ -11,6 +11,10 @@
11
11
  #
12
12
  # 退出码:0=允许 2=拦截
13
13
 
14
+ # Defensive short-circuit: run SDD gates only in SDD projects
15
+ # (openspec/, .sdd/, .sdd-enforced); zero-cost exit elsewhere.
16
+ [ -d openspec ] || [ -d .sdd ] || [ -f .sdd-enforced ] || exit 0
17
+
14
18
  INPUT=$(cat)
15
19
 
16
20
  FILE_PATH=$(echo "$INPUT" | python3 -c "
@@ -2,6 +2,10 @@
2
2
  # SDD phase-aware hook guard.
3
3
  # Blocks writes that conflict with .sdd/state.yaml when .sdd-enforced is active.
4
4
 
5
+ # Defensive short-circuit: run SDD gates only in SDD projects
6
+ # (openspec/, .sdd/, .sdd-enforced); zero-cost exit elsewhere.
7
+ [ -d openspec ] || [ -d .sdd ] || [ -f .sdd-enforced ] || exit 0
8
+
5
9
  set -u
6
10
 
7
11
  INPUT=""
@@ -3,6 +3,10 @@
3
3
  # When .sdd-enforced is active, block git commit for runtime code changes unless
4
4
  # staged test-report evidence passes superflow-verify-integration.sh.
5
5
 
6
+ # Defensive short-circuit: run SDD gates only in SDD projects
7
+ # (openspec/, .sdd/, .sdd-enforced); zero-cost exit elsewhere.
8
+ [ -d openspec ] || [ -d .sdd ] || [ -f .sdd-enforced ] || exit 0
9
+
6
10
  INPUT=$(cat)
7
11
 
8
12
  COMMAND=$(echo "$INPUT" | python3 -c "
@@ -0,0 +1,178 @@
1
+ #!/usr/bin/env python3
2
+ """Record Java edits and compile affected Maven/Gradle modules once at Stop."""
3
+
4
+ import json
5
+ import os
6
+ import re
7
+ import shutil
8
+ import subprocess
9
+ import sys
10
+ import tempfile
11
+ from pathlib import Path
12
+
13
+
14
+ BUILD_FILES = {"pom.xml", "build.gradle", "build.gradle.kts",
15
+ "settings.gradle", "settings.gradle.kts", "gradle.properties"}
16
+
17
+
18
+ def project_root():
19
+ result = subprocess.run(["git", "rev-parse", "--show-toplevel"],
20
+ capture_output=True, text=True, check=False)
21
+ return Path(result.stdout.strip()).resolve() if result.returncode == 0 else None
22
+
23
+
24
+ def state_file(root, payload):
25
+ session = str(payload.get("session_id") or os.environ.get("CODEX_SESSION_ID")
26
+ or os.environ.get("CLAUDE_CODE_SESSION_ID") or "default")
27
+ safe = "".join(char if char.isalnum() or char in "-_" else "_" for char in session)
28
+ import hashlib
29
+ digest = hashlib.sha256(str(root).encode()).hexdigest()[:12]
30
+ return Path(tempfile.gettempdir()) / f"superflow-java-edits-{digest}-{safe}.txt"
31
+
32
+
33
+ def in_root(root, file_path):
34
+ try:
35
+ path = Path(file_path).expanduser()
36
+ path = (path if path.is_absolute() else root / path).resolve()
37
+ path.relative_to(root)
38
+ return path
39
+ except (ValueError, OSError):
40
+ return None
41
+
42
+
43
+ def record(root, payload):
44
+ tool_input = payload.get("tool_input") or {}
45
+ targets = [tool_input.get("file_path")]
46
+ patch = tool_input.get("patch") or ""
47
+ targets += re.findall(r"^\*\*\* (?:Add|Update|Delete) File: (.+)$",
48
+ patch, re.M)
49
+ paths = [in_root(root, target) for target in targets if target]
50
+ relevant = {path for path in paths if path and
51
+ (path.suffix == ".java" or path.name in BUILD_FILES)}
52
+ if relevant:
53
+ with state_file(root, payload).open("a", encoding="utf-8") as state:
54
+ state.write("\n".join(str(path) for path in sorted(relevant)) + "\n")
55
+ return 0
56
+
57
+
58
+ def build_root(root, file_path):
59
+ directory = file_path.parent
60
+ while directory == root or root in directory.parents:
61
+ if any((directory / name).is_file()
62
+ for name in ("pom.xml", "build.gradle", "build.gradle.kts")):
63
+ return directory
64
+ if directory == root:
65
+ break
66
+ directory = directory.parent
67
+ return None
68
+
69
+
70
+ def maven_reactor(root, module):
71
+ current = module.parent
72
+ while current == root or root in current.parents:
73
+ pom = current / "pom.xml"
74
+ if pom.is_file():
75
+ relative = module.relative_to(current).as_posix()
76
+ pattern = r"<module>\s*" + re.escape(relative) + r"\s*</module>"
77
+ if re.search(pattern, pom.read_text(encoding="utf-8")):
78
+ return current, relative
79
+ if current == root:
80
+ break
81
+ current = current.parent
82
+ return module, None
83
+
84
+
85
+ def gradle_reactor(root, module):
86
+ current = module.parent
87
+ while current == root or root in current.parents:
88
+ settings = current / "settings.gradle"
89
+ kotlin = current / "settings.gradle.kts"
90
+ config = settings if settings.is_file() else kotlin
91
+ if config.is_file():
92
+ relative = module.relative_to(current).as_posix().replace("/", ":")
93
+ if f":{relative}" in config.read_text(encoding="utf-8"):
94
+ return current, f":{relative}:compileJava"
95
+ if current == root:
96
+ break
97
+ current = current.parent
98
+ return module, "compileJava"
99
+
100
+
101
+ def compile_command(root, modules):
102
+ module = modules[0]
103
+ if (module / "pom.xml").is_file():
104
+ reactor, _ = maven_reactor(root, module)
105
+ executable = reactor / "mvnw"
106
+ command = str(executable) if executable.is_file() else shutil.which("mvn")
107
+ if not command:
108
+ return None, "未找到 mvn/mvnw,无法确认编译结果"
109
+ args = [command, "-q", "-f", str(reactor / "pom.xml")]
110
+ relatives = [maven_reactor(root, item)[1] for item in modules]
111
+ if all(relatives):
112
+ args += ["-pl", ",".join(sorted(relatives)), "-am"]
113
+ return args + ["compile"], None
114
+ reactor, _ = gradle_reactor(root, module)
115
+ executable = reactor / "gradlew"
116
+ command = str(executable) if executable.is_file() else shutil.which("gradle")
117
+ if not command:
118
+ return None, "未找到 gradle/gradlew,无法确认编译结果"
119
+ tasks = sorted({gradle_reactor(root, item)[1] for item in modules})
120
+ return [command, "-q", "-p", str(reactor), *tasks], None
121
+
122
+
123
+ def stop(root, payload):
124
+ state = state_file(root, payload)
125
+ if not state.is_file():
126
+ return 0
127
+ files = {in_root(root, line.strip()) for line in state.read_text().splitlines()}
128
+ modules = {build_root(root, file) for file in files if file}
129
+ modules.discard(None)
130
+ if not modules:
131
+ state.unlink(missing_ok=True)
132
+ return 0
133
+ groups = {}
134
+ for module in modules:
135
+ if (module / "pom.xml").is_file():
136
+ reactor, _ = maven_reactor(root, module)
137
+ else:
138
+ reactor, _ = gradle_reactor(root, module)
139
+ groups.setdefault(reactor, []).append(module)
140
+ for reactor, members in sorted(groups.items()):
141
+ command, error = compile_command(root, sorted(members))
142
+ if error:
143
+ print(f"[Superflow Java 编译] {reactor}: {error}", file=sys.stderr)
144
+ return 2
145
+ try:
146
+ result = subprocess.run(command, cwd=root, capture_output=True,
147
+ text=True, timeout=120, check=False)
148
+ except (OSError, subprocess.TimeoutExpired) as exc:
149
+ print(f"[Superflow Java 编译] {reactor}: {exc}", file=sys.stderr)
150
+ return 2
151
+ if result.returncode:
152
+ detail = (result.stdout + result.stderr).strip()[-3000:]
153
+ print(f"[Superflow Java 编译] {reactor} 编译失败:\n{detail}",
154
+ file=sys.stderr)
155
+ return 2
156
+ state.unlink(missing_ok=True)
157
+ return 0
158
+
159
+
160
+ def main():
161
+ try:
162
+ payload = json.loads(sys.stdin.read() or "{}")
163
+ except json.JSONDecodeError:
164
+ return 0
165
+ root = project_root()
166
+ if not root or not ((root / "openspec").exists() or
167
+ (root / ".sdd").exists() or
168
+ (root / ".sdd-enforced").exists()):
169
+ return 0
170
+ try:
171
+ return stop(root, payload) if "--stop" in sys.argv else record(root, payload)
172
+ except OSError as exc:
173
+ print(f"[Superflow Java 编译] Hook 内部错误:{exc}", file=sys.stderr)
174
+ return 0
175
+
176
+
177
+ if __name__ == "__main__":
178
+ sys.exit(main())
@@ -0,0 +1,3 @@
1
+ #!/bin/sh
2
+ SCRIPT_DIR=$(CDPATH= cd -- "$(dirname -- "$0")" && pwd)
3
+ exec python3 "$SCRIPT_DIR/superflow-java-build-hook.py" --stop
@@ -0,0 +1,3 @@
1
+ #!/bin/sh
2
+ SCRIPT_DIR=$(CDPATH= cd -- "$(dirname -- "$0")" && pwd)
3
+ exec python3 "$SCRIPT_DIR/superflow-loop-observer.py" --end
@@ -0,0 +1,3 @@
1
+ #!/bin/sh
2
+ SCRIPT_DIR=$(CDPATH= cd -- "$(dirname -- "$0")" && pwd)
3
+ exec python3 "$SCRIPT_DIR/superflow-loop-observer.py" --failure
@@ -0,0 +1,149 @@
1
+ #!/usr/bin/env python3
2
+ """Fail-open session observation for repeated edits and tool failures."""
3
+
4
+ import hashlib
5
+ import json
6
+ import os
7
+ import re
8
+ import subprocess
9
+ import sys
10
+ import tempfile
11
+ import time
12
+ from pathlib import Path
13
+
14
+
15
+ EDIT_WINDOW = 600
16
+ FAILURE_WINDOW = 300
17
+ EDIT_LIMIT = 5
18
+ FAILURE_LIMIT = 3
19
+
20
+
21
+ def project_root():
22
+ result = subprocess.run(["git", "rev-parse", "--show-toplevel"],
23
+ capture_output=True, text=True, check=False)
24
+ return Path(result.stdout.strip()).resolve() if result.returncode == 0 else None
25
+
26
+
27
+ def state_path(root, payload):
28
+ session = (payload.get("session_id") or
29
+ os.environ.get("CODEX_SESSION_ID") or
30
+ os.environ.get("CLAUDE_CODE_SESSION_ID"))
31
+ if not session:
32
+ return None
33
+ identity = f"{root}:{session}"
34
+ digest = hashlib.sha256(identity.encode()).hexdigest()[:24]
35
+ return Path(tempfile.gettempdir()) / f"superflow-loop-{digest}.json"
36
+
37
+
38
+ def load_state(file):
39
+ if not file.is_file():
40
+ return {"edits": {}, "failures": {}}
41
+ try:
42
+ data = json.loads(file.read_text(encoding="utf-8"))
43
+ if isinstance(data, dict):
44
+ return {"edits": data.get("edits", {}),
45
+ "failures": data.get("failures", {})}
46
+ except (OSError, ValueError):
47
+ pass
48
+ return {"edits": {}, "failures": {}}
49
+
50
+
51
+ def save_state(file, state):
52
+ temporary = file.with_suffix(".tmp")
53
+ temporary.write_text(json.dumps(state, ensure_ascii=False), encoding="utf-8")
54
+ temporary.replace(file)
55
+
56
+
57
+ def recent(values, now, window):
58
+ return [stamp for stamp in values if isinstance(stamp, (int, float))
59
+ and 0 <= now - stamp < window]
60
+
61
+
62
+ def edited_paths(root, payload):
63
+ tool_input = payload.get("tool_input") or {}
64
+ paths = [tool_input.get("file_path")]
65
+ patch = tool_input.get("patch") or ""
66
+ paths += re.findall(r"^\*\*\* (?:Add|Update|Delete) File: (.+)$", patch, re.M)
67
+ result = set()
68
+ for raw in paths:
69
+ if not isinstance(raw, str) or not raw:
70
+ continue
71
+ candidate = Path(raw).expanduser()
72
+ candidate = (candidate if candidate.is_absolute() else root / candidate).resolve()
73
+ try:
74
+ result.add(candidate.relative_to(root).as_posix())
75
+ except ValueError:
76
+ continue
77
+ return result
78
+
79
+
80
+ def observe_success(root, payload, state, now):
81
+ tool = str(payload.get("tool_name") or "unknown")
82
+ state["failures"].pop(tool, None)
83
+ if not re.search(r"(?:^|[._])(Edit|Write|MultiEdit|apply_patch)$", tool, re.I):
84
+ return
85
+ for path in edited_paths(root, payload):
86
+ entry = state["edits"].get(path, {})
87
+ stamps = recent(entry.get("times", []), now, EDIT_WINDOW)
88
+ was_warned = bool(entry.get("warned")) and bool(stamps)
89
+ stamps.append(now)
90
+ state["edits"][path] = {"times": stamps, "warned": was_warned}
91
+ if len(stamps) > EDIT_LIMIT and not was_warned:
92
+ print(f"[Superflow 循环提醒] {path} 在 10 分钟内修改 {len(stamps)} 次;"
93
+ "请核对失败原因和实现方案。", file=sys.stderr)
94
+ state["edits"][path]["warned"] = True
95
+
96
+
97
+ def observe_failure(payload, state, now):
98
+ tool = str(payload.get("tool_name") or "unknown")
99
+ entry = state["failures"].get(tool, {})
100
+ stamps = recent(entry.get("times", []), now, FAILURE_WINDOW)
101
+ was_warned = bool(entry.get("warned")) and bool(stamps)
102
+ stamps.append(now)
103
+ state["failures"][tool] = {"times": stamps, "warned": was_warned}
104
+ if len(stamps) >= FAILURE_LIMIT and not was_warned:
105
+ print(f"[Superflow 失败提醒] {tool} 在 5 分钟内连续失败 "
106
+ f"{len(stamps)} 次;先分析原因,再决定是否重试。", file=sys.stderr)
107
+ state["failures"][tool]["warned"] = True
108
+
109
+
110
+ def prune_state(state, now):
111
+ for bucket, window in (("edits", EDIT_WINDOW), ("failures", FAILURE_WINDOW)):
112
+ for key, entry in list(state[bucket].items()):
113
+ if not isinstance(entry, dict):
114
+ del state[bucket][key]
115
+ continue
116
+ entry["times"] = recent(entry.get("times", []), now, window)
117
+ if not entry["times"]:
118
+ del state[bucket][key]
119
+
120
+
121
+ def main():
122
+ try:
123
+ payload = json.loads(sys.stdin.read() or "{}")
124
+ root = project_root()
125
+ if not root or not ((root / "openspec").exists() or
126
+ (root / ".sdd").exists() or
127
+ (root / ".sdd-enforced").exists()):
128
+ return 0
129
+ file = state_path(root, payload)
130
+ if file is None:
131
+ return 0
132
+ if "--end" in sys.argv:
133
+ file.unlink(missing_ok=True)
134
+ return 0
135
+ state = load_state(file)
136
+ now = time.time()
137
+ prune_state(state, now)
138
+ if "--failure" in sys.argv:
139
+ observe_failure(payload, state, now)
140
+ else:
141
+ observe_success(root, payload, state, now)
142
+ save_state(file, state)
143
+ except (OSError, ValueError, TypeError, AttributeError):
144
+ pass
145
+ return 0
146
+
147
+
148
+ if __name__ == "__main__":
149
+ sys.exit(main())
@@ -1,6 +1,10 @@
1
1
  #!/bin/bash
2
2
  # Managed-work role guard. Active only inside a Superflow managed Agent process.
3
3
 
4
+ # Defensive short-circuit: run SDD gates only in SDD projects
5
+ # (openspec/, .sdd/, .sdd-enforced); zero-cost exit elsewhere.
6
+ [ -d openspec ] || [ -d .sdd ] || [ -f .sdd-enforced ] || exit 0
7
+
4
8
  set -u
5
9
 
6
10
  ROLE="${SUPERFLOW_MANAGED_ROLE:-}"