feat(cli): wrap config, completion, and feedback - #35
Merged
Conversation
Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
…k tests The contract suite's own probe shows a trailing `--no-color` is ACCEPTED on every `openspec config` subcommand (commander resolves a program-level option from a leaf), but proposal.md, design.md, verification.md row 1.5, the delta spec, and tasks.md 7.6 all still asserted a rejection — and tasks.md 7.6 seeded a follow-up `fix` change on that falsified premise. Correct every artefact to the observed behaviour, probe the flag without a leading `--no-color` in the argv (so the probe is about the trailing copy alone), and add a row proving `schemas`/`templates` accept it too, which settles the supposed hazard on the shared passthrough helper instead of handing it on. Also fix two tests that asserted less than they claimed: - pack-standalone ran `cospec feedback --help`, which `cli.ts` answers from the static COMMANDS table before it ever looks up COMMAND_MODULES — it passes even when the module was dropped from the compiled binary, the exact regression the row exists to catch. Run the command itself with `gh` stripped from PATH (manual-submission fallback, exit 0, `submitted: false`). - The `--upstream` gh-failure row only asserted a non-0/non-1 exit; upstream does `process.exit(error.status ?? 1)`, so assert the exact code (42).
Adds cospec config's machine-global section (call classes, --json envelope shapes per subcommand, --store refusal, defaultStore cross-link) to reference/configuration.md, three command rows plus the config exceptions note to reference/commands.md, per-shell completion snippets to guide/installation.md, the config passthrough exceptions and the named terminal-handover class to docs/architecture.md, and the config/completion/ feedback mention to how-it-relates-to-openspec.md, README.md, and apps/cli/README.md. Updates .agents/shared.md's route-through-cospec bullet and re-syncs CLAUDE.md/AGENTS.md via mise run agents:sync. Checks off tasks.md 7.1-7.5 and verification.md 8.1-8.2 with observed mise run docs:build / agents:sync / agents:check results; 8.3 stays for the human manual-read row.
Re-ran every named command on the final branch state and recorded the observed result on each verification row: unit (732 pass), contract (50 pass), integration (158 pass), pack (2 pass), pack:standalone (2 pass), docs:build, generate:check, agents:check, and direct observations for the rows no shipped test covers (defaultStore precedence end to end, EDITOR/ VISUAL unset relay, config profile --json refusal, the profile preset note, and the --store/no-subcommand refusals). Four rows are deferred with reasons: 2.4 and 4.4 (need a real TTY), 6.5 (would file a real public issue), and 7.3 (@eval, no DEEPSEEK_API_KEY). Row 4.2 records that its fish leg did not run — fish is installed neither here nor in CI. Three claims are corrected to what shipped rather than checked off as written: 1.4 (upstream prints nothing on a config get miss, not a message), 7.1 (the standalone bundling probe is feedback --json, not --help, and lives in test:pack:standalone), and 7.4 (mise run check has two non-green steps, both pre-existing on main, both outside CI's gate). tasks.md 1.2 and design.md are corrected likewise: openspec schemas does emit --json, so the omission of a schemas completion source rests on there being no cospec slot to fill, not on fragility. Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
cospec archive validated the change, passed both hard gates (archive/verification-incomplete, archive/scenario-preservation) with no --force, delegated to openspec archive, verified the move on disk, and fanned blocker sync out. Specs: +11 ~1 -0, all applied and verified. Three new capability specs land (openspec-config-passthrough, cospec-shell-completion, cospec-feedback) and openspec-read-passthroughs gains the terminal-handover class shared by workset open and config edit|profile|reset. Each new spec gets a real ## Purpose in place of the archive-generated placeholder, which the specs/purpose-tbd rule correctly refused to let through. Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
replygirl
force-pushed
the
worktree-openspec-passthroughs
branch
from
September 8, 2026 19:47
f2c9802 to
953319e
Compare
replygirl
added a commit
that referenced
this pull request
Sep 9, 2026
oxfmt column-aligns the merged config/completion/feedback rows (from #35) with the init/update/doctor rows (agents harness additions from this branch); the hand-resolved conflict left them misaligned. Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
replygirl
added a commit
that referenced
this pull request
Sep 9, 2026
…/skills (#36) * feat(harness): add the shared .agents/skills root and an agents target Introduce a body-dialect layer in the harness renderer so a skill body can be respelled per target, and move the `codex` target's skills from `.codex/skills/` to the vendor-neutral `.agents/skills/` root. Add a fourth harness, `agents`, which writes that root byte-identically to `codex` (`codex` differs only by also emitting `.codex/rules/cospec.rules`), so `--harness all` now means four targets. Two harnesses mapping one path to different bytes is a hard render error instead of last-writer-wins. `init` and `update` migrate an existing `.codex/skills` install: files whose contents still hash-match the generated output are removed, hand-edited copies are left in place until `--force`, and `.codex/` itself is never removed. Both commands report the migration in `--json` under a top-level `migration` key; `update --check` counts a remaining legacy layout as drift and `doctor` reports it as the `legacy-layout` warning. Regenerate this repo's own skills into `.agents/skills/` and move the formatter exemption with them — the shared-dialect bodies exceed the 80-col markdown proseWrap, so without the `.prettierignore` move `format:fix` and `generate:check` become mutually unsatisfiable. Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com> * test(harness): cover the shared dialect, codex/agents byte-identity, and migration Retarget the adapter tests at `transformBody(body, dialect, skillById)` and assert an unknown skill id is left verbatim so doctor still flags a real dangling reference. Add render coverage for the `agents` file set, the byte-identity of the `codex` and `agents` roots (contentHash included), the first-selected-harness ownership rule for a shared path, and a real dedupe guard that flips a copied canon's dialect and asserts the render throws. Extend the dangling-ref sweep to resolve a slash token as a workflow id or a `cospec-<suffix>` skill name, mirroring doctor's two-way resolution. New suite for the legacy `.codex/skills` migration. Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com> * docs: document the shared .agents/skills root and the agents harness target Record the new `--harness agents` value and the four-target meaning of `all`, the byte-identity of the `codex` and `agents` roots, the `$cospec-<skill>` / `/cospec-<skill>` invocation spelling keyed on the skill directory name rather than the workflow id, `.agents/skills` (not a bare `.agents/`) as the auto-detection key, the `.codex/skills` migration and its `--force` behaviour, and doctor's `legacy-layout` warning counting as drift. Remove the now-false claim in docs/harness-integration.md that cospec never writes under `.agents/`, and re-sync the shared agent-doc block. Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com> * fix(harness): stop a codex-only install detecting as codex+agents `codex` and `agents` render the same `.agents/skills` tree, so the tree alone cannot say which target was selected. Detection had a marker guard for `codex` (`.codex/rules/cospec.rules`) but none for `agents`, so every codex repo reported `agents` too the moment it migrated off `.codex/skills` — inventing a target the user never chose in the one place that records what they opted into. A marker-less harness is now reported only when no detected marker-bearing harness already accounts for the same skill base. A repo that really selected both re-generates as `codex`, which writes a strict byte-for-byte superset, so nothing is lost. Tests: detectHarnesses across legacy-only, migrated, agents-only, both, four-target and bare-`.agents/` trees, each proved a detect->generate-> detect fixpoint. Doctor gains coverage for `legacy-layout`, the shared dialect's two-way skill/workflow reference resolution, and the deduped `.agents` / `.agents/skills` walk (all three fail under mutation). Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com> * docs: mention the shared .agents/skills root in the CLI package README Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com> * feat(harness): author harness-agents-target change Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com> * feat(harness): archive harness-agents-target Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com> * fix(hooks): exclude the generated .agents root from oxfmt batches .prettierignore already lists .agents/skills/cospec-*/ as generator-owned (the shared vendor-neutral skills root the codex/agents targets write), but hk's oxfmt step only excluded .claude/**, .codex/**, and .opencode/** from its file list. Any push or commit touching those SKILL.md files handed oxfmt a batch of nothing but ignore-excluded files, which it reports as `Expected at least one target file` and exits non-zero — the same latent-hook class fixed for the vendored openspec bundle in 206d965. Add .agents/skills/** (not the broader .agents/**, since .agents/shared.md is hand-authored and must stay in oxfmt's check) to generatedOutputGlobs. Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com> * style: reformat commands.md table after rebase conflict resolution oxfmt column-aligns the merged config/completion/feedback rows (from #35) with the init/update/doctor rows (agents harness additions from this branch); the hand-resolved conflict left them misaligned. Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com> --------- Co-authored-by: Claude Fable 5.1 <noreply@anthropic.com>
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Why
openspec-1-11-parityclosed every everyday OpenSpec surface except three, andnamed them in its own Non-Goals:
config,completion, andfeedback. Untilthey are wrapped, the standing rule — never call bare
openspec— isunenforceable in practice: a user who wants to inspect the machine-global
config, install shell completion, or file a bug has no cospec command to reach
for.
The gap is not only cosmetic. cospec's
WRAPPED_ENVforcesOPENSPEC_TELEMETRY=0andOPENSPEC_NO_COMPLETIONS=1on every wrapped call,and cospec generates its harness files from cospec canon rather than from
OpenSpec's
profile/workflows/deliverykeys — so several global config keysare genuinely misleading when read through a cospec repo, and nothing told the
user so.
defaultStoreis a key cospec already reads during root resolutionbut offered no way to set.
Each surface got its own decision rather than one blanket treatment, because
each one fails differently.
config— a disciplined passthrough that deliberately skips the shared helpercospec config <path|list|get|set|unset|reset|edit|profile>wrapsopenspec config, and it is the one passthrough command that does not routethrough
core/passthrough-command.ts. Every one of that helper's threeunconditional appends is wrong here:
--storeis not aconfigoption at all — upstream declares a parent-level--scope, and OpenSpec config is machine-global, soresolveRootandroot.storeArgsnever apply.cospec config --store <id>is refused withexit 1 and a named message rather than absorbed and ignored.
--jsonexists onlistalone.--no-colorturned out to be accepted (commander resolves theprogram-level flag from a leaf), so it is merely redundant — cospec's spawn
already prefixes one, so
confignever appends a second.Adding three opt-outs to a helper that serves eight happy callers would make it
harder to reason about, so
commands/config.tsgets a local, pure argv builder,the way
commands/workset.tsalready does.--scopeis hoisted to sit betweenconfigand the subcommand, where upstream expects it.The surface splits into two call classes:
path,list,get,set,unset,reset --all -y,profile <preset>) is a pipedpassthroughOpenspeccall withexpect.exitCodes = [0, 1], because upstream uses exit 1 for ordinarynegative results (a missing key) that are results to relay, not wrapped-call
violations.
edit, bareprofile,reset --allwithout-y) is a terminalhandover — inherited stdio,
shell: false, verbatim child exit code including130on cancellation. Upstream spawns$EDITORand runs@inquirermenus,which cannot survive cospec's piped
stdin: 'ignore'spawn. This turnsworkset open's one-off into a named class with a fixed contract, documentedin
docs/architecture.mdand specified inopenspec-read-passthroughs.The one-JSON-document invariant holds on every subcommand, not just the one
upstream supports.
list --jsonis a verbatim relay;path/get/set/unset/resetget cospec-ownedversion: 1envelopes built from the text run;--jsonagainst a Class B subcommand is refused with an envelope and exit 1rather than faked. The
version: 1stamp is what makes switching to a relayedupstream document later a version bump instead of a silent shape change.
Config precedence vs cospec's forced env keys
Where cospec's own behaviour outranks the key just written, cospec says so — on
stderr, so
--jsonstdout stays exactly one document:config set telemetry.enabled …→ a note that cospec forcesOPENSPEC_TELEMETRY=0inWRAPPED_ENVon every wrapped call, so the settingaffects bare
openspecruns only.WRAPPED_ENVis unchanged by this PR; thenote is the disclosure, not a new escape hatch.
config profile <preset>andconfig set profile|workflows|delivery→ a notethat cospec's harness files come from cospec canon via
cospec update, notopenspec update(which is exactly what upstream's own success line tells youto run).
defaultStoregets no note, because cospec genuinely honours it — as afallback strictly below local-root resolution. Verified end to end in the
ledger: with
defaultStoreset, a bare directory resolves against the namedstore; a directory with a local
openspec/still wins; unset falls back to thelocal cwd; a stale
defaultStorefails loudly rather than silently fallingthrough. Every other key is annotated for nothing.
completion— native, not passed throughcospec completion [bash|zsh|fish]is generated from cospec's own exportedCOMMANDStable andGLOBAL_OPTIONS, printed to stdout, with no filesystemwrites and no rc-file mutation. Shell is auto-detected from
$SHELLwhenomitted.
Passing upstream's generator through was rejected on principle: its installer
writes a completion function for the
openspecbinary into the user's shellrc, whose dynamic completions shell out to bare
openspec— a permanentinstruction in a dotfile to do the one thing this repo forbids, which no printed
warning undoes. cospec already owns a complete static command table, so
generating from it is both cheaper and more accurate.
A hidden
cospec __complete <changes|specs|types>serves the dynamic slots,emitting tab-separated id/description lines and exiting 1 silently on both
streams on any failure — a Tab press that prints an error into the middle of a
command line is worse than one that completes nothing.
typesis served fromCOSPEC_TYPESwith no wrapped spawn at all.The fragile part is extracting per-command flags from each entry's
pre-formatted help string, so that extraction is a pure function with a snapshot
test over the real table: a new command or flag the extractor cannot see turns
red in CI instead of quietly vanishing from completion.
feedback— files onaligned-team/cospec,--upstreamrelayscospec feedback "<message>" [--body <text>]files ataligned-team/cospecviagh issue createwith array argv,shell: false, and no--label. Defaultingto
Fission-AI/OpenSpecwould route cospec bug reports to a project that cannotfix them and did not ask for them; excluding the surface would leave an
openspeccommand unanswered.The native path follows upstream's shape — grapheme-aware 72-char title
truncation,
ghpresence andgh auth statusgates, a manual-submissionfallback that prints the formatted issue plus a prefilled URL and exits 0 —
minus
--label, whose only purpose upstream is to be retried when the repodoesn't define the label. Dropping the flag deletes the whole failure mode. The
provenance footer names the cospec version plus the resolved wrapped-OpenSpec
source and version.
cospec feedback --upstreamrelays the wrappedopenspec feedbackverbatimwith the destination named on stderr. It uses a raw version-asserted spawn
rather than
passthroughOpenspec, because upstream exits withgh's ownarbitrary status, which no
expect.exitCodesallow-list can enumerate honestly— that exception is stated in the spec rather than hidden.
Review outcome
e981e3dcorrected a claim this change had been carrying as folklore. Theproposal originally asserted that upstream rejects a trailing
--no-coloronevery leaf but
show, and used that as the reasonconfigskips the sharedhelper. Probed against the real pinned binary, it is accepted everywhere —
config's subcommands,schemas, andtemplatesall take it. So the reasonwas rewritten to what is true (it is redundant, not fatal —
--storeand--jsonare the fatal ones), contract rows were added as the regression guardshould upstream ever change, and no follow-up
fixchange was proposed for ahazard that does not exist. The same commit strengthened two tests that would
have passed without proving their claim.
The ledger pass made three further corrections rather than checking off text
that did not match what shipped: upstream prints nothing at all on a
config getmiss (not a message); the standalone bundling probe isfeedback --json,not
feedback --help(cli.tsanswers--helpfrom the static table before itconsults
COMMAND_MODULES, so--helpwould pass even with the moduledropped); and
openspec schemasdoes emit--json, so omitting aschemascompletion source rests on there being no cospec slot to fill —
cospec schema'sfirst positional is a verb — rather than on parsing fragility.
Tests
config-args(35:--scopehoisting, no--store/--no-coloracross all eight subcommands with a signature proof that the builder takes no
root at all,
--jsononly onlist, the Class A/B split, the note selector),feedback-format(19: grapheme-aware truncation, array argv, the twohardcoded destinations),
completions(18: flag extraction plus a snapshotover the real
COMMANDStable),complete-types(spawn-free).XDG_CONFIG_HOMEand
HOMEsandboxed into a temp dir —config-surface.test.ts(15) pinsthe trailing-
--no-coloracceptance (with a control row proving it isn'tspecial-cased), the
--json-on-list-only shape, the--storerejection,--scope globalvs upstream's own not-implemented error, and both handovercases.
config(19),completion(16),feedback(10). Everyghrow stubsghonPATH; no test touches the network.pack-standalone.test.tsrunsconfig path,completion zsh, andfeedback --jsonfrom the compiled binary with nonode_modules, guarding the literal-import()bundling trap that silentlydrops a command module.
docs:build,generate:check,agents:checkallgreen;
.codex/rules/cospec.rulesgains the five read-only prefixes(
config get|list|path,completion,__complete) and none of the mutatingones, for the same reason
archiveis already omitted.Ledger: 29/33 rows verified, 4 deferred, 0 unresolved. Deferrals are 2.4 and
4.4 (need a real TTY and a human at the keyboard), 6.5 (would file a real public
issue), and 7.3 (
@eval, advisory-only, noDEEPSEEK_API_KEY). Row 4.2 recordsthat its
fish --no-executeleg did not run —fishis installed neitherlocally nor in CI — while
bash -nandzsh -nboth pass.Non-goals
init/updatestay cospec-native. Passing them through would write theopsx files cospec's own leftover scan flags.
completion install/uninstall. Rc-file mutation with backups, idempotency,and a matching uninstaller is the bulk of upstream's completion code; docs
ship copy-paste one-liners instead.
cospec relays that verbatim and adds nothing.
prototype-pollution guard. cospec never reads or writes
~/.config/openspec/config.jsonitself and grows no config file of its own.WRAPPED_ENV, or changingcore/passthrough-command.ts's trailing--no-colorappend.🤖 Generated with Claude Code