mirror of
https://github.com/Fission-AI/OpenSpec.git
synced 2026-10-18 05:59:56 +08:00
Compare commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
e162cd74e5 | ||
|
|
3e93916a3f | ||
|
|
bab8d0fdb2 | ||
|
|
1801969750 | ||
|
|
b9741ba821 | ||
|
|
53441a2bc6 | ||
|
|
aa2eac0fb6 | ||
|
|
a5e8fe509c | ||
|
|
bf662cddb8 | ||
|
|
dab75ea37c | ||
|
|
16eb813046 |
@@ -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.
|
||||
@@ -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:
|
||||
|
||||
@@ -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.
|
||||
|
||||
@@ -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
|
||||
@@ -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
|
||||
|
||||
@@ -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
@@ -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).
|
||||
|
||||
@@ -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
|
||||
|
||||
|
||||
@@ -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
@@ -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
@@ -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.
|
||||
|
||||
@@ -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
@@ -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
@@ -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);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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.'
|
||||
);
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
Reference in New Issue
Block a user