@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.
- package/LICENSE +21 -21
- package/README.md +41 -17
- package/assets/manifest.json +1 -1
- package/assets/skills/comet/SKILL.md +19 -1
- package/assets/skills/comet/scripts/comet-archive.sh +255 -258
- package/assets/skills/comet/scripts/comet-guard.sh +407 -257
- package/assets/skills/comet/scripts/comet-state.sh +680 -616
- package/assets/skills/comet/scripts/comet-yaml-validate.sh +157 -138
- package/assets/skills/comet-archive/SKILL.md +3 -0
- package/assets/skills/comet-build/SKILL.md +27 -1
- package/assets/skills-zh/comet/SKILL.md +15 -1
- package/assets/skills-zh/comet-archive/SKILL.md +3 -0
- package/assets/skills-zh/comet-build/SKILL.md +27 -1
- package/bin/comet.js +3 -3
- package/dist/cli/index.js +9 -3
- package/dist/cli/index.js.map +1 -1
- package/dist/commands/doctor.d.ts +3 -0
- package/dist/commands/doctor.d.ts.map +1 -1
- package/dist/commands/doctor.js +52 -29
- package/dist/commands/doctor.js.map +1 -1
- package/dist/commands/init.d.ts +9 -1
- package/dist/commands/init.d.ts.map +1 -1
- package/dist/commands/init.js +72 -13
- package/dist/commands/init.js.map +1 -1
- package/dist/commands/status.d.ts.map +1 -1
- package/dist/commands/status.js +19 -0
- package/dist/commands/status.js.map +1 -1
- package/dist/commands/update.d.ts +22 -2
- package/dist/commands/update.d.ts.map +1 -1
- package/dist/commands/update.js +175 -29
- package/dist/commands/update.js.map +1 -1
- package/package.json +1 -1
- package/scripts/postinstall.js +44 -44
|
@@ -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
|
-
|
|
48
|
-
}
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
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
|
|
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
|
|
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
|
-
.
|
|
52
|
-
.
|
|
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
|
});
|
package/dist/cli/index.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.js","sourceRoot":"","sources":["../../src/cli/index.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,OAAO,EAAE,MAAM,WAAW,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":"
|
|
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"}
|
package/dist/commands/doctor.js
CHANGED
|
@@ -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
|
-
|
|
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
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
const
|
|
72
|
-
|
|
73
|
-
|
|
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:
|
|
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
|
-
|
|
143
|
-
|
|
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
|
|
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(
|
|
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
|
}
|