@rpamis/comet 0.3.5 → 0.3.7
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 -21
- package/README.md +180 -56
- package/assets/manifest.json +11 -1
- package/assets/skills/comet/SKILL.md +59 -24
- package/assets/skills/comet/reference/dirty-worktree.md +1 -0
- package/assets/skills/comet/rules/comet-phase-guard.md +90 -0
- package/assets/skills/comet/scripts/comet-archive.sh +75 -57
- package/assets/skills/comet/scripts/comet-env.sh +57 -2
- package/assets/skills/comet/scripts/comet-guard.sh +187 -29
- package/assets/skills/comet/scripts/comet-handoff.sh +137 -8
- package/assets/skills/comet/scripts/comet-hook-guard.sh +260 -0
- package/assets/skills/comet/scripts/comet-state.sh +312 -25
- package/assets/skills/comet/scripts/comet-yaml-validate.sh +26 -1
- package/assets/skills/comet-archive/SKILL.md +45 -12
- package/assets/skills/comet-build/SKILL.md +159 -33
- package/assets/skills/comet-design/SKILL.md +129 -23
- package/assets/skills/comet-hotfix/SKILL.md +57 -15
- package/assets/skills/comet-open/SKILL.md +90 -17
- package/assets/skills/comet-tweak/SKILL.md +40 -15
- package/assets/skills/comet-verify/SKILL.md +64 -25
- package/assets/skills-zh/comet/SKILL.md +63 -25
- package/assets/skills-zh/comet/reference/dirty-worktree.md +2 -1
- package/assets/skills-zh/comet-archive/SKILL.md +46 -13
- package/assets/skills-zh/comet-build/SKILL.md +161 -35
- package/assets/skills-zh/comet-design/SKILL.md +132 -25
- package/assets/skills-zh/comet-hotfix/SKILL.md +53 -15
- package/assets/skills-zh/comet-open/SKILL.md +90 -17
- package/assets/skills-zh/comet-tweak/SKILL.md +36 -15
- package/assets/skills-zh/comet-verify/SKILL.md +64 -27
- package/bin/comet.js +3 -3
- 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/update.d.ts.map +1 -1
- package/dist/commands/update.js +60 -1
- package/dist/commands/update.js.map +1 -1
- package/dist/core/codegraph.d.ts +9 -0
- package/dist/core/codegraph.d.ts.map +1 -0
- package/dist/core/codegraph.js +96 -0
- package/dist/core/codegraph.js.map +1 -0
- package/dist/core/platforms.d.ts +10 -0
- package/dist/core/platforms.d.ts.map +1 -1
- package/dist/core/platforms.js +163 -15
- package/dist/core/platforms.js.map +1 -1
- package/dist/core/skills.d.ts +34 -1
- package/dist/core/skills.d.ts.map +1 -1
- package/dist/core/skills.js +360 -1
- package/dist/core/skills.js.map +1 -1
- package/package.json +3 -1
- package/scripts/postinstall.js +44 -44
|
@@ -7,6 +7,8 @@
|
|
|
7
7
|
|
|
8
8
|
set -euo pipefail
|
|
9
9
|
|
|
10
|
+
COMET_BASH="${COMET_BASH:-${BASH:-bash}}"
|
|
11
|
+
|
|
10
12
|
red() { echo -e "\033[31m$1\033[0m" >&2; }
|
|
11
13
|
green() { echo -e "\033[32m$1\033[0m" >&2; }
|
|
12
14
|
warn() { echo -e "\033[33m$1\033[0m" >&2; }
|
|
@@ -32,18 +34,26 @@ validate_change_name() {
|
|
|
32
34
|
fi
|
|
33
35
|
}
|
|
34
36
|
|
|
35
|
-
|
|
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"
|
|
36
45
|
|
|
37
|
-
CHANGE="$1"
|
|
38
|
-
PHASE="$2"
|
|
39
|
-
APPLY=0
|
|
40
|
-
SCRIPT_DIR="$(
|
|
41
|
-
if [[ "${3:-}" == "--apply" ]]; then
|
|
42
|
-
|
|
43
|
-
fi
|
|
44
|
-
CHANGE_DIR="openspec/changes/$CHANGE"
|
|
45
|
-
if [ "$PHASE" = "archive" ] && [ ! -d "$CHANGE_DIR" ] && [ -d "openspec/changes/archive/$CHANGE" ]; then
|
|
46
|
-
|
|
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
|
|
47
57
|
fi
|
|
48
58
|
|
|
49
59
|
BLOCK=0
|
|
@@ -92,6 +102,27 @@ tasks_has_any() {
|
|
|
92
102
|
[ -f "$tasks" ] && grep -q '\- \[' "$tasks"
|
|
93
103
|
}
|
|
94
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
|
+
|
|
95
126
|
yaml_field_value() {
|
|
96
127
|
local field="$1"
|
|
97
128
|
local yaml="$CHANGE_DIR/.comet.yaml"
|
|
@@ -180,8 +211,19 @@ is_windows_bash() {
|
|
|
180
211
|
|
|
181
212
|
run_command_string() {
|
|
182
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
|
|
183
225
|
echo "+ $command" >&2
|
|
184
|
-
|
|
226
|
+
"$COMET_BASH" -lc "$command"
|
|
185
227
|
}
|
|
186
228
|
|
|
187
229
|
hash_stream() {
|
|
@@ -217,12 +259,14 @@ handoff_source_files() {
|
|
|
217
259
|
}
|
|
218
260
|
|
|
219
261
|
compute_handoff_hash() {
|
|
220
|
-
|
|
262
|
+
local hash_input
|
|
263
|
+
hash_input=$(handoff_source_files | while IFS= read -r file; do
|
|
221
264
|
if [ -f "$file" ]; then
|
|
222
265
|
printf 'path:%s\n' "$file"
|
|
223
266
|
printf 'sha256:%s\n' "$(hash_file "$file")"
|
|
224
267
|
fi
|
|
225
|
-
done
|
|
268
|
+
done)
|
|
269
|
+
printf '%s' "$hash_input" | hash_stream
|
|
226
270
|
}
|
|
227
271
|
|
|
228
272
|
preflight() {
|
|
@@ -240,8 +284,8 @@ preflight() {
|
|
|
240
284
|
local validate_script
|
|
241
285
|
validate_script="$SCRIPT_DIR/comet-yaml-validate.sh"
|
|
242
286
|
if [ -f "$validate_script" ]; then
|
|
243
|
-
if !
|
|
244
|
-
|
|
287
|
+
if ! "$COMET_BASH" "$validate_script" "$CHANGE" 2>/dev/null; then
|
|
288
|
+
"$COMET_BASH" "$validate_script" "$CHANGE" || true
|
|
245
289
|
red "FATAL: .comet.yaml schema validation failed"
|
|
246
290
|
exit 1
|
|
247
291
|
fi
|
|
@@ -300,7 +344,7 @@ isolation_selected() {
|
|
|
300
344
|
*)
|
|
301
345
|
echo "isolation must be branch or worktree, got '${isolation:-null}'" >&2
|
|
302
346
|
echo "Next: ask the user to choose branch or worktree, create the chosen isolation, then run:" >&2
|
|
303
|
-
echo "
|
|
347
|
+
echo " \"\$COMET_BASH\" \"\$COMET_STATE\" set $CHANGE isolation <branch|worktree>" >&2
|
|
304
348
|
return 1
|
|
305
349
|
;;
|
|
306
350
|
esac
|
|
@@ -313,8 +357,8 @@ build_mode_selected() {
|
|
|
313
357
|
subagent-driven-development|executing-plans|direct) return 0 ;;
|
|
314
358
|
*)
|
|
315
359
|
echo "build_mode must be selected before leaving build, got '${build_mode:-null}'" >&2
|
|
316
|
-
echo "Next: ask the user to choose an
|
|
317
|
-
echo "
|
|
360
|
+
echo "Next: ask the user to choose an execution mode, then run:" >&2
|
|
361
|
+
echo " \"\$COMET_BASH\" \"\$COMET_STATE\" set $CHANGE build_mode <subagent-driven-development|executing-plans>" >&2
|
|
318
362
|
return 1
|
|
319
363
|
;;
|
|
320
364
|
esac
|
|
@@ -336,7 +380,48 @@ build_mode_allowed_for_workflow() {
|
|
|
336
380
|
return 0
|
|
337
381
|
fi
|
|
338
382
|
echo "build_mode=direct is only allowed for hotfix/tweak unless direct_override: true is recorded" >&2
|
|
339
|
-
echo "Next:
|
|
383
|
+
echo "Next: choose executing-plans or subagent-driven-development, or stop and ask the user for an explicit direct override." >&2
|
|
384
|
+
return 1
|
|
385
|
+
;;
|
|
386
|
+
esac
|
|
387
|
+
}
|
|
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
|
|
340
425
|
return 1
|
|
341
426
|
;;
|
|
342
427
|
esac
|
|
@@ -367,7 +452,7 @@ design_handoff_context_valid() {
|
|
|
367
452
|
|
|
368
453
|
if [ -z "$context" ] || [ "$context" = "null" ]; then
|
|
369
454
|
echo "handoff_context is missing from .comet.yaml" >&2
|
|
370
|
-
echo "Next: run
|
|
455
|
+
echo "Next: run \"\$COMET_BASH\" \"\$COMET_HANDOFF\" $CHANGE design --write before invoking Superpowers." >&2
|
|
371
456
|
return 1
|
|
372
457
|
fi
|
|
373
458
|
if [ ! -s "$context" ]; then
|
|
@@ -414,7 +499,7 @@ design_handoff_markdown_traceable() {
|
|
|
414
499
|
echo "handoff markdown is missing Generated-by marker" >&2
|
|
415
500
|
missing=1
|
|
416
501
|
}
|
|
417
|
-
grep -Eq '^- Mode: (compact|full)$' "$markdown" || {
|
|
502
|
+
grep -Eq '^- Mode: (compact|full|beta)$' "$markdown" || {
|
|
418
503
|
echo "handoff markdown is missing Mode marker" >&2
|
|
419
504
|
missing=1
|
|
420
505
|
}
|
|
@@ -433,6 +518,47 @@ design_handoff_markdown_traceable() {
|
|
|
433
518
|
[ "$missing" -eq 0 ]
|
|
434
519
|
}
|
|
435
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
|
+
|
|
436
562
|
design_doc_frontmatter_has() {
|
|
437
563
|
local design_doc="$1"
|
|
438
564
|
local field="$2"
|
|
@@ -492,32 +618,55 @@ guard_open() {
|
|
|
492
618
|
guard_design() {
|
|
493
619
|
echo "=== Guard: design → build ===" >&2
|
|
494
620
|
|
|
495
|
-
local design_doc
|
|
621
|
+
local design_doc workflow
|
|
496
622
|
design_doc=$(yaml_field_value "design_doc" 2>/dev/null || true)
|
|
623
|
+
workflow=$(yaml_field_value "workflow" 2>/dev/null || true)
|
|
497
624
|
|
|
498
625
|
check "proposal.md exists" file_nonempty "$CHANGE_DIR/proposal.md"
|
|
499
626
|
check "design.md exists" file_nonempty "$CHANGE_DIR/design.md"
|
|
500
627
|
check "tasks.md exists" file_nonempty "$CHANGE_DIR/tasks.md"
|
|
501
628
|
check "design handoff context exists" design_handoff_context_valid
|
|
502
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
|
|
503
638
|
|
|
504
639
|
if [ -n "$design_doc" ] && [ "$design_doc" != "null" ]; then
|
|
505
640
|
check "Design Doc ($design_doc) exists" file_nonempty "$design_doc"
|
|
506
641
|
check "Design Doc frontmatter links current change" design_doc_links_current_change
|
|
507
642
|
check "Design Doc declares technical design role" design_doc_declares_technical_role
|
|
508
643
|
check "Design Doc declares OpenSpec as canonical spec" design_doc_declares_canonical_spec
|
|
509
|
-
|
|
510
|
-
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)"
|
|
511
646
|
fi
|
|
512
647
|
}
|
|
513
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
|
+
|
|
514
660
|
guard_build() {
|
|
515
661
|
echo "=== Guard: build → verify ===" >&2
|
|
516
662
|
|
|
517
663
|
check "isolation selected" isolation_selected
|
|
518
664
|
check "build_mode selected" build_mode_selected
|
|
519
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
|
|
520
668
|
check "tasks.md all tasks checked" tasks_all_done
|
|
669
|
+
check "Superpowers plan all tasks checked" plan_tasks_all_done
|
|
521
670
|
check "proposal.md exists" file_nonempty "$CHANGE_DIR/proposal.md"
|
|
522
671
|
check "Build passes" build_passes
|
|
523
672
|
}
|
|
@@ -536,6 +685,7 @@ guard_archive() {
|
|
|
536
685
|
|
|
537
686
|
check "archived is true" archived_is_true
|
|
538
687
|
check "proposal.md exists" file_nonempty "$CHANGE_DIR/proposal.md"
|
|
688
|
+
check "design.md exists" file_nonempty "$CHANGE_DIR/design.md"
|
|
539
689
|
check "tasks.md all tasks checked" tasks_all_done
|
|
540
690
|
}
|
|
541
691
|
|
|
@@ -545,10 +695,10 @@ apply_state_update() {
|
|
|
545
695
|
|
|
546
696
|
if [ -f "$state_sh" ]; then
|
|
547
697
|
case "$p" in
|
|
548
|
-
open)
|
|
549
|
-
design)
|
|
550
|
-
build)
|
|
551
|
-
verify)
|
|
698
|
+
open) "$COMET_BASH" "$state_sh" transition "$CHANGE" open-complete ;;
|
|
699
|
+
design) "$COMET_BASH" "$state_sh" transition "$CHANGE" design-complete ;;
|
|
700
|
+
build) "$COMET_BASH" "$state_sh" transition "$CHANGE" build-complete ;;
|
|
701
|
+
verify) "$COMET_BASH" "$state_sh" transition "$CHANGE" verify-pass ;;
|
|
552
702
|
esac
|
|
553
703
|
else
|
|
554
704
|
red "FATAL: comet-state.sh not found; cannot apply state transition"
|
|
@@ -558,6 +708,14 @@ apply_state_update() {
|
|
|
558
708
|
|
|
559
709
|
# --- Main ---
|
|
560
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
|
+
|
|
561
719
|
case "$PHASE" in
|
|
562
720
|
open) preflight ; guard_open ;;
|
|
563
721
|
design) preflight ; guard_design ;;
|
|
@@ -1,11 +1,15 @@
|
|
|
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
|
+
COMET_BASH="${COMET_BASH:-${BASH:-bash}}"
|
|
9
|
+
|
|
7
10
|
red() { echo -e "\033[31m$1\033[0m" >&2; }
|
|
8
11
|
green() { echo -e "\033[32m$1\033[0m" >&2; }
|
|
12
|
+
warn() { echo -e "\033[33m$1\033[0m" >&2; }
|
|
9
13
|
|
|
10
14
|
validate_change_name() {
|
|
11
15
|
local name="$1"
|
|
@@ -101,12 +105,14 @@ source_files() {
|
|
|
101
105
|
}
|
|
102
106
|
|
|
103
107
|
compute_context_hash() {
|
|
104
|
-
|
|
108
|
+
local hash_input
|
|
109
|
+
hash_input=$(source_files | while IFS= read -r file; do
|
|
105
110
|
if [ -f "$file" ]; then
|
|
106
111
|
printf 'path:%s\n' "$file"
|
|
107
112
|
printf 'sha256:%s\n' "$(hash_file "$file")"
|
|
108
113
|
fi
|
|
109
|
-
done
|
|
114
|
+
done)
|
|
115
|
+
printf '%s' "$hash_input" | hash_stream
|
|
110
116
|
}
|
|
111
117
|
|
|
112
118
|
json_escape() {
|
|
@@ -194,6 +200,86 @@ write_json_context() {
|
|
|
194
200
|
} > "$output"
|
|
195
201
|
}
|
|
196
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
|
+
|
|
197
283
|
CHANGE="${1:-}"
|
|
198
284
|
PHASE="${2:-}"
|
|
199
285
|
MODE="${3:-}"
|
|
@@ -201,6 +287,25 @@ FULL_FLAG="${4:-}"
|
|
|
201
287
|
|
|
202
288
|
validate_change_name "$CHANGE"
|
|
203
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
|
+
|
|
204
309
|
if [ "$PHASE" != "design" ] || [ "$MODE" != "--write" ]; then
|
|
205
310
|
red "Usage: comet-handoff.sh <change-name> design --write [--full]"
|
|
206
311
|
exit 1
|
|
@@ -240,17 +345,41 @@ for required in proposal.md design.md tasks.md; do
|
|
|
240
345
|
done
|
|
241
346
|
|
|
242
347
|
HANDOFF_DIR="$CHANGE_DIR/.comet/handoff"
|
|
243
|
-
|
|
244
|
-
|
|
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
|
|
245
369
|
mkdir -p "$HANDOFF_DIR"
|
|
246
370
|
|
|
247
371
|
CONTEXT_HASH="$(compute_context_hash)"
|
|
248
|
-
|
|
249
|
-
|
|
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
|
|
250
379
|
|
|
251
380
|
if [ -x "$STATE_SH" ] || [ -f "$STATE_SH" ]; then
|
|
252
|
-
|
|
253
|
-
|
|
381
|
+
"$COMET_BASH" "$STATE_SH" set "$CHANGE" handoff_context "$CONTEXT_JSON" >/dev/null
|
|
382
|
+
"$COMET_BASH" "$STATE_SH" set "$CHANGE" handoff_hash "$CONTEXT_HASH" >/dev/null
|
|
254
383
|
else
|
|
255
384
|
red "ERROR: comet-state.sh not found; cannot record handoff fields"
|
|
256
385
|
exit 1
|