great-cto 3.32.0 → 3.34.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/board/.claude-plugin/plugin.json +38 -39
- package/board/packages/board/mcp-server.mjs +25 -0
- package/board/scripts/lib/board-watch.mjs +91 -0
- package/board/scripts/lib/project-root.mjs +28 -0
- package/board/scripts/lib/session-status.mjs +3 -1
- package/board/scripts/lib/ship-evidence.mjs +115 -0
- package/package.json +1 -1
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "great-cto",
|
|
3
3
|
"description": "You already have the agent. This is everything around it. great_cto runs Claude Code as a pipeline of 70 specialist agents \u2014 an independent model checks each stage before the next builds on it, spending caps refuse rather than warn, and three decisions stay yours: what gets built, how, and whether it ships.",
|
|
4
|
-
"version": "3.
|
|
4
|
+
"version": "3.34.0",
|
|
5
5
|
"author": {
|
|
6
6
|
"name": "Alexander Velikiy",
|
|
7
7
|
"url": "https://hashnode.com/@Greatcto"
|
|
@@ -55,31 +55,30 @@
|
|
|
55
55
|
"hooks": [
|
|
56
56
|
{
|
|
57
57
|
"type": "command",
|
|
58
|
-
"command": "PLUGIN_DIR=${CLAUDE_PLUGIN_ROOT:-$(ls -d ~/.claude/plugins/cache/*/great_cto/*/ 2>/dev/null | sort -V | tail -1 | sed 's|/$||')};
|
|
59
|
-
"timeout": 5
|
|
60
|
-
"statusMessage": "Refreshing orchestrator contract..."
|
|
58
|
+
"command": "_gd=\"$PWD\"; while [ -n \"$_gd\" ] && [ ! -f \"$_gd/.great_cto/PROJECT.md\" ]; do _gd=\"${_gd%/*}\"; done; [ -n \"$_gd\" ] && cd \"$_gd\"; PLUGIN_DIR=${CLAUDE_PLUGIN_ROOT:-$(ls -d ~/.claude/plugins/cache/*/great_cto/*/ 2>/dev/null | sort -V | tail -1 | sed 's|/$||')}; [ -n \"$PLUGIN_DIR\" ] && node \"${PLUGIN_DIR}/scripts/lib/state-gitignore.mjs\" 2>/dev/null; true",
|
|
59
|
+
"timeout": 5
|
|
61
60
|
},
|
|
62
61
|
{
|
|
63
62
|
"type": "command",
|
|
64
|
-
"command": "export PATH=\"/opt/homebrew/bin:$HOME/.local/bin:/usr/local/bin:$PATH\"; PLUGIN_DIR=${CLAUDE_PLUGIN_ROOT:-$(ls -d ~/.claude/plugins/cache/*/great_cto/*/ 2>/dev/null | sort -V | tail -1 | sed 's|/$||')}; if [ -z \"$PLUGIN_DIR\" ]; then echo 'GREAT_CTO: plugin dir not found \u2014 run /update'; else mkdir -p ~/.great_cto .great_cto; node \"${PLUGIN_DIR}/scripts/lib/sync-managed.mjs\" --plugin-dir \"${PLUGIN_DIR}\" 2>/dev/null || true; cp \"${PLUGIN_DIR}/skills/great_cto/ARCHETYPES.md\" .great_cto/ARCHETYPES.md 2>/dev/null || true; cp \"${PLUGIN_DIR}/skills/great_cto/SKILL.md\" .great_cto/SKILL.md 2>/dev/null || true; { printf \"export PATH=/opt/homebrew/bin:$HOME/.local/bin:/usr/local/bin:$PATH\\nexport ARCHETYPES_MD=.great_cto/ARCHETYPES.md\\n\"; } > .great_cto/env.sh; fi; if [ ! -d ~/.great_cto/catalog ]; then git clone --depth=1 https://github.com/davila7/claude-code-templates.git ~/.great_cto/catalog 2>/dev/null & fi; if [ ! -d ~/.great_cto/anthropic-skills ]; then git clone --depth=1 https://github.com/anthropics/skills.git ~/.great_cto/anthropic-skills 2>/dev/null & elif [ $(($(date +%s) - $(stat -f %m ~/.great_cto/anthropic-skills/.git 2>/dev/null || stat -c %Y ~/.great_cto/anthropic-skills/.git 2>/dev/null || echo 0))) -gt 604800 ]; then (cd ~/.great_cto/anthropic-skills && git pull --quiet 2>/dev/null) & fi; if [ ! -d ~/.great_cto/personal-skills ]; then git clone --depth=1 https://github.com/avelikiy/ai-agent-skills.git ~/.great_cto/personal-skills 2>/dev/null & elif [ $(($(date +%s) - $(stat -f %m ~/.great_cto/personal-skills/.git 2>/dev/null || stat -c %Y ~/.great_cto/personal-skills/.git 2>/dev/null || echo 0))) -gt 86400 ]; then (cd ~/.great_cto/personal-skills && git pull --quiet 2>/dev/null) & fi; if [ ! -f ~/.great_cto/skills-registry.json ] || [ $(($(date +%s) - $(stat -f %m ~/.great_cto/skills-registry.json 2>/dev/null || stat -c %Y ~/.great_cto/skills-registry.json 2>/dev/null || echo 0))) -gt 86400 ]; then [ -x \"${PLUGIN_DIR}/scripts/skill-discover.sh\" ] && bash \"${PLUGIN_DIR}/scripts/skill-discover.sh\" >/dev/null 2>&1 & fi; [ -x \"${PLUGIN_DIR}/scripts/hooks/welcome.sh\" ] && bash \"${PLUGIN_DIR}/scripts/hooks/welcome.sh\" 2>/dev/null || true; bash \"${PLUGIN_DIR}/scripts/apply-model-override.sh\" 2>/dev/null || true; bd prime 2>/dev/null || true; cat \"${PLUGIN_DIR}/scripts/hooks/operating-rules.md\" 2>/dev/null || true; echo '=== PREFERENCES ==='; node \"${PLUGIN_DIR}/scripts/hooks/read-global-memory.mjs\" 2>&1 || true; echo '=== PROJECT ==='; cat .great_cto/PROJECT.md 2>/dev/null || echo 'GREAT_CTO: No PROJECT.md \u2014 run /start'; PHASE=$(grep '^phase:' .great_cto/PROJECT.md 2>/dev/null | awk '{print $2}' || echo implementation); echo '=== PHASE ==='; echo \"$PHASE\"; { [ \"$PHASE\" != review ] && [ \"$PHASE\" != release ]; } && { echo '=== BRAIN ==='; head -30 .great_cto/brain.md 2>/dev/null; } || true; [ \"$PHASE\" = implementation ] && { echo '=== CODEBASE ==='; head -20 .great_cto/CODEBASE.md 2>/dev/null; } || true; echo '=== LOCAL ==='; cat .great_cto/local.md 2>/dev/null || true; [ \"$PHASE\" != planning ] && { echo '=== HANDOFF ==='; cat .great_cto/HANDOFF.md 2>/dev/null || echo 'No handoff state \u2014 fresh session'; } || true; [ \"$PHASE\" = review ] && { echo '=== LATEST QA ==='; ls docs/qa-reports/QA-*.md 2>/dev/null | sort -V | tail -1 | xargs head -20 2>/dev/null; echo '=== LATEST CSO ==='; ls docs/security/CSO-*.md 2>/dev/null | sort -V | tail -1 | xargs head -20 2>/dev/null; } || true; [ \"$PHASE\" = release ] && { echo '=== PERF BASELINE ==='; tail -10 .great_cto/perf-baseline.log 2>/dev/null; } || true; echo '=== STATUS ==='; OPEN_GATES=$(bd list --label gate --status open 2>/dev/null | wc -l | tr -d ' '); OPEN_TASKS=$(bd list --status open 2>/dev/null | wc -l | tr -d ' '); BRANCH=$(git branch --show-current 2>/dev/null || echo '?'); LAST_AGENT=$(ls -t .great_cto/verdicts/*.log 2>/dev/null | head -1 | xargs tail -1 2>/dev/null | awk '{print $2}' || echo 'none'); printf 'branch=%s | gates=%s | tasks=%s | last_agent=%s\\n' \"$BRANCH\" \"${OPEN_GATES:-0}\" \"${OPEN_TASKS:-0}\" \"${LAST_AGENT:-none}\"; [ \"${OPEN_GATES:-0}\" -gt 0 ] && echo '\u2192 run /inbox to see pending decisions' || true; P0_OPEN=$(bd list --status open 2>/dev/null | grep -c 'P0'); P0_OPEN=${P0_OPEN:-0}; if [ \"$P0_OPEN\" -gt 0 ]; then echo ''; printf '\\033[41;97m \ud83d\udea8 P0 OPEN: %s \u2014 run /inbox NOW \\033[0m\\n' \"$P0_OPEN\"; bd list --status open 2>/dev/null | grep 'P0' | head -3 | sed 's/^/ /'; fi; NOW_EPOCH=$(date +%s); LAST_AUDIT_FILE=$(find docs/audit -maxdepth 1 -name 'AUDIT-*.md' 2>/dev/null | sort | tail -1); if [ -n \"$LAST_AUDIT_FILE\" ]; then AUD_M=$(stat -f %m \"$LAST_AUDIT_FILE\" 2>/dev/null || stat -c %Y \"$LAST_AUDIT_FILE\" 2>/dev/null || echo $NOW_EPOCH); AUD_AGE=$(( (NOW_EPOCH - AUD_M) / 86400 )); [ \"$AUD_AGE\" -gt 30 ] && echo \"\u26a0 audit $AUD_AGE days old \u2014 run /audit\"; else echo '\u26a0 no audit artefact \u2014 run /audit or /doctor'; fi; if [ -f .great_cto/digest-latest.md ]; then DIG_M=$(stat -f %m .great_cto/digest-latest.md 2>/dev/null || stat -c %Y .great_cto/digest-latest.md 2>/dev/null || echo $NOW_EPOCH); DIG_AGE=$(( (NOW_EPOCH - DIG_M) / 86400 )); [ \"$DIG_AGE\" -gt 8 ] && echo \"\u26a0 digest $DIG_AGE days old \u2014 run /digest 7\"; fi; [ \"$PHASE\" != implementation ] && cat .great_cto/digest-latest.md 2>/dev/null | head -5 || true; [ -f \"${PLUGIN_DIR}/scripts/hooks/auto-attach-reviewers.mjs\" ] && node \"${PLUGIN_DIR}/scripts/hooks/auto-attach-reviewers.mjs\" 2>/dev/null; [ -n \"$PLUGIN_DIR\" ] && [ -f .great_cto/PROJECT.md ] && [ ! -f .great_cto/CODEBASE.md ] && node \"${PLUGIN_DIR}/scripts/first-run-codebase.mjs\" >/dev/null 2>&1 & node \"${PLUGIN_DIR}/scripts/hooks/quota-check.mjs\" 2>/dev/null || true; echo '=== PATTERNS ==='; GP_DIR=\"$HOME/.great_cto/global-patterns\"; ARCH=$(grep '^primary:' .great_cto/PROJECT.md 2>/dev/null | awk '{print $2}' | head -1); if [ -d \"$GP_DIR\" ] && ls \"$GP_DIR\"/GP-*.md >/dev/null 2>&1; then ACTIVE_COUNT=$(grep -rl 'status: active' \"$GP_DIR\" 2>/dev/null | wc -l | tr -d ' '); echo \"Active patterns: ${ACTIVE_COUNT:-0}\"; if [ -n \"$ARCH\" ] && [ \"${ACTIVE_COUNT:-0}\" -gt 0 ]; then grep -rl 'status: active' \"$GP_DIR\" 2>/dev/null | xargs grep -lE \"applies_to:.*$ARCH\" 2>/dev/null | sort -V | tail -5 | while read f; do SLUG=$(basename \"$f\" .md); SYMPT=$(grep -m1 '^symptom:' \"$f\" 2>/dev/null | sed 's/symptom: //'); DETECT=$(grep -A 2 '^detection_order:' \"$f\" 2>/dev/null | grep '^ - ' | head -1 | sed 's/^ - //'); HITS=$(grep -m1 '^hits:' \"$f\" 2>/dev/null | awk '{print $2}'); printf ' %s (hits=%s): %s\\n \\u2192 Check first: %s\\n' \"$SLUG\" \"${HITS:-0}\" \"$SYMPT\" \"$DETECT\"; done || echo \" no patterns match archetype=$ARCH yet \u2014 /crystallize builds the library\"; fi; else echo 'no global patterns yet \u2014 run /crystallize after first incident'; fi; [ -x \"${PLUGIN_DIR}/scripts/check-update.sh\" ] && bash \"${PLUGIN_DIR}/scripts/check-update.sh\" \"${PLUGIN_DIR}/.claude-plugin/plugin.json\" 2>/dev/null || true ; # great_cto-cache-cleanup: keep the newest 3 cached versions, and never one a live session runs from (prune-versions; the check install-local --prune got after 2026-09-11). for CR in \"$HOME\"/.claude/plugins/cache/*/great_cto; do [ -d \"$CR\" ] || continue; node \"${PLUGIN_DIR}/scripts/lib/prune-versions.mjs\" --cache-root \"$CR\" --keep \"${PLUGIN_DIR:-$CR/none}\" --keep-newest 3 2>/dev/null | while IFS= read -r d; do case \"$d\" in \"$CR\"/*) rm -rf \"$d\" ;; esac; done; done 2>/dev/null || true",
|
|
63
|
+
"command": "_gd=\"$PWD\"; while [ -n \"$_gd\" ] && [ ! -f \"$_gd/.great_cto/PROJECT.md\" ]; do _gd=\"${_gd%/*}\"; done; [ -n \"$_gd\" ] && cd \"$_gd\"; export PATH=\"/opt/homebrew/bin:$HOME/.local/bin:/usr/local/bin:$PATH\"; PLUGIN_DIR=${CLAUDE_PLUGIN_ROOT:-$(ls -d ~/.claude/plugins/cache/*/great_cto/*/ 2>/dev/null | sort -V | tail -1 | sed 's|/$||')}; if [ -z \"$PLUGIN_DIR\" ]; then echo 'GREAT_CTO: plugin dir not found \u2014 run /update'; else mkdir -p ~/.great_cto .great_cto; node \"${PLUGIN_DIR}/scripts/lib/sync-managed.mjs\" --plugin-dir \"${PLUGIN_DIR}\" 2>/dev/null || true; cp \"${PLUGIN_DIR}/skills/great_cto/ARCHETYPES.md\" .great_cto/ARCHETYPES.md 2>/dev/null || true; cp \"${PLUGIN_DIR}/skills/great_cto/SKILL.md\" .great_cto/SKILL.md 2>/dev/null || true; { printf \"export PATH=/opt/homebrew/bin:$HOME/.local/bin:/usr/local/bin:$PATH\\nexport ARCHETYPES_MD=.great_cto/ARCHETYPES.md\\n\"; } > .great_cto/env.sh; fi; if [ ! -d ~/.great_cto/catalog ]; then git clone --depth=1 https://github.com/davila7/claude-code-templates.git ~/.great_cto/catalog 2>/dev/null & fi; if [ ! -d ~/.great_cto/anthropic-skills ]; then git clone --depth=1 https://github.com/anthropics/skills.git ~/.great_cto/anthropic-skills 2>/dev/null & elif [ $(($(date +%s) - $(stat -f %m ~/.great_cto/anthropic-skills/.git 2>/dev/null || stat -c %Y ~/.great_cto/anthropic-skills/.git 2>/dev/null || echo 0))) -gt 604800 ]; then (cd ~/.great_cto/anthropic-skills && git pull --quiet 2>/dev/null) & fi; if [ ! -d ~/.great_cto/personal-skills ]; then git clone --depth=1 https://github.com/avelikiy/ai-agent-skills.git ~/.great_cto/personal-skills 2>/dev/null & elif [ $(($(date +%s) - $(stat -f %m ~/.great_cto/personal-skills/.git 2>/dev/null || stat -c %Y ~/.great_cto/personal-skills/.git 2>/dev/null || echo 0))) -gt 86400 ]; then (cd ~/.great_cto/personal-skills && git pull --quiet 2>/dev/null) & fi; if [ ! -f ~/.great_cto/skills-registry.json ] || [ $(($(date +%s) - $(stat -f %m ~/.great_cto/skills-registry.json 2>/dev/null || stat -c %Y ~/.great_cto/skills-registry.json 2>/dev/null || echo 0))) -gt 86400 ]; then [ -x \"${PLUGIN_DIR}/scripts/skill-discover.sh\" ] && bash \"${PLUGIN_DIR}/scripts/skill-discover.sh\" >/dev/null 2>&1 & fi; [ -x \"${PLUGIN_DIR}/scripts/hooks/welcome.sh\" ] && bash \"${PLUGIN_DIR}/scripts/hooks/welcome.sh\" 2>/dev/null || true; bash \"${PLUGIN_DIR}/scripts/apply-model-override.sh\" 2>/dev/null || true; bd prime 2>/dev/null || true; cat \"${PLUGIN_DIR}/scripts/hooks/operating-rules.md\" 2>/dev/null || true; echo '=== PREFERENCES ==='; node \"${PLUGIN_DIR}/scripts/hooks/read-global-memory.mjs\" 2>&1 || true; echo '=== PROJECT ==='; cat .great_cto/PROJECT.md 2>/dev/null || echo 'GREAT_CTO: No PROJECT.md \u2014 run /start'; PHASE=$(grep '^phase:' .great_cto/PROJECT.md 2>/dev/null | awk '{print $2}' || echo implementation); echo '=== PHASE ==='; echo \"$PHASE\"; { [ \"$PHASE\" != review ] && [ \"$PHASE\" != release ]; } && { echo '=== BRAIN ==='; head -30 .great_cto/brain.md 2>/dev/null; } || true; [ \"$PHASE\" = implementation ] && { echo '=== CODEBASE ==='; head -20 .great_cto/CODEBASE.md 2>/dev/null; } || true; echo '=== LOCAL ==='; cat .great_cto/local.md 2>/dev/null || true; [ \"$PHASE\" != planning ] && { echo '=== HANDOFF ==='; cat .great_cto/HANDOFF.md 2>/dev/null || echo 'No handoff state \u2014 fresh session'; } || true; [ \"$PHASE\" = review ] && { echo '=== LATEST QA ==='; ls docs/qa-reports/QA-*.md 2>/dev/null | sort -V | tail -1 | xargs head -20 2>/dev/null; echo '=== LATEST CSO ==='; ls docs/security/CSO-*.md 2>/dev/null | sort -V | tail -1 | xargs head -20 2>/dev/null; } || true; [ \"$PHASE\" = release ] && { echo '=== PERF BASELINE ==='; tail -10 .great_cto/perf-baseline.log 2>/dev/null; } || true; echo '=== STATUS ==='; OPEN_GATES=$(bd list --label gate --status open 2>/dev/null | wc -l | tr -d ' '); OPEN_TASKS=$(bd list --status open 2>/dev/null | wc -l | tr -d ' '); BRANCH=$(git branch --show-current 2>/dev/null || echo '?'); LAST_AGENT=$(ls -t .great_cto/verdicts/*.log 2>/dev/null | head -1 | xargs tail -1 2>/dev/null | awk '{print $2}' || echo 'none'); printf 'branch=%s | gates=%s | tasks=%s | last_agent=%s\\n' \"$BRANCH\" \"${OPEN_GATES:-0}\" \"${OPEN_TASKS:-0}\" \"${LAST_AGENT:-none}\"; [ \"${OPEN_GATES:-0}\" -gt 0 ] && echo '\u2192 run /inbox to see pending decisions' || true; P0_OPEN=$(bd list --status open 2>/dev/null | grep -c 'P0'); P0_OPEN=${P0_OPEN:-0}; if [ \"$P0_OPEN\" -gt 0 ]; then echo ''; printf '\\033[41;97m \ud83d\udea8 P0 OPEN: %s \u2014 run /inbox NOW \\033[0m\\n' \"$P0_OPEN\"; bd list --status open 2>/dev/null | grep 'P0' | head -3 | sed 's/^/ /'; fi; NOW_EPOCH=$(date +%s); LAST_AUDIT_FILE=$(find docs/audit -maxdepth 1 -name 'AUDIT-*.md' 2>/dev/null | sort | tail -1); if [ -n \"$LAST_AUDIT_FILE\" ]; then AUD_M=$(stat -f %m \"$LAST_AUDIT_FILE\" 2>/dev/null || stat -c %Y \"$LAST_AUDIT_FILE\" 2>/dev/null || echo $NOW_EPOCH); AUD_AGE=$(( (NOW_EPOCH - AUD_M) / 86400 )); [ \"$AUD_AGE\" -gt 30 ] && echo \"\u26a0 audit $AUD_AGE days old \u2014 run /audit\"; else echo '\u26a0 no audit artefact \u2014 run /audit or /doctor'; fi; if [ -f .great_cto/digest-latest.md ]; then DIG_M=$(stat -f %m .great_cto/digest-latest.md 2>/dev/null || stat -c %Y .great_cto/digest-latest.md 2>/dev/null || echo $NOW_EPOCH); DIG_AGE=$(( (NOW_EPOCH - DIG_M) / 86400 )); [ \"$DIG_AGE\" -gt 8 ] && echo \"\u26a0 digest $DIG_AGE days old \u2014 run /digest 7\"; fi; [ \"$PHASE\" != implementation ] && cat .great_cto/digest-latest.md 2>/dev/null | head -5 || true; [ -f \"${PLUGIN_DIR}/scripts/hooks/auto-attach-reviewers.mjs\" ] && node \"${PLUGIN_DIR}/scripts/hooks/auto-attach-reviewers.mjs\" 2>/dev/null; [ -n \"$PLUGIN_DIR\" ] && [ -f .great_cto/PROJECT.md ] && [ ! -f .great_cto/CODEBASE.md ] && node \"${PLUGIN_DIR}/scripts/first-run-codebase.mjs\" >/dev/null 2>&1 & node \"${PLUGIN_DIR}/scripts/hooks/quota-check.mjs\" 2>/dev/null || true; echo '=== PATTERNS ==='; GP_DIR=\"$HOME/.great_cto/global-patterns\"; ARCH=$(grep '^primary:' .great_cto/PROJECT.md 2>/dev/null | awk '{print $2}' | head -1); if [ -d \"$GP_DIR\" ] && ls \"$GP_DIR\"/GP-*.md >/dev/null 2>&1; then ACTIVE_COUNT=$(grep -rl 'status: active' \"$GP_DIR\" 2>/dev/null | wc -l | tr -d ' '); echo \"Active patterns: ${ACTIVE_COUNT:-0}\"; if [ -n \"$ARCH\" ] && [ \"${ACTIVE_COUNT:-0}\" -gt 0 ]; then grep -rl 'status: active' \"$GP_DIR\" 2>/dev/null | xargs grep -lE \"applies_to:.*$ARCH\" 2>/dev/null | sort -V | tail -5 | while read f; do SLUG=$(basename \"$f\" .md); SYMPT=$(grep -m1 '^symptom:' \"$f\" 2>/dev/null | sed 's/symptom: //'); DETECT=$(grep -A 2 '^detection_order:' \"$f\" 2>/dev/null | grep '^ - ' | head -1 | sed 's/^ - //'); HITS=$(grep -m1 '^hits:' \"$f\" 2>/dev/null | awk '{print $2}'); printf ' %s (hits=%s): %s\\n \\u2192 Check first: %s\\n' \"$SLUG\" \"${HITS:-0}\" \"$SYMPT\" \"$DETECT\"; done || echo \" no patterns match archetype=$ARCH yet \u2014 /crystallize builds the library\"; fi; else echo 'no global patterns yet \u2014 run /crystallize after first incident'; fi; [ -x \"${PLUGIN_DIR}/scripts/check-update.sh\" ] && bash \"${PLUGIN_DIR}/scripts/check-update.sh\" \"${PLUGIN_DIR}/.claude-plugin/plugin.json\" 2>/dev/null || true ; # great_cto-cache-cleanup: keep the newest 3 cached versions, and never one a live session runs from (prune-versions; the check install-local --prune got after 2026-09-11). for CR in \"$HOME\"/.claude/plugins/cache/*/great_cto; do [ -d \"$CR\" ] || continue; node \"${PLUGIN_DIR}/scripts/lib/prune-versions.mjs\" --cache-root \"$CR\" --keep \"${PLUGIN_DIR:-$CR/none}\" --keep-newest 3 2>/dev/null | while IFS= read -r d; do case \"$d\" in \"$CR\"/*) rm -rf \"$d\" ;; esac; done; done 2>/dev/null || true",
|
|
65
64
|
"timeout": 30,
|
|
66
65
|
"statusMessage": "Loading Great CTO context..."
|
|
67
66
|
},
|
|
68
67
|
{
|
|
69
68
|
"type": "command",
|
|
70
|
-
"command": "PLUGIN_DIR=${CLAUDE_PLUGIN_ROOT:-$(ls -d ~/.claude/plugins/cache/*/great_cto/*/ 2>/dev/null | sort -V | tail -1 | sed 's|/$||')}; node \"${PLUGIN_DIR}/scripts/hooks/gate-expiry.mjs\" 2>/dev/null || true",
|
|
69
|
+
"command": "_gd=\"$PWD\"; while [ -n \"$_gd\" ] && [ ! -f \"$_gd/.great_cto/PROJECT.md\" ]; do _gd=\"${_gd%/*}\"; done; [ -n \"$_gd\" ] && cd \"$_gd\"; PLUGIN_DIR=${CLAUDE_PLUGIN_ROOT:-$(ls -d ~/.claude/plugins/cache/*/great_cto/*/ 2>/dev/null | sort -V | tail -1 | sed 's|/$||')}; node \"${PLUGIN_DIR}/scripts/hooks/gate-expiry.mjs\" 2>/dev/null || true",
|
|
71
70
|
"timeout": 10,
|
|
72
71
|
"statusMessage": "Checking gate expiry (72h policy)..."
|
|
73
72
|
},
|
|
74
73
|
{
|
|
75
74
|
"type": "command",
|
|
76
|
-
"command": "PLUGIN_DIR=${CLAUDE_PLUGIN_ROOT:-$(ls -d ~/.claude/plugins/cache/*/great_cto/*/ 2>/dev/null | sort -V | tail -1 | sed 's|/$||')}; [ -n \"$PLUGIN_DIR\" ] && node \"${PLUGIN_DIR}/scripts/hooks/session-pipeline-resume.mjs\" 2>/dev/null || true",
|
|
75
|
+
"command": "_gd=\"$PWD\"; while [ -n \"$_gd\" ] && [ ! -f \"$_gd/.great_cto/PROJECT.md\" ]; do _gd=\"${_gd%/*}\"; done; [ -n \"$_gd\" ] && cd \"$_gd\"; PLUGIN_DIR=${CLAUDE_PLUGIN_ROOT:-$(ls -d ~/.claude/plugins/cache/*/great_cto/*/ 2>/dev/null | sort -V | tail -1 | sed 's|/$||')}; [ -n \"$PLUGIN_DIR\" ] && node \"${PLUGIN_DIR}/scripts/hooks/session-pipeline-resume.mjs\" 2>/dev/null || true",
|
|
77
76
|
"timeout": 10,
|
|
78
77
|
"statusMessage": "Checking for pipeline work in flight..."
|
|
79
78
|
},
|
|
80
79
|
{
|
|
81
80
|
"type": "command",
|
|
82
|
-
"command": "PLUGIN_DIR=${CLAUDE_PLUGIN_ROOT:-$(ls -d ~/.claude/plugins/cache/*/great_cto/*/ 2>/dev/null | sort -V | tail -1 | sed \"s|/$||\")}; [ -n \"$PLUGIN_DIR\" ] && node \"${PLUGIN_DIR}/scripts/hooks/register-project.mjs\" </dev/null 2>/dev/null || true",
|
|
81
|
+
"command": "_gd=\"$PWD\"; while [ -n \"$_gd\" ] && [ ! -f \"$_gd/.great_cto/PROJECT.md\" ]; do _gd=\"${_gd%/*}\"; done; [ -n \"$_gd\" ] && cd \"$_gd\"; PLUGIN_DIR=${CLAUDE_PLUGIN_ROOT:-$(ls -d ~/.claude/plugins/cache/*/great_cto/*/ 2>/dev/null | sort -V | tail -1 | sed \"s|/$||\")}; [ -n \"$PLUGIN_DIR\" ] && node \"${PLUGIN_DIR}/scripts/hooks/register-project.mjs\" </dev/null 2>/dev/null || true",
|
|
83
82
|
"timeout": 10,
|
|
84
83
|
"statusMessage": "Registering project on the board..."
|
|
85
84
|
}
|
|
@@ -92,7 +91,7 @@
|
|
|
92
91
|
"hooks": [
|
|
93
92
|
{
|
|
94
93
|
"type": "command",
|
|
95
|
-
"command": "bd prime 2>/dev/null; echo '---GREAT_CTO-CONTEXT---'; cat .great_cto/PROJECT.md 2>/dev/null | head -30; echo '---BEADS-READY---'; bd ready 2>/dev/null | head -10; BRANCH=$(git branch --show-current 2>/dev/null || echo 'unknown'); LAST_COMMIT=$(git log --oneline -1 2>/dev/null || echo 'none'); UNCOMMITTED=$(git status --short 2>/dev/null | wc -l | tr -d ' '); OPEN_GATES=$(bd list --label gate --status open 2>/dev/null | head -5 || grep '\\[GATE:' .great_cto/tasks.md 2>/dev/null | head -5 || echo 'none'); OPEN_TASKS=$(bd list --status open 2>/dev/null | head -8 || echo 'none'); LAST_VERDICT=$(ls -t .great_cto/verdicts/*.log 2>/dev/null | head -1 | xargs tail -1 2>/dev/null || echo 'none'); LATEST_ARCH=$(ls docs/architecture/ARCH-*.md 2>/dev/null | sort | tail -1 || echo 'none'); LATEST_QA=$(ls docs/qa-reports/QA-*.md 2>/dev/null | sort | tail -1 || echo 'none'); LATEST_CSO=$(ls docs/security/CSO-*.md 2>/dev/null | sort | tail -1 || echo 'none'); printf '# Auto-Handoff \u2014 %s\\n\\n> Auto-saved by PreCompact hook. Next session: read this, then /inbox.\\n\\n## Git\\n- Branch: `%s` | Last commit: `%s` | Uncommitted: %s files\\n\\n## Open Gates\\n%s\\n\\n## Open Tasks\\n%s\\n\\n## Last Agent Verdict\\n%s\\n\\n## Latest Docs\\n- Arch: %s\\n- QA: %s\\n- Security: %s\\n\\n## Resume\\nRun `/inbox` to see pending gates, then continue pipeline.\\n' \"$(date '+%Y-%m-%d %H:%M')\" \"$BRANCH\" \"$LAST_COMMIT\" \"$UNCOMMITTED\" \"$OPEN_GATES\" \"$OPEN_TASKS\" \"$LAST_VERDICT\" \"$LATEST_ARCH\" \"$LATEST_QA\" \"$LATEST_CSO\" > .great_cto/HANDOFF.md 2>/dev/null; echo '---HANDOFF-SAVED---'; mkdir -p .great_cto/logs; LOG_DATE=$(date +%Y-%m-%d); LOG_TIME=$(date +%H:%M); LOG_FILE=\".great_cto/logs/session-${LOG_DATE}-autocompact.md\"; printf '---\\ndate: %s\\ntime: %s\\nduration: auto-saved (context compact)\\n---\\n\\n# Session: context compact snapshot\\n\\n## Done\\n%s\\n\\n## Pending\\n%s\\n\\n## Commits\\n%s\\n' \"$LOG_DATE\" \"$LOG_TIME\" \"$OPEN_TASKS\" \"$OPEN_TASKS\" \"$LAST_COMMIT\" > \"$LOG_FILE\" 2>/dev/null; echo '---SESSIONLOG-SAVED---'",
|
|
94
|
+
"command": "_gd=\"$PWD\"; while [ -n \"$_gd\" ] && [ ! -f \"$_gd/.great_cto/PROJECT.md\" ]; do _gd=\"${_gd%/*}\"; done; [ -n \"$_gd\" ] && cd \"$_gd\"; bd prime 2>/dev/null; echo '---GREAT_CTO-CONTEXT---'; cat .great_cto/PROJECT.md 2>/dev/null | head -30; echo '---BEADS-READY---'; bd ready 2>/dev/null | head -10; BRANCH=$(git branch --show-current 2>/dev/null || echo 'unknown'); LAST_COMMIT=$(git log --oneline -1 2>/dev/null || echo 'none'); UNCOMMITTED=$(git status --short 2>/dev/null | wc -l | tr -d ' '); OPEN_GATES=$(bd list --label gate --status open 2>/dev/null | head -5 || grep '\\[GATE:' .great_cto/tasks.md 2>/dev/null | head -5 || echo 'none'); OPEN_TASKS=$(bd list --status open 2>/dev/null | head -8 || echo 'none'); LAST_VERDICT=$(ls -t .great_cto/verdicts/*.log 2>/dev/null | head -1 | xargs tail -1 2>/dev/null || echo 'none'); LATEST_ARCH=$(ls docs/architecture/ARCH-*.md 2>/dev/null | sort | tail -1 || echo 'none'); LATEST_QA=$(ls docs/qa-reports/QA-*.md 2>/dev/null | sort | tail -1 || echo 'none'); LATEST_CSO=$(ls docs/security/CSO-*.md 2>/dev/null | sort | tail -1 || echo 'none'); printf '# Auto-Handoff \u2014 %s\\n\\n> Auto-saved by PreCompact hook. Next session: read this, then /inbox.\\n\\n## Git\\n- Branch: `%s` | Last commit: `%s` | Uncommitted: %s files\\n\\n## Open Gates\\n%s\\n\\n## Open Tasks\\n%s\\n\\n## Last Agent Verdict\\n%s\\n\\n## Latest Docs\\n- Arch: %s\\n- QA: %s\\n- Security: %s\\n\\n## Resume\\nRun `/inbox` to see pending gates, then continue pipeline.\\n' \"$(date '+%Y-%m-%d %H:%M')\" \"$BRANCH\" \"$LAST_COMMIT\" \"$UNCOMMITTED\" \"$OPEN_GATES\" \"$OPEN_TASKS\" \"$LAST_VERDICT\" \"$LATEST_ARCH\" \"$LATEST_QA\" \"$LATEST_CSO\" > .great_cto/HANDOFF.md 2>/dev/null; echo '---HANDOFF-SAVED---'; mkdir -p .great_cto/logs; LOG_DATE=$(date +%Y-%m-%d); LOG_TIME=$(date +%H:%M); LOG_FILE=\".great_cto/logs/session-${LOG_DATE}-autocompact.md\"; printf '---\\ndate: %s\\ntime: %s\\nduration: auto-saved (context compact)\\n---\\n\\n# Session: context compact snapshot\\n\\n## Done\\n%s\\n\\n## Pending\\n%s\\n\\n## Commits\\n%s\\n' \"$LOG_DATE\" \"$LOG_TIME\" \"$OPEN_TASKS\" \"$OPEN_TASKS\" \"$LAST_COMMIT\" > \"$LOG_FILE\" 2>/dev/null; echo '---SESSIONLOG-SAVED---'",
|
|
96
95
|
"timeout": 15,
|
|
97
96
|
"statusMessage": "Saving session state to HANDOFF.md..."
|
|
98
97
|
}
|
|
@@ -105,13 +104,13 @@
|
|
|
105
104
|
"hooks": [
|
|
106
105
|
{
|
|
107
106
|
"type": "command",
|
|
108
|
-
"command": "echo '=== PROJECT ==='; cat .great_cto/PROJECT.md 2>/dev/null | head -15; echo '=== BRAIN (stable knowledge) ==='; awk '/^## SEMANTIC/,/^## EPISODIC/' .great_cto/brain.md 2>/dev/null | head -60; echo '---'; awk '/^## PROCEDURAL/,/^## EPISODIC/' .great_cto/brain.md 2>/dev/null | head -40 || head -30 .great_cto/brain.md 2>/dev/null; echo '=== HANDOFF ==='; cat .great_cto/HANDOFF.md 2>/dev/null | head -20",
|
|
107
|
+
"command": "_gd=\"$PWD\"; while [ -n \"$_gd\" ] && [ ! -f \"$_gd/.great_cto/PROJECT.md\" ]; do _gd=\"${_gd%/*}\"; done; [ -n \"$_gd\" ] && cd \"$_gd\"; echo '=== PROJECT ==='; cat .great_cto/PROJECT.md 2>/dev/null | head -15; echo '=== BRAIN (stable knowledge) ==='; awk '/^## SEMANTIC/,/^## EPISODIC/' .great_cto/brain.md 2>/dev/null | head -60; echo '---'; awk '/^## PROCEDURAL/,/^## EPISODIC/' .great_cto/brain.md 2>/dev/null | head -40 || head -30 .great_cto/brain.md 2>/dev/null; echo '=== HANDOFF ==='; cat .great_cto/HANDOFF.md 2>/dev/null | head -20",
|
|
109
108
|
"timeout": 5,
|
|
110
109
|
"statusMessage": "Injecting project + brain + handoff context into agent..."
|
|
111
110
|
},
|
|
112
111
|
{
|
|
113
112
|
"type": "command",
|
|
114
|
-
"command": "PLUGIN_DIR=${CLAUDE_PLUGIN_ROOT:-$(ls -d ~/.claude/plugins/cache/*/great_cto/*/ 2>/dev/null | sort -V | tail -1 | sed 's|/$||')}; node \"${PLUGIN_DIR}/scripts/hooks/orchestrator-check.mjs\" 2>/dev/null || true",
|
|
113
|
+
"command": "_gd=\"$PWD\"; while [ -n \"$_gd\" ] && [ ! -f \"$_gd/.great_cto/PROJECT.md\" ]; do _gd=\"${_gd%/*}\"; done; [ -n \"$_gd\" ] && cd \"$_gd\"; PLUGIN_DIR=${CLAUDE_PLUGIN_ROOT:-$(ls -d ~/.claude/plugins/cache/*/great_cto/*/ 2>/dev/null | sort -V | tail -1 | sed 's|/$||')}; node \"${PLUGIN_DIR}/scripts/hooks/orchestrator-check.mjs\" 2>/dev/null || true",
|
|
115
114
|
"timeout": 5,
|
|
116
115
|
"statusMessage": "Loading orchestrator contract..."
|
|
117
116
|
}
|
|
@@ -124,14 +123,14 @@
|
|
|
124
123
|
"hooks": [
|
|
125
124
|
{
|
|
126
125
|
"type": "command",
|
|
127
|
-
"command": "PLUGIN_DIR=${CLAUDE_PLUGIN_ROOT:-$(ls -d ~/.claude/plugins/cache/*/great_cto/*/ 2>/dev/null | sort -V | tail -1 | sed 's|/$||')}; node \"${PLUGIN_DIR}/scripts/hooks/subagent-stop-completion.mjs\" 2>&1; EXIT=$?; if [ $EXIT -eq 2 ]; then exit 2; fi; exit 0",
|
|
126
|
+
"command": "_gd=\"$PWD\"; while [ -n \"$_gd\" ] && [ ! -f \"$_gd/.great_cto/PROJECT.md\" ]; do _gd=\"${_gd%/*}\"; done; [ -n \"$_gd\" ] && cd \"$_gd\"; PLUGIN_DIR=${CLAUDE_PLUGIN_ROOT:-$(ls -d ~/.claude/plugins/cache/*/great_cto/*/ 2>/dev/null | sort -V | tail -1 | sed 's|/$||')}; node \"${PLUGIN_DIR}/scripts/hooks/subagent-stop-completion.mjs\" 2>&1; EXIT=$?; if [ $EXIT -eq 2 ]; then exit 2; fi; exit 0",
|
|
128
127
|
"timeout": 5,
|
|
129
128
|
"statusMessage": "Three-state completion check..."
|
|
130
129
|
},
|
|
131
130
|
{
|
|
132
131
|
"type": "command",
|
|
133
132
|
"async": true,
|
|
134
|
-
"command": "node \"${CLAUDE_PLUGIN_ROOT}/scripts/hooks/turn-snapshot.mjs\" 2>/dev/null || true"
|
|
133
|
+
"command": "_gd=\"$PWD\"; while [ -n \"$_gd\" ] && [ ! -f \"$_gd/.great_cto/PROJECT.md\" ]; do _gd=\"${_gd%/*}\"; done; [ -n \"$_gd\" ] && cd \"$_gd\"; node \"${CLAUDE_PLUGIN_ROOT}/scripts/hooks/turn-snapshot.mjs\" 2>/dev/null || true"
|
|
135
134
|
}
|
|
136
135
|
]
|
|
137
136
|
}
|
|
@@ -142,13 +141,13 @@
|
|
|
142
141
|
"hooks": [
|
|
143
142
|
{
|
|
144
143
|
"type": "command",
|
|
145
|
-
"command": "PLUGIN_DIR=${CLAUDE_PLUGIN_ROOT:-$(ls -d ~/.claude/plugins/cache/*/great_cto/*/ 2>/dev/null | sort -V | tail -1 | sed 's|/$||')}; node \"${PLUGIN_DIR}/scripts/hooks/orchestrator-check.mjs\" 2>/dev/null; EXIT=$?; if [ $EXIT -eq 2 ]; then exit 2; fi; exit 0",
|
|
144
|
+
"command": "_gd=\"$PWD\"; while [ -n \"$_gd\" ] && [ ! -f \"$_gd/.great_cto/PROJECT.md\" ]; do _gd=\"${_gd%/*}\"; done; [ -n \"$_gd\" ] && cd \"$_gd\"; PLUGIN_DIR=${CLAUDE_PLUGIN_ROOT:-$(ls -d ~/.claude/plugins/cache/*/great_cto/*/ 2>/dev/null | sort -V | tail -1 | sed 's|/$||')}; node \"${PLUGIN_DIR}/scripts/hooks/orchestrator-check.mjs\" 2>/dev/null; EXIT=$?; if [ $EXIT -eq 2 ]; then exit 2; fi; exit 0",
|
|
146
145
|
"timeout": 5,
|
|
147
146
|
"statusMessage": "Inline subagent check..."
|
|
148
147
|
},
|
|
149
148
|
{
|
|
150
149
|
"type": "command",
|
|
151
|
-
"command": "INPUT=$(cat 2>/dev/null); if [ -z \"$INPUT\" ]; then exit 0; fi; CMD=$(echo \"$INPUT\" | python3 -c \"import sys,json; d=json.load(sys.stdin); print(d.get('command',''))\" 2>/dev/null); if [ -z \"$CMD\" ]; then exit 0; fi; if echo \"$CMD\" | grep -qiE '(^|[;&|[:space:]])rm[[:space:]]+(-[a-zA-Z]*r[a-zA-Z]*f|-[a-zA-Z]*f[a-zA-Z]*r|--recursive)|(^|[;&|[:space:]])git[[:space:]]+(push[[:space:]].*(--force(-with-lease)?|-f([[:space:]]|$))|reset[[:space:]]+--hard)|(DROP[[:space:]]+(TABLE|DATABASE|SCHEMA))|(^|[;&|[:space:]])truncate[[:space:]]+--all|(^|[;&|[:space:]])(mkfs|dd)[[:space:]]|(^|[;&|[:space:]])chmod[[:space:]]+[0-7]*0{3}[[:space:]]|curl[[:space:]]+.*[|][[:space:]]*(sudo|sh|bash|zsh|python[23]?|perl|ruby)'; then echo \"BLOCKED: Dangerous command pattern detected.\"; exit 2; fi; exit 0",
|
|
150
|
+
"command": "_gd=\"$PWD\"; while [ -n \"$_gd\" ] && [ ! -f \"$_gd/.great_cto/PROJECT.md\" ]; do _gd=\"${_gd%/*}\"; done; [ -n \"$_gd\" ] && cd \"$_gd\"; INPUT=$(cat 2>/dev/null); if [ -z \"$INPUT\" ]; then exit 0; fi; CMD=$(echo \"$INPUT\" | python3 -c \"import sys,json; d=json.load(sys.stdin); print(d.get('command',''))\" 2>/dev/null); if [ -z \"$CMD\" ]; then exit 0; fi; if echo \"$CMD\" | grep -qiE '(^|[;&|[:space:]])rm[[:space:]]+(-[a-zA-Z]*r[a-zA-Z]*f|-[a-zA-Z]*f[a-zA-Z]*r|--recursive)|(^|[;&|[:space:]])git[[:space:]]+(push[[:space:]].*(--force(-with-lease)?|-f([[:space:]]|$))|reset[[:space:]]+--hard)|(DROP[[:space:]]+(TABLE|DATABASE|SCHEMA))|(^|[;&|[:space:]])truncate[[:space:]]+--all|(^|[;&|[:space:]])(mkfs|dd)[[:space:]]|(^|[;&|[:space:]])chmod[[:space:]]+[0-7]*0{3}[[:space:]]|curl[[:space:]]+.*[|][[:space:]]*(sudo|sh|bash|zsh|python[23]?|perl|ruby)'; then echo \"BLOCKED: Dangerous command pattern detected.\"; exit 2; fi; exit 0",
|
|
152
151
|
"timeout": 5,
|
|
153
152
|
"statusMessage": "Safety check..."
|
|
154
153
|
}
|
|
@@ -159,19 +158,19 @@
|
|
|
159
158
|
"hooks": [
|
|
160
159
|
{
|
|
161
160
|
"type": "command",
|
|
162
|
-
"command": "PLUGIN_DIR=${CLAUDE_PLUGIN_ROOT:-$(ls -d ~/.claude/plugins/cache/*/great_cto/*/ 2>/dev/null | sort -V | tail -1 | sed 's|/$||')}; node \"${PLUGIN_DIR}/scripts/hooks/secret-scan.mjs\" 2>&1; EXIT=$?; if [ $EXIT -eq 2 ]; then exit 2; fi; exit 0",
|
|
161
|
+
"command": "_gd=\"$PWD\"; while [ -n \"$_gd\" ] && [ ! -f \"$_gd/.great_cto/PROJECT.md\" ]; do _gd=\"${_gd%/*}\"; done; [ -n \"$_gd\" ] && cd \"$_gd\"; PLUGIN_DIR=${CLAUDE_PLUGIN_ROOT:-$(ls -d ~/.claude/plugins/cache/*/great_cto/*/ 2>/dev/null | sort -V | tail -1 | sed 's|/$||')}; node \"${PLUGIN_DIR}/scripts/hooks/secret-scan.mjs\" 2>&1; EXIT=$?; if [ $EXIT -eq 2 ]; then exit 2; fi; exit 0",
|
|
163
162
|
"timeout": 5,
|
|
164
163
|
"statusMessage": "Scanning for secrets..."
|
|
165
164
|
},
|
|
166
165
|
{
|
|
167
166
|
"type": "command",
|
|
168
|
-
"command": "PLUGIN_DIR=${CLAUDE_PLUGIN_ROOT:-$(ls -d ~/.claude/plugins/cache/*/great_cto/*/ 2>/dev/null | sort -V | tail -1 | sed 's|/$||')}; node \"${PLUGIN_DIR}/scripts/hooks/frozen-gates-guard.mjs\" 2>&1; EXIT=$?; if [ $EXIT -eq 2 ]; then exit 2; fi; exit 0",
|
|
167
|
+
"command": "_gd=\"$PWD\"; while [ -n \"$_gd\" ] && [ ! -f \"$_gd/.great_cto/PROJECT.md\" ]; do _gd=\"${_gd%/*}\"; done; [ -n \"$_gd\" ] && cd \"$_gd\"; PLUGIN_DIR=${CLAUDE_PLUGIN_ROOT:-$(ls -d ~/.claude/plugins/cache/*/great_cto/*/ 2>/dev/null | sort -V | tail -1 | sed 's|/$||')}; node \"${PLUGIN_DIR}/scripts/hooks/frozen-gates-guard.mjs\" 2>&1; EXIT=$?; if [ $EXIT -eq 2 ]; then exit 2; fi; exit 0",
|
|
169
168
|
"timeout": 5,
|
|
170
169
|
"statusMessage": "Checking frozen gates..."
|
|
171
170
|
},
|
|
172
171
|
{
|
|
173
172
|
"type": "command",
|
|
174
|
-
"command": "PLUGIN_DIR=${CLAUDE_PLUGIN_ROOT:-$(ls -d ~/.claude/plugins/cache/*/great_cto/*/ 2>/dev/null | sort -V | tail -1 | sed 's|/$||')}; node \"${PLUGIN_DIR}/scripts/hooks/edit-scope-guard.mjs\" 2>&1; EXIT=$?; if [ $EXIT -eq 2 ]; then exit 2; fi; exit 0",
|
|
173
|
+
"command": "_gd=\"$PWD\"; while [ -n \"$_gd\" ] && [ ! -f \"$_gd/.great_cto/PROJECT.md\" ]; do _gd=\"${_gd%/*}\"; done; [ -n \"$_gd\" ] && cd \"$_gd\"; PLUGIN_DIR=${CLAUDE_PLUGIN_ROOT:-$(ls -d ~/.claude/plugins/cache/*/great_cto/*/ 2>/dev/null | sort -V | tail -1 | sed 's|/$||')}; node \"${PLUGIN_DIR}/scripts/hooks/edit-scope-guard.mjs\" 2>&1; EXIT=$?; if [ $EXIT -eq 2 ]; then exit 2; fi; exit 0",
|
|
175
174
|
"timeout": 5,
|
|
176
175
|
"statusMessage": "Checking edit scope..."
|
|
177
176
|
}
|
|
@@ -184,35 +183,35 @@
|
|
|
184
183
|
"hooks": [
|
|
185
184
|
{
|
|
186
185
|
"type": "command",
|
|
187
|
-
"command": "INPUT=$(cat 2>/dev/null); FILE=$(echo \"$INPUT\" | python3 -c \"import sys,json; d=json.load(sys.stdin); print(d.get('file_path',''))\" 2>/dev/null); TOOL=$(echo \"$INPUT\" | python3 -c \"import sys,json; d=json.load(sys.stdin); print(d.get('tool_name',''))\" 2>/dev/null); if [ -n \"$FILE\" ]; then mkdir -p .great_cto 2>/dev/null; printf '%s %s %s\\n' \"$(date -u +%Y-%m-%dT%H:%M:%SZ)\" \"$TOOL\" \"$FILE\" >> .great_cto/agent-writes.log 2>/dev/null; fi; true",
|
|
186
|
+
"command": "_gd=\"$PWD\"; while [ -n \"$_gd\" ] && [ ! -f \"$_gd/.great_cto/PROJECT.md\" ]; do _gd=\"${_gd%/*}\"; done; [ -n \"$_gd\" ] && cd \"$_gd\"; INPUT=$(cat 2>/dev/null); FILE=$(echo \"$INPUT\" | python3 -c \"import sys,json; d=json.load(sys.stdin); print(d.get('file_path',''))\" 2>/dev/null); TOOL=$(echo \"$INPUT\" | python3 -c \"import sys,json; d=json.load(sys.stdin); print(d.get('tool_name',''))\" 2>/dev/null); if [ -n \"$FILE\" ]; then mkdir -p .great_cto 2>/dev/null; printf '%s %s %s\\n' \"$(date -u +%Y-%m-%dT%H:%M:%SZ)\" \"$TOOL\" \"$FILE\" >> .great_cto/agent-writes.log 2>/dev/null; fi; true",
|
|
188
187
|
"timeout": 3,
|
|
189
188
|
"async": true
|
|
190
189
|
},
|
|
191
190
|
{
|
|
192
191
|
"type": "command",
|
|
193
|
-
"command": "PLUGIN_DIR=${CLAUDE_PLUGIN_ROOT:-$(ls -d ~/.claude/plugins/cache/*/great_cto/*/ 2>/dev/null | sort -V | tail -1 | sed 's|/$||')}; node \"${PLUGIN_DIR}/scripts/hooks/format-check.mjs\" 2>/dev/null; true",
|
|
192
|
+
"command": "_gd=\"$PWD\"; while [ -n \"$_gd\" ] && [ ! -f \"$_gd/.great_cto/PROJECT.md\" ]; do _gd=\"${_gd%/*}\"; done; [ -n \"$_gd\" ] && cd \"$_gd\"; PLUGIN_DIR=${CLAUDE_PLUGIN_ROOT:-$(ls -d ~/.claude/plugins/cache/*/great_cto/*/ 2>/dev/null | sort -V | tail -1 | sed 's|/$||')}; node \"${PLUGIN_DIR}/scripts/hooks/format-check.mjs\" 2>/dev/null; true",
|
|
194
193
|
"timeout": 12,
|
|
195
194
|
"async": true
|
|
196
195
|
},
|
|
197
196
|
{
|
|
198
197
|
"type": "command",
|
|
199
|
-
"command": "PLUGIN_DIR=${CLAUDE_PLUGIN_ROOT:-$(ls -d ~/.claude/plugins/cache/*/great_cto/*/ 2>/dev/null | sort -V | tail -1 | sed 's|/$||')}; node \"${PLUGIN_DIR}/scripts/hooks/summary-enforce.mjs\" 2>/dev/null; true",
|
|
198
|
+
"command": "_gd=\"$PWD\"; while [ -n \"$_gd\" ] && [ ! -f \"$_gd/.great_cto/PROJECT.md\" ]; do _gd=\"${_gd%/*}\"; done; [ -n \"$_gd\" ] && cd \"$_gd\"; PLUGIN_DIR=${CLAUDE_PLUGIN_ROOT:-$(ls -d ~/.claude/plugins/cache/*/great_cto/*/ 2>/dev/null | sort -V | tail -1 | sed 's|/$||')}; node \"${PLUGIN_DIR}/scripts/hooks/summary-enforce.mjs\" 2>/dev/null; true",
|
|
200
199
|
"timeout": 5,
|
|
201
200
|
"async": true
|
|
202
201
|
},
|
|
203
202
|
{
|
|
204
203
|
"type": "command",
|
|
205
|
-
"command": "PLUGIN_DIR=${CLAUDE_PLUGIN_ROOT:-$(ls -d ~/.claude/plugins/cache/*/great_cto/*/ 2>/dev/null | sort -V | tail -1 | sed 's|/$||')}; node \"${PLUGIN_DIR}/scripts/hooks/reviewer-nudge.mjs\" 2>/dev/null || true",
|
|
204
|
+
"command": "_gd=\"$PWD\"; while [ -n \"$_gd\" ] && [ ! -f \"$_gd/.great_cto/PROJECT.md\" ]; do _gd=\"${_gd%/*}\"; done; [ -n \"$_gd\" ] && cd \"$_gd\"; PLUGIN_DIR=${CLAUDE_PLUGIN_ROOT:-$(ls -d ~/.claude/plugins/cache/*/great_cto/*/ 2>/dev/null | sort -V | tail -1 | sed 's|/$||')}; node \"${PLUGIN_DIR}/scripts/hooks/reviewer-nudge.mjs\" 2>/dev/null || true",
|
|
206
205
|
"timeout": 5,
|
|
207
206
|
"statusMessage": "Checking reviewer rules for this file..."
|
|
208
207
|
},
|
|
209
208
|
{
|
|
210
209
|
"type": "command",
|
|
211
|
-
"command": "PLUGIN_DIR=${CLAUDE_PLUGIN_ROOT:-$(ls -d ~/.claude/plugins/cache/*/great_cto/*/ 2>/dev/null | sort -V | tail -1 | sed 's|/$||')}; node \"${PLUGIN_DIR}/scripts/hooks/lesson-rules-check.mjs\" 2>/dev/null; true"
|
|
210
|
+
"command": "_gd=\"$PWD\"; while [ -n \"$_gd\" ] && [ ! -f \"$_gd/.great_cto/PROJECT.md\" ]; do _gd=\"${_gd%/*}\"; done; [ -n \"$_gd\" ] && cd \"$_gd\"; PLUGIN_DIR=${CLAUDE_PLUGIN_ROOT:-$(ls -d ~/.claude/plugins/cache/*/great_cto/*/ 2>/dev/null | sort -V | tail -1 | sed 's|/$||')}; node \"${PLUGIN_DIR}/scripts/hooks/lesson-rules-check.mjs\" 2>/dev/null; true"
|
|
212
211
|
},
|
|
213
212
|
{
|
|
214
213
|
"type": "command",
|
|
215
|
-
"command": "PLUGIN_DIR=${CLAUDE_PLUGIN_ROOT:-$(ls -d ~/.claude/plugins/cache/*/great_cto/*/ 2>/dev/null | sort -V | tail -1 | sed 's|/$||')}; node \"${PLUGIN_DIR}/scripts/hooks/docs-reference-sync.mjs\" 2>/dev/null; true",
|
|
214
|
+
"command": "_gd=\"$PWD\"; while [ -n \"$_gd\" ] && [ ! -f \"$_gd/.great_cto/PROJECT.md\" ]; do _gd=\"${_gd%/*}\"; done; [ -n \"$_gd\" ] && cd \"$_gd\"; PLUGIN_DIR=${CLAUDE_PLUGIN_ROOT:-$(ls -d ~/.claude/plugins/cache/*/great_cto/*/ 2>/dev/null | sort -V | tail -1 | sed 's|/$||')}; node \"${PLUGIN_DIR}/scripts/hooks/docs-reference-sync.mjs\" 2>/dev/null; true",
|
|
216
215
|
"timeout": 25,
|
|
217
216
|
"async": true
|
|
218
217
|
}
|
|
@@ -223,7 +222,7 @@
|
|
|
223
222
|
"hooks": [
|
|
224
223
|
{
|
|
225
224
|
"type": "command",
|
|
226
|
-
"command": "PLUGIN_DIR=${CLAUDE_PLUGIN_ROOT:-$(ls -d ~/.claude/plugins/cache/*/great_cto/*/ 2>/dev/null | sort -V | tail -1 | sed 's|/$||')}; node \"${PLUGIN_DIR}/scripts/hooks/tool-failure.mjs\" 2>/dev/null || true",
|
|
225
|
+
"command": "_gd=\"$PWD\"; while [ -n \"$_gd\" ] && [ ! -f \"$_gd/.great_cto/PROJECT.md\" ]; do _gd=\"${_gd%/*}\"; done; [ -n \"$_gd\" ] && cd \"$_gd\"; PLUGIN_DIR=${CLAUDE_PLUGIN_ROOT:-$(ls -d ~/.claude/plugins/cache/*/great_cto/*/ 2>/dev/null | sort -V | tail -1 | sed 's|/$||')}; node \"${PLUGIN_DIR}/scripts/hooks/tool-failure.mjs\" 2>/dev/null || true",
|
|
227
226
|
"timeout": 3,
|
|
228
227
|
"statusMessage": "Capturing tool failure...",
|
|
229
228
|
"async": true
|
|
@@ -235,7 +234,7 @@
|
|
|
235
234
|
"hooks": [
|
|
236
235
|
{
|
|
237
236
|
"type": "command",
|
|
238
|
-
"command": "PLUGIN_DIR=${CLAUDE_PLUGIN_ROOT:-$(ls -d ~/.claude/plugins/cache/*/great_cto/*/ 2>/dev/null | sort -V | tail -1 | sed 's|/$||')}; node \"${PLUGIN_DIR}/scripts/hooks/pipeline-dispatcher.mjs\" 2>/dev/null || true",
|
|
237
|
+
"command": "_gd=\"$PWD\"; while [ -n \"$_gd\" ] && [ ! -f \"$_gd/.great_cto/PROJECT.md\" ]; do _gd=\"${_gd%/*}\"; done; [ -n \"$_gd\" ] && cd \"$_gd\"; PLUGIN_DIR=${CLAUDE_PLUGIN_ROOT:-$(ls -d ~/.claude/plugins/cache/*/great_cto/*/ 2>/dev/null | sort -V | tail -1 | sed 's|/$||')}; node \"${PLUGIN_DIR}/scripts/hooks/pipeline-dispatcher.mjs\" 2>/dev/null || true",
|
|
239
238
|
"timeout": 5,
|
|
240
239
|
"statusMessage": "Computing next pipeline stage..."
|
|
241
240
|
}
|
|
@@ -248,13 +247,13 @@
|
|
|
248
247
|
"hooks": [
|
|
249
248
|
{
|
|
250
249
|
"type": "command",
|
|
251
|
-
"command": "PLUGIN_DIR=${CLAUDE_PLUGIN_ROOT:-$(ls -d ~/.claude/plugins/cache/*/great_cto/*/ 2>/dev/null | sort -V | tail -1 | sed 's|/$||')}; node \"${PLUGIN_DIR}/scripts/hooks/session-end.mjs\" 2>/dev/null; true",
|
|
250
|
+
"command": "_gd=\"$PWD\"; while [ -n \"$_gd\" ] && [ ! -f \"$_gd/.great_cto/PROJECT.md\" ]; do _gd=\"${_gd%/*}\"; done; [ -n \"$_gd\" ] && cd \"$_gd\"; PLUGIN_DIR=${CLAUDE_PLUGIN_ROOT:-$(ls -d ~/.claude/plugins/cache/*/great_cto/*/ 2>/dev/null | sort -V | tail -1 | sed 's|/$||')}; node \"${PLUGIN_DIR}/scripts/hooks/session-end.mjs\" 2>/dev/null; true",
|
|
252
251
|
"timeout": 8,
|
|
253
252
|
"statusMessage": "Saving session summary..."
|
|
254
253
|
},
|
|
255
254
|
{
|
|
256
255
|
"type": "command",
|
|
257
|
-
"command": "PLUGIN_DIR=${CLAUDE_PLUGIN_ROOT:-$(ls -d ~/.claude/plugins/cache/*/great_cto/*/ 2>/dev/null | sort -V | tail -1 | sed 's|/$||')}; node \"${PLUGIN_DIR}/scripts/hooks/session-status.mjs\" 2>/dev/null; true",
|
|
256
|
+
"command": "_gd=\"$PWD\"; while [ -n \"$_gd\" ] && [ ! -f \"$_gd/.great_cto/PROJECT.md\" ]; do _gd=\"${_gd%/*}\"; done; [ -n \"$_gd\" ] && cd \"$_gd\"; PLUGIN_DIR=${CLAUDE_PLUGIN_ROOT:-$(ls -d ~/.claude/plugins/cache/*/great_cto/*/ 2>/dev/null | sort -V | tail -1 | sed 's|/$||')}; node \"${PLUGIN_DIR}/scripts/hooks/session-status.mjs\" 2>/dev/null; true",
|
|
258
257
|
"timeout": 5
|
|
259
258
|
}
|
|
260
259
|
]
|
|
@@ -266,22 +265,22 @@
|
|
|
266
265
|
"hooks": [
|
|
267
266
|
{
|
|
268
267
|
"type": "command",
|
|
269
|
-
"command": "PLUGIN_DIR=${CLAUDE_PLUGIN_ROOT:-$(ls -d ~/.claude/plugins/cache/*/great_cto/*/ 2>/dev/null | sort -V | tail -1 | sed 's|/$||')}; python3 \"${PLUGIN_DIR}/scripts/hooks/user-prompt-submit.py\" 2>/dev/null; true",
|
|
268
|
+
"command": "_gd=\"$PWD\"; while [ -n \"$_gd\" ] && [ ! -f \"$_gd/.great_cto/PROJECT.md\" ]; do _gd=\"${_gd%/*}\"; done; [ -n \"$_gd\" ] && cd \"$_gd\"; PLUGIN_DIR=${CLAUDE_PLUGIN_ROOT:-$(ls -d ~/.claude/plugins/cache/*/great_cto/*/ 2>/dev/null | sort -V | tail -1 | sed 's|/$||')}; python3 \"${PLUGIN_DIR}/scripts/hooks/user-prompt-submit.py\" 2>/dev/null; true",
|
|
270
269
|
"timeout": 3
|
|
271
270
|
},
|
|
272
271
|
{
|
|
273
272
|
"type": "command",
|
|
274
|
-
"command": "PLUGIN_DIR=${CLAUDE_PLUGIN_ROOT:-$(ls -d ~/.claude/plugins/cache/*/great_cto/*/ 2>/dev/null | sort -V | tail -1 | sed 's|/$||')}; node \"${PLUGIN_DIR}/scripts/hooks/cost-guard.mjs\" 2>&1 1>/dev/null; true",
|
|
273
|
+
"command": "_gd=\"$PWD\"; while [ -n \"$_gd\" ] && [ ! -f \"$_gd/.great_cto/PROJECT.md\" ]; do _gd=\"${_gd%/*}\"; done; [ -n \"$_gd\" ] && cd \"$_gd\"; PLUGIN_DIR=${CLAUDE_PLUGIN_ROOT:-$(ls -d ~/.claude/plugins/cache/*/great_cto/*/ 2>/dev/null | sort -V | tail -1 | sed 's|/$||')}; node \"${PLUGIN_DIR}/scripts/hooks/cost-guard.mjs\" 2>&1 1>/dev/null; true",
|
|
275
274
|
"timeout": 3
|
|
276
275
|
},
|
|
277
276
|
{
|
|
278
277
|
"type": "command",
|
|
279
|
-
"command": "[ -n \"$GREAT_CTO_CLASS_TELEMETRY\" ] && { PLUGIN_DIR=${CLAUDE_PLUGIN_ROOT:-$(ls -d ~/.claude/plugins/cache/*/great_cto/*/ 2>/dev/null | sort -V | tail -1 | sed 's|/$||')}; node \"${PLUGIN_DIR}/scripts/hooks/classify-telemetry.mjs\" 2>/dev/null; }; true",
|
|
278
|
+
"command": "_gd=\"$PWD\"; while [ -n \"$_gd\" ] && [ ! -f \"$_gd/.great_cto/PROJECT.md\" ]; do _gd=\"${_gd%/*}\"; done; [ -n \"$_gd\" ] && cd \"$_gd\"; [ -n \"$GREAT_CTO_CLASS_TELEMETRY\" ] && { PLUGIN_DIR=${CLAUDE_PLUGIN_ROOT:-$(ls -d ~/.claude/plugins/cache/*/great_cto/*/ 2>/dev/null | sort -V | tail -1 | sed 's|/$||')}; node \"${PLUGIN_DIR}/scripts/hooks/classify-telemetry.mjs\" 2>/dev/null; }; true",
|
|
280
279
|
"timeout": 3
|
|
281
280
|
},
|
|
282
281
|
{
|
|
283
282
|
"type": "command",
|
|
284
|
-
"command": "PLUGIN_DIR=${CLAUDE_PLUGIN_ROOT:-$(ls -d ~/.claude/plugins/cache/*/great_cto/*/ 2>/dev/null | sort -V | tail -1 | sed 's|/$||')}; node \"${PLUGIN_DIR}/scripts/hooks/session-status.mjs\" 2>/dev/null; true",
|
|
283
|
+
"command": "_gd=\"$PWD\"; while [ -n \"$_gd\" ] && [ ! -f \"$_gd/.great_cto/PROJECT.md\" ]; do _gd=\"${_gd%/*}\"; done; [ -n \"$_gd\" ] && cd \"$_gd\"; PLUGIN_DIR=${CLAUDE_PLUGIN_ROOT:-$(ls -d ~/.claude/plugins/cache/*/great_cto/*/ 2>/dev/null | sort -V | tail -1 | sed 's|/$||')}; node \"${PLUGIN_DIR}/scripts/hooks/session-status.mjs\" 2>/dev/null; true",
|
|
285
284
|
"timeout": 5
|
|
286
285
|
}
|
|
287
286
|
]
|
|
@@ -293,7 +292,7 @@
|
|
|
293
292
|
"hooks": [
|
|
294
293
|
{
|
|
295
294
|
"type": "command",
|
|
296
|
-
"command": "INPUT=$(cat 2>/dev/null); TOOL=$(echo \"$INPUT\" | python3 -c \"import sys,json; d=json.load(sys.stdin); print(d.get('tool_name','unknown'))\" 2>/dev/null); mkdir -p .great_cto 2>/dev/null; printf '%s PERMISSION_DENIED tool=%s\\n' \"$(date -u +%Y-%m-%dT%H:%M:%SZ)\" \"$TOOL\" >> .great_cto/permission-denied.log 2>/dev/null; if [ \"$TOOL\" = \"Bash\" ] || [ \"$TOOL\" = \"Write\" ]; then printf '%s PERMISSION_DENIED %s \u2014 agent may be stuck. Run /inbox.\\n' \"$(date -u +%Y-%m-%dT%H:%M:%SZ)\" \"$TOOL\" >> .great_cto/permission-denied.log 2>/dev/null; fi; true; node \"${CLAUDE_PLUGIN_ROOT}/scripts/lib/agent-events.mjs\" --emit denied --tool \"$TOOL\" 2>/dev/null || true",
|
|
295
|
+
"command": "_gd=\"$PWD\"; while [ -n \"$_gd\" ] && [ ! -f \"$_gd/.great_cto/PROJECT.md\" ]; do _gd=\"${_gd%/*}\"; done; [ -n \"$_gd\" ] && cd \"$_gd\"; INPUT=$(cat 2>/dev/null); TOOL=$(echo \"$INPUT\" | python3 -c \"import sys,json; d=json.load(sys.stdin); print(d.get('tool_name','unknown'))\" 2>/dev/null); mkdir -p .great_cto 2>/dev/null; printf '%s PERMISSION_DENIED tool=%s\\n' \"$(date -u +%Y-%m-%dT%H:%M:%SZ)\" \"$TOOL\" >> .great_cto/permission-denied.log 2>/dev/null; if [ \"$TOOL\" = \"Bash\" ] || [ \"$TOOL\" = \"Write\" ]; then printf '%s PERMISSION_DENIED %s \u2014 agent may be stuck. Run /inbox.\\n' \"$(date -u +%Y-%m-%dT%H:%M:%SZ)\" \"$TOOL\" >> .great_cto/permission-denied.log 2>/dev/null; fi; true; node \"${CLAUDE_PLUGIN_ROOT}/scripts/lib/agent-events.mjs\" --emit denied --tool \"$TOOL\" 2>/dev/null || true",
|
|
297
296
|
"timeout": 3
|
|
298
297
|
}
|
|
299
298
|
]
|
|
@@ -304,25 +303,25 @@
|
|
|
304
303
|
"hooks": [
|
|
305
304
|
{
|
|
306
305
|
"type": "command",
|
|
307
|
-
"command": "node \"${CLAUDE_PLUGIN_ROOT}/scripts/hooks/pipeline-stall-guard.mjs\" 2>/dev/null || true"
|
|
306
|
+
"command": "_gd=\"$PWD\"; while [ -n \"$_gd\" ] && [ ! -f \"$_gd/.great_cto/PROJECT.md\" ]; do _gd=\"${_gd%/*}\"; done; [ -n \"$_gd\" ] && cd \"$_gd\"; node \"${CLAUDE_PLUGIN_ROOT}/scripts/hooks/pipeline-stall-guard.mjs\" 2>/dev/null || true"
|
|
308
307
|
},
|
|
309
308
|
{
|
|
310
309
|
"type": "command",
|
|
311
|
-
"command": "node \"${CLAUDE_PLUGIN_ROOT}/scripts/hooks/cross-review-gate.mjs\" 2>/dev/null || true"
|
|
310
|
+
"command": "_gd=\"$PWD\"; while [ -n \"$_gd\" ] && [ ! -f \"$_gd/.great_cto/PROJECT.md\" ]; do _gd=\"${_gd%/*}\"; done; [ -n \"$_gd\" ] && cd \"$_gd\"; node \"${CLAUDE_PLUGIN_ROOT}/scripts/hooks/cross-review-gate.mjs\" 2>/dev/null || true"
|
|
312
311
|
},
|
|
313
312
|
{
|
|
314
313
|
"type": "command",
|
|
315
314
|
"async": true,
|
|
316
|
-
"command": "node \"${CLAUDE_PLUGIN_ROOT}/scripts/hooks/turn-snapshot.mjs\" 2>/dev/null || true"
|
|
315
|
+
"command": "_gd=\"$PWD\"; while [ -n \"$_gd\" ] && [ ! -f \"$_gd/.great_cto/PROJECT.md\" ]; do _gd=\"${_gd%/*}\"; done; [ -n \"$_gd\" ] && cd \"$_gd\"; node \"${CLAUDE_PLUGIN_ROOT}/scripts/hooks/turn-snapshot.mjs\" 2>/dev/null || true"
|
|
317
316
|
},
|
|
318
317
|
{
|
|
319
318
|
"type": "command",
|
|
320
319
|
"async": true,
|
|
321
|
-
"command": "node \"${CLAUDE_PLUGIN_ROOT}/scripts/hooks/register-project.mjs\" 2>/dev/null || true"
|
|
320
|
+
"command": "_gd=\"$PWD\"; while [ -n \"$_gd\" ] && [ ! -f \"$_gd/.great_cto/PROJECT.md\" ]; do _gd=\"${_gd%/*}\"; done; [ -n \"$_gd\" ] && cd \"$_gd\"; node \"${CLAUDE_PLUGIN_ROOT}/scripts/hooks/register-project.mjs\" 2>/dev/null || true"
|
|
322
321
|
},
|
|
323
322
|
{
|
|
324
323
|
"type": "command",
|
|
325
|
-
"command": "PLUGIN_DIR=${CLAUDE_PLUGIN_ROOT:-$(ls -d ~/.claude/plugins/cache/*/great_cto/*/ 2>/dev/null | sort -V | tail -1 | sed 's|/$||')}; node \"${PLUGIN_DIR}/scripts/hooks/session-status.mjs\" 2>/dev/null; true",
|
|
324
|
+
"command": "_gd=\"$PWD\"; while [ -n \"$_gd\" ] && [ ! -f \"$_gd/.great_cto/PROJECT.md\" ]; do _gd=\"${_gd%/*}\"; done; [ -n \"$_gd\" ] && cd \"$_gd\"; PLUGIN_DIR=${CLAUDE_PLUGIN_ROOT:-$(ls -d ~/.claude/plugins/cache/*/great_cto/*/ 2>/dev/null | sort -V | tail -1 | sed 's|/$||')}; node \"${PLUGIN_DIR}/scripts/hooks/session-status.mjs\" 2>/dev/null; true",
|
|
326
325
|
"timeout": 5
|
|
327
326
|
}
|
|
328
327
|
]
|
|
@@ -334,7 +333,7 @@
|
|
|
334
333
|
"hooks": [
|
|
335
334
|
{
|
|
336
335
|
"type": "command",
|
|
337
|
-
"command": "PLUGIN_DIR=${CLAUDE_PLUGIN_ROOT:-$(ls -d ~/.claude/plugins/cache/*/great_cto/*/ 2>/dev/null | sort -V | tail -1 | sed 's|/$||')}; node \"${PLUGIN_DIR}/scripts/hooks/session-status.mjs\" 2>/dev/null; true",
|
|
336
|
+
"command": "_gd=\"$PWD\"; while [ -n \"$_gd\" ] && [ ! -f \"$_gd/.great_cto/PROJECT.md\" ]; do _gd=\"${_gd%/*}\"; done; [ -n \"$_gd\" ] && cd \"$_gd\"; PLUGIN_DIR=${CLAUDE_PLUGIN_ROOT:-$(ls -d ~/.claude/plugins/cache/*/great_cto/*/ 2>/dev/null | sort -V | tail -1 | sed 's|/$||')}; node \"${PLUGIN_DIR}/scripts/hooks/session-status.mjs\" 2>/dev/null; true",
|
|
338
337
|
"timeout": 5
|
|
339
338
|
}
|
|
340
339
|
]
|
|
@@ -10,6 +10,7 @@
|
|
|
10
10
|
* cost_summary — LLM spend, budget burn, top features
|
|
11
11
|
* pipeline_stages — detailed stage list with verdicts
|
|
12
12
|
* recent_verdicts — last N agent verdicts (default 10)
|
|
13
|
+
* wait_for_board_change — block until a verdict lands or a session is blocked
|
|
13
14
|
*
|
|
14
15
|
* Usage:
|
|
15
16
|
* # Start as MCP server (board must be running on --port)
|
|
@@ -31,6 +32,7 @@
|
|
|
31
32
|
*/
|
|
32
33
|
|
|
33
34
|
import { createInterface } from 'node:readline';
|
|
35
|
+
import { waitForChange } from '../../scripts/lib/board-watch.mjs';
|
|
34
36
|
|
|
35
37
|
const PORT = parseInt(process.env.GREAT_CTO_PORT || '3141', 10);
|
|
36
38
|
const BASE_URL = `http://127.0.0.1:${PORT}`;
|
|
@@ -115,6 +117,23 @@ const TOOLS = [
|
|
|
115
117
|
},
|
|
116
118
|
},
|
|
117
119
|
},
|
|
120
|
+
{
|
|
121
|
+
name: 'wait_for_board_change',
|
|
122
|
+
description:
|
|
123
|
+
'Blocks until something in this project needs you — an agent wrote a verdict ' +
|
|
124
|
+
'(BLOCKED/FAIL first), or a session stopped on a permission prompt — then returns ' +
|
|
125
|
+
'only those changes. Use it instead of polling status in a loop while parallel ' +
|
|
126
|
+
'work runs: one call, not a sleep-and-list per turn. Call first with no `since` ' +
|
|
127
|
+
'to get a cursor; pass the returned `cursor` back each time so nothing between ' +
|
|
128
|
+
'calls is missed. Reads the project files directly; the board need not be running.',
|
|
129
|
+
inputSchema: {
|
|
130
|
+
type: 'object',
|
|
131
|
+
properties: {
|
|
132
|
+
since: { type: 'string', description: 'Cursor from the previous call. Omit on the first call.' },
|
|
133
|
+
timeout_s: { type: 'number', description: 'Longest wait in seconds (1–600). Default: 300.' },
|
|
134
|
+
},
|
|
135
|
+
},
|
|
136
|
+
},
|
|
118
137
|
];
|
|
119
138
|
|
|
120
139
|
// ── HTTP helpers ───────────────────────────────────────────────────────────
|
|
@@ -245,6 +264,12 @@ async function callTool(name, args = {}) {
|
|
|
245
264
|
return lines.join('\n');
|
|
246
265
|
}
|
|
247
266
|
|
|
267
|
+
if (name === 'wait_for_board_change') {
|
|
268
|
+
// The MCP server runs in the project directory (codex-host starts it there).
|
|
269
|
+
const r = await waitForChange({ cwd: process.cwd(), since: args.since || null, timeoutS: args.timeout_s || 300 });
|
|
270
|
+
return JSON.stringify(r);
|
|
271
|
+
}
|
|
272
|
+
|
|
248
273
|
throw new Error(`Unknown tool: ${name}`);
|
|
249
274
|
}
|
|
250
275
|
|
|
@@ -0,0 +1,91 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
/**
|
|
3
|
+
* board-watch — wait until something in this project needs the caller, then say
|
|
4
|
+
* what. One blocking call instead of a polling loop.
|
|
5
|
+
*
|
|
6
|
+
* Why this exists
|
|
7
|
+
* ---------------
|
|
8
|
+
* An orchestrator waiting on parallel work (coordinator, codex-host) has no way to
|
|
9
|
+
* wait: it sleeps, lists, sleeps, lists, and every round trip is a turn. On the
|
|
10
|
+
* projects measured on 2026-09-21, 69 agent runs ended at their turn cap.
|
|
11
|
+
* fynnfluegge/agtx (Apache-2.0) counted the same cost in one 14-task run — 73
|
|
12
|
+
* sleeps and 96 listings — and replaced it with `wait_for_board_change`: block,
|
|
13
|
+
* and return only the changes that need the caller. Written here, from files the
|
|
14
|
+
* project already has, so it needs no board process:
|
|
15
|
+
*
|
|
16
|
+
* verdict an agent wrote a new verdict (its value and kind: negative first)
|
|
17
|
+
* blocked a session stopped on a permission prompt or an unanswered wait
|
|
18
|
+
*
|
|
19
|
+
* The cursor is the snapshot the caller last saw, so a change that happens between
|
|
20
|
+
* two calls is never missed: pass back the `cursor` from the previous answer.
|
|
21
|
+
*
|
|
22
|
+
* Usage:
|
|
23
|
+
* node scripts/lib/board-watch.mjs [--since <cursor>] [--timeout 300] [--cwd DIR]
|
|
24
|
+
* → one JSON line: { cursor, changes: [...], timedOut }
|
|
25
|
+
* With no --since it answers at once with the current cursor and nothing else.
|
|
26
|
+
*/
|
|
27
|
+
import { fileURLToPath } from 'node:url';
|
|
28
|
+
import { latestVerdicts } from './ship-evidence.mjs';
|
|
29
|
+
import { readSessionStatus } from './session-status.mjs';
|
|
30
|
+
|
|
31
|
+
export const MAX_TIMEOUT_S = 600;
|
|
32
|
+
|
|
33
|
+
/** What the caller could need to react to, reduced to comparable keys. */
|
|
34
|
+
export function snapshot(cwd, { now = Date.now() } = {}) {
|
|
35
|
+
const verdicts = {};
|
|
36
|
+
for (const [agent, v] of latestVerdicts(cwd)) verdicts[agent] = { verdict: v.verdict, ts: v.ts, kind: v.kind };
|
|
37
|
+
const blocked = {};
|
|
38
|
+
for (const s of readSessionStatus(cwd, { now })) {
|
|
39
|
+
if (s.state === 'blocked') blocked[s.session] = { since: s.since, reason: s.reason || '' };
|
|
40
|
+
}
|
|
41
|
+
return { verdicts, blocked };
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
/** Changes from `prev` to `next` that need the caller, most urgent first. */
|
|
45
|
+
export function diffSnapshots(prev, next) {
|
|
46
|
+
const out = [];
|
|
47
|
+
for (const [agent, v] of Object.entries(next.verdicts || {})) {
|
|
48
|
+
const p = prev.verdicts?.[agent];
|
|
49
|
+
if (!p || p.ts !== v.ts || p.verdict !== v.verdict) out.push({ type: 'verdict', agent, verdict: v.verdict, kind: v.kind, ts: v.ts });
|
|
50
|
+
}
|
|
51
|
+
for (const [session, b] of Object.entries(next.blocked || {})) {
|
|
52
|
+
const p = prev.blocked?.[session];
|
|
53
|
+
if (!p || p.since !== b.since) out.push({ type: 'blocked', session, reason: b.reason, since: b.since });
|
|
54
|
+
}
|
|
55
|
+
const rank = (c) => (c.type === 'blocked' ? 0 : c.kind === 'negative' ? 1 : 2);
|
|
56
|
+
return out.sort((a, b) => rank(a) - rank(b));
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
export const encodeCursor = (snap) => Buffer.from(JSON.stringify(snap)).toString('base64url');
|
|
60
|
+
export function decodeCursor(c) {
|
|
61
|
+
try { return JSON.parse(Buffer.from(String(c), 'base64url').toString('utf8')); } catch { return null; }
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
/**
|
|
65
|
+
* Block until a change needs the caller, or the timeout passes.
|
|
66
|
+
* @returns {Promise<{cursor:string, changes:object[], timedOut:boolean, note?:string}>}
|
|
67
|
+
*/
|
|
68
|
+
export async function waitForChange({ cwd = process.cwd(), since = null, timeoutS = 300, pollMs = 2000, sleep = (ms) => new Promise((r) => setTimeout(r, ms)) } = {}) {
|
|
69
|
+
const first = snapshot(cwd);
|
|
70
|
+
const prev = since ? decodeCursor(since) : null;
|
|
71
|
+
if (!prev) {
|
|
72
|
+
return { cursor: encodeCursor(first), changes: [], timedOut: false, note: since ? 'unreadable cursor — here is a fresh one' : 'first call — pass this cursor back to wait' };
|
|
73
|
+
}
|
|
74
|
+
const deadline = Date.now() + Math.min(MAX_TIMEOUT_S, Math.max(1, timeoutS)) * 1000;
|
|
75
|
+
let snap = first;
|
|
76
|
+
for (;;) {
|
|
77
|
+
const changes = diffSnapshots(prev, snap);
|
|
78
|
+
if (changes.length) return { cursor: encodeCursor(snap), changes, timedOut: false };
|
|
79
|
+
if (Date.now() >= deadline) return { cursor: encodeCursor(snap), changes: [], timedOut: true };
|
|
80
|
+
await sleep(Math.min(pollMs, Math.max(0, deadline - Date.now())));
|
|
81
|
+
snap = snapshot(cwd);
|
|
82
|
+
}
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
const isMain = process.argv[1] && fileURLToPath(import.meta.url) === process.argv[1];
|
|
86
|
+
if (isMain) {
|
|
87
|
+
const a = process.argv.slice(2);
|
|
88
|
+
const opt = (k, d) => { const i = a.indexOf(k); return i >= 0 ? a[i + 1] : d; };
|
|
89
|
+
const r = await waitForChange({ cwd: opt('--cwd', process.cwd()), since: opt('--since', null), timeoutS: Number(opt('--timeout', 300)) });
|
|
90
|
+
process.stdout.write(`${JSON.stringify(r)}\n`);
|
|
91
|
+
}
|
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* project-root — the directory whose .great_cto/ a hook should write to.
|
|
3
|
+
*
|
|
4
|
+
* Hooks resolved `.great_cto` against the current working directory, and a
|
|
5
|
+
* session's working directory wanders: one project on the measuring machine had
|
|
6
|
+
* `backend/.great_cto/` and `backend/src/.great_cto/` beside the real one, each
|
|
7
|
+
* with its own event log and handoff, none of them a project. The root is the
|
|
8
|
+
* nearest directory at or above `start` that holds `.great_cto/PROJECT.md`; with
|
|
9
|
+
* none, `start` itself, which is what the hooks did before.
|
|
10
|
+
*
|
|
11
|
+
* The same walk is inlined in shell at the head of every hook command in
|
|
12
|
+
* plugin.json (ROOT_SNIPPET below), so a hook is already in the root when it runs.
|
|
13
|
+
*/
|
|
14
|
+
import { existsSync } from 'node:fs';
|
|
15
|
+
import { dirname, join, resolve } from 'node:path';
|
|
16
|
+
|
|
17
|
+
export function projectRoot(start = process.cwd()) {
|
|
18
|
+
let dir = resolve(start);
|
|
19
|
+
for (;;) {
|
|
20
|
+
if (existsSync(join(dir, '.great_cto', 'PROJECT.md'))) return dir;
|
|
21
|
+
const up = dirname(dir);
|
|
22
|
+
if (up === dir) return resolve(start);
|
|
23
|
+
dir = up;
|
|
24
|
+
}
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
/** POSIX sh, no subprocess: cd to the nearest ancestor with .great_cto/PROJECT.md, if any. */
|
|
28
|
+
export const ROOT_SNIPPET = '_gd="$PWD"; while [ -n "$_gd" ] && [ ! -f "$_gd/.great_cto/PROJECT.md" ]; do _gd="${_gd%/*}"; done; [ -n "$_gd" ] && cd "$_gd"; ';
|
|
@@ -23,6 +23,7 @@
|
|
|
23
23
|
*/
|
|
24
24
|
import { mkdirSync, writeFileSync, readFileSync, readdirSync, statSync, rmSync, renameSync } from 'node:fs';
|
|
25
25
|
import { join } from 'node:path';
|
|
26
|
+
import { projectRoot } from './project-root.mjs';
|
|
26
27
|
|
|
27
28
|
export const STALE_WORKING_MS = 5 * 60 * 1000;
|
|
28
29
|
export const MOVED_ON_SLACK_MS = 2000;
|
|
@@ -34,7 +35,8 @@ const safeId = (id) => String(id || '').replace(/[^A-Za-z0-9_-]/g, '').slice(0,
|
|
|
34
35
|
|
|
35
36
|
/** Apply one hook payload. Returns what was written, or null when nothing applies. */
|
|
36
37
|
export function recordHookEvent(payload, { now = Date.now() } = {}) {
|
|
37
|
-
|
|
38
|
+
// The session's cwd wanders into subdirectories; state belongs at the project root.
|
|
39
|
+
const cwd = projectRoot(payload?.cwd || process.cwd());
|
|
38
40
|
const id = safeId(payload?.session_id);
|
|
39
41
|
if (!id) return null;
|
|
40
42
|
const dir = join(cwd, '.great_cto', 'status');
|
|
@@ -0,0 +1,115 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* ship-evidence — what the verdict logs say about whether gate:ship may pass.
|
|
3
|
+
*
|
|
4
|
+
* Read across the 16 projects with verdicts on the measuring machine (2026-09-21):
|
|
5
|
+
*
|
|
6
|
+
* - a healthcare-reviewer BLOCKED was never followed by a later verdict of its
|
|
7
|
+
* own, and the task it blocked was closed an hour later;
|
|
8
|
+
* - 11 of the 16 had no qa-engineer verdict at all;
|
|
9
|
+
* - great_cto's own last QA verdict was three weeks older than its last ship.
|
|
10
|
+
*
|
|
11
|
+
* gate-check read Beads task states and required domain reviewers; it read no
|
|
12
|
+
* verdict's VALUE. This does, three rules:
|
|
13
|
+
*
|
|
14
|
+
* 1. An agent whose LATEST verdict is negative (BLOCKED, FAIL, REJECTED, REWORK)
|
|
15
|
+
* has an open finding. A later positive verdict from the same agent closes it.
|
|
16
|
+
* 2. qa-engineer and security-officer must each have a positive latest verdict.
|
|
17
|
+
* 3. Both must be newer than the last commit that changed code — a verdict from
|
|
18
|
+
* before the change it is supposed to cover is not evidence about it.
|
|
19
|
+
*
|
|
20
|
+
* Agent names are read from the log's file name and normalised: the same agent
|
|
21
|
+
* was found logged as `code-reviewer`, `great-cto:code-reviewer`,
|
|
22
|
+
* `great_cto:code-reviewer`, and QA as both `qa` and `qa-engineer`.
|
|
23
|
+
*/
|
|
24
|
+
import { readdirSync, readFileSync } from 'node:fs';
|
|
25
|
+
import { join } from 'node:path';
|
|
26
|
+
import { spawnSync } from 'node:child_process';
|
|
27
|
+
import { parseVerdictLog } from './verdict-record.mjs';
|
|
28
|
+
|
|
29
|
+
export const MANDATORY_FOR_SHIP = ['qa-engineer', 'security-officer'];
|
|
30
|
+
|
|
31
|
+
const ALIASES = { qa: 'qa-engineer', security: 'security-officer', sec: 'security-officer' };
|
|
32
|
+
|
|
33
|
+
/** `great-cto:qa`, `great_cto:qa-engineer`, `qa` → `qa-engineer`. */
|
|
34
|
+
export function canonicalAgent(name) {
|
|
35
|
+
const bare = String(name || '').trim().toLowerCase().replace(/^.*:/, '').replace(/\.log$/, '');
|
|
36
|
+
return ALIASES[bare] || bare;
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
/** negative | positive | neutral, from a verdict value in any dialect written so far. */
|
|
40
|
+
export function verdictKind(v) {
|
|
41
|
+
const s = String(v || '').toUpperCase();
|
|
42
|
+
if (/^(BLOCKED|FAIL|FAILED|REJECTED|REWORK)\b/.test(s)) return 'negative';
|
|
43
|
+
// `APPROVED_WITH_DEFERRED_CRITICALS` and friends are approvals that name open
|
|
44
|
+
// criticals; they are not a clean pass and are read as negative.
|
|
45
|
+
if (/^APPROVED.*(DEFERRED|CRITICAL)/.test(s)) return 'negative';
|
|
46
|
+
if (/^(APPROVED|PASS|PASSED|DONE|OK)\b/.test(s)) return 'positive';
|
|
47
|
+
return 'neutral';
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
const tsOf = (ts) => { const t = Date.parse(ts); return Number.isFinite(t) ? t : null; };
|
|
51
|
+
|
|
52
|
+
/**
|
|
53
|
+
* The latest verdict per agent in `<dir>/.great_cto/verdicts/`.
|
|
54
|
+
* `gate:*.log` files record gate decisions, not an agent's verdict, and are skipped.
|
|
55
|
+
* @returns {Map<string, {verdict:string, ts:string, kind:string}>}
|
|
56
|
+
*/
|
|
57
|
+
export function latestVerdicts(projectDir) {
|
|
58
|
+
const dir = join(projectDir, '.great_cto', 'verdicts');
|
|
59
|
+
const out = new Map();
|
|
60
|
+
let files = [];
|
|
61
|
+
try { files = readdirSync(dir).filter((f) => f.endsWith('.log') && !f.startsWith('gate')); } catch { return out; }
|
|
62
|
+
for (const f of files) {
|
|
63
|
+
const agent = canonicalAgent(f);
|
|
64
|
+
let text = '';
|
|
65
|
+
try { text = readFileSync(join(dir, f), 'utf8'); } catch { continue; }
|
|
66
|
+
for (const rec of parseVerdictLog(text, { agent }).records) {
|
|
67
|
+
const t = tsOf(rec.ts);
|
|
68
|
+
if (t === null) continue;
|
|
69
|
+
const prev = out.get(agent);
|
|
70
|
+
if (!prev || t >= tsOf(prev.ts)) out.set(agent, { verdict: rec.verdict, ts: rec.ts, kind: verdictKind(rec.verdict) });
|
|
71
|
+
}
|
|
72
|
+
}
|
|
73
|
+
return out;
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
/** ISO time of the last commit that changed something other than docs and pipeline records. */
|
|
77
|
+
export function lastCodeChange(projectDir) {
|
|
78
|
+
const r = spawnSync('git', ['log', '-1', '--format=%cI', '--', '.',
|
|
79
|
+
':(exclude).great_cto', ':(exclude)docs', ':(exclude)*.md', ':(exclude).beads'],
|
|
80
|
+
{ cwd: projectDir, encoding: 'utf8', timeout: 10_000 });
|
|
81
|
+
return r.status === 0 && r.stdout.trim() ? r.stdout.trim() : null;
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
/**
|
|
85
|
+
* @param {Map} latest from latestVerdicts()
|
|
86
|
+
* @param {string|null} lastChange from lastCodeChange()
|
|
87
|
+
* @param {{as?: string}} opts `as`: the agent running this check about its own
|
|
88
|
+
* verdict — its own latest is not held against it.
|
|
89
|
+
* @returns {{agent:string, why:string}[]} what blocks gate:ship, each with its reason
|
|
90
|
+
*/
|
|
91
|
+
export function shipBlockers(latest, lastChange, { as } = {}) {
|
|
92
|
+
const self = as ? canonicalAgent(as) : null;
|
|
93
|
+
const out = [];
|
|
94
|
+
for (const [agent, v] of latest) {
|
|
95
|
+
if (agent === self || v.kind !== 'negative') continue;
|
|
96
|
+
out.push({ agent, why: `latest verdict ${v.verdict} (${v.ts}) — no later verdict from ${agent} closes it` });
|
|
97
|
+
}
|
|
98
|
+
for (const agent of MANDATORY_FOR_SHIP) {
|
|
99
|
+
if (agent === self) continue;
|
|
100
|
+
const v = latest.get(agent);
|
|
101
|
+
if (!v) { out.push({ agent, why: 'no verdict in this project' }); continue; }
|
|
102
|
+
if (v.kind === 'neutral') out.push({ agent, why: `latest verdict ${v.verdict} is not a pass` });
|
|
103
|
+
}
|
|
104
|
+
// A verdict counts for the code it saw. Both post-implementation checks must be
|
|
105
|
+
// newer than the last code change; agtx (fynnfluegge/agtx) applies the same rule
|
|
106
|
+
// to a phase artifact — only one written after the phase began counts.
|
|
107
|
+
for (const agent of MANDATORY_FOR_SHIP) {
|
|
108
|
+
if (agent === self) continue;
|
|
109
|
+
const v = latest.get(agent);
|
|
110
|
+
if (v && v.kind === 'positive' && lastChange && tsOf(v.ts) !== null && tsOf(v.ts) < tsOf(lastChange)) {
|
|
111
|
+
out.push({ agent, why: `verdict ${v.ts} predates the last code change ${lastChange} — re-run ${agent} on what ships` });
|
|
112
|
+
}
|
|
113
|
+
}
|
|
114
|
+
return out;
|
|
115
|
+
}
|
package/package.json
CHANGED