@rpamis/comet 0.3.6 → 0.3.8
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/README.md +575 -443
- package/assets/manifest.json +41 -24
- package/assets/skills/comet/SKILL.md +282 -301
- package/assets/skills/comet/reference/auto-transition.md +27 -0
- package/assets/skills/comet/reference/comet-yaml-fields.md +68 -0
- package/assets/skills/comet/reference/context-recovery.md +35 -0
- package/assets/skills/comet/reference/debug-gate.md +17 -0
- package/assets/skills/comet/reference/decision-point.md +20 -0
- package/assets/skills/comet/reference/dirty-worktree.md +1 -0
- package/assets/skills/comet/reference/file-structure.md +28 -0
- package/assets/skills/comet/reference/subagent-dispatch.md +113 -0
- package/assets/skills/comet/rules/comet-phase-guard.en.md +99 -0
- package/assets/skills/comet/rules/comet-phase-guard.md +99 -0
- package/assets/skills/comet/scripts/comet-archive.sh +71 -55
- package/assets/skills/comet/scripts/comet-guard.sh +174 -18
- package/assets/skills/comet/scripts/comet-handoff.sh +133 -6
- package/assets/skills/comet/scripts/comet-hook-guard.sh +260 -0
- package/assets/skills/comet/scripts/comet-state.sh +362 -25
- package/assets/skills/comet/scripts/comet-yaml-validate.sh +24 -1
- package/assets/skills/comet-archive/SKILL.md +100 -73
- package/assets/skills/comet-build/SKILL.md +304 -221
- package/assets/skills/comet-design/SKILL.md +263 -166
- package/assets/skills/comet-hotfix/SKILL.md +204 -169
- package/assets/skills/comet-open/SKILL.md +203 -113
- package/assets/skills/comet-tweak/SKILL.md +180 -154
- package/assets/skills/comet-verify/SKILL.md +234 -201
- package/assets/skills-zh/comet/SKILL.md +269 -301
- package/assets/skills-zh/comet/reference/auto-transition.md +27 -0
- package/assets/skills-zh/comet/reference/comet-yaml-fields.md +68 -0
- package/assets/skills-zh/comet/reference/context-recovery.md +35 -0
- package/assets/skills-zh/comet/reference/debug-gate.md +17 -0
- package/assets/skills-zh/comet/reference/decision-point.md +20 -0
- package/assets/skills-zh/comet/reference/dirty-worktree.md +2 -1
- package/assets/skills-zh/comet/reference/file-structure.md +28 -0
- package/assets/skills-zh/comet/reference/subagent-dispatch.md +113 -0
- package/assets/skills-zh/comet-archive/SKILL.md +100 -73
- package/assets/skills-zh/comet-build/SKILL.md +304 -221
- package/assets/skills-zh/comet-design/SKILL.md +264 -166
- package/assets/skills-zh/comet-hotfix/SKILL.md +200 -169
- package/assets/skills-zh/comet-open/SKILL.md +203 -113
- package/assets/skills-zh/comet-tweak/SKILL.md +176 -154
- package/assets/skills-zh/comet-verify/SKILL.md +232 -201
- package/dist/cli/index.js +19 -0
- package/dist/cli/index.js.map +1 -1
- package/dist/commands/doctor.d.ts.map +1 -1
- package/dist/commands/doctor.js +22 -0
- package/dist/commands/doctor.js.map +1 -1
- package/dist/commands/init.d.ts +5 -1
- package/dist/commands/init.d.ts.map +1 -1
- package/dist/commands/init.js +64 -9
- package/dist/commands/init.js.map +1 -1
- package/dist/commands/uninstall.d.ts +9 -0
- package/dist/commands/uninstall.d.ts.map +1 -0
- package/dist/commands/uninstall.js +113 -0
- package/dist/commands/uninstall.js.map +1 -0
- package/dist/commands/update.d.ts +1 -1
- package/dist/commands/update.d.ts.map +1 -1
- package/dist/commands/update.js +120 -32
- package/dist/commands/update.js.map +1 -1
- package/dist/core/codegraph.d.ts +4 -0
- package/dist/core/codegraph.d.ts.map +1 -0
- package/dist/core/codegraph.js +63 -0
- package/dist/core/codegraph.js.map +1 -0
- package/dist/core/detect.d.ts +8 -1
- package/dist/core/detect.d.ts.map +1 -1
- package/dist/core/detect.js +41 -2
- package/dist/core/detect.js.map +1 -1
- package/dist/core/openspec.d.ts +1 -1
- package/dist/core/openspec.d.ts.map +1 -1
- package/dist/core/openspec.js +40 -17
- package/dist/core/openspec.js.map +1 -1
- package/dist/core/platforms.d.ts +10 -0
- package/dist/core/platforms.d.ts.map +1 -1
- package/dist/core/platforms.js +177 -16
- package/dist/core/platforms.js.map +1 -1
- package/dist/core/skills.d.ts +36 -1
- package/dist/core/skills.d.ts.map +1 -1
- package/dist/core/skills.js +451 -15
- package/dist/core/skills.js.map +1 -1
- package/dist/core/superpowers.d.ts.map +1 -1
- package/dist/core/superpowers.js +1 -0
- package/dist/core/superpowers.js.map +1 -1
- package/dist/core/uninstall.d.ts +28 -0
- package/dist/core/uninstall.d.ts.map +1 -0
- package/dist/core/uninstall.js +421 -0
- package/dist/core/uninstall.js.map +1 -0
- package/dist/core/version.d.ts +31 -0
- package/dist/core/version.d.ts.map +1 -0
- package/dist/core/version.js +111 -0
- package/dist/core/version.js.map +1 -0
- package/dist/utils/file-system.d.ts +23 -0
- package/dist/utils/file-system.d.ts.map +1 -1
- package/dist/utils/file-system.js +100 -5
- package/dist/utils/file-system.js.map +1 -1
- package/package.json +69 -62
|
@@ -6,6 +6,7 @@
|
|
|
6
6
|
set -euo pipefail
|
|
7
7
|
|
|
8
8
|
COMET_BASH="${COMET_BASH:-${BASH:-bash}}"
|
|
9
|
+
COMET_OPENSPEC="${COMET_OPENSPEC:-openspec}"
|
|
9
10
|
|
|
10
11
|
red() { echo -e "\033[31m$1\033[0m" >&2; }
|
|
11
12
|
green() { echo -e "\033[32m$1\033[0m" >&2; }
|
|
@@ -39,9 +40,9 @@ validate_change_name "$CHANGE"
|
|
|
39
40
|
|
|
40
41
|
CHANGE_DIR="openspec/changes/$CHANGE"
|
|
41
42
|
YAML="$CHANGE_DIR/.comet.yaml"
|
|
42
|
-
SCRIPT_DIR="$(cd "$(dirname "$
|
|
43
|
+
SCRIPT_DIR="$(cd "$(dirname "$0")" && pwd -P)"
|
|
43
44
|
STATE_SH="$SCRIPT_DIR/comet-state.sh"
|
|
44
|
-
TODAY=$(date +%Y-%m-%d)
|
|
45
|
+
TODAY=$(date -u +%Y-%m-%d)
|
|
45
46
|
ARCHIVE_NAME="${TODAY}-${CHANGE}"
|
|
46
47
|
ARCHIVE_DIR="openspec/changes/archive/${ARCHIVE_NAME}"
|
|
47
48
|
|
|
@@ -158,45 +159,7 @@ fi
|
|
|
158
159
|
|
|
159
160
|
step_ok "Archive target available"
|
|
160
161
|
|
|
161
|
-
# --- Step 4:
|
|
162
|
-
|
|
163
|
-
sync_delta_specs() {
|
|
164
|
-
local delta_root="$CHANGE_DIR/specs"
|
|
165
|
-
if [ ! -d "$delta_root" ]; then
|
|
166
|
-
return 0
|
|
167
|
-
fi
|
|
168
|
-
|
|
169
|
-
for delta_spec_dir in "$delta_root"/*/; do
|
|
170
|
-
[ -d "$delta_spec_dir" ] || continue
|
|
171
|
-
local capability
|
|
172
|
-
capability=$(basename "$delta_spec_dir")
|
|
173
|
-
local delta_spec="$delta_spec_dir/spec.md"
|
|
174
|
-
local main_spec="openspec/specs/$capability/spec.md"
|
|
175
|
-
|
|
176
|
-
if [ ! -f "$delta_spec" ]; then
|
|
177
|
-
continue
|
|
178
|
-
fi
|
|
179
|
-
|
|
180
|
-
if [ "$DRY_RUN" -eq 1 ]; then
|
|
181
|
-
step_dry_run "Would sync: $capability → $main_spec"
|
|
182
|
-
continue
|
|
183
|
-
fi
|
|
184
|
-
|
|
185
|
-
if [ ! -f "$main_spec" ]; then
|
|
186
|
-
mkdir -p "openspec/specs/$capability"
|
|
187
|
-
elif ! cmp -s "$main_spec" "$delta_spec"; then
|
|
188
|
-
yellow " [DIFF] Delta spec differs from main spec before sync: $capability"
|
|
189
|
-
diff -u "$main_spec" "$delta_spec" >&2 || true
|
|
190
|
-
fi
|
|
191
|
-
cp "$delta_spec" "$main_spec"
|
|
192
|
-
|
|
193
|
-
step_ok "Delta spec synced: $capability → openspec/specs/$capability/spec.md"
|
|
194
|
-
done
|
|
195
|
-
}
|
|
196
|
-
|
|
197
|
-
sync_delta_specs
|
|
198
|
-
|
|
199
|
-
# --- Step 5: Annotate design doc frontmatter ---
|
|
162
|
+
# --- Step 4: Prepare document frontmatter annotation ---
|
|
200
163
|
|
|
201
164
|
annotate_frontmatter() {
|
|
202
165
|
local file="$1"
|
|
@@ -214,6 +177,7 @@ annotate_frontmatter() {
|
|
|
214
177
|
if head -1 "$file" | grep -q '^---'; then
|
|
215
178
|
local tmp_file
|
|
216
179
|
tmp_file=$(mktemp)
|
|
180
|
+
chmod 600 "$tmp_file"
|
|
217
181
|
awk -v archive="$ARCHIVE_NAME" -v extra="$extra_fields" '
|
|
218
182
|
/^archived-with:/ { next }
|
|
219
183
|
NR==1 && /^---/ { print; next }
|
|
@@ -228,6 +192,7 @@ annotate_frontmatter() {
|
|
|
228
192
|
else
|
|
229
193
|
local tmp_file
|
|
230
194
|
tmp_file=$(mktemp)
|
|
195
|
+
chmod 600 "$tmp_file"
|
|
231
196
|
{
|
|
232
197
|
echo "---"
|
|
233
198
|
echo "archived-with: $ARCHIVE_NAME"
|
|
@@ -244,27 +209,78 @@ annotate_frontmatter() {
|
|
|
244
209
|
step_ok "Annotated: $file"
|
|
245
210
|
}
|
|
246
211
|
|
|
247
|
-
|
|
248
|
-
annotate_frontmatter "$DESIGN_DOC" "status: final"
|
|
249
|
-
fi
|
|
212
|
+
# --- Step 5: Run OpenSpec archive for delta merge and move ---
|
|
250
213
|
|
|
251
|
-
|
|
214
|
+
verify_main_specs_clean() {
|
|
215
|
+
if [ ! -d "openspec/specs" ]; then
|
|
216
|
+
return 0
|
|
217
|
+
fi
|
|
252
218
|
|
|
253
|
-
|
|
254
|
-
|
|
255
|
-
|
|
219
|
+
local found=0
|
|
220
|
+
local matches
|
|
221
|
+
for spec_file in openspec/specs/*/spec.md; do
|
|
222
|
+
[ -f "$spec_file" ] || continue
|
|
223
|
+
matches=$(grep -nE '^## (ADDED|MODIFIED|REMOVED|RENAMED) Requirements$' "$spec_file" 2>/dev/null || true)
|
|
224
|
+
if [ -n "$matches" ]; then
|
|
225
|
+
red "FATAL: delta-only section heading leaked into main spec: $spec_file"
|
|
226
|
+
printf '%s\n' "$matches" >&2
|
|
227
|
+
found=1
|
|
228
|
+
fi
|
|
229
|
+
done
|
|
256
230
|
|
|
257
|
-
|
|
231
|
+
if [ "$found" -ne 0 ]; then
|
|
232
|
+
return 1
|
|
233
|
+
fi
|
|
234
|
+
return 0
|
|
235
|
+
}
|
|
236
|
+
|
|
237
|
+
resolve_archive_dir() {
|
|
238
|
+
if [ -d "$ARCHIVE_DIR" ]; then
|
|
239
|
+
return 0
|
|
240
|
+
fi
|
|
241
|
+
# Fallback: search for any directory matching *-$CHANGE in archive
|
|
242
|
+
local found
|
|
243
|
+
found=$(find "openspec/changes/archive" -maxdepth 1 -mindepth 1 -type d -name "*-$CHANGE" 2>/dev/null | head -1 || true)
|
|
244
|
+
if [ -n "$found" ]; then
|
|
245
|
+
ARCHIVE_DIR="$found"
|
|
246
|
+
ARCHIVE_NAME=$(basename "$found")
|
|
247
|
+
return 0
|
|
248
|
+
fi
|
|
249
|
+
return 1
|
|
250
|
+
}
|
|
258
251
|
|
|
259
252
|
if [ "$DRY_RUN" -eq 1 ]; then
|
|
260
|
-
step_dry_run "Would
|
|
253
|
+
step_dry_run "Would run OpenSpec archive: $CHANGE"
|
|
261
254
|
else
|
|
262
|
-
|
|
263
|
-
|
|
264
|
-
|
|
255
|
+
if ! command -v "$COMET_OPENSPEC" >/dev/null 2>&1; then
|
|
256
|
+
red "FATAL: OpenSpec CLI not found: $COMET_OPENSPEC"
|
|
257
|
+
red "Install OpenSpec or set COMET_OPENSPEC to the openspec executable."
|
|
258
|
+
exit 1
|
|
259
|
+
fi
|
|
260
|
+
|
|
261
|
+
"$COMET_OPENSPEC" archive "$CHANGE" --yes >&2
|
|
262
|
+
if ! resolve_archive_dir; then
|
|
263
|
+
step_fail "OpenSpec archive output not found"
|
|
264
|
+
exit 1
|
|
265
|
+
else
|
|
266
|
+
step_ok "OpenSpec archive completed: $ARCHIVE_DIR"
|
|
267
|
+
fi
|
|
268
|
+
|
|
269
|
+
verify_main_specs_clean
|
|
270
|
+
step_ok "Main specs verified clean"
|
|
271
|
+
fi
|
|
272
|
+
|
|
273
|
+
# --- Step 6: Annotate design doc and plan frontmatter ---
|
|
274
|
+
|
|
275
|
+
if [ -n "$DESIGN_DOC" ] && [ "$DESIGN_DOC" != "null" ]; then
|
|
276
|
+
annotate_frontmatter "$DESIGN_DOC" "status: final"
|
|
277
|
+
fi
|
|
278
|
+
|
|
279
|
+
if [ -n "$PLAN_PATH" ] && [ "$PLAN_PATH" != "null" ]; then
|
|
280
|
+
annotate_frontmatter "$PLAN_PATH" ""
|
|
265
281
|
fi
|
|
266
282
|
|
|
267
|
-
# --- Step
|
|
283
|
+
# --- Step 7: Mark archived via comet-state transition ---
|
|
268
284
|
|
|
269
285
|
ARCHIVE_YAML="$ARCHIVE_DIR/.comet.yaml"
|
|
270
286
|
|
|
@@ -279,7 +295,7 @@ else
|
|
|
279
295
|
fi
|
|
280
296
|
fi
|
|
281
297
|
|
|
282
|
-
# --- Step
|
|
298
|
+
# --- Step 8: Print summary ---
|
|
283
299
|
|
|
284
300
|
echo "" >&2
|
|
285
301
|
if [ "$DRY_RUN" -eq 1 ]; then
|
|
@@ -34,18 +34,26 @@ validate_change_name() {
|
|
|
34
34
|
fi
|
|
35
35
|
}
|
|
36
36
|
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
37
|
+
if [ "${COMET_GUARD_SOURCE_ONLY:-0}" = "1" ]; then
|
|
38
|
+
CHANGE="${CHANGE:-}"
|
|
39
|
+
PHASE="${PHASE:-}"
|
|
40
|
+
APPLY="${APPLY:-0}"
|
|
41
|
+
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]:-$0}")" && pwd -P)"
|
|
42
|
+
CHANGE_DIR="${CHANGE_DIR:-}"
|
|
43
|
+
else
|
|
44
|
+
validate_change_name "$1"
|
|
45
|
+
|
|
46
|
+
CHANGE="$1"
|
|
47
|
+
PHASE="$2"
|
|
48
|
+
APPLY=0
|
|
49
|
+
SCRIPT_DIR="$(cd "$(dirname "$0")" && pwd -P)"
|
|
50
|
+
if [[ "${3:-}" == "--apply" ]]; then
|
|
51
|
+
APPLY=1
|
|
52
|
+
fi
|
|
53
|
+
CHANGE_DIR="openspec/changes/$CHANGE"
|
|
54
|
+
if [ "$PHASE" = "archive" ] && [ ! -d "$CHANGE_DIR" ] && [ -d "openspec/changes/archive/$CHANGE" ]; then
|
|
55
|
+
CHANGE_DIR="openspec/changes/archive/$CHANGE"
|
|
56
|
+
fi
|
|
49
57
|
fi
|
|
50
58
|
|
|
51
59
|
BLOCK=0
|
|
@@ -94,6 +102,27 @@ tasks_has_any() {
|
|
|
94
102
|
[ -f "$tasks" ] && grep -q '\- \[' "$tasks"
|
|
95
103
|
}
|
|
96
104
|
|
|
105
|
+
plan_tasks_all_done() {
|
|
106
|
+
local plan
|
|
107
|
+
plan=$(yaml_field_value "plan" 2>/dev/null || true)
|
|
108
|
+
|
|
109
|
+
if [ -z "$plan" ] || [ "$plan" = "null" ]; then
|
|
110
|
+
return 0
|
|
111
|
+
fi
|
|
112
|
+
if [ ! -f "$plan" ]; then
|
|
113
|
+
echo "plan file is missing at $plan" >&2
|
|
114
|
+
echo "Next: restore the Superpowers plan file or update .comet.yaml plan before leaving build." >&2
|
|
115
|
+
return 1
|
|
116
|
+
fi
|
|
117
|
+
if grep -q '^[[:space:]]*- \[ \]' "$plan"; then
|
|
118
|
+
echo "Unfinished Superpowers plan tasks:" >&2
|
|
119
|
+
grep -n '^[[:space:]]*- \[ \]' "$plan" >&2 || true
|
|
120
|
+
echo "Next: check off corresponding completed plan tasks, then commit the plan update." >&2
|
|
121
|
+
return 1
|
|
122
|
+
fi
|
|
123
|
+
return 0
|
|
124
|
+
}
|
|
125
|
+
|
|
97
126
|
yaml_field_value() {
|
|
98
127
|
local field="$1"
|
|
99
128
|
local yaml="$CHANGE_DIR/.comet.yaml"
|
|
@@ -182,6 +211,17 @@ is_windows_bash() {
|
|
|
182
211
|
|
|
183
212
|
run_command_string() {
|
|
184
213
|
local command="$1"
|
|
214
|
+
if [ -z "$command" ]; then
|
|
215
|
+
red "ERROR: build/verify command is empty" >&2
|
|
216
|
+
return 1
|
|
217
|
+
fi
|
|
218
|
+
# Basic command injection guard: reject dangerous shell metacharacters
|
|
219
|
+
# Quotes are allowed to support paths with spaces (e.g. Windows)
|
|
220
|
+
if [[ "$command" =~ [\;\|\&\$\`] ]]; then
|
|
221
|
+
red "ERROR: build/verify command contains shell metacharacters: $command" >&2
|
|
222
|
+
red "Allowed: alphanumeric, spaces, hyphens, underscores, dots, colons, forward slashes, quotes" >&2
|
|
223
|
+
return 1
|
|
224
|
+
fi
|
|
185
225
|
echo "+ $command" >&2
|
|
186
226
|
"$COMET_BASH" -lc "$command"
|
|
187
227
|
}
|
|
@@ -219,12 +259,14 @@ handoff_source_files() {
|
|
|
219
259
|
}
|
|
220
260
|
|
|
221
261
|
compute_handoff_hash() {
|
|
222
|
-
|
|
262
|
+
local hash_input
|
|
263
|
+
hash_input=$(handoff_source_files | while IFS= read -r file; do
|
|
223
264
|
if [ -f "$file" ]; then
|
|
224
265
|
printf 'path:%s\n' "$file"
|
|
225
266
|
printf 'sha256:%s\n' "$(hash_file "$file")"
|
|
226
267
|
fi
|
|
227
|
-
done
|
|
268
|
+
done)
|
|
269
|
+
printf '%s' "$hash_input" | hash_stream
|
|
228
270
|
}
|
|
229
271
|
|
|
230
272
|
preflight() {
|
|
@@ -344,6 +386,47 @@ build_mode_allowed_for_workflow() {
|
|
|
344
386
|
esac
|
|
345
387
|
}
|
|
346
388
|
|
|
389
|
+
subagent_dispatch_confirmed() {
|
|
390
|
+
local build_mode subagent_dispatch
|
|
391
|
+
build_mode=$(yaml_field_value "build_mode" 2>/dev/null || true)
|
|
392
|
+
subagent_dispatch=$(yaml_field_value "subagent_dispatch" 2>/dev/null || true)
|
|
393
|
+
|
|
394
|
+
if [ "$build_mode" != "subagent-driven-development" ]; then
|
|
395
|
+
return 0
|
|
396
|
+
fi
|
|
397
|
+
|
|
398
|
+
if [ "$subagent_dispatch" = "confirmed" ]; then
|
|
399
|
+
return 0
|
|
400
|
+
fi
|
|
401
|
+
|
|
402
|
+
echo "subagent_dispatch must be confirmed before using build_mode=subagent-driven-development" >&2
|
|
403
|
+
echo "Next: confirm the current platform has a real background subagent/Task/multi-agent dispatcher, then run:" >&2
|
|
404
|
+
echo " \"\$COMET_BASH\" \"\$COMET_STATE\" set $CHANGE subagent_dispatch confirmed" >&2
|
|
405
|
+
echo "Or ask the user to switch to executing-plans and run:" >&2
|
|
406
|
+
echo " \"\$COMET_BASH\" \"\$COMET_STATE\" set $CHANGE build_mode executing-plans" >&2
|
|
407
|
+
return 1
|
|
408
|
+
}
|
|
409
|
+
|
|
410
|
+
tdd_mode_selected() {
|
|
411
|
+
local workflow tdd_mode
|
|
412
|
+
workflow=$(yaml_field_value "workflow" 2>/dev/null || true)
|
|
413
|
+
tdd_mode=$(yaml_field_value "tdd_mode" 2>/dev/null || true)
|
|
414
|
+
|
|
415
|
+
case "$workflow" in
|
|
416
|
+
hotfix|tweak) return 0 ;;
|
|
417
|
+
esac
|
|
418
|
+
|
|
419
|
+
case "$tdd_mode" in
|
|
420
|
+
tdd|direct) return 0 ;;
|
|
421
|
+
*)
|
|
422
|
+
echo "tdd_mode must be tdd or direct for full workflow, got '${tdd_mode:-null}'" >&2
|
|
423
|
+
echo "Next: ask the user to choose TDD enforcement level, then run:" >&2
|
|
424
|
+
echo " \"\$COMET_BASH\" \"\$COMET_STATE\" set $CHANGE tdd_mode <tdd|direct>" >&2
|
|
425
|
+
return 1
|
|
426
|
+
;;
|
|
427
|
+
esac
|
|
428
|
+
}
|
|
429
|
+
|
|
347
430
|
verify_result_is_pass() {
|
|
348
431
|
local result
|
|
349
432
|
result=$(yaml_field_value "verify_result" 2>/dev/null || true)
|
|
@@ -416,7 +499,7 @@ design_handoff_markdown_traceable() {
|
|
|
416
499
|
echo "handoff markdown is missing Generated-by marker" >&2
|
|
417
500
|
missing=1
|
|
418
501
|
}
|
|
419
|
-
grep -Eq '^- Mode: (compact|full)$' "$markdown" || {
|
|
502
|
+
grep -Eq '^- Mode: (compact|full|beta)$' "$markdown" || {
|
|
420
503
|
echo "handoff markdown is missing Mode marker" >&2
|
|
421
504
|
missing=1
|
|
422
505
|
}
|
|
@@ -435,6 +518,47 @@ design_handoff_markdown_traceable() {
|
|
|
435
518
|
[ "$missing" -eq 0 ]
|
|
436
519
|
}
|
|
437
520
|
|
|
521
|
+
context_compression_mode() {
|
|
522
|
+
local mode
|
|
523
|
+
mode=$(yaml_field_value "context_compression" 2>/dev/null || true)
|
|
524
|
+
printf '%s\n' "${mode:-off}"
|
|
525
|
+
}
|
|
526
|
+
|
|
527
|
+
beta_spec_json_structurally_valid() {
|
|
528
|
+
local context missing=0
|
|
529
|
+
if [ "$(context_compression_mode)" != "beta" ]; then
|
|
530
|
+
return 0
|
|
531
|
+
fi
|
|
532
|
+
|
|
533
|
+
context=$(yaml_field_value "handoff_context" 2>/dev/null || true)
|
|
534
|
+
if [ -z "$context" ] || [ "$context" = "null" ]; then
|
|
535
|
+
echo "handoff_context is missing from .comet.yaml" >&2
|
|
536
|
+
return 1
|
|
537
|
+
fi
|
|
538
|
+
if [ ! -s "$context" ]; then
|
|
539
|
+
echo "spec-context.json is missing or empty: $context" >&2
|
|
540
|
+
return 1
|
|
541
|
+
fi
|
|
542
|
+
|
|
543
|
+
# Validate required JSON fields
|
|
544
|
+
grep -q '"change"' "$context" || { echo "spec-context.json missing 'change' field" >&2; return 1; }
|
|
545
|
+
grep -q '"phase"' "$context" || { echo "spec-context.json missing 'phase' field" >&2; return 1; }
|
|
546
|
+
grep -q '"mode": "beta"' "$context" || { echo "spec-context.json mode is not beta" >&2; return 1; }
|
|
547
|
+
grep -q '"files"' "$context" || { echo "spec-context.json missing 'files' field" >&2; return 1; }
|
|
548
|
+
grep -q '"context_hash"' "$context" || { echo "spec-context.json missing 'context_hash' field" >&2; return 1; }
|
|
549
|
+
|
|
550
|
+
# Verify all source files are referenced in the JSON
|
|
551
|
+
handoff_source_files | while IFS= read -r file; do
|
|
552
|
+
[ -f "$file" ] || continue
|
|
553
|
+
if ! grep -qF "$file" "$context"; then
|
|
554
|
+
echo "spec-context.json missing source file reference: $file" >&2
|
|
555
|
+
exit 2
|
|
556
|
+
fi
|
|
557
|
+
done || missing=1
|
|
558
|
+
|
|
559
|
+
[ "$missing" -eq 0 ]
|
|
560
|
+
}
|
|
561
|
+
|
|
438
562
|
design_doc_frontmatter_has() {
|
|
439
563
|
local design_doc="$1"
|
|
440
564
|
local field="$2"
|
|
@@ -494,32 +618,55 @@ guard_open() {
|
|
|
494
618
|
guard_design() {
|
|
495
619
|
echo "=== Guard: design → build ===" >&2
|
|
496
620
|
|
|
497
|
-
local design_doc
|
|
621
|
+
local design_doc workflow
|
|
498
622
|
design_doc=$(yaml_field_value "design_doc" 2>/dev/null || true)
|
|
623
|
+
workflow=$(yaml_field_value "workflow" 2>/dev/null || true)
|
|
499
624
|
|
|
500
625
|
check "proposal.md exists" file_nonempty "$CHANGE_DIR/proposal.md"
|
|
501
626
|
check "design.md exists" file_nonempty "$CHANGE_DIR/design.md"
|
|
502
627
|
check "tasks.md exists" file_nonempty "$CHANGE_DIR/tasks.md"
|
|
503
628
|
check "design handoff context exists" design_handoff_context_valid
|
|
504
629
|
check "design handoff markdown is traceable" design_handoff_markdown_traceable
|
|
630
|
+
if [ "$(context_compression_mode)" = "beta" ]; then
|
|
631
|
+
check "beta spec-context.json is structurally valid" beta_spec_json_structurally_valid
|
|
632
|
+
fi
|
|
633
|
+
|
|
634
|
+
if [ "$workflow" = "full" ]; then
|
|
635
|
+
# Full workflow: design_doc is REQUIRED
|
|
636
|
+
check "design_doc is recorded for full workflow" design_doc_recorded
|
|
637
|
+
fi
|
|
505
638
|
|
|
506
639
|
if [ -n "$design_doc" ] && [ "$design_doc" != "null" ]; then
|
|
507
640
|
check "Design Doc ($design_doc) exists" file_nonempty "$design_doc"
|
|
508
641
|
check "Design Doc frontmatter links current change" design_doc_links_current_change
|
|
509
642
|
check "Design Doc declares technical design role" design_doc_declares_technical_role
|
|
510
643
|
check "Design Doc declares OpenSpec as canonical spec" design_doc_declares_canonical_spec
|
|
511
|
-
|
|
512
|
-
warn " [WARN] No design_doc recorded in .comet.yaml"
|
|
644
|
+
elif [ "$workflow" != "full" ]; then
|
|
645
|
+
warn " [WARN] No design_doc recorded in .comet.yaml (optional for hotfix/tweak)"
|
|
513
646
|
fi
|
|
514
647
|
}
|
|
515
648
|
|
|
649
|
+
design_doc_recorded() {
|
|
650
|
+
local design_doc
|
|
651
|
+
design_doc=$(yaml_field_value "design_doc" 2>/dev/null || true)
|
|
652
|
+
if [ -n "$design_doc" ] && [ "$design_doc" != "null" ] && [ -f "$design_doc" ]; then
|
|
653
|
+
return 0
|
|
654
|
+
fi
|
|
655
|
+
echo "design_doc must point to an existing Superpowers Design Doc for full workflow before leaving design." >&2
|
|
656
|
+
echo "Next: create the Design Doc and run: \"\$COMET_BASH\" \"\$COMET_STATE\" set $CHANGE design_doc <path>" >&2
|
|
657
|
+
return 1
|
|
658
|
+
}
|
|
659
|
+
|
|
516
660
|
guard_build() {
|
|
517
661
|
echo "=== Guard: build → verify ===" >&2
|
|
518
662
|
|
|
519
663
|
check "isolation selected" isolation_selected
|
|
520
664
|
check "build_mode selected" build_mode_selected
|
|
521
665
|
check "build_mode allowed for workflow" build_mode_allowed_for_workflow
|
|
666
|
+
check "subagent dispatch confirmed" subagent_dispatch_confirmed
|
|
667
|
+
check "tdd_mode selected" tdd_mode_selected
|
|
522
668
|
check "tasks.md all tasks checked" tasks_all_done
|
|
669
|
+
check "Superpowers plan all tasks checked" plan_tasks_all_done
|
|
523
670
|
check "proposal.md exists" file_nonempty "$CHANGE_DIR/proposal.md"
|
|
524
671
|
check "Build passes" build_passes
|
|
525
672
|
}
|
|
@@ -538,6 +685,7 @@ guard_archive() {
|
|
|
538
685
|
|
|
539
686
|
check "archived is true" archived_is_true
|
|
540
687
|
check "proposal.md exists" file_nonempty "$CHANGE_DIR/proposal.md"
|
|
688
|
+
check "design.md exists" file_nonempty "$CHANGE_DIR/design.md"
|
|
541
689
|
check "tasks.md all tasks checked" tasks_all_done
|
|
542
690
|
}
|
|
543
691
|
|
|
@@ -560,6 +708,14 @@ apply_state_update() {
|
|
|
560
708
|
|
|
561
709
|
# --- Main ---
|
|
562
710
|
|
|
711
|
+
if [ "${COMET_GUARD_SOURCE_ONLY:-0}" = "1" ]; then
|
|
712
|
+
return 0 2>/dev/null
|
|
713
|
+
# shellcheck disable=SC2317 # unreachable if sourced; fallback for direct execution
|
|
714
|
+
red "ERROR: COMET_GUARD_SOURCE_ONLY=1 is only for sourcing, not direct execution" >&2
|
|
715
|
+
# shellcheck disable=SC2317
|
|
716
|
+
exit 1
|
|
717
|
+
fi
|
|
718
|
+
|
|
563
719
|
case "$PHASE" in
|
|
564
720
|
open) preflight ; guard_open ;;
|
|
565
721
|
design) preflight ; guard_design ;;
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
#!/bin/bash
|
|
2
2
|
# Comet Handoff — creates machine-owned context packages between phases
|
|
3
3
|
# Usage: comet-handoff.sh <change-name> design --write [--full]
|
|
4
|
+
# comet-handoff.sh <change-name> --hash-only
|
|
4
5
|
|
|
5
6
|
set -euo pipefail
|
|
6
7
|
|
|
@@ -8,6 +9,7 @@ COMET_BASH="${COMET_BASH:-${BASH:-bash}}"
|
|
|
8
9
|
|
|
9
10
|
red() { echo -e "\033[31m$1\033[0m" >&2; }
|
|
10
11
|
green() { echo -e "\033[32m$1\033[0m" >&2; }
|
|
12
|
+
warn() { echo -e "\033[33m$1\033[0m" >&2; }
|
|
11
13
|
|
|
12
14
|
validate_change_name() {
|
|
13
15
|
local name="$1"
|
|
@@ -103,12 +105,14 @@ source_files() {
|
|
|
103
105
|
}
|
|
104
106
|
|
|
105
107
|
compute_context_hash() {
|
|
106
|
-
|
|
108
|
+
local hash_input
|
|
109
|
+
hash_input=$(source_files | while IFS= read -r file; do
|
|
107
110
|
if [ -f "$file" ]; then
|
|
108
111
|
printf 'path:%s\n' "$file"
|
|
109
112
|
printf 'sha256:%s\n' "$(hash_file "$file")"
|
|
110
113
|
fi
|
|
111
|
-
done
|
|
114
|
+
done)
|
|
115
|
+
printf '%s' "$hash_input" | hash_stream
|
|
112
116
|
}
|
|
113
117
|
|
|
114
118
|
json_escape() {
|
|
@@ -196,6 +200,86 @@ write_json_context() {
|
|
|
196
200
|
} > "$output"
|
|
197
201
|
}
|
|
198
202
|
|
|
203
|
+
write_spec_projection_for_file() {
|
|
204
|
+
local file="$1"
|
|
205
|
+
echo "## $file"
|
|
206
|
+
echo ""
|
|
207
|
+
echo "- Source: $file"
|
|
208
|
+
echo "- Lines: 1-$(file_line_count "$file")"
|
|
209
|
+
echo "- SHA256: $(hash_file "$file")"
|
|
210
|
+
echo ""
|
|
211
|
+
echo '```md'
|
|
212
|
+
cat "$file"
|
|
213
|
+
echo '```'
|
|
214
|
+
echo ""
|
|
215
|
+
}
|
|
216
|
+
|
|
217
|
+
write_spec_markdown_context() {
|
|
218
|
+
local output="$1"
|
|
219
|
+
{
|
|
220
|
+
echo "# Comet Spec Context"
|
|
221
|
+
echo ""
|
|
222
|
+
echo "- Change: $CHANGE"
|
|
223
|
+
echo "- Phase: design"
|
|
224
|
+
echo "- Mode: beta"
|
|
225
|
+
echo "- Context hash: $CONTEXT_HASH"
|
|
226
|
+
echo ""
|
|
227
|
+
echo "Generated-by: comet-handoff.sh"
|
|
228
|
+
echo ""
|
|
229
|
+
echo "OpenSpec remains the canonical capability spec. This beta context pack verbatim-projects spec files and references supporting artifacts by hash, not an agent-authored summary."
|
|
230
|
+
echo ""
|
|
231
|
+
echo "## Source References"
|
|
232
|
+
echo ""
|
|
233
|
+
source_files | while IFS= read -r file; do
|
|
234
|
+
[ -f "$file" ] || continue
|
|
235
|
+
echo "- Source: $file"
|
|
236
|
+
echo "- SHA256: $(hash_file "$file")"
|
|
237
|
+
done
|
|
238
|
+
echo ""
|
|
239
|
+
echo "## Acceptance Projection"
|
|
240
|
+
echo ""
|
|
241
|
+
if [ -d "$CHANGE_DIR/specs" ]; then
|
|
242
|
+
find "$CHANGE_DIR/specs" -path '*/spec.md' -type f 2>/dev/null | sort | while IFS= read -r file; do
|
|
243
|
+
write_spec_projection_for_file "$file"
|
|
244
|
+
done
|
|
245
|
+
else
|
|
246
|
+
echo "No delta spec files found."
|
|
247
|
+
echo ""
|
|
248
|
+
fi
|
|
249
|
+
echo "Full source files remain canonical. If a required heading or scenario is missing here, regenerate the handoff or read the source spec directly. Supporting files (proposal, design, tasks) are referenced by hash only."
|
|
250
|
+
} > "$output"
|
|
251
|
+
}
|
|
252
|
+
|
|
253
|
+
write_spec_json_context() {
|
|
254
|
+
local output="$1"
|
|
255
|
+
{
|
|
256
|
+
echo "{"
|
|
257
|
+
echo " \"change\": \"$(json_escape "$CHANGE")\","
|
|
258
|
+
echo " \"phase\": \"design\","
|
|
259
|
+
echo " \"mode\": \"beta\","
|
|
260
|
+
echo " \"canonical_spec\": \"openspec\","
|
|
261
|
+
echo " \"generated_by\": \"comet-handoff.sh\","
|
|
262
|
+
echo " \"context_hash\": \"$CONTEXT_HASH\","
|
|
263
|
+
echo " \"files\": ["
|
|
264
|
+
local first_file=1
|
|
265
|
+
while IFS= read -r file; do
|
|
266
|
+
[ -f "$file" ] || continue
|
|
267
|
+
local role="supporting"
|
|
268
|
+
case "$file" in
|
|
269
|
+
*/specs/*/spec.md) role="spec" ;;
|
|
270
|
+
esac
|
|
271
|
+
if [ "$first_file" -eq 0 ]; then
|
|
272
|
+
echo ","
|
|
273
|
+
fi
|
|
274
|
+
first_file=0
|
|
275
|
+
printf ' { "path": "%s", "sha256": "%s", "role": "%s" }' "$(json_escape "$file")" "$(hash_file "$file")" "$role"
|
|
276
|
+
done < <(source_files)
|
|
277
|
+
echo ""
|
|
278
|
+
echo " ]"
|
|
279
|
+
echo "}"
|
|
280
|
+
} > "$output"
|
|
281
|
+
}
|
|
282
|
+
|
|
199
283
|
CHANGE="${1:-}"
|
|
200
284
|
PHASE="${2:-}"
|
|
201
285
|
MODE="${3:-}"
|
|
@@ -203,6 +287,25 @@ FULL_FLAG="${4:-}"
|
|
|
203
287
|
|
|
204
288
|
validate_change_name "$CHANGE"
|
|
205
289
|
|
|
290
|
+
# --hash-only: compute and output context hash without generating handoff files
|
|
291
|
+
if [ "${PHASE:-}" = "--hash-only" ]; then
|
|
292
|
+
CHANGE_DIR="openspec/changes/$CHANGE"
|
|
293
|
+
if [ ! -d "$CHANGE_DIR" ]; then
|
|
294
|
+
red "ERROR: change directory not found: $CHANGE_DIR"
|
|
295
|
+
exit 1
|
|
296
|
+
fi
|
|
297
|
+
for required in proposal.md design.md tasks.md; do
|
|
298
|
+
if [ ! -s "$CHANGE_DIR/$required" ]; then
|
|
299
|
+
red "ERROR: required file missing or empty: $CHANGE_DIR/$required"
|
|
300
|
+
exit 1
|
|
301
|
+
fi
|
|
302
|
+
done
|
|
303
|
+
CONTEXT_HASH="$(compute_context_hash)"
|
|
304
|
+
printf '%s
|
|
305
|
+
' "$CONTEXT_HASH"
|
|
306
|
+
exit 0
|
|
307
|
+
fi
|
|
308
|
+
|
|
206
309
|
if [ "$PHASE" != "design" ] || [ "$MODE" != "--write" ]; then
|
|
207
310
|
red "Usage: comet-handoff.sh <change-name> design --write [--full]"
|
|
208
311
|
exit 1
|
|
@@ -242,13 +345,37 @@ for required in proposal.md design.md tasks.md; do
|
|
|
242
345
|
done
|
|
243
346
|
|
|
244
347
|
HANDOFF_DIR="$CHANGE_DIR/.comet/handoff"
|
|
245
|
-
|
|
246
|
-
|
|
348
|
+
CONTEXT_COMPRESSION="$(yaml_field_value context_compression 2>/dev/null || true)"
|
|
349
|
+
CONTEXT_COMPRESSION="${CONTEXT_COMPRESSION:-off}"
|
|
350
|
+
case "$CONTEXT_COMPRESSION" in
|
|
351
|
+
off)
|
|
352
|
+
CONTEXT_JSON="$HANDOFF_DIR/design-context.json"
|
|
353
|
+
CONTEXT_MD="$HANDOFF_DIR/design-context.md"
|
|
354
|
+
;;
|
|
355
|
+
beta)
|
|
356
|
+
if [ "$HANDOFF_MODE" = "full" ]; then
|
|
357
|
+
warn "[HANDOFF] --full is ignored in beta mode; spec files are projected verbatim"
|
|
358
|
+
fi
|
|
359
|
+
HANDOFF_MODE="beta"
|
|
360
|
+
CONTEXT_JSON="$HANDOFF_DIR/spec-context.json"
|
|
361
|
+
CONTEXT_MD="$HANDOFF_DIR/spec-context.md"
|
|
362
|
+
;;
|
|
363
|
+
*)
|
|
364
|
+
red "ERROR: invalid context_compression: $CONTEXT_COMPRESSION"
|
|
365
|
+
red "Valid values: off, beta"
|
|
366
|
+
exit 1
|
|
367
|
+
;;
|
|
368
|
+
esac
|
|
247
369
|
mkdir -p "$HANDOFF_DIR"
|
|
248
370
|
|
|
249
371
|
CONTEXT_HASH="$(compute_context_hash)"
|
|
250
|
-
|
|
251
|
-
|
|
372
|
+
if [ "$CONTEXT_COMPRESSION" = "beta" ]; then
|
|
373
|
+
write_spec_markdown_context "$CONTEXT_MD"
|
|
374
|
+
write_spec_json_context "$CONTEXT_JSON"
|
|
375
|
+
else
|
|
376
|
+
write_markdown_context "$CONTEXT_MD"
|
|
377
|
+
write_json_context "$CONTEXT_JSON"
|
|
378
|
+
fi
|
|
252
379
|
|
|
253
380
|
if [ -x "$STATE_SH" ] || [ -f "$STATE_SH" ]; then
|
|
254
381
|
"$COMET_BASH" "$STATE_SH" set "$CHANGE" handoff_context "$CONTEXT_JSON" >/dev/null
|