shipwright-cli 1.7.0 → 1.9.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 (106) hide show
  1. package/.claude/agents/code-reviewer.md +90 -0
  2. package/.claude/agents/devops-engineer.md +142 -0
  3. package/.claude/agents/pipeline-agent.md +80 -0
  4. package/.claude/agents/shell-script-specialist.md +150 -0
  5. package/.claude/agents/test-specialist.md +196 -0
  6. package/.claude/hooks/post-tool-use.sh +38 -0
  7. package/.claude/hooks/pre-tool-use.sh +25 -0
  8. package/.claude/hooks/session-started.sh +37 -0
  9. package/README.md +212 -814
  10. package/claude-code/CLAUDE.md.shipwright +54 -0
  11. package/claude-code/hooks/notify-idle.sh +2 -2
  12. package/claude-code/hooks/session-start.sh +24 -0
  13. package/claude-code/hooks/task-completed.sh +6 -2
  14. package/claude-code/settings.json.template +12 -0
  15. package/dashboard/public/app.js +4422 -0
  16. package/dashboard/public/index.html +816 -0
  17. package/dashboard/public/styles.css +4755 -0
  18. package/dashboard/server.ts +4315 -0
  19. package/docs/KNOWN-ISSUES.md +18 -10
  20. package/docs/TIPS.md +38 -26
  21. package/docs/patterns/README.md +33 -23
  22. package/package.json +9 -5
  23. package/scripts/adapters/iterm2-adapter.sh +1 -1
  24. package/scripts/adapters/tmux-adapter.sh +52 -23
  25. package/scripts/adapters/wezterm-adapter.sh +26 -14
  26. package/scripts/lib/compat.sh +200 -0
  27. package/scripts/lib/helpers.sh +72 -0
  28. package/scripts/postinstall.mjs +72 -13
  29. package/scripts/{cct → sw} +109 -21
  30. package/scripts/sw-adversarial.sh +274 -0
  31. package/scripts/sw-architecture-enforcer.sh +330 -0
  32. package/scripts/sw-checkpoint.sh +390 -0
  33. package/scripts/{cct-cleanup.sh → sw-cleanup.sh} +3 -1
  34. package/scripts/sw-connect.sh +619 -0
  35. package/scripts/{cct-cost.sh → sw-cost.sh} +368 -34
  36. package/scripts/{cct-daemon.sh → sw-daemon.sh} +2217 -204
  37. package/scripts/sw-dashboard.sh +477 -0
  38. package/scripts/sw-developer-simulation.sh +252 -0
  39. package/scripts/sw-docs.sh +635 -0
  40. package/scripts/sw-doctor.sh +907 -0
  41. package/scripts/{cct-fix.sh → sw-fix.sh} +10 -6
  42. package/scripts/{cct-fleet.sh → sw-fleet.sh} +498 -22
  43. package/scripts/sw-github-checks.sh +521 -0
  44. package/scripts/sw-github-deploy.sh +533 -0
  45. package/scripts/sw-github-graphql.sh +972 -0
  46. package/scripts/sw-heartbeat.sh +293 -0
  47. package/scripts/sw-init.sh +522 -0
  48. package/scripts/sw-intelligence.sh +1196 -0
  49. package/scripts/sw-jira.sh +643 -0
  50. package/scripts/sw-launchd.sh +364 -0
  51. package/scripts/sw-linear.sh +648 -0
  52. package/scripts/{cct-logs.sh → sw-logs.sh} +72 -2
  53. package/scripts/{cct-loop.sh → sw-loop.sh} +534 -44
  54. package/scripts/{cct-memory.sh → sw-memory.sh} +321 -38
  55. package/scripts/sw-patrol-meta.sh +417 -0
  56. package/scripts/sw-pipeline-composer.sh +455 -0
  57. package/scripts/{cct-pipeline.sh → sw-pipeline.sh} +2319 -178
  58. package/scripts/sw-predictive.sh +820 -0
  59. package/scripts/{cct-prep.sh → sw-prep.sh} +339 -49
  60. package/scripts/{cct-ps.sh → sw-ps.sh} +6 -4
  61. package/scripts/{cct-reaper.sh → sw-reaper.sh} +6 -4
  62. package/scripts/sw-remote.sh +687 -0
  63. package/scripts/sw-self-optimize.sh +947 -0
  64. package/scripts/sw-session.sh +519 -0
  65. package/scripts/sw-setup.sh +234 -0
  66. package/scripts/sw-status.sh +605 -0
  67. package/scripts/{cct-templates.sh → sw-templates.sh} +9 -4
  68. package/scripts/sw-tmux.sh +591 -0
  69. package/scripts/sw-tracker-jira.sh +277 -0
  70. package/scripts/sw-tracker-linear.sh +292 -0
  71. package/scripts/sw-tracker.sh +409 -0
  72. package/scripts/{cct-upgrade.sh → sw-upgrade.sh} +103 -46
  73. package/scripts/{cct-worktree.sh → sw-worktree.sh} +3 -0
  74. package/templates/pipelines/autonomous.json +27 -5
  75. package/templates/pipelines/full.json +12 -0
  76. package/templates/pipelines/standard.json +12 -0
  77. package/tmux/{claude-teams-overlay.conf → shipwright-overlay.conf} +27 -9
  78. package/tmux/templates/accessibility.json +34 -0
  79. package/tmux/templates/api-design.json +35 -0
  80. package/tmux/templates/architecture.json +1 -0
  81. package/tmux/templates/bug-fix.json +9 -0
  82. package/tmux/templates/code-review.json +1 -0
  83. package/tmux/templates/compliance.json +36 -0
  84. package/tmux/templates/data-pipeline.json +36 -0
  85. package/tmux/templates/debt-paydown.json +34 -0
  86. package/tmux/templates/devops.json +1 -0
  87. package/tmux/templates/documentation.json +1 -0
  88. package/tmux/templates/exploration.json +1 -0
  89. package/tmux/templates/feature-dev.json +1 -0
  90. package/tmux/templates/full-stack.json +8 -0
  91. package/tmux/templates/i18n.json +34 -0
  92. package/tmux/templates/incident-response.json +36 -0
  93. package/tmux/templates/migration.json +1 -0
  94. package/tmux/templates/observability.json +35 -0
  95. package/tmux/templates/onboarding.json +33 -0
  96. package/tmux/templates/performance.json +35 -0
  97. package/tmux/templates/refactor.json +1 -0
  98. package/tmux/templates/release.json +35 -0
  99. package/tmux/templates/security-audit.json +8 -0
  100. package/tmux/templates/spike.json +34 -0
  101. package/tmux/templates/testing.json +1 -0
  102. package/tmux/tmux.conf +98 -9
  103. package/scripts/cct-doctor.sh +0 -328
  104. package/scripts/cct-init.sh +0 -282
  105. package/scripts/cct-session.sh +0 -284
  106. package/scripts/cct-status.sh +0 -169
@@ -0,0 +1,635 @@
1
+ #!/usr/bin/env bash
2
+ # ╔═══════════════════════════════════════════════════════════════════════════╗
3
+ # ║ shipwright docs — Documentation Keeper ║
4
+ # ║ Auto-sync documentation from source, detect staleness, generate wiki ║
5
+ # ╚═══════════════════════════════════════════════════════════════════════════╝
6
+ set -euo pipefail
7
+ trap 'echo "ERROR: $BASH_SOURCE:$LINENO exited with status $?" >&2' ERR
8
+
9
+ VERSION="1.9.0"
10
+ SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
11
+ REPO_DIR="$(cd "$SCRIPT_DIR/.." && pwd)"
12
+
13
+ # ─── Colors (matches Seth's tmux theme) ─────────────────────────────────────
14
+ CYAN='\033[38;2;0;212;255m' # #00d4ff — primary accent
15
+ PURPLE='\033[38;2;124;58;237m' # #7c3aed — secondary
16
+ BLUE='\033[38;2;0;102;255m' # #0066ff — tertiary
17
+ GREEN='\033[38;2;74;222;128m' # success
18
+ YELLOW='\033[38;2;250;204;21m' # warning
19
+ RED='\033[38;2;248;113;113m' # error
20
+ DIM='\033[2m'
21
+ BOLD='\033[1m'
22
+ RESET='\033[0m'
23
+
24
+ # ─── Cross-platform compatibility ──────────────────────────────────────────
25
+ # shellcheck source=lib/compat.sh
26
+ [[ -f "$SCRIPT_DIR/lib/compat.sh" ]] && source "$SCRIPT_DIR/lib/compat.sh"
27
+
28
+ # ─── Helpers ─────────────────────────────────────────────────────────────────
29
+
30
+ info() { echo -e "${CYAN}${BOLD}▸${RESET} $*"; }
31
+ success() { echo -e "${GREEN}${BOLD}✓${RESET} $*"; }
32
+ warn() { echo -e "${YELLOW}${BOLD}⚠${RESET} $*"; }
33
+ error() { echo -e "${RED}${BOLD}✗${RESET} $*" >&2; }
34
+
35
+ emit_event() {
36
+ local event_type="$1"; shift
37
+ local events_file="${HOME}/.shipwright/events.jsonl"
38
+ mkdir -p "$(dirname "$events_file")"
39
+ local payload="{\"ts\":\"$(date -u +%Y-%m-%dT%H:%M:%SZ)\",\"type\":\"$event_type\""
40
+ while [[ $# -gt 0 ]]; do
41
+ local key="${1%%=*}" val="${1#*=}"
42
+ payload="${payload},\"${key}\":\"${val}\""
43
+ shift
44
+ done
45
+ payload="${payload}}"
46
+ echo "$payload" >> "$events_file"
47
+ }
48
+
49
+ # ─── AUTO Section Processing ────────────────────────────────────────────────
50
+
51
+ # Find all files with AUTO markers
52
+ docs_find_auto_files() {
53
+ grep -rl '<!-- AUTO:' "$REPO_DIR" --include='*.md' 2>/dev/null || true
54
+ }
55
+
56
+ # Extract section IDs from a file
57
+ docs_get_sections() {
58
+ local file="$1"
59
+ grep -oE '<!-- AUTO:[a-z0-9_-]+ -->' "$file" 2>/dev/null | sed 's/<!-- AUTO://;s/ -->//' || true
60
+ }
61
+
62
+ # Replace content between AUTO markers (using temp file for multi-line content)
63
+ docs_replace_section() {
64
+ local file="$1" section_id="$2" new_content="$3"
65
+ local tmp_file content_file
66
+ tmp_file=$(mktemp)
67
+ content_file=$(mktemp)
68
+
69
+ printf '%s\n' "$new_content" > "$content_file"
70
+
71
+ awk -v section="$section_id" -v cfile="$content_file" '
72
+ $0 ~ "<!-- AUTO:" section " -->" {
73
+ print
74
+ while ((getline line < cfile) > 0) print line
75
+ close(cfile)
76
+ skip=1
77
+ next
78
+ }
79
+ $0 ~ "<!-- /AUTO:" section " -->" { skip=0 }
80
+ !skip { print }
81
+ ' "$file" > "$tmp_file"
82
+
83
+ mv "$tmp_file" "$file"
84
+ rm -f "$content_file"
85
+ }
86
+
87
+ # Check if a section is stale (content differs from generated)
88
+ docs_check_section() {
89
+ local file="$1" section_id="$2" expected="$3"
90
+ local current
91
+ current=$(awk -v section="$section_id" '
92
+ $0 ~ "<!-- AUTO:" section " -->" { capture=1; next }
93
+ $0 ~ "<!-- /AUTO:" section " -->" { capture=0 }
94
+ capture { print }
95
+ ' "$file" 2>/dev/null || true)
96
+
97
+ if [[ "$current" != "$expected" ]]; then
98
+ return 1 # stale
99
+ fi
100
+ return 0 # fresh
101
+ }
102
+
103
+ # ─── Section Generators ─────────────────────────────────────────────────────
104
+
105
+ # Generate architecture table for a given section
106
+ docs_gen_architecture_table() {
107
+ local section="$1"
108
+
109
+ case "$section" in
110
+ core-scripts)
111
+ echo ""
112
+ echo "| File | Lines | Purpose |"
113
+ echo "| --- | ---: | --- |"
114
+ for f in "$REPO_DIR"/scripts/sw-*.sh; do
115
+ [[ -f "$f" ]] || continue
116
+ local basename
117
+ basename=$(basename "$f")
118
+ # Skip test files, tracker providers, github modules
119
+ [[ "$basename" == *-test.sh ]] && continue
120
+ [[ "$basename" == sw-tracker-*.sh ]] && continue
121
+ [[ "$basename" == sw-github-*.sh ]] && continue
122
+ local lines purpose
123
+ lines=$(wc -l < "$f" | xargs)
124
+ # Extract purpose from header: "# ║ shipwright X — Description ║"
125
+ purpose=$(sed -n '3p' "$f" 2>/dev/null | sed 's/^# *║ *//;s/ *║ *$//;s/.*— *//' || echo "")
126
+ if [[ -z "$purpose" || "$purpose" == "#"* ]]; then
127
+ purpose=$(head -5 "$f" | grep -m1 '# .*—' | sed 's/.*— *//' | sed 's/ *║.*//' || echo "")
128
+ fi
129
+ echo "| \`scripts/${basename}\` | ${lines} | ${purpose} |"
130
+ done
131
+ # Also include the CLI router
132
+ if [[ -f "$REPO_DIR/scripts/sw" ]]; then
133
+ local sw_lines
134
+ sw_lines=$(wc -l < "$REPO_DIR/scripts/sw" | xargs)
135
+ echo "| \`scripts/sw\` | ${sw_lines} | CLI router — dispatches subcommands via exec |"
136
+ fi
137
+ ;;
138
+ github-modules)
139
+ echo ""
140
+ echo "| File | Lines | Purpose |"
141
+ echo "| --- | ---: | --- |"
142
+ for f in "$REPO_DIR"/scripts/sw-github-*.sh; do
143
+ [[ -f "$f" ]] || continue
144
+ [[ "$f" == *-test.sh ]] && continue
145
+ local basename lines purpose
146
+ basename=$(basename "$f")
147
+ lines=$(wc -l < "$f" | xargs)
148
+ purpose=$(head -5 "$f" | grep -m1 '# .*—' | sed 's/.*— *//;s/ *║.*//' || echo "")
149
+ echo "| \`scripts/${basename}\` | ${lines} | ${purpose} |"
150
+ done
151
+ ;;
152
+ tracker-adapters)
153
+ echo ""
154
+ echo "| File | Lines | Purpose |"
155
+ echo "| --- | ---: | --- |"
156
+ for f in "$REPO_DIR"/scripts/sw-linear.sh "$REPO_DIR"/scripts/sw-jira.sh "$REPO_DIR"/scripts/sw-tracker-linear.sh "$REPO_DIR"/scripts/sw-tracker-jira.sh; do
157
+ [[ -f "$f" ]] || continue
158
+ local basename lines purpose
159
+ basename=$(basename "$f")
160
+ lines=$(wc -l < "$f" | xargs)
161
+ purpose=$(head -5 "$f" | grep -m1 '# .*—' | sed 's/.*— *//;s/ *║.*//' || echo "")
162
+ echo "| \`scripts/${basename}\` | ${lines} | ${purpose} |"
163
+ done
164
+ ;;
165
+ test-suites)
166
+ echo ""
167
+ echo "| File | Lines | Purpose |"
168
+ echo "| --- | ---: | --- |"
169
+ for f in "$REPO_DIR"/scripts/sw-*-test.sh; do
170
+ [[ -f "$f" ]] || continue
171
+ local basename lines purpose
172
+ basename=$(basename "$f")
173
+ lines=$(wc -l < "$f" | xargs)
174
+ purpose=$(head -5 "$f" | grep -m1 '# .*—' | sed 's/.*— *//;s/ *║.*//' || echo "")
175
+ echo "| \`scripts/${basename}\` | ${lines} | ${purpose} |"
176
+ done
177
+ ;;
178
+ esac
179
+ }
180
+
181
+ # Generate command table from CLI router
182
+ docs_gen_command_table() {
183
+ local sw_file="$REPO_DIR/scripts/sw"
184
+ [[ -f "$sw_file" ]] || return 0
185
+ echo ""
186
+ echo "| Command | Purpose |"
187
+ echo "| --- | --- |"
188
+ # Parse the show_help() output lines that match the command pattern
189
+ awk '
190
+ /^show_help\(\)/ { in_help=1 }
191
+ in_help && /^}/ { in_help=0 }
192
+ in_help && /echo.*CYAN.*RESET/ {
193
+ line = $0
194
+ # Extract command name between CYAN} and RESET}
195
+ gsub(/.*CYAN\}/, "", line)
196
+ gsub(/\$\{RESET\}.*/, "", line)
197
+ gsub(/^[[:space:]]*/, "", line)
198
+ cmd = line
199
+ # Extract description — everything after the last RESET}
200
+ line = $0
201
+ n = split(line, parts, "\\$\\{RESET\\}")
202
+ desc = parts[n]
203
+ gsub(/^[[:space:]]*/, "", desc)
204
+ gsub(/"[[:space:]]*$/, "", desc)
205
+ if (cmd != "" && desc != "" && cmd !~ /shipwright/) {
206
+ printf "| `shipwright %s` | %s |\n", cmd, desc
207
+ }
208
+ }
209
+ ' "$sw_file" 2>/dev/null || true
210
+ }
211
+
212
+ # Generate test table from package.json
213
+ docs_gen_test_table() {
214
+ local pkg="$REPO_DIR/package.json"
215
+ [[ -f "$pkg" ]] || return 0
216
+ echo ""
217
+ local idx=1
218
+ local test_files
219
+ test_files=$(jq -r '.scripts | to_entries[] | select(.key | startswith("test:")) | .value' "$pkg" 2>/dev/null || true)
220
+ if [[ -z "$test_files" ]]; then
221
+ return 0
222
+ fi
223
+ while IFS= read -r cmd; do
224
+ local test_file
225
+ test_file=$(echo "$cmd" | grep -oE 'sw-[a-z-]+-test\.sh' || true)
226
+ if [[ -n "$test_file" ]] && [[ -f "$REPO_DIR/scripts/$test_file" ]]; then
227
+ local purpose
228
+ purpose=$(head -5 "$REPO_DIR/scripts/$test_file" 2>/dev/null | grep -m1 '# .*—' | sed 's/.*— *//' || echo "")
229
+ echo "${idx}. \`${test_file}\` — ${purpose}"
230
+ idx=$((idx + 1))
231
+ fi
232
+ done <<< "$test_files"
233
+ }
234
+
235
+ # Generate feature flags table from daemon config defaults
236
+ docs_gen_feature_flags() {
237
+ local daemon="$REPO_DIR/scripts/sw-daemon.sh"
238
+ [[ -f "$daemon" ]] || return 0
239
+ echo ""
240
+ echo "| Flag | Default | Purpose |"
241
+ echo "| --- | --- | --- |"
242
+ # Extract intelligence config from the heredoc in sw-daemon.sh
243
+ # Lines look like: "enabled": true,
244
+ local in_intel=false
245
+ while IFS= read -r line; do
246
+ if echo "$line" | grep -q '"intelligence"' 2>/dev/null; then
247
+ in_intel=true
248
+ continue
249
+ fi
250
+ if [[ "$in_intel" == "true" ]] && echo "$line" | grep -q '^\s*}' 2>/dev/null; then
251
+ in_intel=false
252
+ continue
253
+ fi
254
+ if [[ "$in_intel" == "true" ]]; then
255
+ local key val
256
+ key=$(echo "$line" | sed -n 's/.*"\([a-z_]*\)".*/\1/p' 2>/dev/null || true)
257
+ val=$(echo "$line" | sed -n 's/.*: *\([a-z0-9.]*\).*/\1/p' 2>/dev/null || true)
258
+ if [[ -n "$key" && -n "$val" ]]; then
259
+ echo "| \`intelligence.${key}\` | \`${val}\` | |"
260
+ fi
261
+ fi
262
+ done < "$daemon"
263
+ }
264
+
265
+ # Generate runtime state file locations
266
+ docs_gen_file_locations() {
267
+ echo ""
268
+ local locations
269
+ locations="Pipeline state:.claude/pipeline-state.md
270
+ Pipeline artifacts:.claude/pipeline-artifacts/
271
+ Composed pipeline:.claude/pipeline-artifacts/composed-pipeline.json
272
+ Events log:~/.shipwright/events.jsonl
273
+ Daemon config:.claude/daemon-config.json
274
+ Fleet config:.claude/fleet-config.json
275
+ Heartbeats:~/.shipwright/heartbeats/<job-id>.json
276
+ Checkpoints:.claude/pipeline-artifacts/checkpoints/
277
+ Machine registry:~/.shipwright/machines.json
278
+ Cost data:~/.shipwright/costs.json, ~/.shipwright/budget.json
279
+ Intelligence cache:.claude/intelligence-cache.json
280
+ Optimization data:~/.shipwright/optimization/
281
+ Baselines:~/.shipwright/baselines/
282
+ Architecture models:~/.shipwright/memory/<repo-hash>/architecture.json
283
+ Team config:~/.shipwright/team-config.json
284
+ Developer registry:~/.shipwright/developer-registry.json
285
+ Team events:~/.shipwright/team-events.jsonl
286
+ Invite tokens:~/.shipwright/invite-tokens.json
287
+ Connect PID:~/.shipwright/connect.pid
288
+ Connect log:~/.shipwright/connect.log
289
+ GitHub cache:~/.shipwright/github-cache/
290
+ Check run IDs:.claude/pipeline-artifacts/check-run-ids.json
291
+ Deployment tracking:.claude/pipeline-artifacts/deployment.json
292
+ Error log:.claude/pipeline-artifacts/error-log.jsonl"
293
+
294
+ while IFS= read -r loc; do
295
+ [[ -z "$loc" ]] && continue
296
+ local label="${loc%%:*}"
297
+ local path="${loc#*:}"
298
+ echo "- ${label}: \`${path}\`"
299
+ done <<< "$locations"
300
+ }
301
+
302
+ # Generate codebase stats
303
+ docs_gen_stats() {
304
+ local script_count=0 test_count=0 total_lines=0
305
+
306
+ for f in "$REPO_DIR"/scripts/sw-*.sh "$REPO_DIR"/scripts/sw; do
307
+ [[ -f "$f" ]] || continue
308
+ if [[ "$f" == *-test.sh ]]; then
309
+ test_count=$((test_count + 1))
310
+ else
311
+ script_count=$((script_count + 1))
312
+ fi
313
+ local lines
314
+ lines=$(wc -l < "$f" | xargs)
315
+ total_lines=$((total_lines + lines))
316
+ done
317
+
318
+ local template_count=0 team_template_count=0
319
+ if [[ -d "$REPO_DIR/templates/pipelines" ]]; then
320
+ template_count=$(find "$REPO_DIR/templates/pipelines" -name '*.json' 2>/dev/null | wc -l | xargs)
321
+ fi
322
+ if [[ -d "$REPO_DIR/tmux/templates" ]]; then
323
+ team_template_count=$(find "$REPO_DIR/tmux/templates" -name '*.json' 2>/dev/null | wc -l | xargs)
324
+ fi
325
+
326
+ echo "- **${script_count}** core scripts + CLI router"
327
+ echo "- **${test_count}** test suites"
328
+ echo "- **${total_lines}** total lines of shell"
329
+ echo "- **${template_count}** pipeline templates"
330
+ echo "- **${team_template_count}** team composition templates"
331
+ }
332
+
333
+ # ─── Section Router ──────────────────────────────────────────────────────────
334
+
335
+ docs_generate_section() {
336
+ local section_id="$1"
337
+ case "$section_id" in
338
+ core-scripts) docs_gen_architecture_table "core-scripts" ;;
339
+ github-modules) docs_gen_architecture_table "github-modules" ;;
340
+ tracker-adapters) docs_gen_architecture_table "tracker-adapters" ;;
341
+ test-suites) docs_gen_architecture_table "test-suites" ;;
342
+ commands-table) docs_gen_command_table ;;
343
+ test-list) docs_gen_test_table ;;
344
+ feature-flags) docs_gen_feature_flags ;;
345
+ runtime-state) docs_gen_file_locations ;;
346
+ stats) docs_gen_stats ;;
347
+ *) warn "Unknown AUTO section: $section_id"; return 1 ;;
348
+ esac
349
+ }
350
+
351
+ # ─── Subcommands ─────────────────────────────────────────────────────────────
352
+
353
+ # Check which AUTO sections are stale
354
+ docs_check() {
355
+ info "Checking documentation freshness..."
356
+ local stale=0 fresh=0 total=0
357
+
358
+ local files
359
+ files=$(docs_find_auto_files)
360
+ if [[ -z "$files" ]]; then
361
+ warn "No files with AUTO markers found"
362
+ return 0
363
+ fi
364
+
365
+ while IFS= read -r file; do
366
+ [[ -z "$file" ]] && continue
367
+ local sections
368
+ sections=$(docs_get_sections "$file")
369
+ while IFS= read -r section; do
370
+ [[ -z "$section" ]] && continue
371
+ total=$((total + 1))
372
+ local expected
373
+ expected=$(docs_generate_section "$section")
374
+ if ! docs_check_section "$file" "$section" "$expected"; then
375
+ stale=$((stale + 1))
376
+ local rel_file="${file#$REPO_DIR/}"
377
+ warn "Stale: ${rel_file}#${section}"
378
+ else
379
+ fresh=$((fresh + 1))
380
+ fi
381
+ done <<< "$sections"
382
+ done <<< "$files"
383
+
384
+ echo ""
385
+ echo -e "${BOLD}Documentation Status:${RESET} ${fresh} fresh, ${stale} stale, ${total} total"
386
+
387
+ if [[ "$stale" -gt 0 ]]; then
388
+ warn "Run ${CYAN}shipwright docs sync${RESET} to update stale sections"
389
+ return 1
390
+ fi
391
+ success "All documentation sections are fresh"
392
+ return 0
393
+ }
394
+
395
+ # Regenerate all stale AUTO sections
396
+ docs_sync() {
397
+ info "Syncing documentation..."
398
+ local updated=0 total=0
399
+
400
+ local files
401
+ files=$(docs_find_auto_files)
402
+
403
+ while IFS= read -r file; do
404
+ [[ -z "$file" ]] && continue
405
+ local sections
406
+ sections=$(docs_get_sections "$file")
407
+ while IFS= read -r section; do
408
+ [[ -z "$section" ]] && continue
409
+ total=$((total + 1))
410
+ local expected
411
+ expected=$(docs_generate_section "$section")
412
+ if ! docs_check_section "$file" "$section" "$expected"; then
413
+ docs_replace_section "$file" "$section" "$expected"
414
+ updated=$((updated + 1))
415
+ local rel_file="${file#$REPO_DIR/}"
416
+ info "Updated: ${rel_file}#${section}"
417
+ fi
418
+ done <<< "$sections"
419
+ done <<< "$files"
420
+
421
+ if [[ "$updated" -gt 0 ]]; then
422
+ success "Updated ${updated}/${total} sections"
423
+ emit_event "docs.sync" "updated=$updated" "total=$total"
424
+ else
425
+ success "All ${total} sections already fresh"
426
+ fi
427
+ }
428
+
429
+ # Generate GitHub wiki pages
430
+ docs_wiki() {
431
+ local dry_run="${1:-false}"
432
+ local wiki_dir
433
+ wiki_dir=$(mktemp -d)
434
+
435
+ info "Generating wiki pages..."
436
+
437
+ # Home.md — from README intro
438
+ if [[ -f "$REPO_DIR/README.md" ]]; then
439
+ head -50 "$REPO_DIR/README.md" > "$wiki_dir/Home.md"
440
+ echo "" >> "$wiki_dir/Home.md"
441
+ echo "---" >> "$wiki_dir/Home.md"
442
+ echo "*Auto-generated by \`shipwright docs wiki\` on $(date -u +%Y-%m-%d)*" >> "$wiki_dir/Home.md"
443
+ fi
444
+
445
+ # Architecture.md
446
+ {
447
+ echo "# Architecture"
448
+ echo ""
449
+ echo "## Core Scripts"
450
+ docs_gen_architecture_table "core-scripts"
451
+ echo ""
452
+ echo "## GitHub API Modules"
453
+ docs_gen_architecture_table "github-modules"
454
+ echo ""
455
+ echo "## Issue Tracker Adapters"
456
+ docs_gen_architecture_table "tracker-adapters"
457
+ echo ""
458
+ echo "## Test Suites"
459
+ docs_gen_architecture_table "test-suites"
460
+ echo ""
461
+ echo "## Stats"
462
+ docs_gen_stats
463
+ echo ""
464
+ echo "---"
465
+ echo "*Auto-generated by \`shipwright docs wiki\` on $(date -u +%Y-%m-%d)*"
466
+ } > "$wiki_dir/Architecture.md"
467
+
468
+ # Commands.md
469
+ {
470
+ echo "# Commands"
471
+ docs_gen_command_table
472
+ echo ""
473
+ echo "---"
474
+ echo "*Auto-generated by \`shipwright docs wiki\` on $(date -u +%Y-%m-%d)*"
475
+ } > "$wiki_dir/Commands.md"
476
+
477
+ # Intelligence.md
478
+ {
479
+ echo "# Intelligence Layer"
480
+ echo ""
481
+ echo "## Feature Flags"
482
+ docs_gen_feature_flags
483
+ echo ""
484
+ echo "---"
485
+ echo "*Auto-generated by \`shipwright docs wiki\` on $(date -u +%Y-%m-%d)*"
486
+ } > "$wiki_dir/Intelligence.md"
487
+
488
+ # Configuration.md
489
+ {
490
+ echo "# Configuration"
491
+ echo ""
492
+ echo "## Runtime State & Artifacts"
493
+ docs_gen_file_locations
494
+ echo ""
495
+ echo "---"
496
+ echo "*Auto-generated by \`shipwright docs wiki\` on $(date -u +%Y-%m-%d)*"
497
+ } > "$wiki_dir/Configuration.md"
498
+
499
+ if [[ "$dry_run" == "true" ]] || [[ "$dry_run" == "--dry-run" ]]; then
500
+ info "Dry run — wiki pages generated in: $wiki_dir"
501
+ ls -la "$wiki_dir"
502
+ return 0
503
+ fi
504
+
505
+ # Push to GitHub wiki
506
+ if [[ "${NO_GITHUB:-}" == "true" ]] || ! command -v gh &>/dev/null; then
507
+ warn "GitHub not available — wiki pages saved to: $wiki_dir"
508
+ return 0
509
+ fi
510
+
511
+ local repo_url
512
+ repo_url=$(gh repo view --json url -q '.url' 2>/dev/null || true)
513
+ if [[ -z "$repo_url" ]]; then
514
+ warn "Could not determine repo URL — wiki pages saved to: $wiki_dir"
515
+ return 0
516
+ fi
517
+
518
+ local wiki_repo="${repo_url}.wiki.git"
519
+ local wiki_clone
520
+ wiki_clone=$(mktemp -d)
521
+
522
+ if git clone "$wiki_repo" "$wiki_clone" 2>/dev/null; then
523
+ cp "$wiki_dir"/*.md "$wiki_clone/"
524
+ ( cd "$wiki_clone" && git add -A && git commit -m "docs: auto-update wiki via shipwright docs wiki" && git push ) 2>/dev/null || true
525
+ success "Wiki updated"
526
+ else
527
+ warn "Could not clone wiki repo — wiki pages saved to: $wiki_dir"
528
+ fi
529
+
530
+ rm -rf "$wiki_clone"
531
+ }
532
+
533
+ # Documentation freshness report
534
+ docs_report() {
535
+ echo ""
536
+ echo -e "${CYAN}${BOLD}shipwright${RESET} ${DIM}v${VERSION}${RESET} — ${BOLD}Documentation Report${RESET}"
537
+ echo -e "${CYAN}═══════════════════════════════════════════════${RESET}"
538
+ echo ""
539
+
540
+ # Stats
541
+ echo -e "${BOLD}Codebase Stats:${RESET}"
542
+ docs_gen_stats
543
+ echo ""
544
+
545
+ # AUTO section status
546
+ echo -e "${BOLD}AUTO Section Status:${RESET}"
547
+ local files
548
+ files=$(docs_find_auto_files)
549
+ if [[ -z "$files" ]]; then
550
+ echo " No AUTO sections found"
551
+ else
552
+ while IFS= read -r file; do
553
+ [[ -z "$file" ]] && continue
554
+ local rel_file="${file#$REPO_DIR/}"
555
+ local sections
556
+ sections=$(docs_get_sections "$file")
557
+ while IFS= read -r section; do
558
+ [[ -z "$section" ]] && continue
559
+ local expected
560
+ expected=$(docs_generate_section "$section")
561
+ if docs_check_section "$file" "$section" "$expected"; then
562
+ echo -e " ${GREEN}✓${RESET} ${rel_file}#${section}"
563
+ else
564
+ echo -e " ${YELLOW}⚠${RESET} ${rel_file}#${section} ${DIM}(stale)${RESET}"
565
+ fi
566
+ done <<< "$sections"
567
+ done <<< "$files"
568
+ fi
569
+
570
+ echo ""
571
+
572
+ # File freshness
573
+ echo -e "${BOLD}Document Freshness:${RESET}"
574
+ for doc in README.md .claude/CLAUDE.md CHANGELOG.md; do
575
+ local full_path="$REPO_DIR/$doc"
576
+ [[ -f "$full_path" ]] || continue
577
+ local last_modified
578
+ last_modified=$(git -C "$REPO_DIR" log -1 --format='%cr' -- "$doc" 2>/dev/null || echo "unknown")
579
+ echo " ${doc}: last modified ${last_modified}"
580
+ done
581
+ echo ""
582
+ }
583
+
584
+ # ─── Help & Main ─────────────────────────────────────────────────────────────
585
+
586
+ show_help() {
587
+ echo -e "${CYAN}${BOLD}shipwright docs${RESET} — Documentation Keeper"
588
+ echo ""
589
+ echo -e "${BOLD}USAGE${RESET}"
590
+ echo -e " ${CYAN}shipwright docs${RESET} <command>"
591
+ echo ""
592
+ echo -e "${BOLD}COMMANDS${RESET}"
593
+ echo -e " ${CYAN}sync${RESET} Regenerate all AUTO sections in markdown files"
594
+ echo -e " ${CYAN}check${RESET} Check which sections are stale (exit 1 if any)"
595
+ echo -e " ${CYAN}wiki${RESET} Generate/update GitHub wiki pages"
596
+ echo -e " ${CYAN}report${RESET} Show documentation freshness report"
597
+ echo -e " ${CYAN}help${RESET} Show this help"
598
+ echo ""
599
+ echo -e "${BOLD}AUTO MARKERS${RESET}"
600
+ echo -e " Place markers in any .md file to auto-generate content:"
601
+ echo -e " ${DIM}<!-- AUTO:core-scripts -->${RESET}"
602
+ echo -e " ${DIM}(auto-generated table)${RESET}"
603
+ echo -e " ${DIM}<!-- /AUTO:core-scripts -->${RESET}"
604
+ echo ""
605
+ echo -e "${BOLD}SECTIONS${RESET}"
606
+ echo -e " ${CYAN}core-scripts${RESET} Architecture table of core scripts"
607
+ echo -e " ${CYAN}github-modules${RESET} Architecture table of GitHub API modules"
608
+ echo -e " ${CYAN}tracker-adapters${RESET} Architecture table of tracker adapters"
609
+ echo -e " ${CYAN}test-suites${RESET} Architecture table of test suites"
610
+ echo -e " ${CYAN}commands-table${RESET} Command reference from CLI router"
611
+ echo -e " ${CYAN}test-list${RESET} Numbered test suite list from package.json"
612
+ echo -e " ${CYAN}feature-flags${RESET} Intelligence feature flags"
613
+ echo -e " ${CYAN}runtime-state${RESET} Runtime state file locations"
614
+ echo -e " ${CYAN}stats${RESET} Codebase statistics"
615
+ }
616
+
617
+ main() {
618
+ local cmd="${1:-help}"
619
+ shift 2>/dev/null || true
620
+
621
+ case "$cmd" in
622
+ sync) docs_sync "$@" ;;
623
+ check) docs_check "$@" ;;
624
+ wiki) docs_wiki "$@" ;;
625
+ report) docs_report "$@" ;;
626
+ help|--help|-h) show_help ;;
627
+ *)
628
+ error "Unknown command: $cmd"
629
+ show_help
630
+ exit 1
631
+ ;;
632
+ esac
633
+ }
634
+
635
+ main "$@"