compass-cc 0.1.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 (51) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +162 -0
  3. package/agents/adr-scribe.md +82 -0
  4. package/agents/architect-coach.md +88 -0
  5. package/agents/baseline-analyzer.md +93 -0
  6. package/agents/completion-tracker.md +55 -0
  7. package/agents/domain-researcher.md +122 -0
  8. package/agents/idiom-checker.md +111 -0
  9. package/agents/readiness-checker.md +93 -0
  10. package/agents/review-coach.md +94 -0
  11. package/agents/rubber-duck.md +69 -0
  12. package/agents/scope-guardian.md +98 -0
  13. package/agents/socratic-interrogator.md +93 -0
  14. package/agents/spec-writer.md +106 -0
  15. package/agents/test-auditor.md +92 -0
  16. package/agents/unit-decomposer.md +114 -0
  17. package/bin/install.js +306 -0
  18. package/constitution.md +191 -0
  19. package/hooks/compass-commit-check.sh +65 -0
  20. package/hooks/compass-context-monitor.sh +53 -0
  21. package/hooks/compass-preflight.sh +56 -0
  22. package/hooks/compass-scope-guardian.sh +95 -0
  23. package/package.json +36 -0
  24. package/references/inverted-contract.md +31 -0
  25. package/scripts/compass-tools.sh +602 -0
  26. package/skills/compass-architect/SKILL.md +91 -0
  27. package/skills/compass-build-duck/SKILL.md +53 -0
  28. package/skills/compass-build-idiom/SKILL.md +57 -0
  29. package/skills/compass-build-progress/SKILL.md +55 -0
  30. package/skills/compass-build-ready/SKILL.md +69 -0
  31. package/skills/compass-build-review/SKILL.md +61 -0
  32. package/skills/compass-build-scope/SKILL.md +67 -0
  33. package/skills/compass-build-tests/SKILL.md +57 -0
  34. package/skills/compass-build-transform/SKILL.md +63 -0
  35. package/skills/compass-build-units/SKILL.md +76 -0
  36. package/skills/compass-decide/SKILL.md +105 -0
  37. package/skills/compass-frame/SKILL.md +105 -0
  38. package/skills/compass-init/SKILL.md +142 -0
  39. package/skills/compass-next/SKILL.md +60 -0
  40. package/skills/compass-research/SKILL.md +81 -0
  41. package/skills/compass-spec/SKILL.md +115 -0
  42. package/skills/compass-status/SKILL.md +79 -0
  43. package/templates/ADR.md +48 -0
  44. package/templates/ARCHITECTURE.md +44 -0
  45. package/templates/BASELINE.md +32 -0
  46. package/templates/FRAMING.md +51 -0
  47. package/templates/RESEARCH-DOSSIER.md +36 -0
  48. package/templates/SESSION.md +23 -0
  49. package/templates/SPEC.md +40 -0
  50. package/templates/UNIT.md +33 -0
  51. package/templates/config.yaml +23 -0
@@ -0,0 +1,602 @@
1
+ #!/usr/bin/env bash
2
+
3
+ # compass-tools.sh — Deterministic CLI for COMPASS state management.
4
+ # No LLM calls. No network. Pure file operations.
5
+ #
6
+ # Usage: compass-tools.sh <command> [args...]
7
+ #
8
+ # Commands:
9
+ # status --json Full project state as JSON
10
+ # preflight <phase> Check prerequisites for a phase
11
+ # adr next-number Return next ADR number
12
+ # progress Unit completion stats
13
+ # drift [--json] Compare recent diff against spec/ADRs/framing
14
+ # session update Update SESSION.md with current state
15
+ # unit update-status <unit-number> <status> Update unit status
16
+ # config get <key> Read config value (dot notation)
17
+ # config set <key> <val> Write config value
18
+ # init <project-root> Scaffold .compass/ directory
19
+
20
+ COMPASS_DIR=""
21
+ CONFIG_FILE=""
22
+
23
+ # --- Utility functions ---
24
+
25
+ find_compass_dir() {
26
+ local dir="$PWD"
27
+ while [[ "$dir" != "/" ]]; do
28
+ if [[ -d "$dir/.compass" ]]; then
29
+ COMPASS_DIR="$dir/.compass"
30
+ CONFIG_FILE="$COMPASS_DIR/config.yaml"
31
+ return 0
32
+ fi
33
+ dir="$(dirname "$dir")"
34
+ done
35
+ return 1
36
+ }
37
+
38
+ die() {
39
+ echo "error: $1" >&2
40
+ exit 1
41
+ }
42
+
43
+ # Simple YAML value reader — handles flat "key: value" and dotted paths
44
+ # Supports: paths.adr, project.language, style.socratic_level, etc.
45
+ yaml_get() {
46
+ local file="$1"
47
+ local key="$2"
48
+
49
+ [[ -f "$file" ]] || return 1
50
+
51
+ # Convert dot notation to nested lookup
52
+ # For "paths.adr", find the "paths:" block then "adr:" within it
53
+ local IFS='.'
54
+ read -ra parts <<< "$key"
55
+
56
+ if [[ ${#parts[@]} -eq 1 ]]; then
57
+ grep -E "^${parts[0]}:" "$file" | head -1 | sed 's/^[^:]*:[[:space:]]*//' | sed 's/^"//' | sed 's/"$//'
58
+ elif [[ ${#parts[@]} -eq 2 ]]; then
59
+ awk -v section="${parts[0]}" -v key="${parts[1]}" '
60
+ $0 ~ "^"section":" { in_section=1; next }
61
+ in_section && /^[a-zA-Z]/ { in_section=0 }
62
+ in_section && $0 ~ "^[[:space:]]+"key":" {
63
+ sub(/^[[:space:]]+[^:]+:[[:space:]]+/, "")
64
+ gsub(/^"/, ""); gsub(/"$/, "")
65
+ print
66
+ exit
67
+ }
68
+ ' "$file"
69
+ fi
70
+ }
71
+
72
+ yaml_set() {
73
+ local file="$1"
74
+ local key="$2"
75
+ local value="$3"
76
+
77
+ [[ -f "$file" ]] || return 1
78
+
79
+ local IFS='.'
80
+ read -ra parts <<< "$key"
81
+
82
+ if [[ ${#parts[@]} -eq 1 ]]; then
83
+ sed -i "s|^${parts[0]}:.*|${parts[0]}: ${value}|" "$file"
84
+ elif [[ ${#parts[@]} -eq 2 ]]; then
85
+ # Find the section, then replace the key within it
86
+ awk -v section="${parts[0]}" -v key="${parts[1]}" -v val="$value" '
87
+ $0 ~ "^"section":" { in_section=1; print; next }
88
+ in_section && /^[a-zA-Z]/ { in_section=0 }
89
+ in_section && $0 ~ "^[[:space:]]+"key":" {
90
+ # Preserve indentation
91
+ match($0, /^[[:space:]]+/)
92
+ indent = substr($0, RSTART, RLENGTH)
93
+ print indent key ": " val
94
+ next
95
+ }
96
+ { print }
97
+ ' "$file" > "$file.tmp" && mv "$file.tmp" "$file"
98
+ fi
99
+ }
100
+
101
+ # --- Commands ---
102
+
103
+ cmd_status() {
104
+ find_compass_dir || die "no .compass/ directory found"
105
+
106
+ local phase="unknown"
107
+ local has_config="false"
108
+ local has_baseline="false"
109
+ local has_framing="false"
110
+ local has_architecture="false"
111
+ local has_spec="false"
112
+ local dossier_count=0
113
+ local adr_count=0
114
+ local unit_total=0
115
+ local unit_done=0
116
+ local unit_progress=0
117
+ local unit_pending=0
118
+ local unit_blocked=0
119
+
120
+ [[ -f "$CONFIG_FILE" ]] && has_config="true"
121
+ [[ -f "$COMPASS_DIR/BASELINE.md" ]] && has_baseline="true"
122
+ [[ -f "$COMPASS_DIR/FRAMING.md" ]] && has_framing="true"
123
+ [[ -f "$COMPASS_DIR/ARCHITECTURE.md" ]] && has_architecture="true"
124
+
125
+ # Resolve spec path from config
126
+ local spec_path
127
+ spec_path=$(yaml_get "$CONFIG_FILE" "paths.spec" 2>/dev/null)
128
+ spec_path="${spec_path:-.compass/SPEC.md}"
129
+ local project_root="${COMPASS_DIR%/.compass}"
130
+ local has_spec="false"
131
+ [[ -f "$project_root/$spec_path" ]] && has_spec="true"
132
+
133
+ # Count research dossiers
134
+ local research_dir
135
+ research_dir=$(yaml_get "$CONFIG_FILE" "paths.research" 2>/dev/null)
136
+ research_dir="${research_dir:-.compass/RESEARCH}"
137
+ if [[ -d "$project_root/$research_dir" ]]; then
138
+ dossier_count=$(find "$project_root/$research_dir" -name 'dossier-*.md' 2>/dev/null | wc -l)
139
+ fi
140
+
141
+ # Count ADRs
142
+ local adr_dir
143
+ adr_dir=$(yaml_get "$CONFIG_FILE" "paths.adr" 2>/dev/null)
144
+ adr_dir="${adr_dir:-.compass/ADR}"
145
+ if [[ -d "$project_root/$adr_dir" ]]; then
146
+ adr_count=$(find "$project_root/$adr_dir" -name 'ADR-*.md' 2>/dev/null | wc -l)
147
+ fi
148
+
149
+ # Count units by status
150
+ local units_dir
151
+ units_dir=$(yaml_get "$CONFIG_FILE" "paths.units" 2>/dev/null)
152
+ units_dir="${units_dir:-.compass/UNITS}"
153
+ if [[ -d "$project_root/$units_dir" ]]; then
154
+ unit_total=$(find "$project_root/$units_dir" -name 'unit-*.md' 2>/dev/null | wc -l)
155
+ for f in "$project_root/$units_dir"/unit-*.md; do
156
+ [[ -f "$f" ]] || continue
157
+ local status
158
+ status=$(awk '/^## Status/{getline; print; exit}' "$f" | tr -d '[:space:]')
159
+ case "$status" in
160
+ done) ((unit_done++)) ;;
161
+ in-progress) ((unit_progress++)) ;;
162
+ blocked) ((unit_blocked++)) ;;
163
+ *) ((unit_pending++)) ;;
164
+ esac
165
+ done
166
+ fi
167
+
168
+ # Determine current phase
169
+ if [[ "$has_config" == "false" ]]; then
170
+ phase="not-initialized"
171
+ elif [[ "$has_framing" == "false" ]]; then
172
+ phase="frame"
173
+ elif [[ $dossier_count -eq 0 ]]; then
174
+ phase="research"
175
+ elif [[ "$has_architecture" == "false" ]]; then
176
+ phase="architect"
177
+ elif [[ $adr_count -eq 0 ]]; then
178
+ phase="decide"
179
+ elif [[ "$has_spec" == "false" ]]; then
180
+ phase="spec"
181
+ elif [[ $unit_total -eq 0 ]]; then
182
+ phase="build-units"
183
+ else
184
+ phase="build"
185
+ fi
186
+
187
+ if [[ "$1" == "--json" ]]; then
188
+ cat <<ENDJSON
189
+ {
190
+ "phase": "$phase",
191
+ "config": $has_config,
192
+ "baseline": $has_baseline,
193
+ "framing": $has_framing,
194
+ "architecture": $has_architecture,
195
+ "spec": $has_spec,
196
+ "dossiers": $dossier_count,
197
+ "adrs": $adr_count,
198
+ "units": {
199
+ "total": $unit_total,
200
+ "done": $unit_done,
201
+ "in_progress": $unit_progress,
202
+ "pending": $unit_pending,
203
+ "blocked": $unit_blocked
204
+ }
205
+ }
206
+ ENDJSON
207
+ else
208
+ echo "Phase: $phase"
209
+ echo "Dossiers: $dossier_count | ADRs: $adr_count | Units: $unit_done/$unit_total done"
210
+ fi
211
+ }
212
+
213
+ cmd_preflight() {
214
+ local phase="$1"
215
+ [[ -n "$phase" ]] || die "usage: compass-tools.sh preflight <phase>"
216
+
217
+ find_compass_dir || die "no .compass/ directory found — run /compass:init first"
218
+
219
+ local project_root="${COMPASS_DIR%/.compass}"
220
+ local missing=()
221
+
222
+ case "$phase" in
223
+ init)
224
+ # No prerequisites for init
225
+ ;;
226
+ frame)
227
+ [[ -f "$CONFIG_FILE" ]] || missing+=("config.yaml — run /compass:init")
228
+ ;;
229
+ research)
230
+ [[ -f "$COMPASS_DIR/FRAMING.md" ]] || missing+=("FRAMING.md — run /compass:frame")
231
+ ;;
232
+ architect)
233
+ [[ -f "$COMPASS_DIR/FRAMING.md" ]] || missing+=("FRAMING.md — run /compass:frame")
234
+ local research_dir
235
+ research_dir=$(yaml_get "$CONFIG_FILE" "paths.research" 2>/dev/null)
236
+ research_dir="${research_dir:-.compass/RESEARCH}"
237
+ local count
238
+ count=$(find "$project_root/$research_dir" -name 'dossier-*.md' 2>/dev/null | wc -l)
239
+ [[ $count -gt 0 ]] || missing+=("research dossiers — run /compass:research")
240
+ ;;
241
+ decide)
242
+ [[ -f "$COMPASS_DIR/FRAMING.md" ]] || missing+=("FRAMING.md — run /compass:frame")
243
+ ;;
244
+ spec)
245
+ [[ -f "$COMPASS_DIR/FRAMING.md" ]] || missing+=("FRAMING.md — run /compass:frame")
246
+ [[ -f "$COMPASS_DIR/ARCHITECTURE.md" ]] || missing+=("ARCHITECTURE.md — run /compass:architect")
247
+ local adr_dir
248
+ adr_dir=$(yaml_get "$CONFIG_FILE" "paths.adr" 2>/dev/null)
249
+ adr_dir="${adr_dir:-.compass/ADR}"
250
+ local count
251
+ count=$(find "$project_root/$adr_dir" -name 'ADR-*.md' 2>/dev/null | wc -l)
252
+ [[ $count -gt 0 ]] || missing+=("ADRs — run /compass:decide")
253
+ ;;
254
+ build-units|build-ready|build-duck|build-idiom|build-tests|build-review|build-progress|build-transform)
255
+ local spec_path
256
+ spec_path=$(yaml_get "$CONFIG_FILE" "paths.spec" 2>/dev/null)
257
+ spec_path="${spec_path:-.compass/SPEC.md}"
258
+ [[ -f "$project_root/$spec_path" ]] || missing+=("SPEC.md — run /compass:spec")
259
+ ;;
260
+ esac
261
+
262
+ if [[ ${#missing[@]} -eq 0 ]]; then
263
+ echo '{"ready": true, "missing": []}'
264
+ else
265
+ local json_missing=""
266
+ for m in "${missing[@]}"; do
267
+ [[ -n "$json_missing" ]] && json_missing+=","
268
+ json_missing+="\"$m\""
269
+ done
270
+ echo "{\"ready\": false, \"missing\": [$json_missing]}"
271
+ fi
272
+ }
273
+
274
+ cmd_adr_next() {
275
+ find_compass_dir || die "no .compass/ directory found"
276
+
277
+ local project_root="${COMPASS_DIR%/.compass}"
278
+ local adr_dir
279
+ adr_dir=$(yaml_get "$CONFIG_FILE" "paths.adr" 2>/dev/null)
280
+ adr_dir="${adr_dir:-.compass/ADR}"
281
+
282
+ local max=0
283
+ for f in "$project_root/$adr_dir"/ADR-*.md; do
284
+ [[ -f "$f" ]] || continue
285
+ local num
286
+ num=$(basename "$f" | grep -oE 'ADR-([0-9]+)' | grep -oE '[0-9]+')
287
+ if [[ $num -gt $max ]]; then
288
+ max=$num
289
+ fi
290
+ done
291
+
292
+ printf "%03d\n" $((max + 1))
293
+ }
294
+
295
+ cmd_progress() {
296
+ find_compass_dir || die "no .compass/ directory found"
297
+
298
+ local project_root="${COMPASS_DIR%/.compass}"
299
+ local units_dir
300
+ units_dir=$(yaml_get "$CONFIG_FILE" "paths.units" 2>/dev/null)
301
+ units_dir="${units_dir:-.compass/UNITS}"
302
+
303
+ if [[ ! -d "$project_root/$units_dir" ]]; then
304
+ echo '{"total": 0, "done": 0, "in_progress": 0, "pending": 0, "blocked": 0, "units": []}'
305
+ return
306
+ fi
307
+
308
+ local total=0 done=0 progress=0 pending=0 blocked=0
309
+ local units_json="["
310
+ local first=true
311
+
312
+ for f in "$project_root/$units_dir"/unit-*.md; do
313
+ [[ -f "$f" ]] || continue
314
+ ((total++))
315
+
316
+ local name status title deps
317
+ name=$(basename "$f" .md)
318
+ status=$(awk '/^## Status/{getline; print; exit}' "$f" | tr -d '[:space:]')
319
+ title=$(head -1 "$f" | sed 's/^# //')
320
+
321
+ case "$status" in
322
+ done) ((done++)) ;;
323
+ in-progress) ((progress++)) ;;
324
+ blocked) ((blocked++)) ;;
325
+ *) status="pending"; ((pending++)) ;;
326
+ esac
327
+
328
+ [[ "$first" == "true" ]] && first=false || units_json+=","
329
+ units_json+="{\"name\":\"$name\",\"title\":\"$title\",\"status\":\"$status\"}"
330
+ done
331
+
332
+ units_json+="]"
333
+
334
+ cat <<ENDJSON
335
+ {
336
+ "total": $total,
337
+ "done": $done,
338
+ "in_progress": $progress,
339
+ "pending": $pending,
340
+ "blocked": $blocked,
341
+ "units": $units_json
342
+ }
343
+ ENDJSON
344
+ }
345
+
346
+ cmd_session_update() {
347
+ find_compass_dir || die "no .compass/ directory found"
348
+
349
+ local session_file="$COMPASS_DIR/SESSION.md"
350
+ local timestamp
351
+ timestamp=$(date -u '+%Y-%m-%dT%H:%M:%SZ')
352
+
353
+ # Get current status
354
+ local status_output
355
+ status_output=$(cmd_status)
356
+
357
+ local phase
358
+ phase=$(echo "$status_output" | head -1 | sed 's/Phase: //')
359
+
360
+ cat > "$session_file" <<EOF
361
+ # COMPASS Session State
362
+
363
+ Last updated: $timestamp
364
+
365
+ ## Current phase
366
+
367
+ $phase
368
+
369
+ ## Phase progress
370
+
371
+ $status_output
372
+
373
+ ## Recent decisions
374
+
375
+ (updated by agents after decisions)
376
+
377
+ ## Stopped at
378
+
379
+ (updated by agents or context monitor)
380
+
381
+ ## Next recommended action
382
+
383
+ (determined by /compass:next)
384
+ EOF
385
+ }
386
+
387
+ cmd_config_get() {
388
+ local key="$1"
389
+ [[ -n "$key" ]] || die "usage: compass-tools.sh config get <key>"
390
+
391
+ find_compass_dir || die "no .compass/ directory found"
392
+
393
+ local value
394
+ value=$(yaml_get "$CONFIG_FILE" "$key")
395
+ [[ -n "$value" ]] && echo "$value" || die "key not found: $key"
396
+ }
397
+
398
+ cmd_config_set() {
399
+ local key="$1"
400
+ local value="$2"
401
+ [[ -n "$key" && -n "$value" ]] || die "usage: compass-tools.sh config set <key> <value>"
402
+
403
+ find_compass_dir || die "no .compass/ directory found"
404
+
405
+ yaml_set "$CONFIG_FILE" "$key" "$value"
406
+ }
407
+
408
+ cmd_drift() {
409
+ find_compass_dir || die "no .compass/ directory found"
410
+
411
+ local project_root="${COMPASS_DIR%/.compass}"
412
+
413
+ # Check spec exists
414
+ local spec_path
415
+ spec_path=$(yaml_get "$CONFIG_FILE" "paths.spec" 2>/dev/null)
416
+ spec_path="${spec_path:-.compass/SPEC.md}"
417
+ [[ -f "$project_root/$spec_path" ]] || die "no spec file found at $spec_path"
418
+
419
+ # Get the diff — either from argument or git
420
+ local diff_content=""
421
+ if [[ -n "${1:-}" && -f "$1" ]]; then
422
+ diff_content=$(cat "$1")
423
+ else
424
+ # Use git diff of staged + unstaged changes
425
+ diff_content=$(cd "$project_root" && git diff HEAD 2>/dev/null)
426
+ if [[ -z "$diff_content" ]]; then
427
+ diff_content=$(cd "$project_root" && git diff 2>/dev/null)
428
+ fi
429
+ fi
430
+
431
+ [[ -n "$diff_content" ]] || die "no diff found (provide a file or have uncommitted git changes)"
432
+
433
+ # Extract changed files (outside .compass/)
434
+ local changed_files
435
+ changed_files=$(echo "$diff_content" | grep -E '^\+\+\+ b/' | sed 's|^+++ b/||' | grep -v '\.compass/' || true)
436
+
437
+ [[ -n "$changed_files" ]] || { echo '{"files": 0, "drift_possible": false}'; return; }
438
+
439
+ local file_count
440
+ file_count=$(echo "$changed_files" | wc -l)
441
+
442
+ # Collect spec sections and ADR titles for reference
443
+ local spec_sections=""
444
+ if [[ -f "$project_root/$spec_path" ]]; then
445
+ spec_sections=$(grep -E '^##+ ' "$project_root/$spec_path" | head -20)
446
+ fi
447
+
448
+ local adr_dir
449
+ adr_dir=$(yaml_get "$CONFIG_FILE" "paths.adr" 2>/dev/null)
450
+ adr_dir="${adr_dir:-.compass/ADR}"
451
+ local adr_titles=""
452
+ if [[ -d "$project_root/$adr_dir" ]]; then
453
+ adr_titles=$(for f in "$project_root/$adr_dir"/ADR-*.md; do
454
+ [[ -f "$f" ]] && head -1 "$f" | sed 's/^# //'
455
+ done)
456
+ fi
457
+
458
+ if [[ "$1" == "--json" || "${2:-}" == "--json" ]]; then
459
+ # Output JSON for the scope-guardian agent to consume
460
+ local files_json="["
461
+ local first=true
462
+ while IFS= read -r f; do
463
+ [[ "$first" == "true" ]] && first=false || files_json+=","
464
+ files_json+="\"$f\""
465
+ done <<< "$changed_files"
466
+ files_json+="]"
467
+
468
+ cat <<ENDJSON
469
+ {
470
+ "files_changed": $file_count,
471
+ "drift_possible": true,
472
+ "changed_files": $files_json,
473
+ "spec_path": "$spec_path",
474
+ "adr_dir": "$adr_dir",
475
+ "framing_path": ".compass/FRAMING.md"
476
+ }
477
+ ENDJSON
478
+ else
479
+ echo "Files changed outside .compass/: $file_count"
480
+ echo "$changed_files" | sed 's/^/ /'
481
+ echo ""
482
+ echo "Spec sections to check against:"
483
+ echo "$spec_sections" | sed 's/^/ /'
484
+ echo ""
485
+ echo "Active ADRs:"
486
+ echo "$adr_titles" | sed 's/^/ /'
487
+ fi
488
+ }
489
+
490
+ cmd_unit_update_status() {
491
+ local unit_num="$1"
492
+ local new_status="$2"
493
+
494
+ [[ -n "$unit_num" && -n "$new_status" ]] || die "usage: compass-tools.sh unit update-status <unit-number> <status>"
495
+
496
+ # Validate status
497
+ case "$new_status" in
498
+ pending|in-progress|done|blocked) ;;
499
+ *) die "invalid status: $new_status (valid: pending, in-progress, done, blocked)" ;;
500
+ esac
501
+
502
+ find_compass_dir || die "no .compass/ directory found"
503
+
504
+ local project_root="${COMPASS_DIR%/.compass}"
505
+ local units_dir
506
+ units_dir=$(yaml_get "$CONFIG_FILE" "paths.units" 2>/dev/null)
507
+ units_dir="${units_dir:-.compass/UNITS}"
508
+
509
+ # Find the unit file matching the number
510
+ local padded
511
+ padded=$(printf "%03d" "$unit_num")
512
+ local unit_file=""
513
+ for f in "$project_root/$units_dir"/unit-"$padded"-*.md; do
514
+ [[ -f "$f" ]] && unit_file="$f" && break
515
+ done
516
+
517
+ [[ -n "$unit_file" ]] || die "no unit file found for number $unit_num"
518
+
519
+ # Replace the status line (line after ## Status)
520
+ awk -v new_status="$new_status" '
521
+ /^## Status/ { print; getline; print new_status; next }
522
+ { print }
523
+ ' "$unit_file" > "$unit_file.tmp" && mv "$unit_file.tmp" "$unit_file"
524
+
525
+ echo "unit-$padded status updated to: $new_status"
526
+ }
527
+
528
+ cmd_init() {
529
+ local project_root="${1:-.}"
530
+ local compass_dir="$project_root/.compass"
531
+
532
+ [[ -d "$compass_dir" ]] && die ".compass/ already exists at $project_root"
533
+
534
+ mkdir -p "$compass_dir"/{RESEARCH,ADR,UNITS}
535
+
536
+ echo "created: $compass_dir/"
537
+ echo "created: $compass_dir/RESEARCH/"
538
+ echo "created: $compass_dir/ADR/"
539
+ echo "created: $compass_dir/UNITS/"
540
+ }
541
+
542
+ # --- Main dispatch ---
543
+
544
+ case "${1:-}" in
545
+ status)
546
+ shift; cmd_status "$@" ;;
547
+ preflight)
548
+ shift; cmd_preflight "$@" ;;
549
+ adr)
550
+ shift
551
+ case "${1:-}" in
552
+ next-number) cmd_adr_next ;;
553
+ *) die "unknown adr subcommand: ${1:-}" ;;
554
+ esac
555
+ ;;
556
+ progress)
557
+ shift; cmd_progress "$@" ;;
558
+ drift)
559
+ shift; cmd_drift "$@" ;;
560
+ unit)
561
+ shift
562
+ case "${1:-}" in
563
+ update-status) shift; cmd_unit_update_status "$@" ;;
564
+ *) die "unknown unit subcommand: ${1:-}" ;;
565
+ esac
566
+ ;;
567
+ session)
568
+ shift
569
+ case "${1:-}" in
570
+ update) cmd_session_update ;;
571
+ *) die "unknown session subcommand: ${1:-}" ;;
572
+ esac
573
+ ;;
574
+ config)
575
+ shift
576
+ case "${1:-}" in
577
+ get) shift; cmd_config_get "$@" ;;
578
+ set) shift; cmd_config_set "$@" ;;
579
+ *) die "unknown config subcommand: ${1:-}" ;;
580
+ esac
581
+ ;;
582
+ init)
583
+ shift; cmd_init "$@" ;;
584
+ *)
585
+ cat <<USAGE
586
+ compass-tools.sh — Deterministic CLI for COMPASS state management.
587
+
588
+ Commands:
589
+ status [--json] Project state overview
590
+ preflight <phase> Check phase prerequisites
591
+ adr next-number Next ADR number (zero-padded)
592
+ progress Unit completion stats (JSON)
593
+ drift [--json] Diff analysis for scope guardian
594
+ unit update-status N S Update unit N to status S
595
+ session update Update SESSION.md
596
+ config get <key> Read config value (dot notation)
597
+ config set <key> <val> Write config value
598
+ init [project-root] Scaffold .compass/ directory
599
+ USAGE
600
+ exit 1
601
+ ;;
602
+ esac
@@ -0,0 +1,91 @@
1
+ ---
2
+ name: compass-architect
3
+ description: "Collaborative architecture review. Present research findings, challenge proposals, document decisions."
4
+ ---
5
+
6
+ # /compass:architect
7
+
8
+ Collaborative architecture session. The human proposes architectural ideas; COMPASS
9
+ challenges them with trade-offs from research, asks hard questions, and helps
10
+ document the result.
11
+
12
+ ## Pre-flight
13
+
14
+ 1. Run `compass-tools.sh preflight architect` to verify:
15
+ - `.compass/FRAMING.md` exists
16
+ - `.compass/RESEARCH/` has at least one dossier
17
+ - If prerequisites missing, inform the user which phases to complete first.
18
+ 2. Check if `.compass/ARCHITECTURE.md` already exists.
19
+ - If yes: inform the user. Ask if they want to revise or continue from current state.
20
+
21
+ ## Required reading
22
+
23
+ Load before proceeding:
24
+
25
+ - `~/.claude/compass/constitution.md`
26
+ - `~/.claude/compass/references/inverted-contract.md`
27
+ - `.compass/config.yaml`
28
+ - `.compass/FRAMING.md`
29
+ - `.compass/BASELINE.md` (if exists)
30
+ - All files in `.compass/RESEARCH/` — these are your ammunition for the conversation.
31
+
32
+ ## Execution
33
+
34
+ You ARE the architect-coach for this phase. Read and embody the agent definition at
35
+ `~/.claude/compass/agents/architect-coach.md`.
36
+
37
+ ### Interaction flow
38
+
39
+ 1. **Opening**: Briefly summarize what you learned from the research dossiers that
40
+ is relevant to architecture. Highlight trade-offs and tensions found.
41
+ Then ask: "What architectural approach are you considering?"
42
+
43
+ 2. **Reactive challenging**: As the human proposes ideas, your job is to:
44
+ - Connect proposals to research findings: "Dossier 002 found that approach X
45
+ has failure mode Y in contexts similar to yours."
46
+ - Surface trade-offs: "That gives you A but costs you B. Is that acceptable?"
47
+ - Test boundaries: "What happens at the boundary between module X and module Y?"
48
+ - Probe assumptions: "You're assuming Z is available — what if it isn't?"
49
+ - Identify what's missing: "You've covered communication but haven't mentioned
50
+ failure recovery."
51
+
52
+ 3. **Socratic level adjustment**: Read `style.socratic_level` from config.yaml.
53
+ - `high`: challenge relentlessly. Don't validate. Every answer gets a harder question.
54
+ - `balanced`: challenge, then acknowledge good reasoning. "That addresses the
55
+ concern from dossier 003 well. Now, what about...?"
56
+ - `low`: challenge key decisions, help structure the rest more directly.
57
+
58
+ 4. **Do NOT propose architecture**: If the human asks "what should the architecture be?",
59
+ redirect:
60
+ - "Based on dossier 001, there are three common approaches for this: [list from
61
+ research]. Which resonates with your constraints?"
62
+ - "What properties matter most to you: modularity, performance, simplicity?"
63
+ - Present research findings as options — never as recommendations.
64
+
65
+ 5. **Iterate**: Architecture is rarely defined in one pass. Expect multiple rounds.
66
+ After each round, summarize the current state and ask what needs more work.
67
+
68
+ ## Producing ARCHITECTURE.md
69
+
70
+ When the human is satisfied with the architecture, write `.compass/ARCHITECTURE.md`
71
+ using the template at `~/.claude/compass/templates/ARCHITECTURE.md`.
72
+
73
+ The document captures:
74
+ - The architecture as the human defined it (their words, your structure)
75
+ - Key trade-offs acknowledged
76
+ - References to research dossiers that informed decisions
77
+ - Open architectural questions (if any remain)
78
+ - Components/modules identified and their responsibilities
79
+ - Boundaries and interfaces between components
80
+
81
+ Present the document to the user for review before finalizing.
82
+
83
+ ## Closing
84
+
85
+ 1. Run `compass-tools.sh session update` to record progress.
86
+ 2. Show summary of what was documented.
87
+ 3. Note any decisions that surfaced during the architecture session that should
88
+ become formal ADRs.
89
+ 4. Suggest: "Architecture documented. Next step: `/compass:decide` to formalize
90
+ the key decisions as ADRs."
91
+ 5. Suggest `/clear` before the next phase.