@rpamis/comet 0.4.0-beta.2 → 0.4.0-beta.3

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 (34) hide show
  1. package/README.md +17 -18
  2. package/assets/manifest.json +1 -1
  3. package/assets/skills/comet/reference/comet-yaml-fields.md +2 -3
  4. package/assets/skills/comet/scripts/comet-runtime.mjs +290 -101
  5. package/assets/skills/comet-build/SKILL.md +1 -9
  6. package/assets/skills-zh/comet/reference/comet-yaml-fields.md +1 -3
  7. package/assets/skills-zh/comet-build/SKILL.md +1 -9
  8. package/dist/app/cli/index.js +1 -1
  9. package/dist/app/cli/index.js.map +1 -1
  10. package/dist/app/commands/doctor.d.ts +1 -0
  11. package/dist/app/commands/doctor.d.ts.map +1 -1
  12. package/dist/app/commands/doctor.js +62 -12
  13. package/dist/app/commands/doctor.js.map +1 -1
  14. package/dist/domains/comet-classic/classic-guard.d.ts.map +1 -1
  15. package/dist/domains/comet-classic/classic-guard.js +44 -83
  16. package/dist/domains/comet-classic/classic-guard.js.map +1 -1
  17. package/dist/domains/comet-classic/classic-hook-guard.d.ts.map +1 -1
  18. package/dist/domains/comet-classic/classic-hook-guard.js +1 -6
  19. package/dist/domains/comet-classic/classic-hook-guard.js.map +1 -1
  20. package/dist/domains/comet-classic/classic-runtime-run.d.ts.map +1 -1
  21. package/dist/domains/comet-classic/classic-runtime-run.js +231 -4
  22. package/dist/domains/comet-classic/classic-runtime-run.js.map +1 -1
  23. package/dist/domains/comet-classic/classic-state-command.d.ts.map +1 -1
  24. package/dist/domains/comet-classic/classic-state-command.js +1 -3
  25. package/dist/domains/comet-classic/classic-state-command.js.map +1 -1
  26. package/dist/domains/comet-classic/classic-state.d.ts +1 -3
  27. package/dist/domains/comet-classic/classic-state.d.ts.map +1 -1
  28. package/dist/domains/comet-classic/classic-state.js +0 -6
  29. package/dist/domains/comet-classic/classic-state.js.map +1 -1
  30. package/dist/domains/comet-classic/classic-store.d.ts.map +1 -1
  31. package/dist/domains/comet-classic/classic-store.js +24 -7
  32. package/dist/domains/comet-classic/classic-store.js.map +1 -1
  33. package/dist/domains/dashboard/web/index.html +14 -14
  34. package/package.json +1 -1
package/README.md CHANGED
@@ -101,8 +101,8 @@ Many Comet capabilities have parallels in current industry practice.
101
101
  from necessary user choices, Comet's five-phase flow can trigger core Skills automatically while the state machine keeps
102
102
  transitions reliable.
103
103
  - **How to make a Spec lifecycle resumable** — Comet links OpenSpec change/spec artifacts with Superpowers design and
104
- plan documents, then records phase, execution mode, verification result, and archive status in `.comet.yaml`, so an
105
- agent can continue after interruption instead of rereading documents and guessing progress.
104
+ plan documents, then records phase, execution mode, verification result, and archive status in each change's
105
+ `.comet.yaml`, so an agent can continue after interruption instead of rereading documents and guessing progress.
106
106
  - **How to turn doc synchronization from reminders into automation** — Comet scripts handoff, state updates, validation,
107
107
  and archive sync, reducing repeated prompts such as "remember to update the design doc" or "remember to archive the
108
108
  change."
@@ -483,17 +483,17 @@ Comet uses a decoupled state architecture with separate files
483
483
  <details>
484
484
  <summary>View State Management</summary>
485
485
 
486
- | File | Owner | Purpose |
487
- | --------------------------- | -------- | --------------------------------------------------- |
488
- | `.openspec.yaml` | OpenSpec | Spec lifecycle, change metadata |
489
- | `.comet.yaml` | Comet | Workflow phase, execution mode, verification status |
490
- | `.comet/run-state.json` | Engine | Run identity and execution state (machine-owned) |
491
- | `.comet/state-events.jsonl` | Comet | Append-only state transition audit log |
486
+ | File | Owner | Purpose |
487
+ | ----------------------------------------- | -------- | --------------------------------------------------- |
488
+ | `.openspec.yaml` | OpenSpec | Spec lifecycle, change metadata |
489
+ | `openspec/changes/<name>/.comet.yaml` | Comet | Workflow phase, execution mode, verification status |
490
+ | `.comet/run-state.json` | Engine | Run identity and execution state (machine-owned) |
491
+ | `.comet/state-events.jsonl` | Comet | Append-only state transition audit log |
492
492
 
493
- `.comet.yaml` stores Classic workflow state and only keeps `run_id` as the link to the Engine Run. Machine-owned Engine
494
- state lives in `.comet/run-state.json` with camelCase fields such as `currentStep`, `status`, and `iteration`. Legacy
495
- Run fields left in YAML are migrated after compatibility reads, and `skill` is no longer a valid current
496
- `.comet.yaml` field.
493
+ Each change-level `.comet.yaml` stores Classic workflow state and only keeps `run_id` as the link to the Engine Run.
494
+ Machine-owned Engine state lives in the change's `.comet/run-state.json` with camelCase fields such as `currentStep`,
495
+ `status`, and `iteration`. Legacy Run fields left in YAML are migrated after compatibility reads, and `skill` is no
496
+ longer a valid current `.comet.yaml` field. Project defaults live in `.comet/config.yaml`.
497
497
 
498
498
  Phase progression is handled consistently by the TypeScript transition table, `comet-state transition`,
499
499
  `comet-guard --apply`, and archive commands. Each successful progression appends an audit event to
@@ -505,9 +505,9 @@ breakpoint recovery. Agents can use Comet commands to know which phase the curre
505
505
  </details>
506
506
 
507
507
  <details>
508
- <summary>View key .comet.yaml fields</summary>
508
+ <summary>View key change .comet.yaml fields</summary>
509
509
 
510
- **Key Fields in `.comet.yaml`:**
510
+ **Key Fields in change `.comet.yaml`:**
511
511
 
512
512
  ```yaml
513
513
  workflow: full # Workflow type: full | tweak | hotfix
@@ -532,15 +532,13 @@ branch_status: pending # Branch handling statu
532
532
  verified_at: null # Verification timestamp; null before verification passes
533
533
  archived: false # Archived changes are blocked from further mutation
534
534
  direct_override: null # Must be true when a full workflow chooses direct build
535
- build_command: null # Optional build command; may also live in repo-root config
536
- verify_command: null # Optional verify command; may also live in repo-root config
537
535
  handoff_context: null # Design handoff context path written by comet-handoff.mjs
538
536
  handoff_hash: null # SHA256 for handoff_context; 64 hex chars when present
539
537
  classic_profile: full # Machine-maintained Classic profile
540
538
  classic_migration: 1 # Machine-maintained migration version
541
539
  ```
542
540
 
543
- Current `.comet.yaml` no longer contains `skill`; legacy Run fields in YAML are migrated to `.comet/run-state.json`.
541
+ Current change `.comet.yaml` no longer contains `skill`; legacy Run fields in YAML are migrated to `.comet/run-state.json`.
544
542
 
545
543
  </details>
546
544
 
@@ -666,7 +664,8 @@ manual handoff. Phase advancement itself always happens — this setting only af
666
664
  | `true` | Auto-invoke the next skill after each phase (default) |
667
665
  | `false` | Pause after each phase; user manually triggers the next skill |
668
666
 
669
- Three-layer configuration with precedence: `COMET_AUTO_TRANSITION` env var > `.comet/config.yaml` (project) > `.comet.yaml` (change).
667
+ Three-layer configuration with precedence: `COMET_AUTO_TRANSITION` env var > `.comet/config.yaml` (project) > change
668
+ `.comet.yaml`.
670
669
 
671
670
  See [AUTO-TRANSITION.md](docs/AUTO-TRANSITION.md) for configuration details, workflow mapping, and FAQ.
672
671
 
@@ -1,5 +1,5 @@
1
1
  {
2
- "version": "0.4.0-beta.1",
2
+ "version": "0.4.0-beta.3",
3
3
  "skills": [
4
4
  "comet/SKILL.md",
5
5
  "comet/reference/auto-transition.md",
@@ -2,7 +2,8 @@
2
2
 
3
3
  Canonical path: `comet/reference/comet-yaml-fields.md`
4
4
 
5
- This file is the field reference for the `.comet.yaml` state file. Consult on demand; not loaded inline with skills.
5
+ This file is the field reference for each change-level `.comet.yaml` state file under `openspec/changes/<name>/`.
6
+ Consult on demand; not loaded inline with skills. Project defaults live in `.comet/config.yaml`.
6
7
 
7
8
  ## Example
8
9
 
@@ -59,8 +60,6 @@ archived: false
59
60
  | Field | Meaning |
60
61
  |-------|---------|
61
62
  | `direct_override` | `true`/`false`. Full workflow must explicitly set to `true` to use `build_mode: direct` |
62
- | `build_command` | Project build command. Guard runs this first; supports restricted command words/quotes/paths plus `&&` sequential steps; rejects `;`, pipes, bare `&`, `$`, and backticks |
63
- | `verify_command` | Project verify command. Verify guard runs this first; same restricted command grammar as `build_command`; falls back to build command when unset |
64
63
 
65
64
  ## State Machine Hard Constraints
66
65
 
@@ -7827,8 +7827,6 @@ var CLASSIC_WIRE_KEYS = [
7827
7827
  "verified_at",
7828
7828
  "archived",
7829
7829
  "direct_override",
7830
- "build_command",
7831
- "verify_command",
7832
7830
  "handoff_context",
7833
7831
  "handoff_hash",
7834
7832
  "classic_profile",
@@ -7937,8 +7935,6 @@ function classicStateFromDocument(doc) {
7937
7935
  verifiedAt: nullableString(doc, "verified_at"),
7938
7936
  archived: booleanValue(doc, "archived", false),
7939
7937
  directOverride: booleanValue(doc, "direct_override"),
7940
- buildCommand: nullableString(doc, "build_command"),
7941
- verifyCommand: nullableString(doc, "verify_command"),
7942
7938
  handoffContext: relativePath(doc, "handoff_context"),
7943
7939
  handoffHash: sha256(doc, "handoff_hash"),
7944
7940
  classicProfile: enumValue(doc, "classic_profile", CLASSIC_PROFILES),
@@ -8002,8 +7998,6 @@ function classicStateToDocument(state) {
8002
7998
  verified_at: state.verifiedAt,
8003
7999
  archived: state.archived,
8004
8000
  direct_override: state.directOverride,
8005
- build_command: state.buildCommand,
8006
- verify_command: state.verifyCommand,
8007
8001
  handoff_context: state.handoffContext,
8008
8002
  handoff_hash: state.handoffHash,
8009
8003
  classic_profile: state.classicProfile,
@@ -8058,6 +8052,16 @@ function stripLegacyRunFields(document) {
8058
8052
  ];
8059
8053
  for (const key of LEGACY_RUN_KEYS) document.delete(key);
8060
8054
  }
8055
+ function stripLegacyCommandFields(document) {
8056
+ let changed = false;
8057
+ for (const key of ["build_command", "verify_command"]) {
8058
+ if (document.has(key)) {
8059
+ document.delete(key);
8060
+ changed = true;
8061
+ }
8062
+ }
8063
+ return changed;
8064
+ }
8061
8065
  async function readDocument(file) {
8062
8066
  let source;
8063
8067
  try {
@@ -8074,8 +8078,11 @@ async function readDocument(file) {
8074
8078
  return document;
8075
8079
  }
8076
8080
  async function readClassicState(changeDir) {
8077
- const document = await readDocument(path4.join(changeDir, ".comet.yaml"));
8078
- const doc = documentRecord(document);
8081
+ const file = path4.join(changeDir, ".comet.yaml");
8082
+ const document = await readDocument(file);
8083
+ let doc = documentRecord(document);
8084
+ let migrated = stripLegacyCommandFields(document);
8085
+ if (migrated) doc = documentRecord(document);
8079
8086
  let run = await readRunState(changeDir);
8080
8087
  if (!run && doc.run_id && doc.skill) {
8081
8088
  const { runStateFromDocument: runStateFromDocument2 } = await Promise.resolve().then(() => (init_state(), state_exports));
@@ -8083,13 +8090,15 @@ async function readClassicState(changeDir) {
8083
8090
  if (run) {
8084
8091
  await writeRunState(changeDir, run);
8085
8092
  stripLegacyRunFields(document);
8086
- const file = path4.join(changeDir, ".comet.yaml");
8087
- const temporary = path4.join(changeDir, `.comet.yaml.${randomUUID2()}.tmp`);
8088
- await fs4.writeFile(temporary, document.toString(), "utf8");
8089
- await fs4.rename(temporary, file);
8093
+ migrated = true;
8090
8094
  }
8091
8095
  }
8092
- return parseClassicStateDocument(doc, run);
8096
+ if (migrated) {
8097
+ const temporary = path4.join(changeDir, `.comet.yaml.${randomUUID2()}.tmp`);
8098
+ await fs4.writeFile(temporary, document.toString(), "utf8");
8099
+ await fs4.rename(temporary, file);
8100
+ }
8101
+ return parseClassicStateDocument(documentRecord(document), run);
8093
8102
  }
8094
8103
  async function readLegacyState(changeDir) {
8095
8104
  const document = await readDocument(path4.join(changeDir, ".comet.yaml"));
@@ -8980,6 +8989,230 @@ async function fileExists2(file) {
8980
8989
  throw error;
8981
8990
  }
8982
8991
  }
8992
+ function embeddedClassicRuntimePackage(root) {
8993
+ return {
8994
+ root,
8995
+ packageKind: "runtime",
8996
+ definition: {
8997
+ apiVersion: "comet/v1alpha1",
8998
+ kind: "Skill",
8999
+ metadata: {
9000
+ name: "comet-classic",
9001
+ version: "1",
9002
+ description: "Internal compatibility orchestration for classic Comet full, hotfix, and tweak workflows"
9003
+ },
9004
+ goal: {
9005
+ statement: "Advance or restore a classic Comet Run without changing the user command surface",
9006
+ inputs: [
9007
+ {
9008
+ name: "classic-state",
9009
+ description: "Validated ClassicState consistent with the Run projection",
9010
+ required: true
9011
+ },
9012
+ {
9013
+ name: "evidence",
9014
+ description: "Structured evidence produced by the Classic Evidence collector",
9015
+ required: true
9016
+ }
9017
+ ],
9018
+ outputs: [
9019
+ {
9020
+ name: "run-state",
9021
+ description: "Atomically synchronized Classic and Run state",
9022
+ required: true
9023
+ }
9024
+ ],
9025
+ success: [
9026
+ "Legacy fields and Run fields remain consistent",
9027
+ "Every step invokes only a declared public Comet Skill",
9028
+ "The completed state passes its completion eval"
9029
+ ]
9030
+ },
9031
+ orchestration: {
9032
+ mode: "deterministic",
9033
+ entry: "full.open",
9034
+ steps: [
9035
+ {
9036
+ id: "full.open",
9037
+ action: { type: "invoke_skill", ref: "comet-open" },
9038
+ next: "full.design.handoff"
9039
+ },
9040
+ {
9041
+ id: "full.design.handoff",
9042
+ action: { type: "invoke_skill", ref: "comet-design" },
9043
+ next: "full.design.document"
9044
+ },
9045
+ {
9046
+ id: "full.design.document",
9047
+ action: { type: "invoke_skill", ref: "comet-design" },
9048
+ next: "full.build.plan"
9049
+ },
9050
+ {
9051
+ id: "full.build.plan",
9052
+ action: { type: "invoke_skill", ref: "comet-build" },
9053
+ next: "full.build.plan-ready"
9054
+ },
9055
+ {
9056
+ id: "full.build.plan-ready",
9057
+ action: { type: "invoke_skill", ref: "comet-build" },
9058
+ next: "full.build.configure"
9059
+ },
9060
+ {
9061
+ id: "full.build.configure",
9062
+ action: { type: "invoke_skill", ref: "comet-build" },
9063
+ next: "full.build.execute"
9064
+ },
9065
+ {
9066
+ id: "full.build.execute",
9067
+ action: { type: "invoke_skill", ref: "comet-build" },
9068
+ next: "full.build.complete"
9069
+ },
9070
+ {
9071
+ id: "full.build.complete",
9072
+ action: { type: "invoke_skill", ref: "comet-build" },
9073
+ next: "full.verify.run"
9074
+ },
9075
+ {
9076
+ id: "full.build.fix",
9077
+ action: { type: "invoke_skill", ref: "comet-build" },
9078
+ next: "full.build.execute"
9079
+ },
9080
+ {
9081
+ id: "full.verify.run",
9082
+ action: { type: "invoke_skill", ref: "comet-verify" },
9083
+ next: "full.verify.branch"
9084
+ },
9085
+ {
9086
+ id: "full.verify.branch",
9087
+ action: { type: "invoke_skill", ref: "comet-verify" },
9088
+ next: "full.archive.confirm"
9089
+ },
9090
+ {
9091
+ id: "full.archive.confirm",
9092
+ action: { type: "invoke_skill", ref: "comet-archive" },
9093
+ next: "full.archive.execute"
9094
+ },
9095
+ {
9096
+ id: "full.archive.execute",
9097
+ action: { type: "invoke_skill", ref: "comet-archive" },
9098
+ next: "completed"
9099
+ },
9100
+ {
9101
+ id: "hotfix.open",
9102
+ action: { type: "invoke_skill", ref: "comet-hotfix" },
9103
+ next: "hotfix.build.execute"
9104
+ },
9105
+ {
9106
+ id: "hotfix.build.execute",
9107
+ action: { type: "invoke_skill", ref: "comet-build" },
9108
+ next: "hotfix.build.complete"
9109
+ },
9110
+ {
9111
+ id: "hotfix.build.complete",
9112
+ action: { type: "invoke_skill", ref: "comet-build" },
9113
+ next: "hotfix.verify.run"
9114
+ },
9115
+ {
9116
+ id: "hotfix.verify.run",
9117
+ action: { type: "invoke_skill", ref: "comet-verify" },
9118
+ next: "hotfix.verify.branch"
9119
+ },
9120
+ {
9121
+ id: "hotfix.verify.branch",
9122
+ action: { type: "invoke_skill", ref: "comet-verify" },
9123
+ next: "hotfix.archive.confirm"
9124
+ },
9125
+ {
9126
+ id: "hotfix.archive.confirm",
9127
+ action: { type: "invoke_skill", ref: "comet-archive" },
9128
+ next: "hotfix.archive.execute"
9129
+ },
9130
+ {
9131
+ id: "hotfix.archive.execute",
9132
+ action: { type: "invoke_skill", ref: "comet-archive" },
9133
+ next: "completed"
9134
+ },
9135
+ {
9136
+ id: "tweak.open",
9137
+ action: { type: "invoke_skill", ref: "comet-tweak" },
9138
+ next: "tweak.build.execute"
9139
+ },
9140
+ {
9141
+ id: "tweak.build.execute",
9142
+ action: { type: "invoke_skill", ref: "comet-build" },
9143
+ next: "tweak.build.complete"
9144
+ },
9145
+ {
9146
+ id: "tweak.build.complete",
9147
+ action: { type: "invoke_skill", ref: "comet-build" },
9148
+ next: "tweak.verify.run"
9149
+ },
9150
+ {
9151
+ id: "tweak.verify.run",
9152
+ action: { type: "invoke_skill", ref: "comet-verify" },
9153
+ next: "tweak.verify.branch"
9154
+ },
9155
+ {
9156
+ id: "tweak.verify.branch",
9157
+ action: { type: "invoke_skill", ref: "comet-verify" },
9158
+ next: "tweak.archive.confirm"
9159
+ },
9160
+ {
9161
+ id: "tweak.archive.confirm",
9162
+ action: { type: "invoke_skill", ref: "comet-archive" },
9163
+ next: "tweak.archive.execute"
9164
+ },
9165
+ {
9166
+ id: "tweak.archive.execute",
9167
+ action: { type: "invoke_skill", ref: "comet-archive" },
9168
+ next: "completed"
9169
+ },
9170
+ {
9171
+ id: "completed",
9172
+ action: { type: "checkpoint" },
9173
+ completionEvals: ["classic-completed"]
9174
+ }
9175
+ ]
9176
+ },
9177
+ skills: [
9178
+ { id: "comet-open" },
9179
+ { id: "comet-design" },
9180
+ { id: "comet-build" },
9181
+ { id: "comet-verify" },
9182
+ { id: "comet-archive" },
9183
+ { id: "comet-hotfix" },
9184
+ { id: "comet-tweak" }
9185
+ ],
9186
+ agents: [],
9187
+ tools: []
9188
+ },
9189
+ guardrails: {
9190
+ allowedSkills: [
9191
+ "comet-open",
9192
+ "comet-design",
9193
+ "comet-build",
9194
+ "comet-verify",
9195
+ "comet-archive",
9196
+ "comet-hotfix",
9197
+ "comet-tweak"
9198
+ ],
9199
+ allowedAgents: [],
9200
+ allowedTools: [],
9201
+ maxIterations: 500,
9202
+ maxRetriesPerAction: 3,
9203
+ confirmationRequiredFor: []
9204
+ },
9205
+ evals: [
9206
+ {
9207
+ id: "classic-completed",
9208
+ scope: "completion",
9209
+ type: "state_equals",
9210
+ field: "status",
9211
+ equals: "completed"
9212
+ }
9213
+ ]
9214
+ };
9215
+ }
8983
9216
  async function classicRuntimeRoot() {
8984
9217
  const runtimeDirectory = path10.dirname(fileURLToPath(import.meta.url));
8985
9218
  const candidates = [
@@ -8996,7 +9229,7 @@ async function classicRuntimeRoot() {
8996
9229
  for (const candidate of candidates) {
8997
9230
  if (await directoryExists(candidate)) return candidate;
8998
9231
  }
8999
- throw new Error("Comet classic runtime package is not installed");
9232
+ return null;
9000
9233
  }
9001
9234
  async function loadClassicRuntimePackage(root) {
9002
9235
  if (await fileExists2(path10.join(root, "skill.yaml"))) {
@@ -9007,15 +9240,14 @@ async function loadClassicRuntimePackage(root) {
9007
9240
  async function ensureClassicRuntimeRun(changeDir) {
9008
9241
  const root = await classicRuntimeRoot();
9009
9242
  return ensureClassicRun(changeDir, {
9010
- skillPackage: await loadClassicRuntimePackage(root)
9243
+ skillPackage: root ? await loadClassicRuntimePackage(root) : embeddedClassicRuntimePackage(path10.dirname(fileURLToPath(import.meta.url)))
9011
9244
  });
9012
9245
  }
9013
9246
  async function ensureStrictClassicRuntimeRun(changeDir) {
9014
9247
  const projection = await readClassicState(changeDir);
9015
- if (projection.unknownKeys.length > 0) {
9016
- throw new Error(
9017
- `Invalid Classic state: unknown field(s): ${projection.unknownKeys.join(", ")}`
9018
- );
9248
+ const unknownKeys = Array.from(new Set(projection.unknownKeys)).sort();
9249
+ if (unknownKeys.length > 0) {
9250
+ throw new Error(`Invalid Classic state: unknown field(s): ${unknownKeys.join(", ")}`);
9019
9251
  }
9020
9252
  return ensureClassicRuntimeRun(changeDir);
9021
9253
  }
@@ -9939,13 +10171,7 @@ async function readField(changeDir, field2) {
9939
10171
  async function projectConfigValue(field2, changeDir) {
9940
10172
  const changeValue = await readField(changeDir, field2);
9941
10173
  if (changeValue && changeValue !== "null") return changeValue;
9942
- for (const config of [
9943
- ".comet/config.yaml",
9944
- ".comet.yaml",
9945
- "comet.yaml",
9946
- ".comet.yml",
9947
- "comet.yml"
9948
- ]) {
10174
+ for (const config of [".comet/config.yaml"]) {
9949
10175
  if (!await exists4(config)) continue;
9950
10176
  for (const line of (await fs13.readFile(config, "utf8")).split(/\r?\n/u)) {
9951
10177
  if (new RegExp(`^${field2}:`, "u").test(line)) {
@@ -10001,68 +10227,6 @@ Next: regenerate or rewrite this artifact in English while preserving necessary
10001
10227
  }
10002
10228
  return pass();
10003
10229
  }
10004
- function runCommandString(command) {
10005
- if (!command) return { status: 1, output: red2("ERROR: build/verify command is empty") };
10006
- const split = splitCommandChain(command);
10007
- if (typeof split === "string") {
10008
- return {
10009
- status: 1,
10010
- output: `${red2(`ERROR: build/verify command contains shell metacharacters: ${command}`)}
10011
- ${red2(
10012
- split
10013
- )}`
10014
- };
10015
- }
10016
- const output = [];
10017
- for (const part of split) {
10018
- const segment = part.trim();
10019
- if (!segment) {
10020
- return { status: 1, output: red2("ERROR: build/verify command contains an empty && step") };
10021
- }
10022
- const result3 = spawnSync2(segment, { shell: true, encoding: "utf8", timeout: 3e5 });
10023
- const combined = `${result3.stdout ?? ""}${result3.stderr ?? ""}`.replace(/\n+$/u, "");
10024
- output.push(`${red2(`+ ${segment}`)}${combined ? `
10025
- ${combined}` : ""}`);
10026
- if (result3.status !== 0) {
10027
- return { status: result3.status ?? 1, output: output.join("\n") };
10028
- }
10029
- }
10030
- return { status: 0, output: output.join("\n") };
10031
- }
10032
- function splitCommandChain(command) {
10033
- const parts = [];
10034
- let current = "";
10035
- let quote = "";
10036
- for (let i = 0; i < command.length; i += 1) {
10037
- const c = command[i];
10038
- if (c === "$" || c === "`") {
10039
- return "Allowed: command words, quotes, paths, and && between sequential commands";
10040
- }
10041
- if (quote) {
10042
- current += c;
10043
- if (c === quote) quote = "";
10044
- continue;
10045
- }
10046
- if (c === '"' || c === "'") {
10047
- quote = c;
10048
- current += c;
10049
- continue;
10050
- }
10051
- if (c === "&" && command[i + 1] === "&") {
10052
- parts.push(current);
10053
- current = "";
10054
- i += 1;
10055
- continue;
10056
- }
10057
- if (c === ";" || c === "|" || c === "&") {
10058
- return "Allowed: command words, quotes, paths, and && between sequential commands";
10059
- }
10060
- current += c;
10061
- }
10062
- if (quote) return "Command has an unmatched quote";
10063
- parts.push(current);
10064
- return parts;
10065
- }
10066
10230
  function hashFile(file) {
10067
10231
  return createHash4("sha256").update(readFileSync(file)).digest("hex");
10068
10232
  }
@@ -10100,6 +10264,13 @@ async function preflight(changeDir, name) {
10100
10264
  `);
10101
10265
  throw new GuardFailure(red2("FATAL: .comet.yaml schema validation failed"));
10102
10266
  }
10267
+ const projection = await readClassicState(changeDir);
10268
+ const unknownKeys = Array.from(new Set(projection.unknownKeys)).sort();
10269
+ if (unknownKeys.length > 0) {
10270
+ throw new GuardFailure(
10271
+ red2(`FATAL: .comet.yaml has unknown field(s): ${unknownKeys.join(", ")}`)
10272
+ );
10273
+ }
10103
10274
  }
10104
10275
  function pushCheck(output, outcome) {
10105
10276
  if (outcome.passed) {
@@ -10144,6 +10315,24 @@ async function runChecks(output, builders) {
10144
10315
  }
10145
10316
  return blocked2;
10146
10317
  }
10318
+ async function removedProjectCommandField(field2) {
10319
+ const config = path14.join(".comet", "config.yaml");
10320
+ if (!await exists4(config)) return false;
10321
+ const document = (0, import_yaml4.parseDocument)(await fs13.readFile(config, "utf8"));
10322
+ if (document.errors.length > 0) {
10323
+ throw new Error(
10324
+ `.comet/config.yaml is invalid YAML (${document.errors[0].message}); cannot check for removed "${field2}" field. Fix the config and retry.`
10325
+ );
10326
+ }
10327
+ const value = document.toJS();
10328
+ return Boolean(value) && typeof value === "object" && !Array.isArray(value) && Object.prototype.hasOwnProperty.call(value, field2);
10329
+ }
10330
+ function removedProjectCommandRun(field2) {
10331
+ return {
10332
+ status: 1,
10333
+ output: `${field2} has been removed from .comet/config.yaml. Delete this field and run any required ${field2 === "build_command" ? "build" : "verification"} command manually before retrying.`
10334
+ };
10335
+ }
10147
10336
  function runInferred(command) {
10148
10337
  const result3 = spawnSync2(command, { shell: true, encoding: "utf8", timeout: 3e5 });
10149
10338
  return {
@@ -10151,10 +10340,11 @@ function runInferred(command) {
10151
10340
  output: `${result3.stdout ?? ""}${result3.stderr ?? ""}`.replace(/\n+$/u, "")
10152
10341
  };
10153
10342
  }
10154
- async function buildPasses(changeDir) {
10343
+ async function buildPasses() {
10155
10344
  if (process.env.COMET_SKIP_BUILD === "1") return { status: 0, output: "" };
10156
- const configured = await projectConfigValue("build_command", changeDir);
10157
- if (configured) return runCommandString(configured);
10345
+ if (await removedProjectCommandField("build_command")) {
10346
+ return removedProjectCommandRun("build_command");
10347
+ }
10158
10348
  if (await exists4("package.json") && /"build"/u.test(await fs13.readFile("package.json", "utf8"))) {
10159
10349
  return runInferred("npm run build");
10160
10350
  }
@@ -10169,11 +10359,12 @@ async function buildPasses(changeDir) {
10169
10359
  if (await exists4("Cargo.toml")) return runInferred("cargo build");
10170
10360
  return { status: 1, output: "" };
10171
10361
  }
10172
- async function verificationCommandPasses(changeDir) {
10362
+ async function verificationCommandPasses() {
10173
10363
  if (process.env.COMET_SKIP_BUILD === "1") return { status: 0, output: "" };
10174
- const configured = await projectConfigValue("verify_command", changeDir);
10175
- if (configured) return runCommandString(configured);
10176
- return buildPasses(changeDir);
10364
+ if (await removedProjectCommandField("verify_command")) {
10365
+ return removedProjectCommandRun("verify_command");
10366
+ }
10367
+ return buildPasses();
10177
10368
  }
10178
10369
  async function tasksAllDone(changeDir) {
10179
10370
  const tasks = path14.join(changeDir, "tasks.md");
@@ -10567,7 +10758,7 @@ async function guardBuildChecks(output, changeDir, change) {
10567
10758
  // Build check runs last — only after all config checks pass — to avoid
10568
10759
  // wasting time on a build that would be rejected by a config failure.
10569
10760
  check("Build passes", async () => {
10570
- const buildResult = await buildPasses(changeDir);
10761
+ const buildResult = await buildPasses();
10571
10762
  return buildResult.status === 0 ? pass() : fail(buildResult.output);
10572
10763
  })
10573
10764
  ]);
@@ -10578,7 +10769,7 @@ async function guardVerifyChecks(output, changeDir) {
10578
10769
  // Verification command runs after tasks check — no point running tests
10579
10770
  // if tasks.md is incomplete.
10580
10771
  check("Verification passes", async () => {
10581
- const verifyResult = await verificationCommandPasses(changeDir);
10772
+ const verifyResult = await verificationCommandPasses();
10582
10773
  return verifyResult.status === 0 ? pass() : fail(verifyResult.output);
10583
10774
  }),
10584
10775
  check(
@@ -11364,7 +11555,7 @@ function isRootMarkdown(relativePath2) {
11364
11555
  return !relativePath2.includes("/") && relativePath2.endsWith(".md");
11365
11556
  }
11366
11557
  function isCometConfig(relativePath2) {
11367
- return relativePath2 === ".comet.yaml" || relativePath2 === "comet.yaml" || relativePath2 === ".comet.yml" || relativePath2 === "comet.yml" || relativePath2.startsWith(".comet/") || relativePath2.includes("/.comet/");
11558
+ return relativePath2.startsWith(".comet/") || relativePath2.includes("/.comet/");
11368
11559
  }
11369
11560
  function isSuperpowersWorkspace(relativePath2) {
11370
11561
  return relativePath2 === ".superpowers" || relativePath2.startsWith(".superpowers/");
@@ -12110,8 +12301,6 @@ function sparseClassicState(record) {
12110
12301
  verifiedAt: nullableRecordString(record, "verified_at"),
12111
12302
  archived: nullableRecordBoolean(record, "archived") ?? false,
12112
12303
  directOverride: nullableRecordBoolean(record, "direct_override"),
12113
- buildCommand: nullableRecordString(record, "build_command"),
12114
- verifyCommand: nullableRecordString(record, "verify_command"),
12115
12304
  handoffContext: nullableRecordString(record, "handoff_context"),
12116
12305
  handoffHash: nullableRecordString(record, "handoff_hash"),
12117
12306
  classicProfile: enumRecordValue(record, "classic_profile", PROFILES, workflow),
@@ -12294,7 +12483,7 @@ async function init(output, name, workflow) {
12294
12483
  archived: false
12295
12484
  });
12296
12485
  await atomicWrite2(file, document.toString());
12297
- output.stderr.push(green4(`Initialized: ${label}/.comet.yaml (workflow=${workflow})`));
12486
+ output.stdout.push(green4(`Initialized: ${label}/.comet.yaml (workflow=${workflow})`));
12298
12487
  }
12299
12488
  async function requirePhase(name, expected) {
12300
12489
  const actual = await readField3(name, "phase");
@@ -284,15 +284,7 @@ Build is the longest phase and may span many tasks. To support resume after cont
284
284
  - Code review has been completed per the `executing-plans` review gate (Section "Execute plan") for the chosen `review_mode`: under `standard` or `thorough`, code review has been requested and CRITICAL review findings fixed or non-CRITICAL acceptance rationale recorded; under `review_mode: off`, the reason for skipping automatic code review has been recorded in a persistent artifact
285
285
  - **Phase guard**: Run `node "$COMET_GUARD" <change-name> build --apply`; after all PASS, state advances to `phase: verify`
286
286
 
287
- Guard reads project command configuration first:
288
-
289
- ```yaml
290
- build_command: <build command>
291
- verify_command: <verify command>
292
- ```
293
-
294
- Configuration can live in the change `.comet.yaml`, or in repo-root `.comet.yaml` / `comet.yaml` / `.comet.yml` / `comet.yml`.
295
- Configured commands use a restricted shell grammar: command words, quotes, paths, and `&&` for sequential steps are allowed; `;`, pipes, bare `&`, `$`, and backticks are rejected. 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.
287
+ Guard runs the inferred project build check (`npm run build`, Maven, or Cargo when detected). When the inferred command fails, guard prints the command output as evidence for debugging.
296
288
 
297
289
  Before exit, run guard to auto-transition:
298
290
 
@@ -2,7 +2,7 @@
2
2
 
3
3
  规范路径:`comet/reference/comet-yaml-fields.md`
4
4
 
5
- 本文件是 `.comet.yaml` 状态文件的字段参考。按需查阅,不随 skill 一次性加载。
5
+ 本文件是 `openspec/changes/<name>/` 下每个 change 级 `.comet.yaml` 状态文件的字段参考。按需查阅,不随 skill 一次性加载。项目级默认配置只放在 `.comet/config.yaml`。
6
6
 
7
7
  ## 示例
8
8
 
@@ -59,8 +59,6 @@ archived: false
59
59
  | 字段 | 含义 |
60
60
  |------|------|
61
61
  | `direct_override` | `true`/`false`。full workflow 如需使用 `build_mode: direct`,必须显式设为 `true` |
62
- | `build_command` | 项目构建命令。guard 优先运行;支持命令词、引号、路径和 `&&` 顺序步骤;拒绝 `;`、管道、裸 `&`、`$` 和反引号 |
63
- | `verify_command` | 项目验证命令。verify guard 优先运行;使用与 `build_command` 相同的受限命令语法,未配置时回退到构建命令 |
64
62
 
65
63
  ## 状态机硬约束
66
64