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.
- package/LICENSE +21 -0
- package/README.md +162 -0
- package/agents/adr-scribe.md +82 -0
- package/agents/architect-coach.md +88 -0
- package/agents/baseline-analyzer.md +93 -0
- package/agents/completion-tracker.md +55 -0
- package/agents/domain-researcher.md +122 -0
- package/agents/idiom-checker.md +111 -0
- package/agents/readiness-checker.md +93 -0
- package/agents/review-coach.md +94 -0
- package/agents/rubber-duck.md +69 -0
- package/agents/scope-guardian.md +98 -0
- package/agents/socratic-interrogator.md +93 -0
- package/agents/spec-writer.md +106 -0
- package/agents/test-auditor.md +92 -0
- package/agents/unit-decomposer.md +114 -0
- package/bin/install.js +306 -0
- package/constitution.md +191 -0
- package/hooks/compass-commit-check.sh +65 -0
- package/hooks/compass-context-monitor.sh +53 -0
- package/hooks/compass-preflight.sh +56 -0
- package/hooks/compass-scope-guardian.sh +95 -0
- package/package.json +36 -0
- package/references/inverted-contract.md +31 -0
- package/scripts/compass-tools.sh +602 -0
- package/skills/compass-architect/SKILL.md +91 -0
- package/skills/compass-build-duck/SKILL.md +53 -0
- package/skills/compass-build-idiom/SKILL.md +57 -0
- package/skills/compass-build-progress/SKILL.md +55 -0
- package/skills/compass-build-ready/SKILL.md +69 -0
- package/skills/compass-build-review/SKILL.md +61 -0
- package/skills/compass-build-scope/SKILL.md +67 -0
- package/skills/compass-build-tests/SKILL.md +57 -0
- package/skills/compass-build-transform/SKILL.md +63 -0
- package/skills/compass-build-units/SKILL.md +76 -0
- package/skills/compass-decide/SKILL.md +105 -0
- package/skills/compass-frame/SKILL.md +105 -0
- package/skills/compass-init/SKILL.md +142 -0
- package/skills/compass-next/SKILL.md +60 -0
- package/skills/compass-research/SKILL.md +81 -0
- package/skills/compass-spec/SKILL.md +115 -0
- package/skills/compass-status/SKILL.md +79 -0
- package/templates/ADR.md +48 -0
- package/templates/ARCHITECTURE.md +44 -0
- package/templates/BASELINE.md +32 -0
- package/templates/FRAMING.md +51 -0
- package/templates/RESEARCH-DOSSIER.md +36 -0
- package/templates/SESSION.md +23 -0
- package/templates/SPEC.md +40 -0
- package/templates/UNIT.md +33 -0
- 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.
|