`openspec feedback` passed `--label feedback` unconditionally, but the
repository does not define that label. gh resolves label names before
creating the issue, so it failed with "could not add label: labels not
found: feedback" on every invocation and the command exited non-zero,
discarding the feedback the user had just composed.
Retry once without the label when — and only when — gh's stderr reports
that it could not add the label, and tell the user the label was not
applied. Every other failure keeps its existing behavior: print gh's error
and exit with gh's exit code, with no retry. Only stderr is matched,
because the error message also embeds the command line, which carries the
user's own feedback text.
The cli-feedback spec gains a scenario for the unlabeled path, and its
gh-failure scenario is narrowed to exclude it. The fallback scenarios are
unchanged.
Refs #1091
Co-authored-by: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
* fix(templates): wait for the spec sync before archiving a change
The generated openspec-archive-change skill dispatched the spec sync to a
subagent via the Task tool and then moved changeRoot in the very next step,
with nothing requiring it to wait. Where subagents run asynchronously, the
archive relocates the delta specs out from under the running sync, so the
change is archived while openspec/specs/ is never updated — and the success
summary still reports "Specs: ✓ Synced".
Step 4 now requires waiting for the dispatched sync to return, verifying the
synced requirements are present in the main spec, and stopping without
archiving if either check fails. Adds a matching guardrail bullet and a
parity assertion so the gate cannot silently disappear again.
Fixes#1393
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
* fix(templates): run the spec sync inline and verify it before archiving
Addresses review on #1394. The first pass asked the agent to "wait" for a
dispatched subagent, but subagents run in the background by default and the
wait is not reliably expressible in prose — the race survived. It also gated
the archive on the synced requirements being *present*, which a correct
REMOVED-only or RENAMED-only sync does not satisfy, turning a successful sync
into a hard block.
The sync now runs inline via the Skill tool, with a synchronous-subagent
fallback for harnesses that need one. Verification follows delta semantics:
ADDED/MODIFIED present, REMOVED gone, RENAMED under the new name, checked
across every capability the sync touched.
Also resolves the opsx command variant's contradiction with its own guardrail,
stops the summary reporting a checkmark that step 4 never verified, and updates
openspec/specs/opsx-archive-skill/spec.md, which still said the skill proceeds
with the archive regardless of the sync choice.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
* docs(specs): encode delta verification semantics in the archive skill spec
The scenario said the agent verifies each capability "matches its delta",
which is ambiguous about what a match means — and a REMOVED-only sync
correctly leaves requirements absent. Spell out the predicate the template
implements, and separate an explicit "Archive without syncing" choice from a
requested sync that failed or could not be verified: only the former may skip
verification, the latter must stop.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
* fix(templates): close verification holes and drop Claude-only tool names
Second review pass on #1394.
The gate was weaker than it looked. "MODIFIED requirements present" is
vacuous — a MODIFIED requirement exists in the main spec before the sync runs,
so a no-op sync passed the check for the most common delta shape, which is the
exact symptom #1393 reports. "RENAMED under their new name" passed a sync that
copied rather than renamed, leaving both names behind. And scoping the re-check
to "every capability it touched" derived the verification set from the artifact
being verified, so a silently skipped capability escaped it.
Verification is now bound to the delta specs in artifactPaths.specs, covers the
changes each MODIFIED delta names, and requires RENAMED requirements to be gone
from the old name.
Separately, the previous pass named the Claude Code "Skill tool" and
run_in_background in a template that is also the slash-command source for ~28
other tools, where skills are removed entirely for commands-only delivery. Both
variants now use the runtime-neutral phrasing bulk-archive-change already uses.
Also: route the prompt options explicitly instead of defaulting unknown answers
to archive, tell the user a stopped archive is recoverable, and mark the summary
line as a conditional rather than literal text to copy.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
* fix(templates): verify the sync by re-running step 4's own comparison
The verification predicate restated delta semantics in its own words, which
could drift from what openspec-sync-specs actually does. Anchor it instead to
the comparison step 4 already performs before prompting: a successful sync
leaves nothing to apply, so every capability must read as already synced. The
explicit ADDED/MODIFIED/REMOVED/RENAMED bullets stay as the definition of what
"nothing left to apply" means.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
---------
Co-authored-by: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
* fix(validate): reject a delta spec at the change's specs/ root (#1385)
A `spec.md` written directly under a change's `specs/` directory was
accepted by `validate` — including `--strict` — but skipped by the
apply/archive merge, which only reads capability folders. The change
validated clean, archived successfully, and its requirements never
reached `openspec/specs/`.
Point the validator at the shared `discoverSpecFiles` helper so it applies
exactly the merge path's rules, and report a root-level `specs/spec.md` as
an error naming the capability-folder convention. Archive's delta-detection
gate now also sees that file, so validation runs and blocks the archive
instead of completing with the delta dropped.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
* fix(archive): trip the delta gate on any root-level spec.md
Follow-up to the same divergence class as the parent commit, found while
re-reviewing it.
Archive's gate only ran validation when a candidate file carried delta
headers, so a root-level `specs/spec.md` written in main-spec shape
(`## Requirements`) still archived with exit 0 while `validate` reported an
error — the two commands disagreed again. The file is never merged whatever
its shape, so existence alone now trips the gate.
Also stop reporting a *directory* named `specs/spec.md` as misplaced: that
is an ordinary capability folder the merge path reads normally, and
`fileExists` matched it. Both sites now require a regular file.
Finally, suppress the generic "No deltas found" error when the root-level
error already fired: it contradicted the precise message by claiming there
were no deltas in the very file just named.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
---------
Co-authored-by: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
* feat(schema): resolve symlinked schema directories
Schema discovery filtered directory entries with `Dirent.isDirectory()`,
which reports the raw entry type and returns false for symlinks — even
those pointing at a real directory. As a result, symlinked schema dirs in
the user/project/package schema locations were silently skipped.
Add a shared `isSchemaDir()` helper that accepts real directories and
dereferences symlinks (via statSync) to admit symlinked directories while
still rejecting symlinks-to-files and broken links. Use it at all six
discovery sites in resolver.ts and in `schema validate` in schema.ts.
* test(schema): cover symlinked schema directory resolution
Add unit tests for isSchemaDir (real dir, symlink-to-dir, symlink-to-file,
broken symlink, regular file) plus integration tests confirming listSchemas
and listSchemasWithInfo pick up a symlinked user schema dir while ignoring
symlinks whose target is a file.
---------
Co-authored-by: Clay Good <hi@claygood.com>
findMissingCurrentScenarios stored incoming scenario names in a Set, so
when the current requirement had N scenarios sharing a name and a
MODIFIED block kept fewer, membership still looked covered and archive
silently dropped the extras. Count occurrences per name instead and
report each excess instance as missing, keeping the existing error shape.
Refs #1246 (residual after #1252). Analysis credit: @HerbertGao.
* fix(doctor): note when a store checkout is behind its upstream ref
Stores have no commit pin, so teammates on different commits of the same
store silently resolve different specs. Add a read-only info diagnostic
(store_checkout_drift) reporting ahead/behind counts against the local
upstream ref, and surface them in doctor --json. Fires only when behind
(ahead-only is normal — OpenSpec never pushes stores) and never fetches,
so it flags already-known drift, not a live cross-machine check.
Refs #1273
* refactor(doctor): parallelize git probes and share drift-test setup
Run the independent gitOriginUrl / gitTrackingDrift probes concurrently,
and extract the repeated git-bootstrap in the drift tests into a shared
initGitStore() helper. No behavior change.
* test(doctor): pass the isolated git env when capturing the branch name
Archiving unconditionally prepended today's date to the change name, so a
change already named with the common YYYY-MM-DD- convention came out
double-dated (2026-07-07-2026-07-04-voice-copilot-v1) — and when archived
on a later day, the folder sorted under a day on which the change did not
happen.
Detect a full YYYY-MM-DD- prefix and archive the change under its own name.
Names without one (including partial dates like 2026-07-feature) keep the
current behavior. This also makes the naming idempotent.
Nothing in src/ parses the date back out of archive folder names — the
prefix only drives human chronological sorting — so keeping the original
date is the minimal, non-breaking choice. The cli-archive spec wording is
updated to match.
Fixes#1309
The early-sync idempotency fix (#1376) covered only ADDED requirements.
A change whose RENAMED deltas were already applied to the baseline by
the sync workflow still aborted archive with 'RENAMED failed - source
not found'.
RENAMED now skips when the source header is gone but the target header
exists in the spec — the target's presence is positive evidence the
rename was already applied. A rename whose source and target are both
missing still aborts, as does every other genuine conflict. Reported
counts now reflect only renames actually applied.
REMOVED is intentionally left strict: validation does not compare
REMOVED names against the baseline, so treating a missing requirement
as a no-op would let a typo'd or stale name archive silently.
* fix(skills): use skill references in skills-only delivery mode
When delivery is configured as 'skills', generated SKILL.md files
contained hardcoded /opsx:* command references pointing to commands
that were never generated, breaking cross-skill workflows.
Add transformToSkillReferences() with the explicit command-to-skill
mapping (kept in sync with WORKFLOW_TO_SKILL_DIR) and wire it in
init and update wherever skill content is generated, following the
approach outlined in #881.
Closes#881Closes#879
Generated with Claude (Cowork) using claude-fable-5; verified with the
full vitest suite (1683 tests passing).
* fix(skills): wire skill references in workspace skill generation
Review follow-up for the skills-only delivery fix: workspace skill
setup (src/core/workspace/skills.ts) generates SKILL.md via the same
generateSkillContent path but was not wired with
transformToSkillReferences, leaving dangling /opsx:* references when
delivery is 'skills'. Wire both call sites, add a regression test
(verified to fail against the unwired code), strengthen the update
skills-only test with content assertions, and correct the
COMMAND_TO_SKILL_REFERENCE comment (WORKFLOW_TO_SKILL_DIR exists in
both profile-sync-drift.ts and init.ts).
Generated with Claude (Cowork) using claude-fable-5; verified with
eslint and targeted vitest suites (144 tests passing).
* refactor(skills): extract transformer selection into getTransformerForTool
Address CodeRabbit review: the tool/delivery transformer selection was
duplicated at five call sites across init.ts, update.ts, and
workspace/skills.ts. Extract it into a documented helper in
command-references.ts with unit tests locking the selection matrix
(opencode/pi precedence, skills-only delivery, default).
Generated with Claude (Cowork) using claude-fable-5; verified with
eslint, tsc, and targeted vitest suites (147 tests passing).
* fix(skills): prioritize skill references over hyphen commands in skills-only delivery
Address review: getTransformerForTool returned transformToHyphenCommands
for opencode/pi before checking delivery, so skills-only delivery still
emitted /opsx-* references to commands that were never generated. Check
delivery === 'skills' first so skill references win for every tool, and
keep the hyphen transform for opencode/pi only when commands are
generated. Add unit coverage for the opencode/pi selection matrix and an
opencode skills-only init integration test asserting no /opsx: or /opsx-
references remain.
Generated with Claude (Cowork) using claude-fable-5; verified with
eslint, tsc, and targeted vitest suites (148 tests passing).
* docs(skills): add docstrings to init/update generation entry points
* test(skills): reject stale hyphenated references in skills-only assertions
* fix(skills): add missing update entry to command-to-skill reference map
COMMAND_TO_SKILL_REFERENCE was missing the update workflow, so a
/opsx:update reference in any skill template would survive skills-only
transformation untouched. Align the map with the canonical 12-entry
WORKFLOW_TO_SKILL_DIR and assert the generated openspec-update-change
skill carries no raw command references.
* feat(skills): publish workflow skills to skills.sh
Commit the 12 OpenSpec workflow skills as static skills/<name>/SKILL.md so
`npx skills add Fission-AI/OpenSpec` can install them (skills.sh reads static
files from the repo; OpenSpec otherwise only generates skills at init time).
Files are generated from the existing templates via `pnpm generate:skills`,
not hand-copied, and skillssh-parity.test.ts fails CI if a template changes
without regenerating. The volatile generatedBy frontmatter line is stripped so
the committed copies stay byte-stable across releases.
Closes#1258
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
* fix(skills): force LF on committed skills/ so Windows CI parity holds
The skills.sh distribution files are generated LF-only and compared
byte-for-byte by skillssh-parity.test.ts. Windows autocrlf checked them
out as CRLF, failing the parity assertion. A scoped .gitattributes pins
them to LF on checkout.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
* fix(skills): reject symlinks and assert the exact committed skill set
Review feedback (alfred): the parity test only visited expected templates,
so an extra or renamed skills/ directory shipped with green CI, and the
generator would write through a pre-existing symlinked skill directory to
anywhere on disk.
- generator: refuse to run if skills/ contains any symlink (checked before
any deletion, so a bad tree is left intact), validate dirNames against a
path-segment allowlist, and lstat the target before writing.
- parity test: assert skills/ holds exactly README.md plus one real
directory per template, each containing a single real SKILL.md.
- focused tests cover symlink refusal (no partial deletion), traversal
names, and stale-directory cleanup.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
* fix(templates): abort archive on Cancel, honest summary, fence languages
CodeRabbit review on #1357, fixed at the template source and regenerated:
- archive-change: choosing "Cancel" at the sync prompt now stops the flow
instead of archiving anyway (skill + command templates).
- archive-change skill: the success output no longer hardcodes "All
artifacts complete. All tasks complete." when archiving incomplete work.
- archive/bulk-archive/sync-specs/verify-change: language identifiers on
previously plain code fences (MD040), skill and command twins alike.
Golden hashes in skill-templates-parity.test.ts recomputed from dist/;
skills/ regenerated via pnpm generate:skills.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
---------
Co-authored-by: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
* feat/add codeartsagent to tool list
* feat/add codeartsagent to tool list
* test: clarify CodeArts init log assertions
* docs(cli): union hermes and zcode into the supported tool-ID list
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
---------
Co-authored-by: Clay Good <hi@claygood.com>
Co-authored-by: Claude Fable 5 <noreply@anthropic.com>
* fix(qwen): generate Markdown commands instead of deprecated TOML format
Qwen Code deprecated TOML custom commands in favor of Markdown files
with YAML frontmatter. Update the Qwen adapter to emit
.qwen/commands/opsx-<id>.md and register the old opsx-*.toml files as
legacy artifacts so they are cleaned up on update.
Closes#838
Generated with Claude (Cowork) using claude-fable-5; tested with the
full vitest suite (1663 tests passing).
* test(qwen): assert YAML frontmatter for qwen in registry adapter test
* feat(zcode): add ZCode as supported tool
Register ZCode in the AI tools registry and provide a command adapter
so `openspec init --tools zcode` generates per-project artifacts under
a single .zcode/ root (no split across .agents + .zcode):
- Skills: .zcode/skills/openspec-*/SKILL.md (ZCode-native discovery path,
highest priority among project-level skill roots)
- Commands: .zcode/commands/opsx/<id>.md (Claude-compatible frontmatter)
Both .zcode/skills and .agents/skills are valid ZCode discovery roots
(verified from ZCode source: skillRootsForBase registers them in pairs);
we use .zcode to keep all artifacts under one directory.
ZCode auto-detection triggers on .zcode or .agents at the project root.
Verification:
- pnpm build passes (TypeScript compiles clean)
- pnpm lint passes (no new warnings)
- pnpm test: 1661 tests pass (no regressions)
- E2E: `openspec init --tools zcode --profile core` produces
5 skills + 5 commands, all under .zcode/ (no .agents created)
* fix(zcode): scope auto-detection to .zcode only
ZCode's detectionPaths included '.agents', a generic directory used by
many agent frameworks. A bare '.agents' at the project root caused
false-positive ZCode detection (mirroring the Copilot bare-.github
problem the codebase already guards against).
Drop the detectionPaths override so ZCode is detected solely via its
strongly-identifying skillsDir '.zcode'. Add tests locking the new
contract: a bare '.agents' must not trigger detection, and '.agents'
co-located with '.zcode' must not suppress real detection.
* test(zcode): lock adapter path and frontmatter escaping contract
Add focused coverage for the ZCode command adapter that the existing
broad tests did not protect:
- getFilePath lands under .zcode/commands/opsx/<id>.md and never
references .agents
- formatFile emits name/description/category/tags frontmatter
- YAML escaping across all branches: colons/quotes/newlines (quoted
values), special chars in name/category, per-tag quoting, plus the
previously uncovered backslash-doubling and leading/trailing
whitespace branches
* test(zcode): lock command adapter registry presence
Verify the ZCode adapter is registered in CommandAdapterRegistry so
openspec init/update can resolve it via get/getAll/has. The existing
registry tests only sampled a few tools, so a future refactor that
drops the zcode registration would have passed silently.
* test(zcode): lock init/update generation stays under .zcode
End-to-end coverage that init and update generate ZCode skills and
commands under .zcode/ and never create a .agents directory. The
adapter path/detection unit tests alone cannot catch a generation-time
regression that writes outside .zcode, so this asserts the contract on
disk for both entry points.
---------
Co-authored-by: young <young@example.com>
Co-authored-by: Clay Good <hi@claygood.com>
Co-authored-by: Claude Fable 5 <noreply@anthropic.com>
* feat: add Hermes Agent support
* feat(init): surface Hermes external_dirs setup note during init and update
Hermes only loads skills from ~/.hermes/skills unless the project
.hermes/skills directory is added to skills.external_dirs in
~/.hermes/config.yaml, so init could report success for skills Hermes
ignores. Add a setupNote field to AIToolOption, print it after init and
update (including the up-to-date path), and cover the adapterless init
path, the adapter registry, and both update paths with tests.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
---------
Co-authored-by: Clay Good <hi@claygood.com>
Co-authored-by: Claude Fable 5 <noreply@anthropic.com>
* update Kimi CLI to Kimi Code
* feat(migration): migrate OpenSpec skills from legacy .kimi to .kimi-code
Renaming the Kimi skillsDir stranded OpenSpec-managed skills under
.kimi/skills: update and cleanup only inspect current AI_TOOLS paths, so
old installs would never be detected or refreshed again. Add a legacy
skillsDir migration (run by init and update before tool detection) that
moves openspec-* skill directories to .kimi-code/skills, preserves user
files, and removes the legacy directories only when empty. Keep .kimi as
a detection path and cover the migration with focused init and update
tests.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
* docs(specs): update cli-init Kimi scenario to .kimi-code
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
---------
Co-authored-by: Clay Good <hi@claygood.com>
Co-authored-by: Claude Fable 5 <noreply@anthropic.com>
* fix(update): warn when a custom profile is missing core workflows
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
* fix(update): use singular pronoun when one core workflow is missing
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
---------
Co-authored-by: Claude Fable 5 <noreply@anthropic.com>
* feat(stores): add global defaultStore fallback for root resolution
Adds a machine-level `defaultStore` to the global config. When no --store
flag, local planning root, or project-level `store:` pointer resolves, root
resolution now consults `defaultStore` before erroring — so users who plan
many code repos into one store can set it once instead of editing every
repo's openspec/config.yaml.
Purely additive: existing precedence (--store > local root > project pointer)
is unchanged; the fallback only replaces the failure path. A stale or
unregistered defaultStore degrades to the existing error, reshaped to point
at clearing the global default.
Closes#1359
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
* feat(stores): report distinct global_default root provenance
The machine-level defaultStore fallback resolved with source
'declared', so status, context, and doctor JSON could not tell a
global default from a repo's store: pointer (review feedback).
Add 'global_default' to OpenSpecRootSource, resolve the fallback
with it, and cover the status, context, and doctor JSON surfaces
plus the agent contract and store docs. Add the missing changeset.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
---------
Co-authored-by: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
* fix(archive): treat already-synced ADDED requirements as a no-op
Archiving a change whose specs were synced to the baseline first (the
early-sync pattern from the sync workflow) failed with 'ADDED failed -
already exists'. An ADDED requirement that already exists in the target
spec with identical content is now skipped; differing content still
aborts as a genuine conflict. Fixes#1332.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
* Restore pnpm-lock.yaml from main (accidental v6 rewrite during merge)
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
---------
Co-authored-by: Claude Fable 5 <noreply@anthropic.com>
* fix(specs): discover nested spec paths recursively across parse, apply, and archive
Delta discovery previously read only specs/<name>/spec.md one directory
level below a change's specs root, so nested layouts like
specs/<area>/<capability>/spec.md were silently skipped: show reported
deltaCount 0, and archive/apply completed without merging the delta into
the main specs directory. Main-spec discovery had the same one-level
assumption, so nested capabilities were also invisible to list, show,
view, and validate.
Introduce a shared recursive discoverSpecFiles() helper and use it in the
change parser, findSpecUpdates (apply/sync/archive), archive's delta
detection, and main-spec discovery (item-discovery, list, spec list,
view). Capability ids are the directory path relative to the specs root,
forward-slash separated on every platform, and apply/archive preserve the
relative path when writing the target spec. Symlinks are not followed and
dot-directories are skipped.
Fixes#1353
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
* fix(specs): surface non-ENOENT errors in spec discovery
discoverSpecFiles swallowed every readdir error, so an unreadable
(EACCES/EIO) capability directory silently vanished from validate/show/
archive/apply — recreating the data-loss class #1353 is closing, now on
the merge path. Suppress only the expected missing-root ENOENT and rethrow
everything else. Adds regression tests for non-ENOENT (ENOTDIR + guarded
EACCES) and the documented symlink-not-followed behavior.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
* fix(specs): sort discovered specs by code point, not locale
localeCompare follows the process's ICU locale, so ordering could vary by
OS/CI. Code-point comparison guarantees the deterministic output the
docstring promises.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
---------
Co-authored-by: Claude Fable 5 <noreply@anthropic.com>
The continue/propose/ff workflow instructions said to "read dependency
files for context," but an agent that already saw those files earlier in
the session treats them as read and regenerates downstream artifacts
from its stale in-context copy. Editing spec.md and deleting
design.md/tasks.md to regenerate them silently produced artifacts based
on the pre-edit spec.
The step guidance, the guardrails, and the `openspec instructions`
dependency block now say explicitly: re-read dependency files from disk
even if seen earlier in the conversation, because the user may have
edited them.
Discussion: https://github.com/Fission-AI/OpenSpec/discussions/909
Co-authored-by: Claude Fable 5 <noreply@anthropic.com>
The sync-specs and archive-change workflow instructions hardcoded
`openspec/specs/<capability>/spec.md` for main specs, assuming they always
live in the current repository. With `--store <id>` the change and its main
specs belong to the selected store, so sync could write the repo's specs
instead of the store's, and archive could report specs as already synced
based on the wrong location.
Derive the main-spec path from the store-aware `planningHome.root` the CLI
already returns, and stop labeling CLI-returned delta paths "repo-local"
since they may belong to a store. Repo-local behavior is unchanged.
Fixes#1358
Co-authored-by: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
The design instruction told agents to end design.md with an Open Questions
section but never said what to do with those questions, so blocking decisions
flowed silently into tasks and implementation. Now the design instruction
scopes the section to safely deferrable unknowns and tells the agent to ask
the user about anything that would change the specs, approach, or tasks; the
tasks instruction adds the matching check before writing the task list.
Discussion: https://github.com/Fission-AI/OpenSpec/discussions/1296
Co-authored-by: Claude Fable 5 <noreply@anthropic.com>
* fix(parser): ignore fenced code blocks when parsing delta specs
Requirement headers, delta section headers, scenarios and REMOVED/RENAMED
entries written inside fenced code blocks were parsed as real content by
the delta-spec parser. A fenced `### Requirement:` example became a phantom
requirement, producing spurious `validate` errors and risking incorrect
`archive` output.
Fence detection was duplicated across MarkdownParser and spec-structure but
missing entirely from requirement-blocks (which powers both validate and
archive). Extract a single shared `buildCodeFenceMask` helper and make the
delta-spec parser and validator block helpers honor it, so all parsers
treat fenced code consistently.
Co-authored-by: Cursor <cursoragent@cursor.com>
* test(validator): cover fenced-only scenario headers in delta specs
Add a regression test asserting that a `#### Scenario:` appearing only
inside a fenced code block does not count toward the required scenario
count, so the validator still reports the missing-scenario error.
This guards the fence awareness of `countScenarios()`: the existing
fenced-example test always includes a real (unfenced) scenario, so it
would not catch a regression that began counting fenced scenario
headers. Addresses the review suggestion on PR #1151.
Co-authored-by: Cursor <cursoragent@cursor.com>
---------
Co-authored-by: Cursor <cursoragent@cursor.com>
The global rules: map is validated against only the current change's schema,
so a key valid for a different schema prints a spurious "Unknown artifact ID"
warning on every command in multi-schema projects. Validate against the union
of artifact IDs across all available schemas; warn only when a key matches no
schema.
Fixes#1322.
Co-authored-by: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
* fix(cli): let --change find change names that exist on disk
The --change flag on status and instructions validated names with the
creation-time kebab-case rule, so digit-leading names (e.g. the
date-prefixed convention 2026-07-04-voice-copilot-v1) were rejected at
parse time even though list, validate, and archive all handle them.
Lookup now only guards against unsafe directory names (path separators,
relative segments, null bytes, hidden entries) and otherwise accepts
whatever getAvailableChanges could return. Creating a change still
enforces kebab-case via validateChangeName.
Fixes#1308
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
* fix(cli): reject the reserved 'archive' name in --change lookup
Matches the getAvailableChanges filter so --change archive can't address
the archive directory as if it were a change (CodeRabbit review).
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
---------
Co-authored-by: Claude Fable 5 <noreply@anthropic.com>
* fix(completion): detect the interactive shell from the parent process
`openspec completion install` read only $SHELL, the login shell, so users
whose interactive shell differs (e.g. fish users on distros where the login
shell is bash) got bash completions installed by default (#1197).
Detection now consults the parent process via `ps` before falling back to
$SHELL. It only trusts a parent that maps to a supported shell, so npx/npm
and other non-shell parents still fall back cleanly; Windows is unaffected.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
* fix(completion): match shell basename exactly and pin platform in parent-process tests
Two fixes from CI and review on #1364:
- matchSupportedShell now matches the executable basename exactly
(stripping a login-shell leading dash) instead of substring matching,
so parents like fish-lsp or bash-language-server no longer get
mistaken for the shell (CodeRabbit review).
- The parent-process tests pin process.platform to linux so they
exercise the ps path on Windows CI, where detection otherwise
short-circuits and the tests failed.
Adds regression tests for -zsh login shells and fish-lsp fallback.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
---------
Co-authored-by: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
An empty switch body is a parse error in PowerShell, and the generator
emitted one for every command whose positionals are all path-typed
(18 in the current registry). PowerShell parses the entire file before
execution, so the whole completion script failed to load. Skip the
positional-index block when no positional produces completions.
Fixes#1293
Co-authored-by: Claude Fable 5 <noreply@anthropic.com>
Answers discussion #1024: the README walkthrough showed the workflow
creating specs/ but never the content of a spec. Add a collapsible
example spec delta right after the transcript, plus links to this
repo's own live openspec/specs and openspec/changes as real examples.
Co-authored-by: Claude Fable 5 <noreply@anthropic.com>
* feat: add Oh My Pi (OMP) tool support
Add ToolCommandAdapter for Oh My Pi terminal AI coding agent.
- New adapter: src/core/command-generation/adapters/oh-my-pi.ts
- Commands: .omp/commands/opsx-<id>.md with description frontmatter
- Hyphen transform: /opsx: -> /opsx- (filename = command name)
- Argument injection: **Provided arguments**: $@ after **Input**: heading
- escapeYamlValue applied to description field
- Register in CommandAdapterRegistry and adapters/index.ts
- Add oh-my-pi to AI_TOOLS with skillsDir: '.omp'
- Add to hyphen command transformer whitelist in init.ts and update.ts
- Full test coverage (10 cases) in adapters.test.ts
- Update docs/supported-tools.md with directory reference and tool ID
Closes#713
* fix: address CodeRabbit nitpicks
- Move ohMyPiAdapter import before opencodeAdapter (alphabetical order)
- Break long SHALL sentence and remove redundant 'follows after' in spec
* docs: polish Oh My Pi support
* docs: address Oh My Pi review nits
---------
Co-authored-by: TabishB <tabishbidiwale@gmail.com>
Co-authored-by: Tabish Bidiwale <30385142+TabishB@users.noreply.github.com>
* feat(skills): auto-approve the openspec CLI in generated skills
Emit `allowed-tools: Bash(openspec:*)` in every generated SKILL.md so
agents that honor the Agent Skills standard run `openspec` commands
without prompting on each call. Scope is limited to the CLI; per the
standard `allowed-tools` pre-approves rather than restricts, so every
other tool a skill uses stays available under the user's normal
permission settings.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
* feat(commands): auto-approve the openspec CLI in Claude slash commands
Extend the allowed-tools pre-approval to the second surface: Claude Code
/opsx:* slash commands share the skill frontmatter contract, so the
Claude command adapter now emits `allowed-tools: Bash(openspec:*)` too.
The value is single-sourced in `src/core/shared/allowed-tools.ts` (a
leaf module both surfaces import). Other command adapters are unchanged
— no other tool's slash-command format defines a per-command
pre-approval field; on the skills side every tool already gets the
standard field via generateSkillContent and non-implementing tools
ignore the unknown key.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
---------
Co-authored-by: Claude Opus 4.8 (1M context) <noreply@anthropic.com>