@rpamis/comet 0.2.4 → 0.2.6

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.
@@ -1,138 +1,157 @@
1
- #!/bin/bash
2
- # Comet YAML Schema Validator — validates .comet.yaml structure
3
- # Usage: comet-yaml-validate.sh <change-name>
4
- # Exit 0 = valid, exit 1 = errors found (printed to stderr)
5
-
6
- set -euo pipefail
7
-
8
- red() { echo -e "\033[31m$1\033[0m" >&2; }
9
- green() { echo -e "\033[32m$1\033[0m" >&2; }
10
- warn() { echo -e "\033[33m$1\033[0m" >&2; }
11
-
12
- # Input validation - prevent path traversal
13
- validate_change_name() {
14
- local name="$1"
15
- # Reject empty names
16
- if [ -z "$name" ]; then
17
- red "ERROR: Change name cannot be empty" >&2
18
- exit 1
19
- fi
20
- # Only allow alphanumeric, hyphens, and underscores
21
- if [[ ! "$name" =~ ^[a-zA-Z0-9_-]+$ ]]; then
22
- red "ERROR: Invalid change name: '$name'" >&2
23
- red "Valid characters: a-z, A-Z, 0-9, -, _" >&2
24
- exit 1
25
- fi
26
- # Reject path traversal attempts
27
- if [[ "$name" =~ \.\. ]]; then
28
- red "ERROR: Change name cannot contain '..' (path traversal not allowed)" >&2
29
- exit 1
30
- fi
31
- }
32
-
33
- validate_change_name "$1"
34
-
35
- CHANGE="$1"
36
- CHANGE_DIR="openspec/changes/$CHANGE"
37
- if [ ! -d "$CHANGE_DIR" ] && [ -d "openspec/changes/archive/$CHANGE" ]; then
38
- CHANGE_DIR="openspec/changes/archive/$CHANGE"
39
- fi
40
- YAML="$CHANGE_DIR/.comet.yaml"
41
-
42
- ERRORS=0
43
- WARNINGS=0
44
-
45
- # Helper: get value of a top-level field (handles null, empty, quoted)
46
- field_value() {
47
- grep "^${1}:" "$YAML" 2>/dev/null | sed "s/^${1}: *//" | tr -d '"' | tr -d "'" || true
48
- }
49
-
50
- fail() { red " FAIL: $1"; ERRORS=$((ERRORS + 1)); }
51
- warn_msg() { warn " WARN: $1"; WARNINGS=$((WARNINGS + 1)); }
52
-
53
- echo "[VALIDATE] $YAML" >&2
54
-
55
- # --- Required fields ---
56
- REQUIRED_FIELDS="workflow phase design_doc plan build_mode isolation verify_mode verify_result verified_at archived"
57
- for field in $REQUIRED_FIELDS; do
58
- if ! grep -q "^${field}:" "$YAML" 2>/dev/null; then
59
- fail "missing required field '$field'"
60
- fi
61
- done
62
-
63
- # --- Enum validation ---
64
- validate_enum() {
65
- local field="$1" value="$2"
66
- shift 2
67
- local valid_values="$*"
68
-
69
- # null or empty is always acceptable
70
- if [ -z "$value" ] || [ "$value" = "null" ]; then
71
- return 0
72
- fi
73
-
74
- for v in $valid_values; do
75
- if [ "$value" = "$v" ]; then
76
- return 0
77
- fi
78
- done
79
- fail "$field='$value' is not valid. Expected: $valid_values"
80
- }
81
-
82
- workflow=$(field_value "workflow")
83
- phase=$(field_value "phase")
84
- build_mode=$(field_value "build_mode")
85
- isolation=$(field_value "isolation")
86
- verify_mode=$(field_value "verify_mode")
87
- verify_result=$(field_value "verify_result")
88
- branch_status=$(field_value "branch_status")
89
- archived=$(field_value "archived")
90
- design_doc=$(field_value "design_doc")
91
- plan=$(field_value "plan")
92
-
93
- validate_enum "workflow" "$workflow" "full hotfix tweak"
94
- validate_enum "phase" "$phase" "open design build verify archive"
95
- validate_enum "build_mode" "$build_mode" "subagent-driven-development executing-plans direct"
96
- validate_enum "isolation" "$isolation" "branch worktree"
97
- validate_enum "verify_mode" "$verify_mode" "light full"
98
- validate_enum "verify_result" "$verify_result" "pending pass fail"
99
- validate_enum "branch_status" "$branch_status" "pending handled"
100
- validate_enum "archived" "$archived" "true false"
101
-
102
- # --- Path validation ---
103
-
104
- if [ -n "$design_doc" ] && [ "$design_doc" != "null" ]; then
105
- if [ ! -f "$design_doc" ]; then
106
- fail "design_doc='$design_doc' does not exist on disk"
107
- fi
108
- fi
109
-
110
- if [ -n "$plan" ] && [ "$plan" != "null" ]; then
111
- if [ ! -f "$plan" ]; then
112
- fail "plan='$plan' does not exist on disk"
113
- fi
114
- fi
115
-
116
- # --- Unknown keys check ---
117
- KNOWN_KEYS="workflow phase design_doc plan build_mode isolation verify_mode verify_result verification_report branch_status verified_at archived"
118
- while IFS=: read -r key _; do
119
- key="${key// /}"
120
- [ -z "$key" ] && continue
121
- found=0
122
- for known in $KNOWN_KEYS; do
123
- [ "$key" = "$known" ] && found=1 && break
124
- done
125
- if [ "$found" -eq 0 ]; then
126
- warn_msg "unknown field '$key' found"
127
- fi
128
- done < "$YAML"
129
-
130
- # --- Summary ---
131
- echo "" >&2
132
- if [ "$ERRORS" -gt 0 ]; then
133
- red "$ERRORS error(s), $WARNINGS warning(s) — validation FAILED"
134
- exit 1
135
- else
136
- green "0 errors, $WARNINGS warning(s) validation PASSED"
137
- exit 0
138
- fi
1
+ #!/bin/bash
2
+ # Comet YAML Schema Validator — validates .comet.yaml structure
3
+ # Usage: comet-yaml-validate.sh <change-name>
4
+ # Exit 0 = valid, exit 1 = errors found (printed to stderr)
5
+
6
+ set -euo pipefail
7
+
8
+ red() { echo -e "\033[31m$1\033[0m" >&2; }
9
+ green() { echo -e "\033[32m$1\033[0m" >&2; }
10
+ warn() { echo -e "\033[33m$1\033[0m" >&2; }
11
+
12
+ # Input validation - prevent path traversal
13
+ validate_change_name() {
14
+ local name="$1"
15
+ # Reject empty names
16
+ if [ -z "$name" ]; then
17
+ red "ERROR: Change name cannot be empty" >&2
18
+ exit 1
19
+ fi
20
+ # Only allow alphanumeric, hyphens, and underscores
21
+ if [[ ! "$name" =~ ^[a-zA-Z0-9_-]+$ ]]; then
22
+ red "ERROR: Invalid change name: '$name'" >&2
23
+ red "Valid characters: a-z, A-Z, 0-9, -, _" >&2
24
+ exit 1
25
+ fi
26
+ # Reject path traversal attempts
27
+ if [[ "$name" =~ \.\. ]]; then
28
+ red "ERROR: Change name cannot contain '..' (path traversal not allowed)" >&2
29
+ exit 1
30
+ fi
31
+ }
32
+
33
+ validate_change_name "$1"
34
+
35
+ CHANGE="$1"
36
+ CHANGE_DIR="openspec/changes/$CHANGE"
37
+ if [ ! -d "$CHANGE_DIR" ] && [ -d "openspec/changes/archive/$CHANGE" ]; then
38
+ CHANGE_DIR="openspec/changes/archive/$CHANGE"
39
+ fi
40
+ YAML="$CHANGE_DIR/.comet.yaml"
41
+
42
+ ERRORS=0
43
+ WARNINGS=0
44
+
45
+ # Helper: get value of a top-level field (handles null, empty, quoted)
46
+ field_value() {
47
+ local value
48
+ value=$(grep "^${1}:" "$YAML" 2>/dev/null | sed "s/^${1}: *//" || true)
49
+ strip_wrapping_quotes "$value"
50
+ }
51
+
52
+ strip_wrapping_quotes() {
53
+ local value="$1"
54
+ case "$value" in
55
+ \"*\")
56
+ printf '%s\n' "${value:1:${#value}-2}"
57
+ ;;
58
+ \'*\')
59
+ printf '%s\n' "${value:1:${#value}-2}"
60
+ ;;
61
+ *)
62
+ printf '%s\n' "$value"
63
+ ;;
64
+ esac
65
+ }
66
+
67
+ fail() { red " FAIL: $1"; ERRORS=$((ERRORS + 1)); }
68
+ warn_msg() { warn " WARN: $1"; WARNINGS=$((WARNINGS + 1)); }
69
+
70
+ echo "[VALIDATE] $YAML" >&2
71
+
72
+ # --- Required fields ---
73
+ REQUIRED_FIELDS="workflow phase design_doc plan build_mode isolation verify_mode verify_result verified_at archived"
74
+ for field in $REQUIRED_FIELDS; do
75
+ if ! grep -q "^${field}:" "$YAML" 2>/dev/null; then
76
+ fail "missing required field '$field'"
77
+ fi
78
+ done
79
+
80
+ # --- Enum validation ---
81
+ validate_enum() {
82
+ local field="$1" value="$2"
83
+ shift 2
84
+ local valid_values="$*"
85
+
86
+ # null or empty is always acceptable
87
+ if [ -z "$value" ] || [ "$value" = "null" ]; then
88
+ return 0
89
+ fi
90
+
91
+ for v in $valid_values; do
92
+ if [ "$value" = "$v" ]; then
93
+ return 0
94
+ fi
95
+ done
96
+ fail "$field='$value' is not valid. Expected: $valid_values"
97
+ }
98
+
99
+ workflow=$(field_value "workflow")
100
+ phase=$(field_value "phase")
101
+ build_mode=$(field_value "build_mode")
102
+ isolation=$(field_value "isolation")
103
+ verify_mode=$(field_value "verify_mode")
104
+ verify_result=$(field_value "verify_result")
105
+ branch_status=$(field_value "branch_status")
106
+ archived=$(field_value "archived")
107
+ direct_override=$(field_value "direct_override")
108
+ design_doc=$(field_value "design_doc")
109
+ plan=$(field_value "plan")
110
+
111
+ validate_enum "workflow" "$workflow" "full hotfix tweak"
112
+ validate_enum "phase" "$phase" "open design build verify archive"
113
+ validate_enum "build_mode" "$build_mode" "subagent-driven-development executing-plans direct"
114
+ validate_enum "isolation" "$isolation" "branch worktree"
115
+ validate_enum "verify_mode" "$verify_mode" "light full"
116
+ validate_enum "verify_result" "$verify_result" "pending pass fail"
117
+ validate_enum "branch_status" "$branch_status" "pending handled"
118
+ validate_enum "archived" "$archived" "true false"
119
+ validate_enum "direct_override" "$direct_override" "true false"
120
+
121
+ # --- Path validation ---
122
+
123
+ if [ -n "$design_doc" ] && [ "$design_doc" != "null" ]; then
124
+ if [ ! -f "$design_doc" ]; then
125
+ fail "design_doc='$design_doc' does not exist on disk"
126
+ fi
127
+ fi
128
+
129
+ if [ -n "$plan" ] && [ "$plan" != "null" ]; then
130
+ if [ ! -f "$plan" ]; then
131
+ fail "plan='$plan' does not exist on disk"
132
+ fi
133
+ fi
134
+
135
+ # --- Unknown keys check ---
136
+ KNOWN_KEYS="workflow phase design_doc plan build_mode isolation verify_mode verify_result verification_report branch_status verified_at archived direct_override build_command verify_command"
137
+ while IFS=: read -r key _; do
138
+ key="${key// /}"
139
+ [ -z "$key" ] && continue
140
+ found=0
141
+ for known in $KNOWN_KEYS; do
142
+ [ "$key" = "$known" ] && found=1 && break
143
+ done
144
+ if [ "$found" -eq 0 ]; then
145
+ warn_msg "unknown field '$key' found"
146
+ fi
147
+ done < "$YAML"
148
+
149
+ # --- Summary ---
150
+ echo "" >&2
151
+ if [ "$ERRORS" -gt 0 ]; then
152
+ red "$ERRORS error(s), $WARNINGS warning(s) — validation FAILED"
153
+ exit 1
154
+ else
155
+ green "0 errors, $WARNINGS warning(s) — validation PASSED"
156
+ exit 0
157
+ fi
@@ -43,6 +43,9 @@ The script automatically executes:
43
43
 
44
44
  If script returns non-zero exit code, report error and stop.
45
45
  If script returns zero exit code, archive is complete.
46
+ The summary `X/Y steps succeeded` counts real executed steps and does not double-count delta spec sync or document annotation.
47
+
48
+ When a delta spec differs from an existing main spec, the script prints a unified diff before overwrite so the archive sync content is visible.
46
49
 
47
50
  Use `--dry-run` flag to preview without executing.
48
51
 
@@ -80,6 +80,11 @@ bash "$COMET_STATE" set <name> isolation <value>
80
80
  - `branch`
81
81
  - `worktree`
82
82
 
83
+ <IMPORTANT>
84
+ This is a script-enforced hard constraint, not a suggestion. Full workflow init may temporarily leave `isolation` as `null`, but only before this step.
85
+ Before implementation starts, stop and ask the user, then write either `branch` or `worktree`. If it remains `null`, both the `build → verify` guard and `comet-state transition build-complete` will fail.
86
+ </IMPORTANT>
87
+
83
88
  **Execute isolation**:
84
89
 
85
90
  - **branch**: Run `git checkout -b <change-name>`, subsequent work on the new branch
@@ -109,7 +114,16 @@ bash "$COMET_STATE" set <name> build_mode <value>
109
114
 
110
115
  - `subagent-driven-development`
111
116
  - `executing-plans`
112
- - `direct` (only for hotfix preset use)
117
+ - `direct` (default only for hotfix/tweak preset use)
118
+
119
+ Full workflow must not default to `direct`. Use it only when the user explicitly asks to bypass the plan execution skills and you record an explicit override:
120
+
121
+ ```bash
122
+ bash "$COMET_STATE" set <name> direct_override true
123
+ bash "$COMET_STATE" set <name> build_mode direct
124
+ ```
125
+
126
+ Without `direct_override: true`, `build_mode=direct` in full workflow is blocked by both guard and state transition.
113
127
 
114
128
  Then, **immediately execute:** Use the Skill tool to load the corresponding skill. Skipping this step is prohibited.
115
129
 
@@ -151,8 +165,20 @@ Build is the longest phase and may span many tasks. To support resume after cont
151
165
  - All tasks.md checked
152
166
  - Code committed
153
167
  - Project-specific build/tests explicitly run and pass; do not rely only on guard auto-detection
168
+ - `isolation` has been written as `branch` or `worktree`
169
+ - `build_mode` has been written as `subagent-driven-development`, `executing-plans`, or `direct` with explicit override
154
170
  - **Phase guard**: Run `bash "$COMET_GUARD" <change-name> build --apply`; after all PASS, state advances to `phase: verify`
155
171
 
172
+ Guard reads project command configuration first:
173
+
174
+ ```yaml
175
+ build_command: <build command>
176
+ verify_command: <verify command>
177
+ ```
178
+
179
+ Configuration can live in the change `.comet.yaml`, or in repo-root `.comet.yaml` / `comet.yaml` / `.comet.yml` / `comet.yml`.
180
+ Only when no command is configured does guard fall back to `npm run build`, Maven, or Cargo auto-detection. When a command fails, guard prints the command output as evidence for debugging.
181
+
156
182
  Before exit, run guard to auto-transition:
157
183
 
158
184
  ```bash
@@ -171,7 +171,7 @@ archived: false
171
171
  | `design_doc` | 关联的 Superpowers Design Doc 路径,可为空 |
172
172
  | `plan` | 关联的 Superpowers Plan 路径,可为空 |
173
173
  | `build_mode` | 已选择的执行方式,可为空 |
174
- | `isolation` | `branch` 或 `worktree`,工作区隔离方式,默认 `branch` |
174
+ | `isolation` | `branch` 或 `worktree`,工作区隔离方式。full 初始化可为 `null`,但只允许持续到 `/comet-build` Step 3 前;hotfix/tweak 默认 `branch` |
175
175
  | `verify_mode` | `light` 或 `full`,可为空 |
176
176
  | `verify_result` | `pending`、`pass` 或 `fail` |
177
177
  | `verification_report` | 验证报告文件路径,verify 通过前必须指向已存在文件 |
@@ -179,6 +179,20 @@ archived: false
179
179
  | `verified_at` | 验证通过时间,可为空 |
180
180
  | `archived` | change 是否已归档 |
181
181
 
182
+ 可选字段:
183
+
184
+ | 字段 | 含义 |
185
+ |------|------|
186
+ | `direct_override` | `true`/`false`。full workflow 如需使用 `build_mode: direct`,必须显式设为 `true` |
187
+ | `build_command` | 项目构建命令。guard 优先运行该命令,失败时打印命令输出 |
188
+ | `verify_command` | 项目验证命令。verify guard 优先运行该命令,未配置时回退到构建命令 |
189
+
190
+ 状态机硬约束:
191
+ - `build → verify` 前,`isolation` 必须是 `branch` 或 `worktree`
192
+ - `build → verify` 前,`build_mode` 必须已选择
193
+ - `build_mode: direct` 默认只允许 `hotfix` / `tweak`;full workflow 需要 `direct_override: true`
194
+ - 这些约束同时存在于 `comet-guard.sh build --apply` 和 `comet-state.sh transition <name> build-complete`
195
+
182
196
  ### 脚本定位
183
197
 
184
198
  Comet 脚本随 skill 包分发在 `comet/scripts/` 下。**不硬编码路径** — 定位一次,缓存到环境变量:
@@ -43,6 +43,9 @@ bash "$COMET_ARCHIVE" "<change-name>"
43
43
 
44
44
  如脚本返回非零退出码,报告错误并停止。
45
45
  如脚本返回零退出码,归档完成。
46
+ 脚本摘要中的 `X/Y steps succeeded` 以真实执行步骤计数,不会因 delta spec 同步或文档标注重复累计。
47
+
48
+ 当待同步的 delta spec 与已有主 spec 不一致时,脚本会在覆盖前打印 unified diff 预览,帮助确认归档同步内容。
46
49
 
47
50
  如需预览而不实际执行,使用 `--dry-run` 参数。
48
51
 
@@ -80,6 +80,11 @@ bash "$COMET_STATE" set <name> isolation <value>
80
80
  - `branch`
81
81
  - `worktree`
82
82
 
83
+ <IMPORTANT>
84
+ 这是脚本级硬约束,不是建议。full workflow 初始化时 `isolation` 可以暂时为 `null`,但只允许存在到本步骤之前。
85
+ 进入实现前必须停下来询问用户并写入 `branch` 或 `worktree`。若保持 `null`,`build → verify` 的 guard 和 `comet-state transition build-complete` 都会失败。
86
+ </IMPORTANT>
87
+
83
88
  **执行隔离**:
84
89
 
85
90
  - **branch**:执行 `git checkout -b <change-name>`,后续工作在新分支上进行
@@ -109,7 +114,16 @@ bash "$COMET_STATE" set <name> build_mode <value>
109
114
 
110
115
  - `subagent-driven-development`
111
116
  - `executing-plans`
112
- - `direct`(仅 hotfix preset 使用)
117
+ - `direct`(默认仅 hotfix/tweak preset 使用)
118
+
119
+ full workflow 不得默认使用 `direct`。只有用户明确要求跳过计划执行技能,且你已记录显式 override 时,才允许:
120
+
121
+ ```bash
122
+ bash "$COMET_STATE" set <name> direct_override true
123
+ bash "$COMET_STATE" set <name> build_mode direct
124
+ ```
125
+
126
+ 没有 `direct_override: true` 时,full workflow 的 `build_mode=direct` 会被 guard 和状态转换同时拦截。
113
127
 
114
128
  然后,**立即执行:** 使用 Skill 工具加载对应技能。禁止跳过此步骤。
115
129
 
@@ -151,8 +165,20 @@ Build 是最长阶段,可能跨越大量任务。为支持上下文压缩后
151
165
  - tasks.md 全部勾选
152
166
  - 代码已提交
153
167
  - 已显式运行项目对应的构建/测试命令并通过(不要只依赖 guard 自动猜测)
168
+ - `isolation` 已写为 `branch` 或 `worktree`
169
+ - `build_mode` 已写为 `subagent-driven-development`、`executing-plans` 或带显式 override 的 `direct`
154
170
  - **阶段守卫**:运行 `bash "$COMET_GUARD" <change-name> build --apply`,全部 PASS 后自动流转到 `phase: verify`
155
171
 
172
+ Guard 会优先读取项目配置中的命令:
173
+
174
+ ```yaml
175
+ build_command: <build command>
176
+ verify_command: <verify command>
177
+ ```
178
+
179
+ 配置位置可为 change 的 `.comet.yaml`,也可为仓库根目录的 `.comet.yaml` / `comet.yaml` / `.comet.yml` / `comet.yml`。
180
+ 未配置时才回退到 `npm run build`、Maven 或 Cargo 的默认探测。构建失败时 guard 会打印失败命令输出,作为排查证据。
181
+
156
182
  退出前运行 guard 自动流转:
157
183
 
158
184
  ```bash
package/bin/comet.js CHANGED
@@ -1,3 +1,3 @@
1
- #!/usr/bin/env node
2
-
3
- import '../dist/cli/index.js';
1
+ #!/usr/bin/env node
2
+
3
+ import '../dist/cli/index.js';
package/dist/cli/index.js CHANGED
@@ -1,4 +1,4 @@
1
- import { Command } from 'commander';
1
+ import { Command, Option } from 'commander';
2
2
  import { createRequire } from 'module';
3
3
  import { initCommand } from '../commands/init.js';
4
4
  import { statusCommand } from '../commands/status.js';
@@ -41,6 +41,11 @@ program
41
41
  .command('doctor [path]')
42
42
  .description('Diagnose Comet installation health')
43
43
  .option('--json', 'Output as JSON')
44
+ .addOption(new Option('--scope <scope>', 'Install scope to diagnose').choices([
45
+ 'auto',
46
+ 'global',
47
+ 'project',
48
+ ]))
44
49
  .action(async (targetPath = '.', options) => {
45
50
  await doctorCommand(targetPath, options);
46
51
  });
@@ -48,8 +53,9 @@ program
48
53
  .command('update [path]')
49
54
  .description('Update comet skill files to latest version')
50
55
  .option('--json', 'Output as JSON')
51
- .option('--language <lang>', 'Language for skills (en, zh)')
52
- .option('--scope <scope>', 'Install scope (global, project)')
56
+ .addOption(new Option('--language <lang>', 'Language for skills').choices(['en', 'zh']))
57
+ .addOption(new Option('--scope <scope>', 'Install scope').choices(['global', 'project']))
58
+ .addOption(new Option('--skip-npm', 'Skip npm package self-update').hideHelp())
53
59
  .action(async (targetPath = '.', options) => {
54
60
  await updateCommand(targetPath, options);
55
61
  });
@@ -1 +1 @@
1
- {"version":3,"file":"index.js","sourceRoot":"","sources":["../../src/cli/index.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,OAAO,EAAE,MAAM,WAAW,CAAC;AACpC,OAAO,EAAE,aAAa,EAAE,MAAM,QAAQ,CAAC;AACvC,OAAO,EAAE,WAAW,EAAE,MAAM,qBAAqB,CAAC;AAClD,OAAO,EAAE,aAAa,EAAE,MAAM,uBAAuB,CAAC;AACtD,OAAO,EAAE,aAAa,EAAE,MAAM,uBAAuB,CAAC;AACtD,OAAO,EAAE,aAAa,EAAE,MAAM,uBAAuB,CAAC;AAEtD,MAAM,OAAO,GAAG,aAAa,CAAC,MAAM,CAAC,IAAI,CAAC,GAAG,CAAC,CAAC;AAC/C,MAAM,EAAE,OAAO,EAAE,GAAG,OAAO,CAAC,oBAAoB,CAAC,CAAC;AAElD,MAAM,OAAO,GAAG,IAAI,OAAO,EAAE,CAAC;AAE9B,OAAO;KACJ,IAAI,CAAC,OAAO,CAAC;KACb,WAAW,CAAC,uDAAuD,CAAC;KACpE,OAAO,CAAC,OAAO,CAAC,CAAC;AAEpB,OAAO;KACJ,OAAO,CAAC,aAAa,CAAC;KACtB,WAAW,CAAC,2CAA2C,CAAC;KACxD,MAAM,CAAC,OAAO,EAAE,gDAAgD,CAAC;KACjE,MAAM,CAAC,iBAAiB,EAAE,qCAAqC,CAAC;KAChE,MAAM,CAAC,aAAa,EAAE,kCAAkC,CAAC;KACzD,MAAM,CAAC,QAAQ,EAAE,gBAAgB,CAAC;KAClC,MAAM,CAAC,KAAK,EAAE,UAAU,GAAG,GAAG,EAAE,OAAO,EAAE,EAAE;IAC1C,IAAI,CAAC;QACH,MAAM,WAAW,CAAC,UAAU,EAAE,OAAO,CAAC,CAAC;IACzC,CAAC;IAAC,OAAO,KAAK,EAAE,CAAC;QACf,IAAI,KAAK,YAAY,KAAK,IAAI,KAAK,CAAC,IAAI,KAAK,iBAAiB,EAAE,CAAC;YAC/D,OAAO,CAAC,GAAG,CAAC,kBAAkB,CAAC,CAAC;YAChC,OAAO,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC;QAClB,CAAC;QACD,MAAM,KAAK,CAAC;IACd,CAAC;AACH,CAAC,CAAC,CAAC;AAEL,OAAO;KACJ,OAAO,CAAC,eAAe,CAAC;KACxB,WAAW,CAAC,yCAAyC,CAAC;KACtD,MAAM,CAAC,QAAQ,EAAE,gBAAgB,CAAC;KAClC,MAAM,CAAC,KAAK,EAAE,UAAU,GAAG,GAAG,EAAE,OAAO,EAAE,EAAE;IAC1C,MAAM,aAAa,CAAC,UAAU,EAAE,OAAO,CAAC,CAAC;AAC3C,CAAC,CAAC,CAAC;AAEL,OAAO;KACJ,OAAO,CAAC,eAAe,CAAC;KACxB,WAAW,CAAC,oCAAoC,CAAC;KACjD,MAAM,CAAC,QAAQ,EAAE,gBAAgB,CAAC;KAClC,MAAM,CAAC,KAAK,EAAE,UAAU,GAAG,GAAG,EAAE,OAAO,EAAE,EAAE;IAC1C,MAAM,aAAa,CAAC,UAAU,EAAE,OAAO,CAAC,CAAC;AAC3C,CAAC,CAAC,CAAC;AAEL,OAAO;KACJ,OAAO,CAAC,eAAe,CAAC;KACxB,WAAW,CAAC,4CAA4C,CAAC;KACzD,MAAM,CAAC,QAAQ,EAAE,gBAAgB,CAAC;KAClC,MAAM,CAAC,mBAAmB,EAAE,8BAA8B,CAAC;KAC3D,MAAM,CAAC,iBAAiB,EAAE,iCAAiC,CAAC;KAC5D,MAAM,CAAC,KAAK,EAAE,UAAU,GAAG,GAAG,EAAE,OAAO,EAAE,EAAE;IAC1C,MAAM,aAAa,CAAC,UAAU,EAAE,OAAO,CAAC,CAAC;AAC3C,CAAC,CAAC,CAAC;AAEL,OAAO,CAAC,KAAK,EAAE,CAAC"}
1
+ {"version":3,"file":"index.js","sourceRoot":"","sources":["../../src/cli/index.ts"],"names":[],"mappings":"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"}
@@ -1,5 +1,8 @@
1
+ import type { InstallScope } from '../core/types.js';
2
+ type DoctorScope = InstallScope | 'auto';
1
3
  interface DoctorOptions {
2
4
  json?: boolean;
5
+ scope?: DoctorScope;
3
6
  }
4
7
  export declare function doctorCommand(targetPath: string, options?: DoctorOptions): Promise<void>;
5
8
  export {};
@@ -1 +1 @@
1
- {"version":3,"file":"doctor.d.ts","sourceRoot":"","sources":["../../src/commands/doctor.ts"],"names":[],"mappings":"AAmLA,UAAU,aAAa;IACrB,IAAI,CAAC,EAAE,OAAO,CAAC;CAChB;AAED,wBAAsB,aAAa,CACjC,UAAU,EAAE,MAAM,EAClB,OAAO,GAAE,aAAkB,GAC1B,OAAO,CAAC,IAAI,CAAC,CAgBf"}
1
+ {"version":3,"file":"doctor.d.ts","sourceRoot":"","sources":["../../src/commands/doctor.ts"],"names":[],"mappings":"AAQA,OAAO,KAAK,EAAE,YAAY,EAAE,MAAM,kBAAkB,CAAC;AAQrD,KAAK,WAAW,GAAG,YAAY,GAAG,MAAM,CAAC;AAsMzC,UAAU,aAAa;IACrB,IAAI,CAAC,EAAE,OAAO,CAAC;IACf,KAAK,CAAC,EAAE,WAAW,CAAC;CACrB;AAED,wBAAsB,aAAa,CACjC,UAAU,EAAE,MAAM,EAClB,OAAO,GAAE,aAAkB,GAC1B,OAAO,CAAC,IAAI,CAAC,CAiBf"}
@@ -1,4 +1,5 @@
1
1
  import path from 'path';
2
+ import os from 'os';
2
3
  import { execSync } from 'child_process';
3
4
  import { promises as fs } from 'fs';
4
5
  import { fileExists, readDir } from '../utils/file-system.js';
@@ -14,6 +15,8 @@ const VALID_YAML_FIELDS = new Set([
14
15
  'verify_result',
15
16
  'design_doc',
16
17
  'plan',
18
+ 'verification_report',
19
+ 'branch_status',
17
20
  'archived',
18
21
  'verified_at',
19
22
  ]);
@@ -57,39 +60,56 @@ async function checkWorkingDirs(projectPath) {
57
60
  message: `partial (missing: ${missing.join(', ')})`,
58
61
  };
59
62
  }
60
- async function checkSkillCompleteness(projectPath) {
63
+ function getScopeBases(projectPath, scope) {
64
+ if (scope === 'project')
65
+ return [{ scope, baseDir: projectPath }];
66
+ if (scope === 'global')
67
+ return [{ scope, baseDir: os.homedir() }];
68
+ const bases = [
69
+ { scope: 'project', baseDir: projectPath },
70
+ ];
71
+ if (path.resolve(projectPath) !== path.resolve(os.homedir())) {
72
+ bases.push({ scope: 'global', baseDir: os.homedir() });
73
+ }
74
+ return bases;
75
+ }
76
+ async function checkSkillCompleteness(projectPath, scope) {
61
77
  const results = [];
62
78
  const manifest = await readManifest();
63
79
  let anyPlatform = false;
64
- for (const platform of PLATFORMS) {
65
- const skillsDir = path.join(projectPath, platform.skillsDir, 'skills');
66
- if (!(await fileExists(skillsDir)))
67
- continue;
68
- anyPlatform = true;
69
- const missing = [];
70
- for (const relPath of manifest.skills) {
71
- const fullPath = path.join(projectPath, platform.skillsDir, 'skills', relPath);
72
- if (!(await fileExists(fullPath))) {
73
- missing.push(relPath);
80
+ for (const base of getScopeBases(projectPath, scope)) {
81
+ for (const platform of PLATFORMS) {
82
+ const skillsDir = path.join(base.baseDir, platform.skillsDir, 'skills');
83
+ if (!(await fileExists(skillsDir)))
84
+ continue;
85
+ anyPlatform = true;
86
+ const missing = [];
87
+ for (const relPath of manifest.skills) {
88
+ const fullPath = path.join(base.baseDir, platform.skillsDir, 'skills', relPath);
89
+ if (!(await fileExists(fullPath))) {
90
+ missing.push(relPath);
91
+ }
74
92
  }
93
+ results.push(missing.length === 0
94
+ ? {
95
+ check: `skills: ${platform.name} (${base.scope})`,
96
+ status: 'pass',
97
+ message: `complete (${manifest.skills.length} files)`,
98
+ }
99
+ : {
100
+ check: `skills: ${platform.name} (${base.scope})`,
101
+ status: 'warn',
102
+ message: `missing ${missing.length}: ${missing.join(', ')}`,
103
+ });
75
104
  }
76
- results.push(missing.length === 0
77
- ? {
78
- check: `skills: ${platform.name}`,
79
- status: 'pass',
80
- message: `complete (${manifest.skills.length} files)`,
81
- }
82
- : {
83
- check: `skills: ${platform.name}`,
84
- status: 'warn',
85
- message: `missing ${missing.length}: ${missing.join(', ')}`,
86
- });
87
105
  }
88
106
  if (!anyPlatform) {
89
107
  results.push({
90
108
  check: 'skills',
91
109
  status: 'warn',
92
- message: 'no platforms detected — run comet init',
110
+ message: scope === 'auto'
111
+ ? 'no platforms detected in project or global scope — run comet init'
112
+ : `no platforms detected in ${scope} scope — run comet init`,
93
113
  });
94
114
  }
95
115
  return results;
@@ -136,11 +156,13 @@ async function checkCometYamlValidity(projectPath) {
136
156
  }
137
157
  return results;
138
158
  }
139
- async function collectResults(projectPath) {
159
+ async function collectResults(projectPath, scope) {
140
160
  const results = [];
141
161
  results.push(await checkOpenSpecCli());
142
- results.push(await checkWorkingDirs(projectPath));
143
- results.push(...(await checkSkillCompleteness(projectPath)));
162
+ if (scope !== 'global') {
163
+ results.push(await checkWorkingDirs(projectPath));
164
+ }
165
+ results.push(...(await checkSkillCompleteness(projectPath, scope)));
144
166
  results.push(await checkScriptsPresent());
145
167
  results.push(...(await checkCometYamlValidity(projectPath)));
146
168
  return results;
@@ -154,12 +176,13 @@ function icon(status) {
154
176
  }
155
177
  export async function doctorCommand(targetPath, options = {}) {
156
178
  const projectPath = path.resolve(targetPath);
157
- const results = await collectResults(projectPath);
179
+ const scope = options.scope ?? 'auto';
180
+ const results = await collectResults(projectPath, scope);
158
181
  if (options.json) {
159
- console.log(JSON.stringify({ results }, null, 2));
182
+ console.log(JSON.stringify({ scope, results }, null, 2));
160
183
  return;
161
184
  }
162
- console.log('Comet Doctor\n');
185
+ console.log(`Comet Doctor (scope: ${scope})\n`);
163
186
  for (const r of results) {
164
187
  console.log(` ${icon(r.status)} ${r.check}: ${r.message}`);
165
188
  }