Compare commits

...
Author SHA1 Message Date
Clay GoodandClaude Opus 5.5 e162cd74e5 docs(feedback): document the bug-form body and link, and ask for redaction
- docs-lab/reference/cli.md (the live CLI reference) still described the old
  feedback body and a generic prefilled new-issue URL. It now describes the
  bug-form headings and the link that opens the bug form with title, what
  happened, version and environment filled in.
- The bug form asks for full command output, logs and agent session traces;
  both fields now tell reporters to remove credentials, personal data and
  private context first.
- The docs form's example pointed at legacy docs/cli.md; it now points at
  the published page, or its file under docs-lab/.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-16 17:12:22 -05:00
Clay GoodandClaude Opus 5 3e93916a3f docs(issue-templates): turn off blank issues
The forms carry the standards the contribution policy asks for, so a
blank issue was a way around the only intake path we have. Nothing emits
a blank-issue URL any more: `openspec feedback` links to the bug form
with its fields prefilled, and the docs link the docs form. The setting
governs the web chooser only, so `openspec feedback` keeps filing
through the API, and the contact links still catch anything the three
forms do not fit.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-10-16 16:29:09 -05:00
Clay Good bab8d0fdb2 Merge remote-tracking branch 'origin/main' into pr2044 2026-10-16 16:24:39 -05:00
Clay GoodandClaude Opus 5 1801969750 docs: route every contribution entry point through an issue
The docs still invited pull requests in four places the new repository
settings make impossible, so a contributor following them would hit a
wall rather than the happy path:

- docs/community.md told people to open a PR adding their project; they
  now open an issue with the line and a maintainer adds it
- docs/customization.md offered a PR for a community schema row
- docs/faq.md and docs/README.md offered "an issue or a PR" for doc fixes
  and now link the docs form directly

Each one links the announcement, and CONTRIBUTING says a proposal PR is
how we build an agreed change rather than a way to pitch one — the step
the announcement moves back to the issue. CONTRIBUTING also points at
`openspec feedback` as the one-command way to file a bug.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-10-16 16:24:10 -05:00
Clay GoodandClaude Opus 5 b9741ba821 fix(feedback): file issues that match the bug form
Issues are now the only entry point, but `openspec feedback` wrote a
generic Summary/Details body and linked to a blank issue, so anything
filed by an agent arrived without the structure the form asks for.
`gh issue create --body` cannot fill an issue form, so each path gets
as close as it can:

- the body uses the bug form's own headings, and names the missing
  repro steps instead of leaving them silently absent
- the manual-submission link opens the bug form with what_happened,
  version and environment prefilled by their field ids, leaving the
  required fields only the reporter can answer

A test reads the field ids out of bug_report.yml and fails if the URL
prefills a key the form does not define, since GitHub drops an unknown
key without complaining.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-10-16 16:19:01 -05:00
Clay GoodandClaude Opus 5 53441a2bc6 docs(issue-templates): sharpen the forms for an issue-first process
Issues are now the only entry point, so the forms carry the intake that
PR review used to. Pull the announcement's standards into them:

- bug form: say up front that reproducibility is what helps most, add
  "What you were trying to do" (bug or deliberate trade-off), ask for
  error messages and logs, and widen the session-trace field to cover
  context and repeats across sessions
- feature form: explain that we want the problem rather than a solution
  and that a limitation can be a design choice; split "What you tried"
  into alternatives considered and the maintenance trade-off; mark the
  proposal field as genuinely optional
- both forms: ask the author to read an agent-drafted issue through first
- docs form: optional "What it should say instead"

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-10-16 11:24:58 -05:00
Clay GoodandClaude Opus 5 aa2eac0fb6 docs(contributing): match the published contribution policy
Align CONTRIBUTING.md, the README and the templates with the announcement
at https://openspec.dev/blog/openspec-contribution-policy/ and with
maintainer-only PR creation:

- state plainly that unsolicited PRs are not accepted, and that PRs come
  from maintainers or invited contributors
- replace the "wait for the label, then open a PR" step with the real
  flow: we discuss on the issue, then a maintainer implements it or
  invites you to build it
- carry over the announcement's issue-quality guidance (repro for bugs,
  agent/model plus session traces for agent behavior, problem + who it
  affects + alternatives + maintenance cost for features)
- add an optional agent session trace field to the bug form
- rename "Make your change" to "Development setup"

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-10-16 11:19:58 -05:00
Clay GoodandClaude Opus 5 a5e8fe509c docs(contributing): drop the CI gate in favor of repo settings
Per review: enforce the issue-first flow through GitHub repository
settings (maintainer-opened PRs) instead of building approval-gate
logic in CI. Removes the Contribution gate and Bug repro steps
workflows and the docs that promised CI enforcement; the issue forms,
PR template, and three-step process stay.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-10-16 10:00:26 -05:00
Clay GoodandClaude Opus 5.5 bf662cddb8 fix(ci): harden the contribution gate and bug-repro checks
- Ignore HTML comments in the PR body, so the template's own
  "Part of #123" example is never read as the link.
- Require `Part of #N` on proposal PRs and `Closes #N` on implementation
  PRs, so a proposal no longer closes its feature issue on merge.
- Accept the `Closes: #N` colon form GitHub also accepts.
- Only touch comments this workflow posted (github-actions[bot]), never
  another bot's, and tolerate deleted users.
- bug-repro: look for steps under a reproduce heading or a "Steps to
  reproduce" line, not the first sentence that says "reproduce".
- README: replace the old discussion-first contributing line.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-09 08:59:05 -05:00
Clay Good dab75ea37c Merge remote-tracking branch 'origin/main' into HEAD 2026-10-08 15:10:55 -05:00
Clay GoodandClaude Opus 5.5 16eb813046 docs(contributing): require an approved issue before every PR
Contributors open an issue, wait for the `approved` label, then open
one PR for a bug or docs fix, or a proposal PR followed by an
implementation PR for a feature. A new Contribution gate workflow checks
this on community PRs, and a Bug repro steps workflow asks for steps on
bug issues that lack them.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-05 15:25:05 -05:00
15 changed files with 226 additions and 78 deletions
+5
View File
@@ -0,0 +1,5 @@
---
'@fission-ai/openspec': patch
---
`openspec feedback` now files issues that match the bug form: the body uses the form's headings and spells out the missing repro steps, and the manual-submission link opens the bug form itself with the message, version, and environment prefilled.
+25 -3
View File
@@ -5,17 +5,31 @@ body:
- type: markdown
attributes:
value: |
Thanks for reporting this.
Thanks for reporting this. Issues are how every change to OpenSpec starts —
we don't accept unsolicited pull requests
([why](https://openspec.dev/blog/openspec-contribution-policy/)).
What helps most here is reproducibility. If an agent wrote this report,
please read it through and check it says what you actually hit.
You can also run `openspec feedback "your report"` to submit an issue
immediately with your version and platform, or get a submission link
if GitHub CLI is unavailable or unauthenticated.
- type: textarea
id: goal
attributes:
label: What you were trying to do
description: The task you were in the middle of, not the symptom. It tells us whether the behavior is a bug or a trade-off.
placeholder: Archiving a finished change so its specs land in openspec/specs/.
validations:
required: true
- type: textarea
id: what_happened
attributes:
label: What happened
description: The actual behavior. Paste the command you ran and its output if you have it.
description: The actual behavior. Paste the command you ran and its full output, plus any error message or log, if you have them. Remove credentials, tokens, personal data, and anything private before posting.
placeholder: |
I ran `openspec archive add-login` and it exited 0 without writing the main spec.
validations:
@@ -33,7 +47,7 @@ body:
id: repro
attributes:
label: Minimal steps to reproduce
description: The shortest path from a fresh project to the problem. This is the single most useful thing you can give us.
description: The shortest path from a fresh project to the problem, ideally a minimal example. This is the single most useful thing you can give us — we can't fix what we can't reproduce.
placeholder: |
1. `openspec init` in an empty directory
2. ...
@@ -59,6 +73,14 @@ body:
validations:
required: false
- type: textarea
id: agent_trace
attributes:
label: Agent session trace
description: If a coding agent was involved, paste or link the session trace, and say whether the behavior repeats across sessions. Traces often hold credentials, personal data, or private code and context; remove them before posting. Which files the agent read, which tools it called, and what was in its context is often what narrows an agent-behavior problem down.
validations:
required: false
- type: input
id: environment
attributes:
+10 -5
View File
@@ -1,9 +1,14 @@
# Keep the prefilled blank-issue URL from `openspec feedback` working.
blank_issues_enabled: true
# Issues are the only way a change to OpenSpec starts, so the forms are the only
# way in: they carry the standards the contribution policy asks for
# (https://openspec.dev/blog/openspec-contribution-policy/). Anything the three
# forms don't fit goes to the contact links below.
#
# Nothing emits a blank-issue URL any more: `openspec feedback` links to the bug
# form with its fields prefilled, and the docs link the docs form. This setting
# governs the web chooser only, so `openspec feedback` keeps filing through the
# API either way.
blank_issues_enabled: false
contact_links:
- name: Core design change
url: https://github.com/Fission-AI/OpenSpec/discussions/categories/ideas
about: Anything that changes how OpenSpec works at its core starts as a discussion, per CONTRIBUTING step 1.
- name: Question or help with your setup
url: https://github.com/Fission-AI/OpenSpec/discussions/categories/q-a
about: Not sure whether it is a bug? Ask here and we will help you narrow it down.
+28
View File
@@ -0,0 +1,28 @@
name: Docs
description: Something in the docs is wrong, missing, or unclear.
labels: ['documentation', 'needs-triage']
body:
- type: input
id: where
attributes:
label: Where
description: A link to the page on openspec.dev, or its file under docs-lab/.
placeholder: https://openspec.dev/docs/cli, "openspec archive" section
validations:
required: true
- type: textarea
id: problem
attributes:
label: What is wrong or missing
placeholder: It says `--yes` skips the spec sync, but it only skips the prompt.
validations:
required: true
- type: textarea
id: expected
attributes:
label: What it should say instead
description: Optional. What you expected to read, or what you found to be true once you worked it out.
validations:
required: false
+19 -6
View File
@@ -5,9 +5,14 @@ body:
- type: markdown
attributes:
value: |
If this would change OpenSpec's core design, open a
[discussion](https://github.com/Fission-AI/OpenSpec/discussions) instead — see
[CONTRIBUTING.md](https://github.com/Fission-AI/OpenSpec/blob/main/CONTRIBUTING.md).
Issues are how every change to OpenSpec starts — we don't accept unsolicited
pull requests ([why](https://openspec.dev/blog/openspec-contribution-policy/)).
We're looking for more than a proposed solution here: we want to understand the
problem, who else it affects, and whether OpenSpec should take it on. Sometimes a
limitation is a deliberate design choice, and that's worth discussing first.
You don't need all the answers. If an agent drafted this, please read it through
first — an agent usually argues from your workflow alone.
- type: textarea
id: problem
@@ -30,8 +35,16 @@ body:
- type: textarea
id: tried
attributes:
label: What you tried
description: Existing commands, flags, or workarounds you reached for, and why they fell short.
label: Alternatives you considered
description: Existing commands, flags, or workarounds you reached for, and why each one fell short.
validations:
required: false
- type: textarea
id: tradeoff
attributes:
label: Why it's worth the added complexity
description: Optional. Every option and flag is something we maintain across many agents, models, and workflows. Tell us what makes this one carry its weight, and what it might cost people who don't need it.
validations:
required: false
@@ -39,6 +52,6 @@ body:
id: proposal
attributes:
label: What you have in mind
description: Optional. A sketch is fine — we will agree on the approach before anyone builds it.
description: Optional, and genuinely optional. If we agree the problem is worth solving, we design the fix from there — a solution here doesn't move the issue along faster.
validations:
required: false
+7 -14
View File
@@ -1,30 +1,23 @@
Closes #
<!--
No issue yet? Every change starts with one (CONTRIBUTING step 1).
Run `openspec feedback "your report"` to submit immediately, or open one here:
https://github.com/Fission-AI/OpenSpec/issues/new/choose
Already discussed instead of filed? Replace the line above with a link to the discussion.
PRs come from maintainers and invited contributors, and every one links an
approved issue (CONTRIBUTING.md).
Feature proposal PR? Write "Part of #123" instead, so the issue stays open.
-->
## What this changes
<!-- What was wrong or missing, and what the new behavior is. Plain language. -->
<!-- What was wrong or missing, and what it does now. -->
## How you verified it
<!--
The failing-then-passing test, repro steps, or before/after output.
Run the core checks for code changes:
pnpm build && pnpm test && pnpm exec tsc --noEmit && pnpm lint
For code: pnpm build && pnpm test && pnpm exec tsc --noEmit && pnpm lint
-->
## Notes
<!-- Optional: scope limits, follow-ups, anything non-blocking. -->
---
- [ ] Ran `pnpm changeset` if this affects users, and committed the file
- [ ] If a coding agent wrote this, named the agent and model in the Notes section, and verified the result myself
- [ ] Ran `pnpm changeset` if this affects users
- [ ] If a coding agent wrote this, named the agent and model above, and verified the result myself
+24 -13
View File
@@ -2,26 +2,38 @@
Thanks for helping improve OpenSpec.
## 1. Open a discussion or an issue first
**We are not accepting unsolicited pull requests.** Contributions start with a GitHub issue, and pull requests come from maintainers or from contributors we have invited to build a change with us. See [An Update to OpenSpec's Contribution Policy](https://openspec.dev/blog/openspec-contribution-policy/) for why.
Every change starts here, including small ones.
## 1. Open an issue
- [Start a discussion](https://github.com/Fission-AI/OpenSpec/discussions) if it affects OpenSpec's core design.
- [Open an issue](https://github.com/Fission-AI/OpenSpec/issues) for bugs and everything else.
[Pick a form](https://github.com/Fission-AI/OpenSpec/issues/new/choose): bug, feature, or docs.
This is so we can agree on the approach before you spend time building. PRs without a linked issue or a prior discussion may be closed.
- **Bug:** what you were trying to do, what happened, what you expected, and the shortest steps that reproduce it from a fresh project. Error messages, logs, and a minimal example help most. We can't fix what we can't reproduce.
- **Agent behavior:** say which agent and model you used, and attach a session trace if you have one. Which files the agent read, which tools it called, and whether the behavior repeats across sessions is usually what narrows it down.
- **Feature:** describe the problem, who else it affects, what you tried instead, and whether the added complexity is worth maintaining. You don't need a solution; sometimes the behavior you hit is a deliberate trade-off, and that is what we want to talk about.
## 2. Decide whether it needs a change proposal
From a project you're working in, `openspec feedback "what happened"` files a bug for you with your version and platform filled in, or hands you a prefilled form link.
A bug fix, a typo, or a small improvement goes straight to a PR.
Write the issue yourself rather than forwarding an agent's diagnosis. We don't expect you to have all the answers, but we do want the issue to show that you have considered the problem.
A new feature, a significant refactor, or anything that changes OpenSpec's architecture needs an OpenSpec change proposal first, so we can align on intent and goals before implementation begins. Open it as a PR containing only `openspec/changes/<name>/` and wait for it to be approved before you write the code.
## 2. We discuss it on the issue
When writing a proposal, keep the OpenSpec philosophy in mind: we serve a wide variety of users across different coding agents, models, and use cases. Changes should work well for everyone.
We work out the problem, the possible approaches, and whether it fits OpenSpec's priorities. OpenSpec runs across many agents, models, and workflows, so a change that helps one workflow can break another.
If you are not sure which side of the line your change falls on, ask in the discussion or issue from step 1.
If we agree to pursue it, a maintainer adds the `approved` label and then either implements it or invites you to build it with us. Say on the issue if you would like to write the fix. We can't promise to take on every suggestion.
## 3. Make your change
Opening pull requests is restricted to maintainers, so please don't start one until we have invited you to.
## 3. Open the PR (invited contributors and maintainers)
**Bug or docs:** one PR with the fix. Write `Closes #123` in the description. It shouldn't change `openspec/`, since specs are for features.
**Feature:** two PRs. This is how we build a change we've already agreed on — a proposal PR is not a way to pitch one, which is the step the announcement moves back to the issue.
1. **Proposal:** only `openspec/changes/<name>/`. Write `Part of #123`, so the issue stays open.
2. **Implementation:** after the proposal merges, the code plus updates to that change, such as checking off its `tasks.md`. Write `Closes #123`.
## Development setup
You need Node 20.19+ and pnpm.
@@ -46,11 +58,10 @@ Editors, agents and OpenSpec Desktop run the CLI many times, and each call pays
`test/cli-e2e/startup-modules.test.ts` checks which modules each command loads, and fails if a definition starts pulling in an implementation. When you add a command, add it to that test's list.
## 4. Open the PR
## PR checklist
- Branch off `main` in your fork.
- Title it as a conventional commit: `type(scope): subject`, for example `fix(archive): keep authored Purpose`.
- Link what you opened in step 1: `Closes #123` for an issue, or a link to the discussion when there is no issue.
- If a coding agent wrote the code, say which agent and model, and confirm you tested it. AI-generated code is welcome when it has been verified.
Maintainers are listed in [MAINTAINERS.md](MAINTAINERS.md).
+1 -1
View File
@@ -220,7 +220,7 @@ openspec update
## Contributing
Open a discussion (for core design changes) or an issue before you open a PR, and link the issue or discussion from the PR. New features, significant refactors, and architectural changes need an OpenSpec change proposal first.
We are not accepting unsolicited pull requests. Open an issue first; if we agree to pursue it, a maintainer implements the change or invites you to build it with us. See [the announcement](https://openspec.dev/blog/openspec-contribution-policy/).
→ **[CONTRIBUTING.md](CONTRIBUTING.md)**: the full process, from first issue to merged PR
+2 -2
View File
@@ -2288,7 +2288,7 @@ openspec feedback "Validate output is hard to scan"
openspec feedback "Archive fails on Windows" --body "Steps: init, propose, archive. Error: EPERM."
```
The CLI files your message as a GitHub issue on the `Fission-AI/OpenSpec` repo through your `gh` CLI. The title becomes `Feedback: <message>`. The body holds your `--body` text plus a footer with CLI version, platform, and timestamp. The issue gets the `feedback` label. If the repo doesn't define that label, the CLI retries without it and says so.
The CLI files your message as a GitHub issue on the `Fission-AI/OpenSpec` repo through your `gh` CLI. The title becomes `Feedback: <message>`. The body uses the bug form's headings: your message under `What happened`, your `--body` text under `Details`, and a `Minimal steps to reproduce` section asking you to add the steps, since the CLI can't fill those in. A footer adds CLI version, platform, and timestamp. The issue gets the `feedback` label. If the repo doesn't define that label, the CLI retries without it and says so.
**Arguments**
@@ -2311,7 +2311,7 @@ On success:
Issue URL: https://github.com/Fission-AI/OpenSpec/issues/1234
```
Without `gh` installed, or with `gh` not logged in, nothing is submitted. The CLI prints your formatted feedback between `--- FORMATTED FEEDBACK ---` markers, then a prefilled new-issue URL to open in the browser. The not-logged-in path adds `To auto-submit in the future: gh auth login`.
Without `gh` installed, or with `gh` not logged in, nothing is submitted. The CLI prints your formatted feedback between `--- FORMATTED FEEDBACK ---` markers, then a link that opens the bug report form with the title, what happened (your message and `--body` text), CLI version, and environment (platform and Node version) filled in. You complete the rest of the form in the browser. The not-logged-in path adds `To auto-submit in the future: gh auth login`.
**Exit codes**
+1 -1
View File
@@ -115,4 +115,4 @@ Steps 1 and 2 happen in your terminal. The rest happen in your AI assistant's ch
- **GitHub Issues:** [github.com/Fission-AI/OpenSpec/issues](https://github.com/Fission-AI/OpenSpec/issues) for bugs and feature requests.
- **`openspec feedback "your message"`** sends feedback straight from your terminal (it opens a GitHub issue).
Found something in these docs that's wrong, stale, or confusing? That's a bug. Open an issue or a PR. Documentation improvements are some of the most valuable contributions you can make.
Found something in these docs that's wrong, stale, or confusing? That's a bug — open a [docs issue](https://github.com/Fission-AI/OpenSpec/issues/new?template=docs.yml). Every change to OpenSpec starts as an issue, including doc fixes ([why](https://openspec.dev/blog/openspec-contribution-policy/)).
+2 -2
View File
@@ -1,6 +1,6 @@
# Community Showcase
A community-owned awesome list of projects and resources built with and for OpenSpec. Tools, integrations, workflows, and learning resources are welcome. Community members grow and maintain this showcase through pull requests.
A community-owned awesome list of projects and resources built with and for OpenSpec. Tools, integrations, workflows, and learning resources are welcome. Community members grow this showcase by opening issues; maintainers add the entries.
Listed projects are maintained independently. Inclusion does not imply official support or endorsement by OpenSpec. See each project's documentation and issue tracker for setup and support.
@@ -19,7 +19,7 @@ Hosted products that add an OpenSpec capability the CLI does not provide. Each e
## Add your project
Open a pull request adding one line to this file with your project's name, a direct link, and a short description of how it relates to OpenSpec.
Open an issue with the one line you would like added: your project's name, a direct link, and a short description of how it relates to OpenSpec. A maintainer adds it to this file — we don't accept unsolicited pull requests ([why](https://openspec.dev/blog/openspec-contribution-policy/)).
- Keep entries focused on something built with OpenSpec or supporting its use, rather than general product advertising.
- Describe what people can use. Avoid promotional claims, referral links, and tracking links.
+1 -1
View File
@@ -425,7 +425,7 @@ Community schemas are not vendored into OpenSpec core — they live in their own
| `anvil` | @jikkujoyce | [jikkujoyce/openspec-schemas](https://github.com/jikkujoyce/openspec-schemas/tree/main/schemas/anvil) | Spec-driven workflow with TDD discipline and an adversarial review step. Flow: `proposal` → `specs` → `design` → `review` → `test-plan` → `tasks` → `apply` → `verify`. `review` is written by a fresh-context, read-only reviewer (a second model when one is available) and emits a `VERDICT:` line telling the agent to gate `test-plan`, `tasks`, and `apply`; OpenSpec only checks that artifacts exist, so enforce the gate with your own CI or hook. `test-plan` maps every spec scenario to a named test and doubles as a red/green ledger that `verify` audits. |
| `ml-research` | @tsenst | [idealo/openspec-schemas](https://github.com/idealo/openspec-schemas/tree/main/openspec/schemas/ml-research) | Machine-learning experiment workflow. Flow: `proposal` → `design` → `evaluation` → `tasks` → `apply` → `specs`. `evaluation` fixes metrics, success criteria, and the falsification condition before the run; `apply` writes `results.md`; `specs` records only evidence-backed findings (refuted hypotheses included) as `Finding:` requirements with linked `Evidence:` blocks. |
> Want to contribute a community schema? Open an issue with a link to your repository, or submit a PR adding a row to this table.
> Want to contribute a community schema? Open an issue with a link to your repository and a one-line description, and a maintainer adds the row.
---
+1 -1
View File
@@ -152,4 +152,4 @@ There's no uninstall command, because it's just a global package plus files in y
### These docs are wrong or confusing. What do I do?
Tell us, or fix it. Documentation PRs are welcome and valued. Open an issue or send a pull request.
Tell us. Open a [docs issue](https://github.com/Fission-AI/OpenSpec/issues/new?template=docs.yml) with the page and what is wrong — doc fixes are some of the most useful reports we get. We don't accept unsolicited pull requests ([why](https://openspec.dev/blog/openspec-contribution-policy/)); a maintainer makes the fix, or invites you to.
+39 -20
View File
@@ -5,6 +5,8 @@ import os from 'os';
const require = createRequire(import.meta.url);
const MAX_TITLE_LENGTH = 72;
const TITLE_PREFIX = 'Feedback: ';
const REPRO_PROMPT =
'_Not provided — filed with `openspec feedback`. If this is a bug, please add the shortest steps that reproduce it from a fresh project._';
/**
* Check if gh CLI is installed and available in PATH
@@ -110,14 +112,20 @@ function formatTitle(message: string): string {
/**
* Format the full feedback body
*
* The headings mirror the bug form's labels (.github/ISSUE_TEMPLATE/bug_report.yml)
* so a CLI-filed issue reads like one filed through the form. `gh issue create
* --body` cannot fill an issue form, so the repro prompt is spelled out instead
* of being left silently empty.
*/
function formatBody(message: string, bodyText?: string): string {
const parts = ['## Summary', '', message];
const parts = ['### What happened', '', message];
if (bodyText) {
parts.push('', '## Details', '', bodyText);
parts.push('', '### Details', '', bodyText);
}
parts.push('', '### Minimal steps to reproduce', '', REPRO_PROMPT);
parts.push('', generateMetadata());
return parts.join('\n');
@@ -125,14 +133,21 @@ function formatBody(message: string, bodyText?: string): string {
/**
* Generate a pre-filled GitHub issue URL for manual submission
*
* Opens the bug form itself. The query keys are the form's own field ids, which
* is how GitHub prefills an issue form; the fields it leaves empty are the
* required ones only the reporter can answer.
*/
function generateManualSubmissionUrl(title: string, body: string): string {
const repo = 'Fission-AI/OpenSpec';
const encodedTitle = encodeURIComponent(title);
const encodedBody = encodeURIComponent(body);
const encodedLabels = encodeURIComponent('feedback');
function generateManualSubmissionUrl(title: string, message: string, bodyText?: string): string {
const params = new URLSearchParams({
template: 'bug_report.yml',
title,
what_happened: bodyText ? `${message}\n\n${bodyText}` : message,
version: getVersion(),
environment: `${getPlatform()}, Node ${process.version}`,
});
return `https://github.com/${repo}/issues/new?title=${encodedTitle}&body=${encodedBody}&labels=${encodedLabels}`;
return `https://github.com/Fission-AI/OpenSpec/issues/new?${params.toString()}`;
}
/**
@@ -166,7 +181,7 @@ function isMissingLabelError(error: any): boolean {
* network, rate limit, ...), so show the same manual-submission path the
* missing-gh and unauthenticated flows get instead of discarding the text.
*/
function reportGhFailure(error: any, title: string, body: string): void {
function reportGhFailure(error: any, title: string, body: string, url: string): void {
// Display the error output from gh CLI
if (error.stderr) {
console.error(error.stderr.toString());
@@ -176,9 +191,8 @@ function reportGhFailure(error: any, title: string, body: string): void {
displayFormattedFeedback(title, body);
const manualUrl = generateManualSubmissionUrl(title, body);
console.log('Please submit your feedback manually:');
console.log(manualUrl);
console.log(url);
// Exit with the same code as gh CLI
process.exit(error.status ?? 1);
@@ -212,7 +226,7 @@ function createIssue(title: string, body: string, labels: string[]): string {
/**
* Submit feedback via gh CLI
*/
function submitViaGhCli(title: string, body: string): void {
function submitViaGhCli(title: string, body: string, url: string): void {
let issueUrl: string;
let labelApplied = true;
@@ -220,7 +234,7 @@ function submitViaGhCli(title: string, body: string): void {
issueUrl = createIssue(title, body, ['feedback']);
} catch (error: any) {
if (!isMissingLabelError(error)) {
reportGhFailure(error, title, body);
reportGhFailure(error, title, body, url);
return;
}
@@ -230,7 +244,7 @@ function submitViaGhCli(title: string, body: string): void {
issueUrl = createIssue(title, body, []);
labelApplied = false;
} catch (retryError: any) {
reportGhFailure(retryError, title, body);
reportGhFailure(retryError, title, body, url);
return;
}
}
@@ -248,7 +262,12 @@ function submitViaGhCli(title: string, body: string): void {
/**
* Handle fallback when gh CLI is not available or not authenticated
*/
function handleFallback(title: string, body: string, reason: 'missing' | 'unauthenticated'): void {
function handleFallback(
title: string,
body: string,
url: string,
reason: 'missing' | 'unauthenticated'
): void {
if (reason === 'missing') {
console.log('⚠️ GitHub CLI not found. Manual submission required.');
} else {
@@ -257,9 +276,8 @@ function handleFallback(title: string, body: string, reason: 'missing' | 'unauth
displayFormattedFeedback(title, body);
const manualUrl = generateManualSubmissionUrl(title, body);
console.log('Please submit your feedback manually:');
console.log(manualUrl);
console.log(url);
if (reason === 'unauthenticated') {
console.log('\nTo auto-submit in the future: gh auth login');
@@ -277,20 +295,21 @@ export class FeedbackCommand {
// Format title and body once for all code paths
const title = formatTitle(message);
const body = formatBody(message, options?.body);
const url = generateManualSubmissionUrl(title, message, options?.body);
// Check if gh CLI is installed
if (!isGhInstalled()) {
handleFallback(title, body, 'missing');
handleFallback(title, body, url, 'missing');
return;
}
// Check if gh CLI is authenticated
if (!isGhAuthenticated()) {
handleFallback(title, body, 'unauthenticated');
handleFallback(title, body, url, 'unauthenticated');
return;
}
// Submit via gh CLI
submitViaGhCli(title, body);
submitViaGhCli(title, body, url);
}
}
+61 -9
View File
@@ -1,6 +1,7 @@
import { describe, it, expect, beforeEach, afterEach, vi } from 'vitest';
import { FeedbackCommand } from '../../src/commands/feedback.js';
import { execFileSync } from 'child_process';
import { readFileSync } from 'fs';
// Every subprocess the command runs — the gh lookup, the auth probe, and the
// issue creation — goes through execFileSync; there is no shell anywhere.
@@ -216,7 +217,7 @@ describe('FeedbackCommand', () => {
const args = ghIssueSync.mock.calls[0][1] as string[];
const body = args[args.indexOf('--body') + 1];
expect(body).toContain(
`## Summary\n\n${message}\n\n## Details\n\n${details}\n\n---`
`### What happened\n\n${message}\n\n### Details\n\n${details}\n\n### Minimal steps to reproduce`
);
});
@@ -238,7 +239,7 @@ describe('FeedbackCommand', () => {
);
expect(title.length).toBeLessThanOrEqual(72);
expect(title).not.toMatch(/[\r\n]/);
expect(body).toContain(`## Summary\n\n${message}`);
expect(body).toContain(`### What happened\n\n${message}`);
});
it('should not split Unicode grapheme clusters when shortening a title', async () => {
@@ -526,10 +527,10 @@ describe('FeedbackCommand', () => {
expect.stringContaining('--- END FEEDBACK ---')
);
expect(consoleLogSpy).toHaveBeenCalledWith(
expect.stringContaining(`## Summary\n\n${message}`)
expect.stringContaining(`### What happened\n\n${message}`)
);
expect(consoleLogSpy).toHaveBeenCalledWith(
expect.stringContaining('## Details\n\nTest body')
expect.stringContaining('### Details\n\nTest body')
);
});
@@ -561,11 +562,62 @@ describe('FeedbackCommand', () => {
});
expect(urlCall).toBeDefined();
// Verify URL has proper parameters
const url = urlCall?.[0];
expect(url).toContain('title=');
expect(url).toContain('body=');
expect(url).toContain('labels=feedback');
// Verify the URL opens the bug form with the fields the CLI knows
// prefilled by their form ids, and leaves the rest to the reporter.
const parsed = new URL(/https?:\/\/\S+/.exec(String(urlCall?.[0]))![0]);
expect(parsed.searchParams.get('template')).toBe('bug_report.yml');
expect(parsed.searchParams.get('title')).toBe('Feedback: Test');
expect(parsed.searchParams.get('what_happened')).toBe('Test');
expect(parsed.searchParams.get('version')).toBeTruthy();
expect(parsed.searchParams.get('environment')).toContain('Node ');
expect(parsed.searchParams.get('repro')).toBeNull();
expect(parsed.searchParams.get('body')).toBeNull();
});
it('should prefill only ids the bug form actually defines', async () => {
setGhProbes({ installed: false });
try {
await feedbackCommand.execute('Test');
} catch {
// Expected to exit
}
const urlCall = consoleLogSpy.mock.calls.find((call: any[]) =>
String(call[0] ?? '').includes('template=bug_report.yml')
);
const parsed = new URL(/https?:\/\/\S+/.exec(String(urlCall?.[0]))![0]);
// GitHub prefills an issue form by field id, so a renamed or mistyped id
// silently drops the value. Check the URL against the form itself.
const form = readFileSync('.github/ISSUE_TEMPLATE/bug_report.yml', 'utf-8');
const fieldIds = [...form.matchAll(/^\s*id:\s*(\S+)$/gm)].map((match) => match[1]);
const prefilled = [...parsed.searchParams.keys()].filter(
(key) => key !== 'template' && key !== 'title'
);
expect(prefilled.length).toBeGreaterThan(0);
for (const key of prefilled) {
expect(fieldIds).toContain(key);
}
});
it('should prefill the form with the message and details together', async () => {
setGhProbes({ installed: false });
try {
await feedbackCommand.execute('Archive wrote nothing', { body: 'Exited 0.' });
} catch {
// Expected to exit
}
const urlCall = consoleLogSpy.mock.calls.find((call: any[]) =>
String(call[0] ?? '').includes('template=bug_report.yml')
);
const parsed = new URL(/https?:\/\/\S+/.exec(String(urlCall?.[0]))![0]);
expect(parsed.searchParams.get('what_happened')).toBe(
'Archive wrote nothing\n\nExited 0.'
);
});
});
});