great-cto 3.70.1 → 3.72.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.
Files changed (48) hide show
  1. package/README.md +9 -1
  2. package/board/.claude-plugin/plugin.json +93 -1
  3. package/board/agents/architect.md +1 -1
  4. package/board/agents/db-migration-reviewer.md +3 -2
  5. package/board/agents/design-advisor.md +1 -1
  6. package/board/agents/devops.md +1 -1
  7. package/board/agents/growth-engineer.md +1 -1
  8. package/board/agents/knowledge-extractor.md +1 -1
  9. package/board/agents/l3-support.md +1 -1
  10. package/board/agents/performance-engineer.md +1 -1
  11. package/board/agents/pm.md +5 -1
  12. package/board/agents/qa-engineer.md +4 -4
  13. package/board/agents/security-officer.md +2 -2
  14. package/board/agents/smart-contract-auditor.md +1 -1
  15. package/board/packages/board/lib/routes.mjs +14 -1
  16. package/board/packages/board/lib/work.mjs +34 -3
  17. package/board/packages/board/public/assets/work-view.js +146 -18
  18. package/board/scripts/codex-pipeline.mjs +2 -0
  19. package/board/scripts/lib/approval-level.mjs +4 -2
  20. package/board/scripts/lib/claude-exec.mjs +49 -6
  21. package/board/scripts/lib/codex-exec.mjs +42 -18
  22. package/board/scripts/lib/codex-host-state.mjs +18 -2
  23. package/board/scripts/lib/codex-pipeline.mjs +44 -7
  24. package/board/scripts/lib/host-graph-bindings.mjs +135 -0
  25. package/board/scripts/lib/host-graph-events.mjs +157 -0
  26. package/board/scripts/lib/host-graph-journal.mjs +210 -0
  27. package/board/scripts/lib/host-graph-observer.mjs +61 -0
  28. package/board/scripts/lib/host-graph-runtime.mjs +38 -0
  29. package/board/scripts/work-task.mjs +17 -4
  30. package/board/shared/pipeline.toml +7 -1
  31. package/dist/adapt.js +10 -21
  32. package/dist/codex.js +4 -15
  33. package/dist/companion.js +6 -122
  34. package/dist/installer.js +4 -72
  35. package/dist/main.js +44 -117
  36. package/dist/release-activation.mjs +561 -0
  37. package/dist/release-archive.mjs +165 -0
  38. package/dist/release-trust.mjs +514 -0
  39. package/dist/release-update.mjs +303 -0
  40. package/dist/self-upgrade.js +10 -39
  41. package/dist/update-check.js +1 -0
  42. package/dist/upgrade.js +5 -120
  43. package/package.json +1 -1
  44. package/board/packages/board/fixtures/beads-read-owner.mjs +0 -3
  45. package/board/packages/board/fixtures/fake-bd-controlled.mjs +0 -55
  46. package/board/packages/board/fixtures/fake-bd-fail.sh +0 -9
  47. package/board/packages/board/fixtures/fake-bd-slow.sh +0 -12
  48. package/board/packages/board/fixtures/held-receipt-git.mjs +0 -21
package/README.md CHANGED
@@ -1,6 +1,14 @@
1
1
  # great-cto
2
2
 
3
- > One command install for the [great_cto](https://github.com/avelikiy/great_cto) Claude Code plugin.
3
+ > CLI for the [great_cto](https://github.com/avelikiy/great_cto) engineering workspace, controlled runs and verified release updates.
4
+
5
+ **Verified distribution boundary:** ADR-045 replaces mutable installation and update
6
+ paths with signed artifact admission. This implementation refuses `init`, native
7
+ plugin refresh and automatic companion installation before mutation. The plain
8
+ npm self-update adapter requires an independently configured verifier and exact
9
+ signed release assets. See [verified updates](../../docs/reference/verified-updates.md).
10
+ The installation walkthrough below describes the legacy 3.70.1 flow; it is not
11
+ an activation route for this implementation.
4
12
 
5
13
  ```bash
6
14
  npx great-cto init
@@ -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 72 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.70.1",
4
+ "version": "3.72.0",
5
5
  "author": {
6
6
  "name": "Alexander Velikiy",
7
7
  "url": "https://hashnode.com/@Greatcto"
@@ -88,6 +88,16 @@
88
88
  "timeout": 10
89
89
  }
90
90
  ]
91
+ },
92
+ {
93
+ "matcher": "",
94
+ "hooks": [
95
+ {
96
+ "type": "command",
97
+ "timeout": 5,
98
+ "command": "_gd=\"$PWD\"; while [ -n \"$_gd\" ] && { [ \"$_gd\" = \"$HOME\" ] || [ ! -f \"$_gd/.great_cto/PROJECT.md\" ]; }; do _gd=\"${_gd%/*}\"; done; [ -n \"$_gd\" ] && cd \"$_gd\"; [ -d \"${GREAT_CTO_GRAPH_BINDINGS_DIR:-$HOME/.great_cto/graph-bindings}\" ] || exit 0; _pd=\"${CLAUDE_PLUGIN_ROOT:-${PLUGIN_ROOT:-}}\"; [ -n \"$_pd\" ] || exit 0; GREAT_CTO_HOST=claude-code node \"$_pd/scripts/hooks/host-graph-observer.mjs\""
99
+ }
100
+ ]
91
101
  }
92
102
  ],
93
103
  "PreCompact": [
@@ -120,6 +130,16 @@
120
130
  "statusMessage": "Loading orchestrator contract..."
121
131
  }
122
132
  ]
133
+ },
134
+ {
135
+ "matcher": "",
136
+ "hooks": [
137
+ {
138
+ "type": "command",
139
+ "timeout": 5,
140
+ "command": "_gd=\"$PWD\"; while [ -n \"$_gd\" ] && { [ \"$_gd\" = \"$HOME\" ] || [ ! -f \"$_gd/.great_cto/PROJECT.md\" ]; }; do _gd=\"${_gd%/*}\"; done; [ -n \"$_gd\" ] && cd \"$_gd\"; [ -d \"${GREAT_CTO_GRAPH_BINDINGS_DIR:-$HOME/.great_cto/graph-bindings}\" ] || exit 0; _pd=\"${CLAUDE_PLUGIN_ROOT:-${PLUGIN_ROOT:-}}\"; [ -n \"$_pd\" ] || exit 0; GREAT_CTO_HOST=claude-code node \"$_pd/scripts/hooks/host-graph-observer.mjs\""
141
+ }
142
+ ]
123
143
  }
124
144
  ],
125
145
  "SubagentStop": [
@@ -138,6 +158,16 @@
138
158
  "command": "_gd=\"$PWD\"; while [ -n \"$_gd\" ] && { [ \"$_gd\" = \"$HOME\" ] || [ ! -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"
139
159
  }
140
160
  ]
161
+ },
162
+ {
163
+ "matcher": "",
164
+ "hooks": [
165
+ {
166
+ "type": "command",
167
+ "timeout": 5,
168
+ "command": "_gd=\"$PWD\"; while [ -n \"$_gd\" ] && { [ \"$_gd\" = \"$HOME\" ] || [ ! -f \"$_gd/.great_cto/PROJECT.md\" ]; }; do _gd=\"${_gd%/*}\"; done; [ -n \"$_gd\" ] && cd \"$_gd\"; [ -d \"${GREAT_CTO_GRAPH_BINDINGS_DIR:-$HOME/.great_cto/graph-bindings}\" ] || exit 0; _pd=\"${CLAUDE_PLUGIN_ROOT:-${PLUGIN_ROOT:-}}\"; [ -n \"$_pd\" ] || exit 0; GREAT_CTO_HOST=claude-code node \"$_pd/scripts/hooks/host-graph-observer.mjs\""
169
+ }
170
+ ]
141
171
  }
142
172
  ],
143
173
  "PreToolUse": [
@@ -208,6 +238,16 @@
208
238
  "statusMessage": "Checking recorded lessons..."
209
239
  }
210
240
  ]
241
+ },
242
+ {
243
+ "matcher": "",
244
+ "hooks": [
245
+ {
246
+ "type": "command",
247
+ "timeout": 5,
248
+ "command": "_gd=\"$PWD\"; while [ -n \"$_gd\" ] && { [ \"$_gd\" = \"$HOME\" ] || [ ! -f \"$_gd/.great_cto/PROJECT.md\" ]; }; do _gd=\"${_gd%/*}\"; done; [ -n \"$_gd\" ] && cd \"$_gd\"; [ -d \"${GREAT_CTO_GRAPH_BINDINGS_DIR:-$HOME/.great_cto/graph-bindings}\" ] || exit 0; _pd=\"${CLAUDE_PLUGIN_ROOT:-${PLUGIN_ROOT:-}}\"; [ -n \"$_pd\" ] || exit 0; GREAT_CTO_HOST=claude-code node \"$_pd/scripts/hooks/host-graph-observer.mjs\""
249
+ }
250
+ ]
211
251
  }
212
252
  ],
213
253
  "PostToolUse": [
@@ -293,6 +333,16 @@
293
333
  "statusMessage": "Computing next pipeline stage..."
294
334
  }
295
335
  ]
336
+ },
337
+ {
338
+ "matcher": "",
339
+ "hooks": [
340
+ {
341
+ "type": "command",
342
+ "timeout": 5,
343
+ "command": "_gd=\"$PWD\"; while [ -n \"$_gd\" ] && { [ \"$_gd\" = \"$HOME\" ] || [ ! -f \"$_gd/.great_cto/PROJECT.md\" ]; }; do _gd=\"${_gd%/*}\"; done; [ -n \"$_gd\" ] && cd \"$_gd\"; [ -d \"${GREAT_CTO_GRAPH_BINDINGS_DIR:-$HOME/.great_cto/graph-bindings}\" ] || exit 0; _pd=\"${CLAUDE_PLUGIN_ROOT:-${PLUGIN_ROOT:-}}\"; [ -n \"$_pd\" ] || exit 0; GREAT_CTO_HOST=claude-code node \"$_pd/scripts/hooks/host-graph-observer.mjs\""
344
+ }
345
+ ]
296
346
  }
297
347
  ],
298
348
  "SessionEnd": [
@@ -311,6 +361,16 @@
311
361
  "timeout": 5
312
362
  }
313
363
  ]
364
+ },
365
+ {
366
+ "matcher": "",
367
+ "hooks": [
368
+ {
369
+ "type": "command",
370
+ "timeout": 5,
371
+ "command": "_gd=\"$PWD\"; while [ -n \"$_gd\" ] && { [ \"$_gd\" = \"$HOME\" ] || [ ! -f \"$_gd/.great_cto/PROJECT.md\" ]; }; do _gd=\"${_gd%/*}\"; done; [ -n \"$_gd\" ] && cd \"$_gd\"; [ -d \"${GREAT_CTO_GRAPH_BINDINGS_DIR:-$HOME/.great_cto/graph-bindings}\" ] || exit 0; _pd=\"${CLAUDE_PLUGIN_ROOT:-${PLUGIN_ROOT:-}}\"; [ -n \"$_pd\" ] || exit 0; GREAT_CTO_HOST=claude-code node \"$_pd/scripts/hooks/host-graph-observer.mjs\""
372
+ }
373
+ ]
314
374
  }
315
375
  ],
316
376
  "UserPromptSubmit": [
@@ -338,6 +398,16 @@
338
398
  "timeout": 5
339
399
  }
340
400
  ]
401
+ },
402
+ {
403
+ "matcher": "",
404
+ "hooks": [
405
+ {
406
+ "type": "command",
407
+ "timeout": 5,
408
+ "command": "_gd=\"$PWD\"; while [ -n \"$_gd\" ] && { [ \"$_gd\" = \"$HOME\" ] || [ ! -f \"$_gd/.great_cto/PROJECT.md\" ]; }; do _gd=\"${_gd%/*}\"; done; [ -n \"$_gd\" ] && cd \"$_gd\"; [ -d \"${GREAT_CTO_GRAPH_BINDINGS_DIR:-$HOME/.great_cto/graph-bindings}\" ] || exit 0; _pd=\"${CLAUDE_PLUGIN_ROOT:-${PLUGIN_ROOT:-}}\"; [ -n \"$_pd\" ] || exit 0; GREAT_CTO_HOST=claude-code node \"$_pd/scripts/hooks/host-graph-observer.mjs\""
409
+ }
410
+ ]
341
411
  }
342
412
  ],
343
413
  "PermissionDenied": [
@@ -384,6 +454,16 @@
384
454
  "timeout": 5
385
455
  }
386
456
  ]
457
+ },
458
+ {
459
+ "matcher": "",
460
+ "hooks": [
461
+ {
462
+ "type": "command",
463
+ "timeout": 5,
464
+ "command": "_gd=\"$PWD\"; while [ -n \"$_gd\" ] && { [ \"$_gd\" = \"$HOME\" ] || [ ! -f \"$_gd/.great_cto/PROJECT.md\" ]; }; do _gd=\"${_gd%/*}\"; done; [ -n \"$_gd\" ] && cd \"$_gd\"; [ -d \"${GREAT_CTO_GRAPH_BINDINGS_DIR:-$HOME/.great_cto/graph-bindings}\" ] || exit 0; _pd=\"${CLAUDE_PLUGIN_ROOT:-${PLUGIN_ROOT:-}}\"; [ -n \"$_pd\" ] || exit 0; GREAT_CTO_HOST=claude-code node \"$_pd/scripts/hooks/host-graph-observer.mjs\""
465
+ }
466
+ ]
387
467
  }
388
468
  ],
389
469
  "Notification": [
@@ -397,6 +477,18 @@
397
477
  }
398
478
  ]
399
479
  }
480
+ ],
481
+ "PostToolUseFailure": [
482
+ {
483
+ "matcher": "",
484
+ "hooks": [
485
+ {
486
+ "type": "command",
487
+ "timeout": 5,
488
+ "command": "_gd=\"$PWD\"; while [ -n \"$_gd\" ] && { [ \"$_gd\" = \"$HOME\" ] || [ ! -f \"$_gd/.great_cto/PROJECT.md\" ]; }; do _gd=\"${_gd%/*}\"; done; [ -n \"$_gd\" ] && cd \"$_gd\"; [ -d \"${GREAT_CTO_GRAPH_BINDINGS_DIR:-$HOME/.great_cto/graph-bindings}\" ] || exit 0; _pd=\"${CLAUDE_PLUGIN_ROOT:-${PLUGIN_ROOT:-}}\"; [ -n \"$_pd\" ] || exit 0; GREAT_CTO_HOST=claude-code node \"$_pd/scripts/hooks/host-graph-observer.mjs\""
489
+ }
490
+ ]
491
+ }
400
492
  ]
401
493
  },
402
494
  "homepage": "https://greatcto.systems",
@@ -376,7 +376,7 @@ constraint in the new ARCH doc — document why this design choice was not taken
376
376
  ARCHETYPE=$(grep "^archetype:" .great_cto/PROJECT.md 2>/dev/null | awk '{print $2}'); ARCHETYPE=${ARCHETYPE:-web-service}
377
377
  PROJECT_SIZE=$(grep "^project_size:" .great_cto/PROJECT.md 2>/dev/null | awk '{print $2}'); PROJECT_SIZE=${PROJECT_SIZE:-medium}
378
378
  SECURITY_GATE=$(grep "^security-gate:" .great_cto/PROJECT.md 2>/dev/null | awk '{print $2}'); SECURITY_GATE=${SECURITY_GATE:-conditional}
379
- COMPLIANCE=$(grep "^compliance:" .great_cto/PROJECT.md 2>/dev/null | sed 's/compliance: //' || echo "[]")
379
+ COMPLIANCE=$(grep "^compliance:" .great_cto/PROJECT.md 2>/dev/null | sed 's/compliance: //'); COMPLIANCE=${COMPLIANCE:-[]}
380
380
  ```
381
381
  Read ARCHETYPES.md for QA strategy, deploy method, and thresholds matching `$ARCHETYPE`. Use these as constraints for the architecture design.
382
382
  - **Greenfield check** — determines how deep to read existing code:
@@ -48,8 +48,9 @@ fi
48
48
  echo "Migrations to review:"
49
49
  echo "$MIGRATIONS"
50
50
 
51
- DB_ENGINE=$(grep "^db:" .great_cto/PROJECT.md 2>/dev/null | awk '{print $2}' || \
52
- grep -rn "postgresql\|mysql\|sqlite\|aurora\|cockroach\|planetscale" .great_cto/PROJECT.md 2>/dev/null | head -1 | grep -oE "postgresql|mysql|sqlite|aurora|cockroach|planetscale" | head -1 || echo "unknown")
51
+ DB_ENGINE=$(grep "^db:" .great_cto/PROJECT.md 2>/dev/null | awk '{print $2}')
52
+ [ -n "$DB_ENGINE" ] || DB_ENGINE=$(grep -oE "postgresql|mysql|sqlite|aurora|cockroach|planetscale" .great_cto/PROJECT.md 2>/dev/null | head -1)
53
+ DB_ENGINE=${DB_ENGINE:-unknown}
53
54
 
54
55
  # Slug from latest ARCH doc; date fallback must be an explicit branch —
55
56
  # `|| echo` after a pipeline never fires (basename "" exits 0 with empty output)
@@ -1,6 +1,6 @@
1
1
  ---
2
2
  name: design-advisor
3
- description: Use after architect, before/parallel to pm, for any UI-bearing feature (landing pages, dashboards, admin panels, web apps, React Native apps). Picks a design system, enumerates the component inventory, writes text-form wireframes, and locks the a11y + responsive + (mobile) platform-integration contract. Outputs docs/design/DESIGN-{slug}.md. Plan altitude only — never writes implementation code.
3
+ description: Use after architect and before pm, for any UI-bearing feature (landing pages, dashboards, admin panels, web apps, React Native apps). Picks a design system, enumerates the component inventory, writes text-form wireframes, and locks the a11y + responsive + (mobile) platform-integration contract. Outputs docs/design/DESIGN-{slug}.md. Plan altitude only — never writes implementation code.
4
4
  model: claude-opus-5
5
5
  authority: autonomous
6
6
  tools: Read, Write, Glob, Grep, WebFetch, Bash(git:*), Bash(bd:*), Bash(ls:*), Bash(cat:*), Bash(find:*), Bash(touch:*), Bash(head:*), Bash(tail:*), Bash(grep:*), Bash(wc:*), Bash(date:*), Bash(printf:*), Bash(echo:*), Bash(mkdir:*), memory_20250929, advisor_20260301
@@ -572,7 +572,7 @@ step for that condition before proceeding to Step 1 (gate:ship check).
572
572
  ESTIMATED=${ESTIMATED_RAW:-0}
573
573
  CONSOLE_URL=$(grep "console-url:" .great_cto/PROJECT.md 2>/dev/null | awk '{print $2}')
574
574
  SERVICE=$(grep "^service:" .great_cto/PROJECT.md 2>/dev/null | awk '{print $2}' || basename "$PWD")
575
- FEATURE=$(basename "$ARCH_DOC" .md 2>/dev/null | sed 's/^ARCH-//' || echo "unknown")
575
+ FEATURE=$(basename "$ARCH_DOC" .md 2>/dev/null | sed 's/^ARCH-//'); FEATURE=${FEATURE:-unknown}
576
576
 
577
577
  COST_LOG=.great_cto/cost-history.log
578
578
  [ ! -f "$COST_LOG" ] && printf '# Cost history — append only. Format: ISO8601 | service | estimated_usd_month | actual_usd_month | source | feature\n' > "$COST_LOG"
@@ -91,7 +91,7 @@ ARCH_FILE=$(ls -t docs/architecture/ARCH-*.md 2>/dev/null | head -1)
91
91
  SLUG=$(basename "$ARCH_FILE" .md | sed 's/^ARCH-//')
92
92
 
93
93
  ARCHETYPE=$(grep "^archetype:" .great_cto/PROJECT.md 2>/dev/null | awk '{print $2}')
94
- GROWTH_GOAL=$(grep "^growth-goal:" .great_cto/PROJECT.md 2>/dev/null | sed 's/growth-goal: //' || echo "not specified")
94
+ GROWTH_GOAL=$(grep "^growth-goal:" .great_cto/PROJECT.md 2>/dev/null | sed 's/growth-goal: //'); GROWTH_GOAL=${GROWTH_GOAL:-not specified}
95
95
  # Is there an analytics/connector layer we can read from?
96
96
  CONNECT=$(ls docs/connectors/CONNECT-*.md 2>/dev/null | head -1)
97
97
  echo "slug=$SLUG archetype=$ARCHETYPE goal='$GROWTH_GOAL' connector=${CONNECT:-none}"
@@ -28,7 +28,7 @@ Run in parallel:
28
28
  cat .great_cto/lessons.md 2>/dev/null || echo "(no lessons yet)"
29
29
 
30
30
  # Cross-project decisions (supplement)
31
- cat ~/.great_cto/decisions.md 2>/dev/null | head -300 || echo "(none)"
31
+ D=$(head -300 ~/.great_cto/decisions.md 2>/dev/null); [ -n "$D" ] && printf '%s\n' "$D" || echo "(none)"
32
32
 
33
33
  # Session log pattern lines across all sessions
34
34
  grep -h "^## pattern:" .great_cto/logs/session-*-end.md 2>/dev/null \
@@ -327,7 +327,7 @@ node "$PLUGIN_DIR/scripts/lib/pattern-lookup.mjs" --role incident
327
327
 
328
328
  # Priority 4: systemd / journalctl
329
329
  if [ -z "$LOG_PATH" ] && [ -z "$APP_CONTAINER" ]; then
330
- journalctl --since "30 min ago" -p err 2>/dev/null | tail -50 || \
330
+ { J=$(journalctl --since "30 min ago" -p err 2>/dev/null | tail -50); [ -n "$J" ] && printf '%s\n' "$J"; } || \
331
331
  echo "No log source found. Add 'error-log: /path/to/log' to PROJECT.md L3 section."
332
332
  fi
333
333
 
@@ -76,7 +76,7 @@ ARCH_FILE=$(ls -t docs/architecture/ARCH-*.md 2>/dev/null | head -1)
76
76
  [ -z "$ARCH_FILE" ] && { echo "BLOCKED: no ARCH doc" >&2; exit 1; }
77
77
  SLUG=$(basename "$ARCH_FILE" .md | sed 's/^ARCH-//')
78
78
 
79
- PERF_SLA=$(grep "^performance-sla:" .great_cto/PROJECT.md 2>/dev/null | sed 's/performance-sla: //' || echo "not specified")
79
+ PERF_SLA=$(grep "^performance-sla:" .great_cto/PROJECT.md 2>/dev/null | sed 's/performance-sla: //'); PERF_SLA=${PERF_SLA:-not specified}
80
80
  MONTHLY_RPS=$(grep "^expected-rps:" .great_cto/PROJECT.md 2>/dev/null | awk '{print $2}'); MONTHLY_RPS=${MONTHLY_RPS:-unknown}
81
81
 
82
82
  # Check for existing baseline
@@ -115,6 +115,10 @@ PHASE=$(grep "^phase:" .great_cto/PROJECT.md 2>/dev/null | awk '{print $2}'); PH
115
115
  TEAM_SIZE=$(grep "^team-size:" .great_cto/PROJECT.md 2>/dev/null | awk '{print $2}'); TEAM_SIZE=${TEAM_SIZE:-1}
116
116
  MONTHLY_BUDGET=$(grep "^monthly-budget-llm-usd:" .great_cto/PROJECT.md 2>/dev/null | awk '{print $2}')
117
117
 
118
+ # Design contract — design-advisor runs before pm on UI features; plan UI tasks against it
119
+ DESIGN_DOC=$(ls -t docs/design/DESIGN-*.md 2>/dev/null | head -1)
120
+ [ -n "$DESIGN_DOC" ] && echo "=== DESIGN CONTRACT: $DESIGN_DOC (components, a11y, responsive — one task per screen/component group) ==="
121
+
118
122
  # Past lessons — calibrate cost/time estimates against actuals
119
123
  if [ -f .great_cto/lessons.md ]; then
120
124
  COST_LESSONS=$(grep -B1 -A4 "shape: B" .great_cto/lessons.md 2>/dev/null | head -20)
@@ -138,7 +142,7 @@ FEATURE_SLUG=$(basename "$ARCH_FILE" .md | sed 's/^ARCH-//' | tr '[:upper:]' '[:
138
142
 
139
143
  # Goal ancestry (Paperclip pattern) — injected into every bd create call.
140
144
  # Built AFTER FEATURE_SLUG is known (it interpolates the slug).
141
- COMPLIANCE=$(grep "^compliance:" .great_cto/PROJECT.md 2>/dev/null | sed 's/compliance: //' || echo "")
145
+ COMPLIANCE=$(grep "^compliance:" .great_cto/PROJECT.md 2>/dev/null | sed 's/compliance: //')
142
146
  _COMP_CLEAN=$(echo "$COMPLIANCE" | sed 's/^\[none\]$//;s/^\[none, *\]//;s/, *\[none\]//')
143
147
  GOAL_ANCESTRY="[archetype:${ARCHETYPE}]$([ -n "$_COMP_CLEAN" ] && echo " [compliance:${_COMP_CLEAN}]") [feature:${FEATURE_SLUG}] [phase:${PHASE}] | Why: see docs/plans/PLAN-${FEATURE_SLUG}.md"
144
148
 
@@ -286,9 +286,9 @@ PROJECT_SIZE=$(grep "^project_size:" .great_cto/PROJECT.md 2>/dev/null | awk '{p
286
286
 
287
287
  ```bash
288
288
  ARCHETYPE=$(grep "^archetype:" .great_cto/PROJECT.md 2>/dev/null | awk '{print $2}'); ARCHETYPE=${ARCHETYPE:-web-service}
289
- COMPLIANCE=$(grep "^compliance:" .great_cto/PROJECT.md 2>/dev/null | sed 's/compliance: //' || echo "[]")
290
- QA_EXTRAS=$(grep "^qa-extras:" .great_cto/PROJECT.md 2>/dev/null | sed 's/qa-extras: //' || echo "[]")
291
- PERF_SLA=$(grep "^performance-sla:" .great_cto/PROJECT.md 2>/dev/null | sed 's/performance-sla: //' || echo "")
289
+ COMPLIANCE=$(grep "^compliance:" .great_cto/PROJECT.md 2>/dev/null | sed 's/compliance: //'); COMPLIANCE=${COMPLIANCE:-[]}
290
+ QA_EXTRAS=$(grep "^qa-extras:" .great_cto/PROJECT.md 2>/dev/null | sed 's/qa-extras: //'); QA_EXTRAS=${QA_EXTRAS:-[]}
291
+ PERF_SLA=$(grep "^performance-sla:" .great_cto/PROJECT.md 2>/dev/null | sed 's/performance-sla: //')
292
292
  ```
293
293
 
294
294
  Read ARCHETYPES.md → find QA strategy row for `$ARCHETYPE`. This gives base QA plan.
@@ -362,7 +362,7 @@ PII column additions) that functional tests miss by design — the
362
362
  specialist agent owns this.
363
363
 
364
364
  ```bash
365
- BASE=$(git rev-parse --abbrev-ref --symbolic-full-name @{u} 2>/dev/null | sed 's|.*/||' || echo "main")
365
+ BASE=$(git rev-parse --abbrev-ref --symbolic-full-name @{u} 2>/dev/null | sed 's|.*/||'); BASE=${BASE:-main}
366
366
  MIGRATION_HIT=$(git diff "$BASE"...HEAD --name-only 2>/dev/null | \
367
367
  grep -E '(^|/)(migrations|alembic/versions|db/migrate|prisma/migrations|flyway|knex/migrations)/.*\.(sql|py|ts|js|rb)$' | head -5)
368
368
 
@@ -424,7 +424,7 @@ Continues from the original Workflow below — produces `CSO-{slug}-{date}.md`,
424
424
  1b. **Read** `.great_cto/PROJECT.md` → get `archetype`, `type`, `stack`, and `compliance` params:
425
425
  ```bash
426
426
  ARCHETYPE=$(grep "^archetype:" .great_cto/PROJECT.md 2>/dev/null | awk '{print $2}'); ARCHETYPE=${ARCHETYPE:-web-service}
427
- COMPLIANCE=$(grep "^compliance:" .great_cto/PROJECT.md 2>/dev/null | sed 's/compliance: //' || echo "[]")
427
+ COMPLIANCE=$(grep "^compliance:" .great_cto/PROJECT.md 2>/dev/null | sed 's/compliance: //'); COMPLIANCE=${COMPLIANCE:-[]}
428
428
  ```
429
429
  Read ARCHETYPES.md → find security rules for `$ARCHETYPE`.
430
430
  The `compliance:` list in PROJECT.md determines which checklists to run (see Step 5).
@@ -670,7 +670,7 @@ Observations (signal < 2 or no direct evidence): record in a separate `## Observ
670
670
  the pipeline dispatcher and the board parse this line):
671
671
  ```bash
672
672
  bash scripts/log-verdict.sh security-officer <APPROVED|BLOCKED> auto \
673
- findings=P0:<n>,P1:<n>,P2:<n> triaged=<n> valid=<n> invalid=<n> feature=<slug> need=<implementer|decision> finding=<id>
673
+ findings=P0:<n>,P1:<n>,P2:<n> triaged=<n> valid=<n> invalid=<n> feature=<slug> report=docs/security/CSO-<YYYY-MM-DD>.md need=<implementer|decision> finding=<id>
674
674
  ```
675
675
  On BLOCKED, `need` mirrors the report's `need` line: `implementer` for
676
676
  "senior-dev fix <finding>", `decision` for "CTO waive risk on <finding>".
@@ -318,7 +318,7 @@ REPORT=docs/security/AUDIT-{slug}.md
318
318
  [ -s "$REPORT" ] || { echo "STOP: no audit report at $REPORT — write it first." >&2; exit 1; }
319
319
 
320
320
  bash scripts/log-verdict.sh smart-contract-auditor <PASS|FAIL|BLOCKED> auto \
321
- feature=<slug> "audit=$REPORT" findings=C:<n>,H:<n>,M:<n> not_checked=<n> need=<implementer|decision> finding=<id>
321
+ feature=<slug> "report=$REPORT" findings=C:<n>,H:<n>,M:<n> not_checked=<n> need=<implementer|decision> finding=<id>
322
322
  ```
323
323
 
324
324
  In prepare mode add `package=docs/security/audit-package-{slug}` and
@@ -45,7 +45,7 @@ import { readVerdicts } from './verdicts.mjs';
45
45
  import { parseAgentBudgets, upsertAgentBudget, removeAgentBudget } from '../../../scripts/lib/agent-budget.mjs';
46
46
  import { resolveSecondOpinion, SECOND_OPINION_PROVIDERS } from '../../../scripts/lib/second-opinion.mjs';
47
47
  import { detectCodex } from '../../../scripts/lib/codex-exec.mjs';
48
- import { getWork } from './work.mjs';
48
+ import { getWork, getHostGraphPage } from './work.mjs';
49
49
  let publicationPreviewBusy = false;
50
50
  let publicationWriteBusy = false;
51
51
  import { publicationTickets, localPublicationOrigin } from './publication-tickets.mjs';
@@ -237,6 +237,19 @@ async function dispatch(req, res, url, cwd) {
237
237
  return true;
238
238
  }
239
239
 
240
+ if (pathname === '/api/host-graph') {
241
+ if (requestedProject && resolveProjectInfo(requestedProject).resolved === 'fallback') {
242
+ res.writeHead(404, { 'Content-Type': 'application/json', 'Cache-Control': 'no-store' }); res.end(JSON.stringify({ error: 'Unknown project' })); return true;
243
+ }
244
+ if (req.method !== 'GET') { res.writeHead(405, { Allow: 'GET' }); res.end(); return true; }
245
+ try {
246
+ const graph = getHostGraphPage(cwd, { host: url.searchParams.get('host'), sessionId: url.searchParams.get('session'),
247
+ runId: url.searchParams.get('run'), cursor: url.searchParams.get('cursor') });
248
+ res.writeHead(200, { 'Content-Type': 'application/json', 'Cache-Control': 'no-store' }); res.end(JSON.stringify(graph));
249
+ } catch { res.writeHead(404, { 'Content-Type': 'application/json', 'Cache-Control': 'no-store' }); res.end(JSON.stringify({ error: 'Bound graph page is unavailable' })); }
250
+ return true;
251
+ }
252
+
240
253
  // New read model refuses project fallback rather than relabeling another project.
241
254
  if (pathname === '/api/work') {
242
255
  if (requestedProject && resolveProjectInfo(requestedProject).resolved === 'fallback') {
@@ -10,6 +10,8 @@ import { measureWork, compareWork } from '../../../scripts/lib/work-metrics.mjs'
10
10
  import { readSessionStatus } from '../../../scripts/lib/session-status.mjs';
11
11
  import { agentActivity } from './agent-activity.mjs';
12
12
  import { taskInspector } from './task-inspector.mjs';
13
+ import { graphBinding, projectHostObservations } from '../../../scripts/lib/host-graph-observer.mjs';
14
+ import { nativeGraphObservations, readNativeGraphPage } from '../../../scripts/lib/host-graph-bindings.mjs';
13
15
 
14
16
  const text = v => typeof v === 'string' ? v : null;
15
17
  const date = v => typeof v === 'string' && Number.isFinite(Date.parse(v)) ? v : null;
@@ -30,7 +32,7 @@ function executionAttempts(run) {
30
32
  }));
31
33
  const truncated = attempts.length > 200;
32
34
  const graph = run?.executionGraph || { state: 'unavailable', complete: false, nodes: [], edges: [], snapshots: [], reason: 'No linked controller-recorded graph is available.' };
33
- return { state: nodes.length ? 'recorded' : 'unknown', nodes, topology: graph.state === 'recorded' ? 'recorded' : 'unknown', graph, truncated,
35
+ return { state: nodes.length ? 'recorded' : 'unknown', nodes, observations: run?.hostObservations || [], observationHealth: run?.hostObservationHealth || null, omittedObservationAttempts: run?.hostObservationOmittedAttempts || 0, topology: graph.state === 'recorded' ? 'recorded' : 'unknown', graph, truncated,
34
36
  partial: !run || truncated || nodes.length !== window.length,
35
37
  reason: !run ? 'Stable attempt observations are unavailable without a single explicitly linked controlled run.'
36
38
  : !nodes.length ? 'No usable attempt observations are recorded.' : null };
@@ -63,7 +65,7 @@ function evidenceSections(entry) {
63
65
  }
64
66
 
65
67
  export function projectWork({ projectId, issues = [], codex = { state: 'absent', runs: [] },
66
- tasks = [], sessions = [], sources = [], activity = { state: 'none', events: [] }, observedAt = new Date().toISOString() }) {
68
+ tasks = [], sessions = [], nativeGraphs = [], sources = [], activity = { state: 'none', events: [] }, observedAt = new Date().toISOString() }) {
67
69
  const entries = [];
68
70
  for (const r of codex.runs || []) {
69
71
  // Completion of a controller run is not proof of the user's acceptance criteria.
@@ -139,6 +141,20 @@ export function projectWork({ projectId, issues = [], codex = { state: 'absent',
139
141
  command: approveCommand || (enabled ? `great-cto resume --task ${t.taskId} --host ${t.host} --revision ${t.revision}` : null),
140
142
  });
141
143
  }
144
+ for (const observed of nativeGraphs) {
145
+ const linked = entries.filter(e => e.taskId && tasks.some(t => t.taskId === e.taskId && t.links.sessions.includes(observed.sessionId) && t.host === observed.host));
146
+ if (linked.length === 1) {
147
+ const entry = linked[0]; entry.execution ||= executionAttempts(null);
148
+ if (entry.execution.graph?.source !== 'host-observation' && entry.execution.topology !== 'recorded') {
149
+ entry.execution.graph = observed.graph; entry.execution.topology = 'observed';
150
+ } else (entry.execution.observations ||= []).push(observed.graph);
151
+ } else if (!linked.length) entries.push({ key: `native:${observed.host}:${observed.sessionId}`, kind: 'native', taskId: null,
152
+ runId: null, issueIds: [], title: `Observed ${observed.host} session`, goal: null, acceptance: [],
153
+ host: observed.host, phase: 'unknown', nativeState: 'observed', terminal: false, updatedAt: null,
154
+ outcome: null, reason: 'Enrolled native observations; task acceptance and execution authority are not established.',
155
+ decisions: [], capabilities: [], command: null, evidence: [], release: null,
156
+ execution: { state: observed.graph.state, nodes: [], topology: 'observed', graph: observed.graph, partial: true, observations: [] } });
157
+ }
142
158
  for (const entry of entries) entry.evidenceSections = evidenceSections(entry);
143
159
  entries.sort((a, b) => (a.terminal - b.terminal) || (b.decisions.length - a.decisions.length)
144
160
  || (Date.parse(b.updatedAt || '') || 0) - (Date.parse(a.updatedAt || '') || 0) || a.key.localeCompare(b.key));
@@ -193,8 +209,23 @@ export function getWork(cwd) {
193
209
  sessions = readSessionStatus(cwd);
194
210
  source('claude_sessions', unreadable ? 'degraded' : 'current', unreadable ? `${unreadable} session state file(s) could not be read` : null);
195
211
  } catch { source('claude_sessions', 'unavailable', 'Cannot read Claude session status'); }
212
+ let nativeGraphs = [];
213
+ try { nativeGraphs = nativeGraphObservations(cwd); source('native_graphs', nativeGraphs.some(r => r.graph.health === 'degraded') ? 'degraded' : 'current', null); }
214
+ catch { source('native_graphs', 'unavailable', 'Native graph enrollment is unreadable'); }
196
215
  let canonical = cwd;
197
216
  try { canonical = realpathSync(cwd); } catch { /* source health already reports inaccessible data */ }
198
217
  const projectId = 'project:' + createHash('sha256').update(canonical).digest('hex');
199
- return { ...projectWork({ projectId, issues, codex, tasks, sessions, sources, activity, observedAt }), projectName: basename(cwd) };
218
+ return { ...projectWork({ projectId, issues, codex, tasks, sessions, nativeGraphs, sources, activity, observedAt }), projectName: basename(cwd) };
219
+ }
220
+
221
+ /** Read-only pagination, with exact enrollment or controlled attempt membership. */
222
+ export function getHostGraphPage(root, { host, sessionId, runId = null, cursor = null }) {
223
+ if (!['codex', 'claude-code'].includes(host)) throw Error('invalid graph host');
224
+ if (!runId || runId === sessionId) return readNativeGraphPage({ root, host, sessionId, cursor });
225
+ const listing = listCodexRuns({ root });
226
+ if (listing.state === 'degraded') throw Error('controlled membership is unreadable');
227
+ const run = listing.runs.find(r => r.id === runId);
228
+ const binding = run?.hostObservationBindings.find(b => b.sessionId === sessionId && b.host === host);
229
+ if (!binding) throw Error('controlled observation does not belong to this project');
230
+ return projectHostObservations(graphBinding({ root, runId, sessionId, attemptId: binding.attemptId, host }), { cursor });
200
231
  }