mirror of
https://github.com/Fission-AI/OpenSpec.git
synced 2026-10-03 22:13:19 +08:00
Compare commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
9b17c537c5 | ||
|
|
f1cab169c0 |
@@ -0,0 +1,2 @@
|
||||
schema: spec-driven
|
||||
created: 2026-09-11
|
||||
@@ -0,0 +1,288 @@
|
||||
## Context
|
||||
|
||||
See proposal.md — Why. Three facts about the existing code shape every decision
|
||||
below.
|
||||
|
||||
- **Workflow ids are a wide seam, and part of it is compile-enforced.**
|
||||
`WORKFLOW_TO_SKILL_DIR` in `src/core/profile-sync-drift.ts:28` is typed
|
||||
`Record<WorkflowId, string>`, so adding an id to `ALL_WORKFLOWS` without adding
|
||||
it there fails `tsc`. Four other maps carry the same information without that
|
||||
protection: the duplicate `WORKFLOW_TO_SKILL_DIR` in `src/core/init.ts:111`
|
||||
(typed `Record<string, string>`), `COMMAND_TO_SKILL_NAME` in
|
||||
`src/utils/command-references.ts:53`, `OPENSPEC_SKILL_NAMES` in
|
||||
`src/core/config.ts:3`, and `COMMAND_IDS` in
|
||||
`src/core/shared/tool-detection.ts:41`. Only the first is protected; the other
|
||||
four drift silently, because the tests around them assert fixed counts rather
|
||||
than derive from `ALL_WORKFLOWS`.
|
||||
- **`feedback` is the only workflow module with no command template.** Every
|
||||
other `src/core/templates/workflows/*.ts` exports a `getOpsx*CommandTemplate`
|
||||
beside its skill template. `getCommandTemplates()` and `COMMAND_IDS` assume the
|
||||
pair, and `profile-sync-drift` reads a missing command file under
|
||||
`delivery: 'both'` as drift to repair on every run.
|
||||
- **The core profile is the default, and the only profile most users ever have.**
|
||||
`getProfileWorkflows` (`src/core/profiles.ts:45`) returns `CORE_WORKFLOWS`
|
||||
unconditionally for any non-`custom` profile and ignores `customWorkflows`
|
||||
entirely for `core`; `DEFAULT_CONFIG.profile` is `'core'`
|
||||
(`src/core/global-config.ts:45`). An id added to `ALL_WORKFLOWS` alone reaches a
|
||||
user only if they open `openspec config profile` and tick it, which also flips
|
||||
them to `custom`.
|
||||
|
||||
## Goals / Non-Goals
|
||||
|
||||
**Goals**
|
||||
|
||||
- A user who says "file that as an issue" gets a well-scoped issue, without
|
||||
reading anything first.
|
||||
- The issue arrives needing less work from a maintainer than it would have. That
|
||||
is the point: refinement at filing time is labour not spent in triage later.
|
||||
- A skill-filed issue and a form-filed issue carry the same fields, so triage
|
||||
reads one shape.
|
||||
- `openspec feedback` with no flags behaves exactly as it does today.
|
||||
|
||||
**Non-Goals**
|
||||
|
||||
- CI enforcement that every PR links an issue. #1834 rules it out, and a gate
|
||||
punishes the people the easy path has not reached yet.
|
||||
- Sending anything anywhere without an explicit confirmation. Feedback submission
|
||||
stays a deliberate act, independent of telemetry
|
||||
(`cli-feedback` — "Feedback always works").
|
||||
- Reading the issue forms at runtime. The CLI never fetches or parses
|
||||
`.github/`; see "Coupling to the forms" below.
|
||||
- Refinement in the CLI. Deciding which question matters next, given what the
|
||||
user just said, is the whole value and it needs a model. A fixed question list
|
||||
in `feedback.ts` would ask a crash and a papercut the same five things and
|
||||
could not tell a vague answer from a usable one. The CLI keeps the
|
||||
deterministic half — validating `--type`, assembling the body, matching the
|
||||
form's fields and labels, building the fallback URL — and the skill keeps the
|
||||
judgement.
|
||||
|
||||
## Decisions
|
||||
|
||||
### Keep the workflow id `feedback`
|
||||
|
||||
#1834 leaves this open: keep `feedback`, or rename to `report`?
|
||||
|
||||
Keep `feedback`. It is already the CLI verb (`openspec feedback`) and already the
|
||||
capability name (`openspec/specs/cli-feedback/`). Renaming would split the skill from the command it tells the agent
|
||||
to run, for a word that is no clearer at the point of use — a user asking to
|
||||
"report a bug" and a user asking to "send feedback" both want the same skill, and
|
||||
the skill's description is what an agent matches on, not its id.
|
||||
|
||||
`report` would also be the first workflow id that names a document rather than an
|
||||
act, against `propose` / `apply` / `archive` / `verify`.
|
||||
|
||||
### Make it a core workflow, not an optional one
|
||||
|
||||
This is the one decision here that changes the default install for every user, and
|
||||
it is the decision most worth arguing with.
|
||||
|
||||
The case for optional: the six core workflows are the steps of one loop — propose,
|
||||
explore, apply, update, sync, archive. `feedback` is not a step in that loop. Every
|
||||
core workflow costs a file in every configured tool's skill directory for every
|
||||
user, forever, and a skill about the tool itself is a weaker claim on that space
|
||||
than a skill about the user's work.
|
||||
|
||||
The case for core, which we take: the problem #1834 describes is not that the
|
||||
feedback skill is hard to find, it is that nobody has it. Shipping it as opt-in
|
||||
reproduces the current outcome at a smaller scale — the users who would tick a box
|
||||
in `openspec config profile` are the users already able to write a good issue by
|
||||
hand. The whole value is in reaching the user who has not read anything, and only
|
||||
the default install reaches them.
|
||||
|
||||
The cost is bounded and reversible: one skill, one command file, no change to any
|
||||
other workflow, and moving it out of `CORE_WORKFLOWS` later is a one-line change
|
||||
with the same update path in reverse.
|
||||
|
||||
Consequence to handle rather than assume: existing projects on a `custom` profile
|
||||
must not silently gain a workflow they did not choose.
|
||||
`displayMissingCoreWorkflowsNote` (`src/core/update.ts:689`, called at `:656`)
|
||||
already handles this: it returns early unless the profile is `custom`, diffs
|
||||
`CORE_WORKFLOWS` against the user's workflows, points at `openspec config
|
||||
profile`, and mutates nothing. This change relies on that path rather than adding
|
||||
one.
|
||||
|
||||
### `--type` defaults to `feedback`, and each type carries the form's labels
|
||||
|
||||
| `--type` | Title | Body sections | Labels |
|
||||
|---|---|---|---|
|
||||
| `feedback` (default) | `Feedback: <msg>` | Summary, Details | `feedback` |
|
||||
| `bug` | `<msg>` | What happened, Expected, Repro, Version, Agent, Environment | `bug`, `needs-triage` |
|
||||
| `feature` | `<msg>` | Problem, Who it affects, What you tried, Proposal | `enhancement`, `needs-triage` |
|
||||
|
||||
Four things follow from this table.
|
||||
|
||||
The default is `feedback` because the bare command must not change behavior for
|
||||
anyone typing it today. `--type bug` and `--type feature` drop the `Feedback: `
|
||||
prefix, because a title reading `Feedback: archive drops the main spec` misfiles
|
||||
a bug report.
|
||||
|
||||
The default keeps asking for the `feedback` label, and the fix for that label not
|
||||
existing is to create it, not to stop asking. The alternative — dropping the label
|
||||
request for the default type — buys one saved round trip and costs the ability to
|
||||
find general feedback in the issue list at all, which is the thing the label is
|
||||
for. `bug` and `enhancement` already exist. The missing-label retry stays for all
|
||||
three, because a label that exists today can be renamed tomorrow and a renamed
|
||||
label must not cost a user their report.
|
||||
|
||||
The labels per type are the forms' labels, `needs-triage` included. A report
|
||||
filed by the CLI and a report filed through the form have to land in the same
|
||||
triage queue, or the CLI path quietly skips it.
|
||||
|
||||
The fields per type are the issue forms' fields, in the forms' order. That is the
|
||||
point: the same report, whoever files it and however.
|
||||
|
||||
### The manual fallback targets the form, and degrades to today's URL
|
||||
|
||||
`generateManualSubmissionUrl()` gains `template=bug_report.yml` (or
|
||||
`feature_request.yml`) and one query parameter per form field id — GitHub's own
|
||||
prefill mechanism, which needs no API and no auth.
|
||||
|
||||
Two failure modes are handled rather than hoped away.
|
||||
|
||||
**Length.** A prefilled URL carries the whole body twice-encoded, and a long
|
||||
report can exceed what GitHub and some browsers accept. The builder caps the
|
||||
encoded URL; over the cap it returns the blank-issue URL the command builds today,
|
||||
with the body intact. A user who lands on a blank form with their text still in it
|
||||
has lost formatting, not their report. `--type feedback` uses that blank-issue URL
|
||||
unconditionally, since it has no form to target.
|
||||
|
||||
**Coupling to the forms.** The field ids (`what_happened`, `expected`, `repro`,
|
||||
`version`, `agent`, `environment`; `problem`, `who`, `tried`, `proposal`) become an
|
||||
interface the CLI depends on and `.github/` owns. The CLI does not read `.github/`
|
||||
— it cannot, since it runs in the user's project. It hard-codes the ids, and a
|
||||
test asserts that the ids it hard-codes are exactly the ids present in
|
||||
`.github/ISSUE_TEMPLATE/*.yml`, so renaming a field in a form fails CI in this
|
||||
repository rather than silently emptying a field for users. If an id ever does go
|
||||
stale in a released version, GitHub ignores unknown prefill parameters: the user
|
||||
gets the right form with one field blank, not an error.
|
||||
|
||||
### What the skill asks, and what it must never ask
|
||||
|
||||
The current template's flow is "review the conversation, draft, show it." #1834
|
||||
sharpens that to "draft, not interrogate," and taken literally that forbids the
|
||||
thing most worth doing: helping the user work out what they are actually asking
|
||||
for. A vague issue filed instantly is not cheaper than a good one filed a minute
|
||||
later — it is the same work, moved to a maintainer and made harder by the missing
|
||||
context. So the skill refines first, and the contract is about *which* things it
|
||||
may put to the user.
|
||||
|
||||
The split is facts versus judgements.
|
||||
|
||||
**Facts are the skill's job.** The OpenSpec version, the platform, the failing
|
||||
command and its output, the agent and model, what the user was doing — all
|
||||
observable. Asking for any of them is the interrogation failure mode, and it is
|
||||
the one users actually resent. This is the half of #1834's "draft, not
|
||||
interrogate" that survives intact, and it is stated as a prohibition in the spec
|
||||
because it is the one place a hard line is right.
|
||||
|
||||
**Judgements are the user's.** What the report is really asking for, who it
|
||||
affects, what it deliberately excludes, whether the reported problem is the
|
||||
problem or a symptom of one. These cannot be inferred, and they are exactly the
|
||||
questions whose absence costs a maintainer a round trip later. Scope is the
|
||||
highest-value one: *what should this not do* settles more downstream argument than
|
||||
any reproduction detail.
|
||||
|
||||
Two mechanics are worth borrowing rather than inventing, from Matt Pocock's
|
||||
`grilling` skill, which does this well:
|
||||
|
||||
- **Each question carries a recommended answer.** Agreement then costs a word, and
|
||||
a user who disagrees is arguing with a concrete proposal instead of composing
|
||||
one. This is also how OpenSpec's own `explore` already works — its shared
|
||||
`PLANNING_GUIDANCE` says to "state your preferred option and why it fits."
|
||||
- **Dependency ordering.** Never ask a question whose answer depends on one still
|
||||
open; settle the blocking decision, then the details it unlocks. `explore`
|
||||
already says "Follow dependencies"; grilling makes it the whole selection rule.
|
||||
|
||||
Grilling asks the entire unblocked frontier in a numbered round; `explore` asks
|
||||
one focused question at a time. The spec deliberately picks neither. Cadence is
|
||||
the kind of thing that should improve without a spec change, and the right answer
|
||||
probably differs between a bug report and a feature idea. What the spec fixes is
|
||||
the ordering rule and the recommendation, which are the parts that make the
|
||||
conversation short.
|
||||
|
||||
**Termination is by exhaustion, not by count.** Grilling refuses a question cap on
|
||||
purpose, and it is right to: some reports need one question and some need ten.
|
||||
What the spec requires instead is that the skill stop when nothing material is
|
||||
unsettled — and, just as importantly, that it be able to ask *nothing at all*. A
|
||||
skill that manufactures three questions for a typo report teaches users to stop
|
||||
invoking it, which costs more than any single badly-scoped issue.
|
||||
|
||||
### One file, no reference layer
|
||||
|
||||
Current skill-authoring practice — Anthropic's `skill-creator`, and Pocock's
|
||||
skills, whose SKILL.md files run 7 to 140 lines — is progressive disclosure: a
|
||||
short SKILL.md that points at sibling reference files loaded only when needed.
|
||||
|
||||
**OpenSpec cannot do that today.** `generateSkillContent`
|
||||
(`src/core/shared/skill-generation.ts:132`) returns a single string, and every
|
||||
adapter writes exactly one `SKILL.md` per skill directory. There is no mechanism
|
||||
for a sibling file — adding one would touch the generator, every adapter's file
|
||||
layout, `tool-detection.ts`'s existence checks, the `skills/` mirror, and the
|
||||
parity hashes. That is its own proposal, and this change should not smuggle it in.
|
||||
|
||||
So the whole skill has to fit one file, which makes leanness a constraint rather
|
||||
than a preference. Two consequences for how it is written, both drawn from the
|
||||
same current practice:
|
||||
|
||||
- **Explain why instead of stacking capitalised MUSTs.** The existing template has
|
||||
seven in a row under "Guardrails." `skill-creator` calls that a yellow flag and
|
||||
asks for the reasoning instead, so the model can generalise to the case nobody
|
||||
wrote down. The genuine invariants — show the draft, get confirmation, anonymise
|
||||
— stay imperative; the rest becomes explanation.
|
||||
- **Phrase instructions positively.** Pocock's `writing-for-agents` puts it well:
|
||||
steering by prohibition drags the forbidden behaviour into context and makes it
|
||||
*more* available. "Fill the version in yourself" beats "DO NOT ask for the
|
||||
version."
|
||||
|
||||
### The skill's own `name` has to change, even though the workflow id does not
|
||||
|
||||
The workflow id and the skill's frontmatter `name` are different things, and
|
||||
`feedback` is currently wrong in the second. `dirName` is a free literal per entry
|
||||
in `getSkillTemplates()` (`src/core/shared/skill-generation.ts:61`), but the
|
||||
SKILL.md frontmatter is written from `template.name`
|
||||
(`src/core/shared/skill-generation.ts:142`), and every registered template sets
|
||||
`name` equal to its dirName — `openspec-verify-change`, `openspec-onboard`, and so
|
||||
on. `src/core/templates/workflows/feedback.ts:11` sets `name: 'feedback'`, written
|
||||
when nothing deployed it.
|
||||
|
||||
Registering it as-is would commit `skills/openspec-feedback/SKILL.md` whose
|
||||
frontmatter says `feedback` — the only skill where the two disagree, and a
|
||||
disagreement with both `OPENSPEC_SKILL_NAMES` and the `/openspec-feedback`
|
||||
reference in `COMMAND_TO_SKILL_NAME`. The template's `name` becomes
|
||||
`openspec-feedback`; the workflow id stays `feedback`, exactly as `verify` maps to
|
||||
`openspec-verify-change`.
|
||||
|
||||
### The command template must take `$ARGUMENTS`
|
||||
|
||||
`test/core/command-generation/adapters.test.ts:158` asserts that `onboard` is the
|
||||
only command with no `$ARGUMENTS` placeholder, and the file describes that
|
||||
assertion as a tripwire for exactly this situation. `/opsx-feedback <what went
|
||||
wrong>` should accept the message anyway — an agent invoking it already has the
|
||||
sentence.
|
||||
|
||||
## Risks / Trade-offs
|
||||
|
||||
- **A seventh core skill for every user.** Accepted above, and cheap to reverse.
|
||||
- **Five parallel id maps, three unprotected.** This change adds a sixth entry to
|
||||
each. It does not consolidate them — that is a refactor with its own blast
|
||||
radius and belongs in its own proposal — but the tasks below touch all of them
|
||||
in one pass. Be clear about what actually catches an omission: only `tsc`, via
|
||||
the `Record<WorkflowId, string>` at `profile-sync-drift.ts:28`. The other guards
|
||||
are fixed-count assertions such as `tool-detection.test.ts:33`, which fail when
|
||||
you *add* an entry and stay silent when you skip one, and
|
||||
`command-references.test.ts:92`, whose list is hand-written and does not derive
|
||||
from `ALL_WORKFLOWS` — it omits `propose`, which is in the map. Omitting
|
||||
`feedback` from `COMMAND_TO_SKILL_NAME` fails nothing at all.
|
||||
- **The skill tells an agent to run a command that posts to GitHub.** Mitigated by
|
||||
contract rather than convention: the amended skill requirement in `cli-feedback`
|
||||
keeps showing the complete draft and receiving explicit confirmation a MUST.
|
||||
- **Anonymization is instruction, not enforcement.** An agent can still include a
|
||||
path it should have redacted. This is the same bound the existing spec accepts;
|
||||
making it mechanical would mean parsing the body the agent wrote, which is a
|
||||
different change.
|
||||
|
||||
## Migration
|
||||
|
||||
None for data. `openspec update` installs the new skill and command on the next
|
||||
run; `openspec init` installs them for new projects. Projects on a `custom`
|
||||
profile keep their selection and are nudged, not modified.
|
||||
@@ -0,0 +1,116 @@
|
||||
## Why
|
||||
|
||||
`CONTRIBUTING.md` asks every change to start with an issue. Filing one is now the
|
||||
path of least resistance for someone who opens this repository on GitHub —
|
||||
[#1847](https://github.com/Fission-AI/OpenSpec/pull/1847) added the issue forms and
|
||||
the PR template. It is still not the path of least resistance for the person who
|
||||
hits the bug: a user in their own project, with `openspec` installed and an agent
|
||||
open, who says "file that as an issue."
|
||||
|
||||
Two things stand between them and a well-formed issue.
|
||||
|
||||
**The skill that was supposed to do this has never existed for anyone.**
|
||||
`openspec/specs/cli-feedback/spec.md` specifies a `/feedback` skill that gathers
|
||||
context, drafts, anonymizes, and submits. The template is written and exported
|
||||
(`src/core/templates/workflows/feedback.ts`, re-exported at
|
||||
`src/core/templates/skill-templates.ts:21`), but `feedback` is not in
|
||||
`ALL_WORKFLOWS` (`src/core/profiles.ts:19`), so it is in no skill registry, no
|
||||
tool's skill directory, and no user's project. The requirement is unimplemented,
|
||||
and the parity suite records that deliberately: `skill-templates-parity.test.ts:83`
|
||||
excludes it from the generated-skill list because it "is covered in function
|
||||
payload parity" — the payload of a template nothing deploys.
|
||||
|
||||
**What the CLI emits does not match what the forms ask for.**
|
||||
`openspec feedback "..."` posts a free-form `## Summary` / `## Details` body under
|
||||
a `Feedback: ` title, with no expected-vs-actual, no repro, and no agent or model —
|
||||
the four things triage asks for every time. It requests the `feedback` label, which
|
||||
this repository does not define, so every submission falls back to unlabeled and
|
||||
prints a note saying so. And the manual URL in `generateManualSubmissionUrl()`
|
||||
(`src/commands/feedback.ts:127`) passes bare `title`/`body`, which now lands on the
|
||||
*blank* issue form — so every user without `gh` bypasses the forms #1847 just added.
|
||||
|
||||
The result is that the two people most able to write a good bug report — the user
|
||||
who just hit it, and the agent that watched it happen — are the two we equip least.
|
||||
|
||||
## What Changes
|
||||
|
||||
- **Register `feedback` as a workflow.** It joins `ALL_WORKFLOWS` and
|
||||
`CORE_WORKFLOWS`, so `openspec init` and `openspec update` install it by default
|
||||
alongside the six workflows already there. A skill nobody opts into does not
|
||||
change the outcome for anyone; see design.md for why this is worth a seventh
|
||||
core workflow and what it costs.
|
||||
- **Author the missing command template.** `feedback` is the only workflow whose
|
||||
module exports a skill template and no `getOpsx*CommandTemplate`. Without one it
|
||||
is a skills-only workflow that `profile-sync-drift` reports as drift forever
|
||||
under `delivery: 'both'`.
|
||||
- **Rewrite the skill to refine the report, then draft it.** Two different jobs
|
||||
that the current template conflates. Facts are the skill's to find — the
|
||||
conversation, the failing command, the version, the platform — and it should
|
||||
never ask for one of those. Judgements are the user's, and those are worth
|
||||
asking about: what the report is really asking for, who it affects, what it
|
||||
deliberately leaves out. A report that arrives already scoped is the labour this
|
||||
saves later, so the skill converges on one with the user before it drafts
|
||||
anything, then shows one complete draft and submits on a single confirmation.
|
||||
Its fields are the form's fields, so a skill-filed issue and a form-filed issue
|
||||
read the same.
|
||||
- **`openspec feedback --type bug|feature|feedback`.** Each type emits the body
|
||||
the matching form asks for and requests the labels that form applies — `bug,
|
||||
needs-triage`, `enhancement, needs-triage`, or `feedback` — so a CLI-filed
|
||||
report lands in the same triage queue as a form-filed one. `feedback` stays the default, so the bare command a
|
||||
user types today behaves exactly as it does today, including the retry that
|
||||
rescues a submission when a label is missing.
|
||||
- **Define the `feedback` label in this repository.** It is the one the command
|
||||
has always asked for and the one this repository has never had, which is why
|
||||
every submission today is created unlabeled and apologizes for it. This is a
|
||||
repository setting, not code — no released version changes.
|
||||
- **Prefill the form in the manual fallback.** The URL gains
|
||||
`?template=bug_report.yml` and the form's own field ids, so a user without `gh`
|
||||
lands on the filled-in form rather than a blank box. The URL is capped, and falls
|
||||
back to the blank-issue URL it builds today when prefill would exceed the cap.
|
||||
- **Not breaking.** `openspec feedback "msg"` with no flags keeps its title,
|
||||
its body shape, and its exit codes. No existing workflow id, skill directory, or
|
||||
command name changes.
|
||||
|
||||
## Capabilities
|
||||
|
||||
### New Capabilities
|
||||
|
||||
None. The skill contract already lives in `cli-feedback` as the "Feedback skill
|
||||
for agents" requirement; it is amended there rather than moved to an
|
||||
`opsx-feedback-skill` capability, because moving it changes nothing for a reader
|
||||
and costs a removal plus a new file.
|
||||
|
||||
### Modified Capabilities
|
||||
|
||||
- `cli-feedback`: `--type` and its effect on title, body, and label; the manual fallback URL
|
||||
targeting the issue form with prefilled fields; the feedback skill requirement
|
||||
amended to refine before drafting — facts found rather than asked for, the
|
||||
user's judgements put to them with a recommendation — and stated as installed
|
||||
rather than merely specified.
|
||||
|
||||
## Depends on
|
||||
|
||||
[#1847](https://github.com/Fission-AI/OpenSpec/pull/1847), unmerged. It adds
|
||||
`.github/ISSUE_TEMPLATE/bug_report.yml` and `feature_request.yml`; every field id
|
||||
this change prefills and the test that pins those ids read those files, so the
|
||||
form-prefill work (tasks 5 and 6.11) cannot start until it lands. Everything else
|
||||
here — registering the workflow, the command template, the skill rewrite, `--type`
|
||||
— is independent of it.
|
||||
|
||||
## Impact
|
||||
|
||||
- **Affected behavior**: `openspec feedback` (new flag, new bodies, new fallback
|
||||
URL); `openspec init` and `openspec update` for every user, which now install a
|
||||
seventh core skill and command; `openspec config profile`, whose picker gains a
|
||||
row.
|
||||
- **Existing projects**: `openspec update` adds the skill on the next run. Projects
|
||||
on a `custom` profile are nudged about the newly-core workflow rather than having
|
||||
it added silently, via the path `src/core/update.ts:689` already takes.
|
||||
- **Unaffected**: every other workflow, the schema system, and
|
||||
`config-schema.ts`, which validates `workflows` as `z.array(z.string())` with no
|
||||
enum.
|
||||
- **Docs**: `docs/commands.md`, `docs/workflows.md`, `docs/supported-tools.md`,
|
||||
`docs/glossary.md`, and `README.md` list workflows by name and gain a row.
|
||||
- **Repository, not product**: the `bug_report.yml` / `feature_request.yml` field
|
||||
ids become an interface the CLI depends on. design.md states how that coupling
|
||||
fails safe.
|
||||
@@ -0,0 +1,245 @@
|
||||
## MODIFIED Requirements
|
||||
|
||||
### Requirement: Feedback command
|
||||
|
||||
The system SHALL provide an `openspec feedback` command that creates a GitHub Issue in the openspec repository using the `gh` CLI. The system SHALL use `execFileSync` with argument arrays to prevent shell injection vulnerabilities.
|
||||
|
||||
The command SHALL accept a `--type` option with the values `bug`, `feature`, and `feedback`, defaulting to `feedback`. The type SHALL determine the issue title, the body sections, and the label requested:
|
||||
|
||||
| Type | Title | Body sections | Labels |
|
||||
|---|---|---|---|
|
||||
| `feedback` | `Feedback: <message>` | Summary, Details | `feedback` |
|
||||
| `bug` | `<message>` | What happened, Expected, Repro, Version, Agent, Environment | `bug`, `needs-triage` |
|
||||
| `feature` | `<message>` | Problem, Who it affects, What you tried, Proposal | `enhancement`, `needs-triage` |
|
||||
|
||||
The `bug` and `feature` body sections SHALL match the fields and order of the repository's issue forms, and the labels requested SHALL be the labels those forms apply, so that a report filed by the command and a report filed through the form read the same and reach the same triage queue. The system SHALL reject an unrecognized `--type` value with a message naming the accepted values, and SHALL NOT submit anything.
|
||||
|
||||
#### Scenario: Simple feedback submission
|
||||
|
||||
- **WHEN** user executes `openspec feedback "Great tool!"`
|
||||
- **THEN** the system executes `gh issue create` with title "Feedback: Great tool!"
|
||||
- **AND** the issue body includes "Great tool!" under a Summary heading
|
||||
- **AND** the issue is created in the openspec repository
|
||||
- **AND** the issue has the `feedback` label
|
||||
- **AND** the system displays the created issue URL
|
||||
|
||||
#### Scenario: Bug report submission
|
||||
|
||||
- **WHEN** user executes `openspec feedback "archive drops the main spec" --type bug`
|
||||
- **THEN** the issue title is "archive drops the main spec" with no "Feedback: " prefix
|
||||
- **AND** the issue body carries the bug form's sections in the form's order
|
||||
- **AND** the system requests the `bug` and `needs-triage` labels
|
||||
- **AND** the system displays the created issue URL
|
||||
|
||||
#### Scenario: Feature request submission
|
||||
|
||||
- **WHEN** user executes `openspec feedback "let me unarchive a change" --type feature`
|
||||
- **THEN** the issue title is "let me unarchive a change" with no "Feedback: " prefix
|
||||
- **AND** the issue body carries the feature form's sections in the form's order
|
||||
- **AND** the system requests the `enhancement` and `needs-triage` labels
|
||||
|
||||
#### Scenario: Unrecognized type
|
||||
|
||||
- **WHEN** user executes `openspec feedback "message" --type question`
|
||||
- **THEN** the system reports that `question` is not an accepted type
|
||||
- **AND** names `bug`, `feature`, and `feedback` as the accepted values
|
||||
- **AND** exits with a non-zero code without contacting GitHub
|
||||
|
||||
#### Scenario: Repository does not define the feedback label
|
||||
|
||||
- **WHEN** user executes `openspec feedback "Great tool!"`
|
||||
- **AND** the repository does not define the `feedback` label, so `gh` refuses to create the issue
|
||||
- **THEN** the system retries `gh issue create` without the label
|
||||
- **AND** the issue is created in the openspec repository without the `feedback` label
|
||||
- **AND** the system displays the created issue URL
|
||||
- **AND** the system notes that the label was not applied
|
||||
|
||||
#### Scenario: Repository does not define a type's label
|
||||
|
||||
- **WHEN** user executes `openspec feedback "message" --type bug`
|
||||
- **AND** the repository does not define one of the labels, so `gh` refuses to create the issue
|
||||
- **THEN** the system retries `gh issue create` without labels, as it does for the `feedback` label
|
||||
- **AND** the issue is created in the openspec repository without labels
|
||||
- **AND** the system notes that the labels were not applied
|
||||
|
||||
#### Scenario: Safe command execution
|
||||
|
||||
- **WHEN** submitting feedback via `gh` CLI
|
||||
- **THEN** the system uses `execFileSync` with separate arguments array
|
||||
- **AND** user input is NOT passed through a shell
|
||||
- **AND** shell metacharacters (quotes, backticks, $(), etc.) are treated as literal text
|
||||
|
||||
#### Scenario: Feedback with body
|
||||
|
||||
- **WHEN** user executes `openspec feedback "Title here" --body "Detailed description..."`
|
||||
- **THEN** the system creates a GitHub Issue with the specified title
|
||||
- **AND** the issue body contains the message under a Summary heading
|
||||
- **AND** the issue body contains the detailed description under a Details heading
|
||||
- **AND** the issue body includes metadata (OpenSpec version, platform, timestamp)
|
||||
|
||||
#### Scenario: Long or multiline feedback message
|
||||
|
||||
- **WHEN** user executes `openspec feedback` with a long or multiline message
|
||||
- **THEN** the issue title is a single whitespace-normalized line of at most 72 characters
|
||||
- **AND** an ellipsis indicates when the title was shortened
|
||||
- **AND** the complete message is preserved in the issue body
|
||||
|
||||
### Requirement: GitHub CLI dependency
|
||||
|
||||
The system SHALL use `gh` CLI for automatic feedback submission when available, and provide a manual submission fallback when `gh` is not installed or not authenticated. The system SHALL use platform-appropriate commands to detect `gh` CLI availability.
|
||||
|
||||
The pre-filled URL offered by the manual fallback SHALL target the repository's issue form matching the `--type`, prefilling each of the form's fields by its field id, so that a user without `gh` reaches the same form a user on GitHub reaches. The system SHALL fall back to the blank-issue URL, carrying the complete body, when no form matches the type or when the prefilled URL would exceed the length the system accepts. The system SHALL NOT read the issue forms at runtime.
|
||||
|
||||
#### Scenario: Missing gh CLI with fallback
|
||||
|
||||
- **WHEN** user runs `openspec feedback "message"`
|
||||
- **AND** `gh` CLI is not installed (not found in PATH)
|
||||
- **THEN** the system displays warning: "GitHub CLI not found. Manual submission required."
|
||||
- **AND** outputs structured feedback content with delimiters:
|
||||
- "--- FORMATTED FEEDBACK ---"
|
||||
- Title line
|
||||
- Labels line
|
||||
- Body content with metadata
|
||||
- "--- END FEEDBACK ---"
|
||||
- **AND** displays pre-filled GitHub issue URL for manual submission
|
||||
- **AND** exits with zero code (successful fallback)
|
||||
|
||||
#### Scenario: Fallback URL targets the bug form
|
||||
|
||||
- **WHEN** the manual fallback is reached for `--type bug`
|
||||
- **THEN** the pre-filled URL selects the repository's bug report form
|
||||
- **AND** carries one query parameter per form field id, holding that field's drafted content
|
||||
- **AND** a field the system cannot fill is omitted rather than sent empty
|
||||
|
||||
#### Scenario: Fallback URL too long for the form
|
||||
|
||||
- **WHEN** the manual fallback is reached for `--type bug`
|
||||
- **AND** the prefilled URL would exceed the length the system accepts
|
||||
- **THEN** the system displays the blank-issue URL instead
|
||||
- **AND** the complete body is still carried in that URL
|
||||
- **AND** the complete body is still printed between the feedback delimiters
|
||||
|
||||
#### Scenario: Fallback URL for general feedback
|
||||
|
||||
- **WHEN** the manual fallback is reached for the default `feedback` type
|
||||
- **THEN** the system displays the blank-issue URL, because no issue form matches general feedback
|
||||
- **AND** the URL carries the complete body, as it does today
|
||||
|
||||
#### Scenario: Cross-platform gh CLI detection on Unix
|
||||
|
||||
- **WHEN** system is running on macOS or Linux (platform is 'darwin' or 'linux')
|
||||
- **AND** checking if `gh` CLI is installed
|
||||
- **THEN** the system executes `which gh` command
|
||||
|
||||
#### Scenario: Cross-platform gh CLI detection on Windows
|
||||
|
||||
- **WHEN** system is running on Windows (platform is 'win32')
|
||||
- **AND** checking if `gh` CLI is installed
|
||||
- **THEN** the system executes `where gh` command
|
||||
|
||||
#### Scenario: Unauthenticated gh CLI with fallback
|
||||
|
||||
- **WHEN** user runs `openspec feedback "message"`
|
||||
- **AND** `gh` CLI is installed but not authenticated
|
||||
- **THEN** the system displays warning: "GitHub authentication required. Manual submission required."
|
||||
- **AND** outputs structured feedback content (same format as missing gh CLI scenario)
|
||||
- **AND** displays pre-filled GitHub issue URL for manual submission
|
||||
- **AND** displays authentication instructions: "To auto-submit in the future: gh auth login"
|
||||
- **AND** exits with zero code (successful fallback)
|
||||
|
||||
#### Scenario: Authenticated gh CLI
|
||||
|
||||
- **WHEN** user runs `openspec feedback "message"`
|
||||
- **AND** `gh auth status` returns success (authenticated)
|
||||
- **THEN** the system proceeds with feedback submission
|
||||
|
||||
### Requirement: Feedback skill for agents
|
||||
|
||||
The system SHALL install a `feedback` workflow — a skill and a slash command — as part of the core profile, so that `openspec init` and `openspec update` deliver it without the user selecting it. The skill SHALL guide an agent through drafting and submitting a report.
|
||||
|
||||
The skill SHALL find facts rather than ask for them. Everything observable from the conversation or the environment — the task in progress, the failing command and its output, the OpenSpec version, the platform, and the agent and model — SHALL be filled in by the skill, and SHALL NOT be put to the user as a question.
|
||||
|
||||
The skill SHALL refine the report with the user before drafting it, because a report that arrives already scoped is the labour it saves later. It SHALL put to the user the judgements only the user holds — what the report is really asking for, who it affects, what it deliberately excludes, and whether a reported problem is the problem or a symptom — and SHALL carry a recommended answer with each, so that agreement costs a word. It SHALL order questions by dependency, never asking one whose answer depends on a question still open.
|
||||
|
||||
Refinement SHALL end when nothing material is left unsettled, rather than at a fixed number of questions: a one-line typo report and a half-formed feature idea do not need the same conversation. The skill SHALL then present one complete draft and submit only on confirmation.
|
||||
|
||||
The drafted fields SHALL be the fields of the issue form matching the report type, so that a report filed by the skill and a report filed through the form read the same.
|
||||
|
||||
#### Scenario: Agent-initiated feedback
|
||||
|
||||
- **WHEN** user invokes the feedback skill in an agent conversation
|
||||
- **THEN** the agent gathers context from the conversation
|
||||
- **AND** drafts a report with enriched content
|
||||
- **AND** anonymizes sensitive information
|
||||
- **AND** presents the draft to the user for approval
|
||||
- **AND** submits via the `openspec feedback` command on user confirmation
|
||||
|
||||
#### Scenario: Installed by default
|
||||
|
||||
- **WHEN** a user runs `openspec init` or `openspec update` on the default profile
|
||||
- **THEN** the feedback skill and its slash command are installed alongside the other core workflows
|
||||
- **AND** a project on a custom profile is told the workflow is available rather than having it added silently
|
||||
|
||||
#### Scenario: Facts are found, not asked for
|
||||
|
||||
- **WHEN** the version, the platform, the failing command, and the model are available from the conversation or the environment
|
||||
- **THEN** the agent fills those fields itself
|
||||
- **AND** does not put any of them to the user as a question
|
||||
|
||||
#### Scenario: Refining before drafting
|
||||
|
||||
- **WHEN** the user reports a problem whose scope, audience, or intended outcome is unsettled
|
||||
- **THEN** the agent puts those judgements to the user before drafting
|
||||
- **AND** carries a recommended answer with each one
|
||||
- **AND** asks nothing whose answer depends on a question still open
|
||||
- **AND** proceeds to the draft once nothing material is left unsettled
|
||||
|
||||
#### Scenario: A report that needs no refining
|
||||
|
||||
- **WHEN** the report is already unambiguous, such as a typo or a one-line reproduction
|
||||
- **THEN** the agent drafts it without manufacturing questions to ask
|
||||
|
||||
#### Scenario: Context enrichment
|
||||
|
||||
- **WHEN** agent drafts feedback
|
||||
- **THEN** the agent includes relevant context such as:
|
||||
- What task was being performed
|
||||
- What worked well or poorly
|
||||
- Specific friction points or praise
|
||||
|
||||
#### Scenario: Anonymization
|
||||
|
||||
- **WHEN** agent drafts feedback
|
||||
- **THEN** the agent removes or replaces:
|
||||
- File paths with `<path>` or generic descriptions
|
||||
- API keys, tokens, secrets with `<redacted>`
|
||||
- Company/organization names with `<company>`
|
||||
- Personal names with `<user>`
|
||||
- Specific URLs with `<url>` unless public/relevant
|
||||
|
||||
#### Scenario: User confirmation required
|
||||
|
||||
- **WHEN** agent has drafted feedback
|
||||
- **THEN** the agent MUST show the complete draft to the user
|
||||
- **AND** ask for explicit approval before submitting
|
||||
- **AND** allow the user to request modifications
|
||||
- **AND** only submit after user confirms
|
||||
|
||||
### Requirement: Shell completions
|
||||
|
||||
The system SHALL provide shell completions for the feedback command.
|
||||
|
||||
#### Scenario: Command completion
|
||||
|
||||
- **WHEN** user types `openspec fee<TAB>`
|
||||
- **THEN** the shell completes to `openspec feedback`
|
||||
|
||||
#### Scenario: Flag completion
|
||||
|
||||
- **WHEN** user types `openspec feedback "msg" --<TAB>`
|
||||
- **THEN** the shell suggests available flags (`--body`, `--type`)
|
||||
|
||||
#### Scenario: Type value completion
|
||||
|
||||
- **WHEN** user types `openspec feedback "msg" --type <TAB>`
|
||||
- **THEN** the shell suggests `bug`, `feature`, and `feedback`
|
||||
@@ -0,0 +1,168 @@
|
||||
## 1. Author the missing command template
|
||||
|
||||
- [ ] 1.1 Add `getOpsxFeedbackCommandTemplate()` to
|
||||
`src/core/templates/workflows/feedback.ts`, following the shape of
|
||||
`getOpsxVerifyCommandTemplate`
|
||||
- [ ] 1.2 Include an `$ARGUMENTS` placeholder so the message can be passed at
|
||||
invocation — `test/core/command-generation/adapters.test.ts:158` asserts
|
||||
`onboard` is the only command without one, and that assertion is the
|
||||
tripwire for this task
|
||||
- [ ] 1.3 Re-export it from `src/core/templates/skill-templates.ts:21` beside the
|
||||
existing skill export
|
||||
|
||||
## 2. Rewrite the feedback skill to refine, then draft
|
||||
|
||||
- [ ] 2.1 Replace the skill instructions in
|
||||
`src/core/templates/workflows/feedback.ts`: find the observable facts
|
||||
first and never ask for one of them; put the user's judgements to them —
|
||||
what the report is really asking for, who it affects, what it excludes,
|
||||
whether the reported problem is the problem or a symptom — each with a
|
||||
recommended answer; order by dependency; stop when nothing material is
|
||||
unsettled; then one complete draft, submitted on one confirmation
|
||||
- [ ] 2.2 Give it a way to skip refinement entirely when the report is already
|
||||
unambiguous. A skill that manufactures questions for a typo teaches users to
|
||||
stop invoking it
|
||||
- [ ] 2.3 Make the drafted fields the issue forms' fields, per type, in the forms'
|
||||
order
|
||||
- [ ] 2.4 Teach it `openspec feedback --type` and when to choose each type
|
||||
- [ ] 2.5 Keep the anonymization rules and the show-draft-before-submitting
|
||||
guardrail from the current template — they are contract, not style
|
||||
- [ ] 2.6 Write it in the current style rather than the template's original: state
|
||||
why something matters instead of stacking capitalised MUSTs, phrase
|
||||
instructions positively rather than as prohibitions, and cut anything not
|
||||
pulling its weight. The whole skill has to fit one SKILL.md — see design.md,
|
||||
"One file, no reference layer"
|
||||
- [ ] 2.7 Change the template's `name` from `feedback` to `openspec-feedback`
|
||||
(`src/core/templates/workflows/feedback.ts:11`). The frontmatter `name:` in
|
||||
the generated SKILL.md comes from this field
|
||||
(`src/core/shared/skill-generation.ts:142`), and every other registered
|
||||
template sets it equal to its skill directory. Left alone, this would be the
|
||||
one skill whose frontmatter disagrees with its directory,
|
||||
`OPENSPEC_SKILL_NAMES`, and the `/openspec-feedback` reference in task 3.7.
|
||||
The workflow id stays `feedback`
|
||||
|
||||
## 3. Register `feedback` as a core workflow
|
||||
|
||||
Six id lists carry this information. Two are caught by `tsc` or a test; the rest
|
||||
drift silently, so all six change in one pass.
|
||||
|
||||
- [ ] 3.1 `src/core/profiles.ts:19` — add `feedback` to `ALL_WORKFLOWS`
|
||||
- [ ] 3.2 `src/core/profiles.ts:14` — add `feedback` to `CORE_WORKFLOWS`
|
||||
- [ ] 3.3 `src/core/profile-sync-drift.ts:28` — add `feedback: 'openspec-feedback'`
|
||||
to `WORKFLOW_TO_SKILL_DIR` (compile-enforced: `Record<WorkflowId, string>`)
|
||||
- [ ] 3.4 `src/core/init.ts:111` — add the same entry to the duplicate map there
|
||||
(typed `Record<string, string>`, so nothing catches its absence)
|
||||
- [ ] 3.5 `src/core/config.ts:3` — add `openspec-feedback` to
|
||||
`OPENSPEC_SKILL_NAMES`, which drives tool detection and skill-status counts
|
||||
- [ ] 3.6 `src/core/shared/tool-detection.ts:41` — add `feedback` to `COMMAND_IDS`
|
||||
- [ ] 3.7 `src/utils/command-references.ts:53` — add the entry to
|
||||
`COMMAND_TO_SKILL_NAME`, without which `transformCommandInvocations` leaves
|
||||
the id unrewritten
|
||||
- [ ] 3.8 `src/core/shared/skill-generation.ts:70` and `:97` — register the skill
|
||||
template and the command template, with their imports
|
||||
- [ ] 3.9 `src/commands/config.ts:47` — add a `WORKFLOW_PROMPT_META` entry, or the
|
||||
picker shows the raw id (`test/commands/config.test.ts:399` enforces this)
|
||||
- [ ] 3.10 Confirm `src/core/legacy-cleanup.ts:78` needs **no** entry: that list
|
||||
cleans up Codex prompts that once shipped, and `feedback` never did
|
||||
|
||||
## 4. `--type` on the CLI
|
||||
|
||||
- [ ] 4.1 `src/cli/index.ts:567` — add `--type <type>` beside `--body`
|
||||
- [ ] 4.2 `src/core/completions/command-registry.ts:479` — add the same flag with
|
||||
its accepted values, or completions and the CLI disagree
|
||||
- [ ] 4.3 `src/commands/feedback.ts` — validate the value, rejecting an
|
||||
unrecognized type before contacting GitHub
|
||||
- [ ] 4.4 Build the title per type: the `Feedback: ` prefix for `feedback`, none
|
||||
for `bug` and `feature`; keep the existing 72-character grapheme-safe
|
||||
shortening for all three
|
||||
- [ ] 4.5 Build the body sections per type, matching the issue forms' fields and
|
||||
order, with the existing metadata footer unchanged
|
||||
- [ ] 4.6 Request the labels the matching form applies — `bug, needs-triage` for
|
||||
`--type bug`, `enhancement, needs-triage` for `--type feature`, `feedback`
|
||||
for the default; keep the missing-label retry for all three
|
||||
- [ ] 4.7 Repository setting, not code: create the `feedback` label in
|
||||
`Fission-AI/OpenSpec`, which the command has always requested and the
|
||||
repository has never defined
|
||||
|
||||
## 5. Prefill the issue form in the manual fallback
|
||||
|
||||
Blocked on [#1847](https://github.com/Fission-AI/OpenSpec/pull/1847), which adds
|
||||
the forms these field ids come from. Nothing else in this change is.
|
||||
|
||||
- [ ] 5.1 `generateManualSubmissionUrl()` in `src/commands/feedback.ts:127` — take
|
||||
the type, target `template=bug_report.yml` / `feature_request.yml`, and emit
|
||||
one parameter per form field id
|
||||
- [ ] 5.2 Omit a field the command has nothing for, rather than sending it empty
|
||||
- [ ] 5.3 Cap the encoded URL; over the cap, return today's blank-issue URL with
|
||||
the body intact
|
||||
- [ ] 5.4 Return the blank-issue URL unconditionally for `--type feedback`, which
|
||||
has no form
|
||||
- [ ] 5.5 Keep the `--- FORMATTED FEEDBACK ---` block printing the complete body
|
||||
on every fallback path, so no report depends on the URL surviving
|
||||
|
||||
## 6. Tests
|
||||
|
||||
The first one fails as soon as task 3.2 lands; the rest fail at 13.
|
||||
|
||||
- [ ] 6.1 `test/core/profiles.test.ts:12` — `expect(CORE_WORKFLOWS).toEqual([...])`
|
||||
is an exact-equality assertion on the six core ids and breaks at seven
|
||||
- [ ] 6.2 `test/core/profiles.test.ts:27` (`ALL_WORKFLOWS` length) and `:31`
|
||||
(exact id list)
|
||||
- [ ] 6.3 `test/core/shared/skill-generation.test.ts` — the count assertions at
|
||||
`:13`, `:94`, `:149`
|
||||
- [ ] 6.4 `test/core/shared/tool-detection.test.ts:33`
|
||||
- [ ] 6.5 `test/core/onboarding-commands.test.ts:52` — the note's exact text; note
|
||||
that `feedback` joining the **core** profile keeps this at "6 more
|
||||
workflows", so verify rather than assume
|
||||
- [ ] 6.6 `test/core/init.test.ts:2117` and `test/core/update.test.ts:3441` —
|
||||
optional-workflow enumerations
|
||||
- [ ] 6.7 `test/utils/command-references.test.ts:92` and `:139`
|
||||
- [ ] 6.8 `test/commands/config-profile.test.ts` — a core workflow is checked by
|
||||
default, unlike the non-core pattern at `:222`
|
||||
|
||||
New coverage:
|
||||
|
||||
- [ ] 6.9 `test/commands/feedback.test.ts` — one case per `--type` asserting
|
||||
title, body sections, and requested label; the unrecognized-type rejection;
|
||||
and that the bare command is byte-identical to today's output
|
||||
- [ ] 6.10 Fallback URL: form targeted per type, fields prefilled by id, blank-issue
|
||||
URL for `feedback`, and blank-issue URL when over the cap
|
||||
- [ ] 6.11 A test asserting the field ids the CLI hard-codes are exactly the ids
|
||||
in `.github/ISSUE_TEMPLATE/bug_report.yml` and `feature_request.yml`, so a
|
||||
renamed form field fails CI here rather than emptying a field for users
|
||||
- [ ] 6.12 `test/core/init.test.ts` / `test/core/update.test.ts` — the feedback
|
||||
skill and command are written on the default profile
|
||||
|
||||
## 7. Parity: hashes and the static mirror
|
||||
|
||||
`scripts/parity-hash-shared.mjs` refuses to run while a registered skill has no
|
||||
pinned hash, so 7.1 comes before 7.2.
|
||||
|
||||
- [ ] 7.1 `test/core/templates/skill-templates-parity.test.ts` — add
|
||||
`openspec-feedback` to `EXPECTED_GENERATED_SKILL_CONTENT_HASHES` and to
|
||||
`GENERATED_SKILL_FACTORIES`, add the command template to
|
||||
`EXPECTED_FUNCTION_HASHES`, and delete the comment at `:83` that explains
|
||||
why feedback was excluded
|
||||
- [ ] 7.2 `pnpm build && pnpm regen:parity-hashes`, then
|
||||
`pnpm vitest run test/core/templates/skill-templates-parity.test.ts`
|
||||
- [ ] 7.3 `pnpm build && pnpm generate:skills` to write
|
||||
`skills/openspec-feedback/SKILL.md`, and commit it —
|
||||
`test/core/templates/skillssh-parity.test.ts` asserts the committed
|
||||
directory set equals `getSkillTemplates()` exactly
|
||||
|
||||
## 8. Docs
|
||||
|
||||
- [ ] 8.1 Add the workflow to `docs/commands.md`, `docs/workflows.md`,
|
||||
`docs/supported-tools.md`, `docs/glossary.md`, and `README.md`, matching how
|
||||
each already lists `verify` and `onboard`
|
||||
- [ ] 8.2 `docs-lab/reference/skills.md`, `docs-lab/customize/profiles.md`, and
|
||||
`docs-lab/reference/configuration/config-json.md`
|
||||
|
||||
## 9. Gate
|
||||
|
||||
- [ ] 9.1 `pnpm build && pnpm test && pnpm exec tsc --noEmit && pnpm lint` — the
|
||||
four commands CI runs; `pnpm build` first, because the suite runs against
|
||||
the build output
|
||||
- [ ] 9.2 `pnpm changeset` — this changes what every user gets from `init` and
|
||||
`update`
|
||||
- [ ] 9.3 `openspec validate ship-feedback-workflow --strict`
|
||||
Reference in New Issue
Block a user