@rpamis/comet 0.2.7 → 0.2.9

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 (38) hide show
  1. package/README.md +98 -63
  2. package/assets/manifest.json +1 -0
  3. package/assets/skills/comet/scripts/comet-archive.sh +39 -1
  4. package/assets/skills/comet/scripts/comet-guard.sh +188 -1
  5. package/assets/skills/comet/scripts/comet-handoff.sh +261 -0
  6. package/assets/skills/comet/scripts/comet-state.sh +34 -3
  7. package/assets/skills/comet/scripts/comet-yaml-validate.sh +42 -1
  8. package/assets/skills-zh/comet/SKILL.md +13 -10
  9. package/assets/skills-zh/comet-design/SKILL.md +59 -9
  10. package/dist/cli/index.js +1 -0
  11. package/dist/cli/index.js.map +1 -1
  12. package/dist/commands/doctor.d.ts.map +1 -1
  13. package/dist/commands/doctor.js +9 -3
  14. package/dist/commands/doctor.js.map +1 -1
  15. package/dist/commands/init.d.ts +2 -0
  16. package/dist/commands/init.d.ts.map +1 -1
  17. package/dist/commands/init.js +10 -7
  18. package/dist/commands/init.js.map +1 -1
  19. package/dist/commands/update.d.ts +1 -1
  20. package/dist/commands/update.d.ts.map +1 -1
  21. package/dist/commands/update.js +13 -9
  22. package/dist/commands/update.js.map +1 -1
  23. package/dist/core/detect.d.ts +1 -1
  24. package/dist/core/detect.d.ts.map +1 -1
  25. package/dist/core/detect.js +19 -10
  26. package/dist/core/detect.js.map +1 -1
  27. package/dist/core/platforms.d.ts +4 -0
  28. package/dist/core/platforms.d.ts.map +1 -1
  29. package/dist/core/platforms.js +16 -1
  30. package/dist/core/platforms.js.map +1 -1
  31. package/dist/core/skills.d.ts +3 -2
  32. package/dist/core/skills.d.ts.map +1 -1
  33. package/dist/core/skills.js +3 -2
  34. package/dist/core/skills.js.map +1 -1
  35. package/dist/core/superpowers.d.ts.map +1 -1
  36. package/dist/core/superpowers.js +2 -1
  37. package/dist/core/superpowers.js.map +1 -1
  38. package/package.json +1 -1
package/README.md CHANGED
@@ -8,9 +8,11 @@
8
8
  </p>
9
9
 
10
10
  <p align="center">
11
- <a href="https://github.com/rpamis/comet/actions/workflows/ci.yml"><img alt="CI" src="https://github.com/rpamis/comet/actions/workflows/ci.yml/badge.svg" /></a>
11
+ <a href="https://github.com/rpamis/comet/actions/workflows/ci.yml"><img alt="CI" src="https://img.shields.io/github/actions/workflow/status/rpamis/comet/ci.yml?branch=master&style=flat-square&label=CI" /></a>
12
+ <a href="https://deepwiki.com/rpamis/comet"><img alt="DeepWiki" src="https://img.shields.io/badge/DeepWiki-rpamis%2Fcomet-blue?style=flat-square" /></a>
12
13
  <a href="https://www.npmjs.com/package/@rpamis/comet"><img alt="npm version" src="https://img.shields.io/npm/v/@rpamis/comet?style=flat-square" /></a>
13
14
  <a href="https://www.npmjs.com/package/@rpamis/comet"><img alt="npm download count" src="https://img.shields.io/npm/dm/@rpamis/comet?style=flat-square&label=Downloads/mo" /></a>
15
+ <a href="https://www.npmjs.com/package/@rpamis/comet"><img alt="npm weekly download count" src="https://img.shields.io/npm/dw/@rpamis/comet?style=flat-square&label=Downloads/wk" /></a>
14
16
  <a href="./LICENSE"><img alt="License: MIT" src="https://img.shields.io/badge/License-MIT-blue.svg?style=flat-square" /></a>
15
17
  </p>
16
18
 
@@ -42,6 +44,28 @@ Superpowers generates Spec documents after brainstorming, but these documents ty
42
44
 
43
45
  The main entry `/comet` supports current Spec state detection, suitable for long tasks — after completing and closing CC midway, just `/comet continue` and Comet will automatically read the active Spec (lists multiple for selection), dynamically identify which phase is currently executing, and continue.
44
46
 
47
+ At the same time, Comet provides full Spec lifecycle management. During execution, it links OpenSpec change/spec artifacts with Superpowers design and planning documents, then automates handoff, state updates, validation, and archive sync so users do not have to repeatedly remind the Agent to keep documents synchronized and connected.
48
+
49
+ ## What You'll Learn
50
+
51
+ Many excellent Skill projects exist in the current Skill market, but they generally have preference issues — users may only like some features. For example, when using both OpenSpec and Superpowers, one might only use OpenSpec's Spec management capabilities, but prefer Superpowers' TDD-driven approach for coding.
52
+
53
+ Long-term Skill users know these capabilities can be freely combined, but exactly how to do so still requires real practice. The Comet project can serve as a reference:
54
+
55
+ - **How to reliably trigger nested Skills** — Not letting the Agent rely on document descriptions to perform "look-alike Skill trigger" operations (like writing files based on Skill descriptions), but truly triggering Skills (key feature: Skill trigger prints on CC). Comet triggers many capabilities from OpenSpec and Superpowers. How is this Prompt written?
56
+
57
+ - **How to make combined Skills flow automatically across phases** — Not relying on manual intervention. Comet's 5-phase flow can automatically trigger Skills for the core process except for necessary user choices, while the state machine also protects state transition reliability.
58
+
59
+ - **How to turn the Spec lifecycle into a resumable workflow** — Comet links OpenSpec change/spec artifacts with Superpowers design and planning documents, then records phase, execution mode, verification results, and archive status in `.comet.yaml`, so the Agent can resume after interruption instead of rereading documents and guessing progress.
60
+
61
+ - **How to turn document synchronization from "user reminders" into automation** — Comet puts handoff, state updates, validation, and archive sync into scripted flows, reducing repeated prompts like "remember to update the design doc", "remember to sync the spec", and "remember to archive the change".
62
+
63
+ - **How to design guard conditions that Agents can execute** — Comet does not simply trust the Agent saying "done" at phase exits. Scripts such as `comet-guard.sh`, `comet-yaml-validate.sh`, and `comet-state.sh` check tasks, state fields, verification evidence, and archive conditions before allowing the workflow to advance.
64
+
65
+ - **How to distribute and install Skills across platforms** — Comet supports multiple AI coding platforms, project/global installation, Chinese/English Skill choices, and platform-specific directory differences such as Antigravity using different project-level and global paths. It can be a reference for CLI installers and Skill package structure.
66
+
67
+ - **How to turn shell scripts into Agent workflow infrastructure** — Comet's scripts need to work across macOS, Linux, and Windows Git Bash while handling hashes, YAML fields, state machines, and archive flows. It shows how to move fragile workflow control out of scattered Prompt text and into testable, reusable tools.
68
+
45
69
  ## Install
46
70
 
47
71
  ```bash
@@ -63,7 +87,7 @@ comet init
63
87
  4. Install [OpenSpec](https://github.com/Fission-AI/OpenSpec) skills
64
88
  5. Install [Superpowers](https://github.com/obra/superpowers) skills
65
89
  6. Deploy Comet skills (in your chosen language) to selected platforms
66
- 7. Create `docs/superpowers/specs/` and `docs/superpowers/plans/` working directories
90
+ 7. Create `docs/superpowers/specs/` and `docs/superpowers/plans/` working directories for project-scope installs
67
91
 
68
92
  > [!TIP]
69
93
  > update version
@@ -81,40 +105,50 @@ comet init
81
105
 
82
106
  ## Commands
83
107
 
84
- | Command | Description |
85
- |---------|-------------|
86
- | `comet init [path]` | Initialize Comet workflow |
87
- | `comet status [path]` | Show active changes, task progress, and next workflow command |
88
- | `comet doctor [path]` | Diagnose project/global Comet installation health |
89
- | `comet update [path]` | Update the npm package and installed Comet skills |
90
- | `comet --help` | Show help |
91
- | `comet --version` | Show version |
108
+ <details>
109
+ <summary><code>comet init [path]</code> — Initialize Comet workflow</summary>
92
110
 
93
- ### init Options
111
+ Initializes OpenSpec, Superpowers, and Comet skills for selected AI coding platforms.
94
112
 
95
113
  | Option | Description |
96
114
  |--------|-------------|
97
- | `--yes` | Non-interactive mode, auto-select detected platforms |
115
+ | `--yes` | Non-interactive mode, auto-select detected platforms (or all if none detected) |
116
+ | `--scope <scope>` | Install scope: `project` or `global` |
98
117
  | `--skip-existing` | Skip already installed components |
99
118
  | `--overwrite` | Overwrite already installed components |
100
119
  | `--json` | Output structured JSON |
101
120
 
102
121
  When multiple existing components are found on the same platform, interactive init offers one bulk choice: overwrite all, skip all, or choose per component.
103
122
 
104
- ### status Options
123
+ </details>
124
+
125
+ <details>
126
+ <summary><code>comet status [path]</code> — Show active changes and next workflow command</summary>
127
+
128
+ Displays active changes, task progress, and the recommended next Comet workflow command.
105
129
 
106
130
  | Option | Description |
107
131
  |--------|-------------|
108
132
  | `--json` | Output active changes with `nextCommand` |
109
133
 
110
- ### doctor Options
134
+ </details>
135
+
136
+ <details>
137
+ <summary><code>comet doctor [path]</code> — Diagnose Comet installation health</summary>
138
+
139
+ Checks project/global installation health, working directories, installed skills, scripts, and Comet state files.
111
140
 
112
141
  | Option | Description |
113
142
  |--------|-------------|
114
143
  | `--json` | Output structured diagnostic results |
115
144
  | `--scope <scope>` | Diagnose `auto`, `project`, or `global` scope (default: `auto`) |
116
145
 
117
- ### update Options
146
+ </details>
147
+
148
+ <details>
149
+ <summary><code>comet update [path]</code> — Update Comet package and skills</summary>
150
+
151
+ Updates the npm package and refreshes installed Comet skills in detected project/global targets.
118
152
 
119
153
  | Option | Description |
120
154
  |--------|-------------|
@@ -122,10 +156,20 @@ When multiple existing components are found on the same platform, interactive in
122
156
  | `--language <lang>` | Override detected skill language (`en`, `zh`) |
123
157
  | `--scope <scope>` | Update only `global` or `project` scope |
124
158
 
159
+ </details>
160
+
161
+ | Command | Description |
162
+ |---------|-------------|
163
+ | `comet --help` | Show help |
164
+ | `comet --version` | Show version |
165
+
125
166
  ## Supported Platforms
126
167
 
127
168
  `comet init` supports 28 AI coding platforms:
128
169
 
170
+ <details>
171
+ <summary>View full platform list</summary>
172
+
129
173
  | Platform | Skills Dir | Platform | Skills Dir |
130
174
  |----------|-----------|----------|-----------|
131
175
  | Claude Code | `.claude/` | Cursor | `.cursor/` |
@@ -140,15 +184,20 @@ When multiple existing components are found on the same platform, interactive in
140
184
  | CoStrict | `.cospec/` | Crush | `.crush/` |
141
185
  | Factory Droid | `.factory/` | iFlow | `.iflow/` |
142
186
  | Pi | `.pi/` | Qoder | `.qoder/` |
143
- | Antigravity | `.agent/` | Bob Shell | `.bob/` |
187
+ | Antigravity | `.agents/` | Bob Shell | `.bob/` |
144
188
  | ForgeCode | `.forge/` | Trae | `.trae/` |
145
189
 
190
+ </details>
191
+
146
192
  ## Skills
147
193
 
148
194
  After `comet init`, three groups of skills are installed to the selected platform's `skills/` directory:
149
195
 
150
196
  ### Comet Skills
151
197
 
198
+ <details>
199
+ <summary>View Comet skills</summary>
200
+
152
201
  | Skill | Description |
153
202
  |-------|-------------|
154
203
  | `/comet` | Main entry — auto-detects phase and dispatches to sub-commands |
@@ -160,15 +209,23 @@ After `comet init`, three groups of skills are installed to the selected platfor
160
209
  | `/comet-hotfix` | Preset: Quick bug fix (skips brainstorming) |
161
210
  | `/comet-tweak` | Preset: Small change (skips brainstorming and full plan) |
162
211
 
212
+ </details>
213
+
163
214
  ### Guard & Automation Scripts
164
215
 
216
+ <details>
217
+ <summary>View script list</summary>
218
+
165
219
  | Script | Purpose |
166
220
  |--------|---------|
167
221
  | `comet-guard.sh` | Phase transition guard — validates exit conditions, `--apply` auto-updates `.comet.yaml` |
222
+ | `comet-handoff.sh` | Design handoff — generates deterministic context packages from OpenSpec artifacts with SHA256 tracing |
168
223
  | `comet-archive.sh` | One-command archive — validates state, syncs specs, moves to archive, updates status |
169
224
  | `comet-yaml-validate.sh` | Schema validator — validates `.comet.yaml` structure and field values |
170
225
  | `comet-state.sh` | Unified state management — init/set/get/check/scale, agents' exclusive YAML interface |
171
226
 
227
+ </details>
228
+
172
229
  ### OpenSpec Skills
173
230
 
174
231
  Spec lifecycle management: propose, explore, sync, verify, archive, and more.
@@ -219,37 +276,50 @@ Comet uses a decoupled state architecture with separate YAML files:
219
276
  | `.openspec.yaml` | OpenSpec | Spec lifecycle, change metadata |
220
277
  | `.comet.yaml` | Comet | Workflow phase, execution mode, verification status |
221
278
 
279
+ All states and execution phases are updated via scripts, and each phase verifies that tasks are truly complete before advancing. Compared to storing complex state rules only in Skill text, this script-backed state machine gives Comet more reliable phase transitions, correct YAML, and easier breakpoint recovery; agents can read the current Spec situation through Comet's built-in commands.
280
+
281
+ <details>
282
+ <summary>View key .comet.yaml fields</summary>
283
+
222
284
  **Key Fields in `.comet.yaml`:**
223
285
 
224
286
  ```yaml
225
287
  workflow: full
226
288
  phase: build
227
- design_doc: docs/superpowers/specs/YYYY-MM-DD-topic-design.md
228
- plan: docs/superpowers/plans/YYYY-MM-DD-feature.md
229
289
  build_mode: subagent-driven-development
230
290
  isolation: branch
231
- verify_mode: light
291
+ verify_mode: null
292
+ design_doc: docs/superpowers/specs/YYYY-MM-DD-topic-design.md
293
+ plan: docs/superpowers/plans/YYYY-MM-DD-feature.md
232
294
  verify_result: pending
233
- verification_report: docs/superpowers/reports/YYYY-MM-DD-change-verify.md
295
+ verification_report: null
234
296
  branch_status: pending
235
297
  verified_at: null
236
298
  archived: false
299
+ direct_override: false
300
+ build_command: null
301
+ verify_command: null
302
+ handoff_context: openspec/changes/<name>/.comet/handoff/design-context.json
303
+ handoff_hash: <sha256>
237
304
  ```
238
305
 
239
- In full workflow, `build_mode` and `isolation` may temporarily be `null` at init time, but they must be resolved before `build → verify`. `direct` is allowed by default only for hotfix/tweak; full workflow requires `direct_override: true`. Projects can configure `build_command` / `verify_command` in the change or repo root, and guard will run those commands first and print failure output.
306
+ In full workflow, `build_mode`, `isolation`, and `verify_mode` may temporarily be `null`; `build_mode` and `isolation` must be resolved before `build → verify`. `verification_report` stays `null` until verification writes a report, and `verify-pass` requires that report to exist plus `branch_status: handled`. Fields after `archived` in the example are optional or script-derived: `direct_override` is only needed for full-workflow direct builds, project commands may be absent unless configured, and `handoff_context` / `handoff_hash` are recorded by `comet-handoff.sh` before leaving design. Projects can configure `build_command` / `verify_command` in the change or repo root, and guard will run those commands first and print failure output.
240
307
 
241
- All states and execution phases are updated via scripts, and **each phase verifies that tasks are truly completed before exiting — conditions are met before the phase exits and state is updated**. Compared to recording complex state management mechanisms in Skills, the script approach strongly guarantees the reliability of core state transitions, correctness of YAML files, and convenience of breakpoint recovery — Agents only need to use Comet's built-in commands to read state and know the current Spec's situation.
308
+ </details>
242
309
 
243
310
  ### Reliability Features
244
311
 
245
312
  Comet ensures agent execution reliability through automated state transitions:
246
313
 
314
+ <details>
315
+ <summary>View reliability features</summary>
316
+
247
317
  1. **Entry Verification** — Each phase validates preconditions before execution
248
318
  - Checks file existence, state consistency, and phase transitions
249
319
  - Outputs `[HARD STOP]` with actionable suggestions if validation fails
250
320
 
251
321
  2. **Automated State Transitions** — `comet-guard.sh --apply` updates `.comet.yaml` automatically
252
- - All phase transitions (design → build → verify → archive) use `guard --apply`
322
+ - All phase transitions (opendesign/build → verify → archive) use `guard --apply`
253
323
  - No manual state editing required — eliminates write-verification errors
254
324
  - `comet-state.sh` is the agents' exclusive interface for state operations
255
325
  - Guard and archive scripts use `comet-state.sh` internally for state management
@@ -257,7 +327,7 @@ Comet ensures agent execution reliability through automated state transitions:
257
327
  3. **Schema Validation** — `comet-yaml-validate.sh` ensures data integrity
258
328
  - Validates required and optional fields
259
329
  - Validates enum values, including `direct_override`
260
- - Validates referenced file paths exist
330
+ - Validates `design_doc`, `plan`, and `handoff_context` paths exist, plus `handoff_hash` format
261
331
  - Detects unknown/typos fields
262
332
 
263
333
  4. **Build Decision Enforcement** — Guard and state transitions both block skipped build choices
@@ -277,7 +347,7 @@ Comet ensures agent execution reliability through automated state transitions:
277
347
  - Moves change to archive directory and updates `archived: true`
278
348
  - Supports `--dry-run` for preview
279
349
 
280
- **Security**: Path traversal protection on all change name inputs
350
+ </details>
281
351
 
282
352
  ## Project Structure
283
353
 
@@ -287,6 +357,7 @@ your-project/
287
357
  │ ├── comet/SKILL.md
288
358
  │ │ └── scripts/
289
359
  │ │ ├── comet-guard.sh # Phase transition guard (--apply auto-updates state)
360
+ │ │ ├── comet-handoff.sh # Design handoff (OpenSpec → Superpowers context tracing)
290
361
  │ │ ├── comet-archive.sh # One-command archive automation
291
362
  │ │ ├── comet-yaml-validate.sh # Schema validator
292
363
  │ │ └── comet-state.sh # Unified state management (init/set/get/check/scale)
@@ -308,51 +379,15 @@ your-project/
308
379
  └── plans/ # Implementation plans
309
380
  ```
310
381
 
311
- ## What You'll Learn
312
-
313
- Many excellent Skill projects exist in the current Skill market, but they generally have preference issues — users may only like some features. For example, when using both OpenSpec and Superpowers, one might only use OpenSpec's Spec management capabilities, but prefer Superpowers' TDD-driven approach for coding.
314
-
315
- Long-term Skill users know these capabilities can be freely combined, but exactly how to do so still requires real practice. The Comet project can serve as a reference:
316
-
317
- - **How to reliably trigger nested Skills** — Not letting the Agent rely on document descriptions to perform "look-alike Skill trigger" operations (like writing files based on Skill descriptions), but truly triggering Skills (key feature: Skill trigger prints on CC). Comet will trigger many capabilities from OpenSpec and Superpowers — how is this Prompt written?
318
-
319
- - **How to make combined Skills multi-phase auto-flow** — Not relying on manual intervention. Comet's 5-phase flow automatically triggers Skills for core processes except necessary user selections, while the **state machine mechanism** also ensures state transition reliability.
320
-
321
382
  ## Development
322
383
 
323
384
  See [CONTRIBUTING.md](CONTRIBUTING.md) for development setup, commit conventions, PR process, and guidance for adding platforms or skills.
324
385
 
325
- ```bash
326
- # Clone
327
- git clone https://github.com/rpamis/comet
328
- cd comet
329
-
330
- # Install dependencies
331
- pnpm install
332
-
333
- # Dev mode (watch)
334
- pnpm dev
335
-
336
- # Build
337
- pnpm build
338
-
339
- # Test (unit + coverage)
340
- pnpm test
341
- pnpm test:coverage
342
- pnpm test:shell # bats shell tests
343
-
344
- # Lint & format
345
- pnpm lint
346
- pnpm format
347
- ```
348
-
349
386
  See [CHANGELOG.md](CHANGELOG.md) for version history and updates.
350
387
 
351
- ## Security
388
+ ## Roadmap
352
389
 
353
- - Pre-publish scan for API keys, secrets, tokens, and private keys
354
- - `.npmignore` prevents source code and config files from entering the npm package
355
- - `.gitignore` covers secrets, credentials, IDE configs, and more
390
+ Track our development progress and upcoming features on the [Comet Roadmap](https://github.com/orgs/rpamis/projects/1).
356
391
 
357
392
  ## License
358
393
 
@@ -4,6 +4,7 @@
4
4
  "comet/SKILL.md",
5
5
  "comet/scripts/comet-guard.sh",
6
6
  "comet/scripts/comet-state.sh",
7
+ "comet/scripts/comet-handoff.sh",
7
8
  "comet/scripts/comet-archive.sh",
8
9
  "comet/scripts/comet-yaml-validate.sh",
9
10
  "comet-open/SKILL.md",
@@ -73,11 +73,49 @@ yaml_field() {
73
73
  bash "$STATE_SH" get "$CHANGE" "$field" 2>/dev/null
74
74
  else
75
75
  if [ -f "$YAML" ]; then
76
- grep "^${field}:" "$YAML" | sed "s/^${field}: *//" | tr -d '"' | tr -d "'"
76
+ local value
77
+ value=$(grep "^${field}:" "$YAML" 2>/dev/null | sed "s/^${field}: *//" || true)
78
+ value=$(strip_inline_comment "$value")
79
+ strip_wrapping_quotes "$value"
77
80
  fi
78
81
  fi
79
82
  }
80
83
 
84
+ strip_inline_comment() {
85
+ local value="$1"
86
+ printf '%s\n' "$value" | awk -v squote="'" '
87
+ {
88
+ out = ""
89
+ quote = ""
90
+ for (i = 1; i <= length($0); i++) {
91
+ c = substr($0, i, 1)
92
+ if (quote == "") {
93
+ if (c == "\"" || c == squote) {
94
+ quote = c
95
+ } else if (c == "#" && (i == 1 || substr($0, i - 1, 1) ~ /[[:space:]]/)) {
96
+ sub(/[[:space:]]+$/, "", out)
97
+ print out
98
+ next
99
+ }
100
+ } else if (c == quote) {
101
+ quote = ""
102
+ }
103
+ out = out c
104
+ }
105
+ print out
106
+ }
107
+ '
108
+ }
109
+
110
+ strip_wrapping_quotes() {
111
+ local value="$1"
112
+ case "$value" in
113
+ \"*\") printf '%s\n' "${value:1:${#value}-2}" ;;
114
+ \'*\') printf '%s\n' "${value:1:${#value}-2}" ;;
115
+ *) printf '%s\n' "$value" ;;
116
+ esac
117
+ }
118
+
81
119
  if [ ! -f "$YAML" ]; then
82
120
  red "FATAL: .comet.yaml not found in $CHANGE_DIR/"
83
121
  exit 1
@@ -98,10 +98,37 @@ yaml_field_value() {
98
98
  if [ -f "$yaml" ]; then
99
99
  local value
100
100
  value=$(grep "^${field}:" "$yaml" 2>/dev/null | sed "s/^${field}: *//" || true)
101
+ value=$(strip_inline_comment "$value")
101
102
  strip_wrapping_quotes "$value"
102
103
  fi
103
104
  }
104
105
 
106
+ strip_inline_comment() {
107
+ local value="$1"
108
+ printf '%s\n' "$value" | awk -v squote="'" '
109
+ {
110
+ out = ""
111
+ quote = ""
112
+ for (i = 1; i <= length($0); i++) {
113
+ c = substr($0, i, 1)
114
+ if (quote == "") {
115
+ if (c == "\"" || c == squote) {
116
+ quote = c
117
+ } else if (c == "#" && (i == 1 || substr($0, i - 1, 1) ~ /[[:space:]]/)) {
118
+ sub(/[[:space:]]+$/, "", out)
119
+ print out
120
+ next
121
+ }
122
+ } else if (c == quote) {
123
+ quote = ""
124
+ }
125
+ out = out c
126
+ }
127
+ print out
128
+ }
129
+ '
130
+ }
131
+
105
132
  strip_wrapping_quotes() {
106
133
  local value="$1"
107
134
  case "$value" in
@@ -130,6 +157,7 @@ project_config_value() {
130
157
  for config in ".comet.yaml" "comet.yaml" ".comet.yml" "comet.yml"; do
131
158
  if [ -f "$config" ]; then
132
159
  value=$(grep "^${field}:" "$config" 2>/dev/null | sed "s/^${field}: *//" || true)
160
+ value=$(strip_inline_comment "$value")
133
161
  value=$(strip_wrapping_quotes "$value")
134
162
  if [ -n "$value" ] && [ "$value" != "null" ]; then
135
163
  echo "$value"
@@ -156,6 +184,47 @@ run_command_string() {
156
184
  bash -lc "$command"
157
185
  }
158
186
 
187
+ hash_stream() {
188
+ if command -v sha256sum >/dev/null 2>&1; then
189
+ sha256sum | awk '{print $1}'
190
+ elif command -v shasum >/dev/null 2>&1; then
191
+ shasum -a 256 | awk '{print $1}'
192
+ else
193
+ echo "sha256sum or shasum is required" >&2
194
+ return 1
195
+ fi
196
+ }
197
+
198
+ hash_file() {
199
+ local file="$1"
200
+ if command -v sha256sum >/dev/null 2>&1; then
201
+ sha256sum "$file" | awk '{print $1}'
202
+ elif command -v shasum >/dev/null 2>&1; then
203
+ shasum -a 256 "$file" | awk '{print $1}'
204
+ else
205
+ echo "sha256sum or shasum is required" >&2
206
+ return 1
207
+ fi
208
+ }
209
+
210
+ handoff_source_files() {
211
+ printf '%s\n' "$CHANGE_DIR/proposal.md"
212
+ printf '%s\n' "$CHANGE_DIR/design.md"
213
+ printf '%s\n' "$CHANGE_DIR/tasks.md"
214
+ if [ -d "$CHANGE_DIR/specs" ]; then
215
+ find "$CHANGE_DIR/specs" -path '*/spec.md' -type f 2>/dev/null | sort
216
+ fi
217
+ }
218
+
219
+ compute_handoff_hash() {
220
+ handoff_source_files | while IFS= read -r file; do
221
+ if [ -f "$file" ]; then
222
+ printf 'path:%s\n' "$file"
223
+ printf 'sha256:%s\n' "$(hash_file "$file")"
224
+ fi
225
+ done | hash_stream
226
+ }
227
+
159
228
  preflight() {
160
229
 
161
230
  if [ ! -d "$CHANGE_DIR" ]; then
@@ -291,6 +360,118 @@ branch_status_handled() {
291
360
  [ "$status" = "handled" ]
292
361
  }
293
362
 
363
+ design_handoff_context_valid() {
364
+ local context recorded_hash actual_hash markdown
365
+ context=$(yaml_field_value "handoff_context" 2>/dev/null || true)
366
+ recorded_hash=$(yaml_field_value "handoff_hash" 2>/dev/null || true)
367
+
368
+ if [ -z "$context" ] || [ "$context" = "null" ]; then
369
+ echo "handoff_context is missing from .comet.yaml" >&2
370
+ echo "Next: run bash \"\$COMET_HANDOFF\" $CHANGE design --write before invoking Superpowers." >&2
371
+ return 1
372
+ fi
373
+ if [ ! -s "$context" ]; then
374
+ echo "handoff_context does not point to a non-empty file: $context" >&2
375
+ echo "Next: regenerate the design handoff with comet-handoff.sh." >&2
376
+ return 1
377
+ fi
378
+ if [[ ! "$recorded_hash" =~ ^[a-f0-9]{64}$ ]]; then
379
+ echo "handoff_hash is missing or invalid: ${recorded_hash:-null}" >&2
380
+ echo "Next: regenerate the design handoff with comet-handoff.sh." >&2
381
+ return 1
382
+ fi
383
+
384
+ actual_hash=$(compute_handoff_hash)
385
+ if [ "$actual_hash" != "$recorded_hash" ]; then
386
+ echo "OpenSpec artifacts changed after handoff was generated." >&2
387
+ echo "Expected handoff_hash: $recorded_hash" >&2
388
+ echo "Actual handoff_hash: $actual_hash" >&2
389
+ echo "Next: rerun comet-handoff.sh so Superpowers receives the current OpenSpec context." >&2
390
+ return 1
391
+ fi
392
+
393
+ markdown="${context%.json}.md"
394
+ if [ ! -s "$markdown" ]; then
395
+ echo "design handoff markdown is missing or empty: $markdown" >&2
396
+ echo "Next: regenerate the design handoff with comet-handoff.sh." >&2
397
+ return 1
398
+ fi
399
+ }
400
+
401
+ design_handoff_markdown_traceable() {
402
+ local context markdown missing=0
403
+ context=$(yaml_field_value "handoff_context" 2>/dev/null || true)
404
+ if [ -z "$context" ] || [ "$context" = "null" ]; then
405
+ echo "handoff_context is missing from .comet.yaml" >&2
406
+ return 1
407
+ fi
408
+ markdown="${context%.json}.md"
409
+ if [ ! -s "$markdown" ]; then
410
+ echo "design handoff markdown is missing or empty: $markdown" >&2
411
+ return 1
412
+ fi
413
+ grep -q '^Generated-by: comet-handoff\.sh$' "$markdown" || {
414
+ echo "handoff markdown is missing Generated-by marker" >&2
415
+ missing=1
416
+ }
417
+ grep -Eq '^- Mode: (compact|full)$' "$markdown" || {
418
+ echo "handoff markdown is missing Mode marker" >&2
419
+ missing=1
420
+ }
421
+ handoff_source_files | while IFS= read -r file; do
422
+ [ -f "$file" ] || continue
423
+ if ! grep -q "^- Source: $file$" "$markdown"; then
424
+ echo "handoff markdown is missing source reference: $file" >&2
425
+ exit 2
426
+ fi
427
+ if ! grep -q "^- SHA256: $(hash_file "$file")$" "$markdown"; then
428
+ echo "handoff markdown is missing current sha256 for: $file" >&2
429
+ exit 2
430
+ fi
431
+ done || missing=1
432
+
433
+ [ "$missing" -eq 0 ]
434
+ }
435
+
436
+ design_doc_frontmatter_has() {
437
+ local design_doc="$1"
438
+ local field="$2"
439
+ local expected="$3"
440
+ awk '
441
+ {
442
+ line = $0
443
+ sub(/^\357\273\277/, "", line)
444
+ }
445
+ !in_fm && line == "---" { in_fm = 1; next }
446
+ in_fm && line == "---" { exit }
447
+ in_fm { print line }
448
+ ' "$design_doc" | grep -Eq "^${field}: ['\"]?${expected}['\"]?[[:space:]]*$"
449
+ }
450
+
451
+ design_doc_links_current_change() {
452
+ local design_doc
453
+ design_doc=$(yaml_field_value "design_doc" 2>/dev/null || true)
454
+ if [ -z "$design_doc" ] || [ "$design_doc" = "null" ] || [ ! -s "$design_doc" ]; then
455
+ echo "design_doc must point to an existing Superpowers Design Doc before leaving design." >&2
456
+ return 1
457
+ fi
458
+ design_doc_frontmatter_has "$design_doc" "comet_change" "$CHANGE"
459
+ }
460
+
461
+ design_doc_declares_technical_role() {
462
+ local design_doc
463
+ design_doc=$(yaml_field_value "design_doc" 2>/dev/null || true)
464
+ [ -n "$design_doc" ] && [ "$design_doc" != "null" ] && [ -s "$design_doc" ] &&
465
+ design_doc_frontmatter_has "$design_doc" "role" "technical-design"
466
+ }
467
+
468
+ design_doc_declares_canonical_spec() {
469
+ local design_doc
470
+ design_doc=$(yaml_field_value "design_doc" 2>/dev/null || true)
471
+ [ -n "$design_doc" ] && [ "$design_doc" != "null" ] && [ -s "$design_doc" ] &&
472
+ design_doc_frontmatter_has "$design_doc" "canonical_spec" "openspec"
473
+ }
474
+
294
475
  archived_is_true() {
295
476
  local val
296
477
  val=$(yaml_field_value "archived" 2>/dev/null || true)
@@ -315,10 +496,16 @@ guard_design() {
315
496
  design_doc=$(yaml_field_value "design_doc" 2>/dev/null || true)
316
497
 
317
498
  check "proposal.md exists" file_nonempty "$CHANGE_DIR/proposal.md"
499
+ check "design.md exists" file_nonempty "$CHANGE_DIR/design.md"
318
500
  check "tasks.md exists" file_nonempty "$CHANGE_DIR/tasks.md"
501
+ check "design handoff context exists" design_handoff_context_valid
502
+ check "design handoff markdown is traceable" design_handoff_markdown_traceable
319
503
 
320
504
  if [ -n "$design_doc" ] && [ "$design_doc" != "null" ]; then
321
505
  check "Design Doc ($design_doc) exists" file_nonempty "$design_doc"
506
+ check "Design Doc frontmatter links current change" design_doc_links_current_change
507
+ check "Design Doc declares technical design role" design_doc_declares_technical_role
508
+ check "Design Doc declares OpenSpec as canonical spec" design_doc_declares_canonical_spec
322
509
  else
323
510
  warn " [WARN] No design_doc recorded in .comet.yaml"
324
511
  fi
@@ -395,7 +582,7 @@ else
395
582
  apply_state_update "$PHASE"
396
583
  case "$PHASE" in
397
584
  open)
398
- new_phase=$(grep "^phase:" "$CHANGE_DIR/.comet.yaml" | sed 's/^phase: *//' | tr -d '"' | tr -d "'")
585
+ new_phase=$(yaml_field_value "phase")
399
586
  green " [APPLY] .comet.yaml updated: phase=$new_phase"
400
587
  ;;
401
588
  design) green " [APPLY] .comet.yaml updated: phase=build" ;;