Skip to content
Merged
Show file tree
Hide file tree
Changes from 20 commits
Commits
Show all changes
23 commits
Select commit Hold shift + click to select a range
6852127
fix(archive): make the scenario-drift check fence-aware
clay-good Jul 28, 2026
287ec35
fix(update): tear down the redirected request when the budget expires
clay-good Jul 28, 2026
0984400
chore(release): add changesets for user-facing changes missing from t…
clay-good Jul 28, 2026
9985d28
fix(adapters): escape TOML-active characters in Gemini command files
clay-good Jul 28, 2026
aabe589
fix(update): harden install detection and redirect handling
clay-good Jul 28, 2026
d345781
chore(cli): export zcodeAdapter from the barrel and sync a completion…
clay-good Jul 28, 2026
b4224ee
fix(parser): strip a UTF-8 BOM before parsing specs and deltas
clay-good Jul 28, 2026
de42498
fix(cli): reject over-long change names with a validation message
clay-good Jul 28, 2026
40de2bc
fix(archive): finish the early-sync no-op rules for MODIFIED and RENAMED
clay-good Jul 28, 2026
2bc0345
fix(validate): stop reporting an unreadable specs dir as 'no deltas'
clay-good Jul 28, 2026
e636339
fix(update): say when commands-only delivery leaves a tool with nothing
clay-good Jul 28, 2026
0d786e8
fix(completion): honor $ZSH and $ZSH_CUSTOM for Oh My Zsh installs
clay-good Jul 28, 2026
da45c68
fix(init): make the static welcome screen wait for the Enter it asks for
clay-good Jul 28, 2026
16594f0
fix(feedback): keep the manual fallback on every gh failure
clay-good Jul 28, 2026
fd89858
test(update): model the npm shim in the Windows prefix fixture
clay-good Jul 28, 2026
07607d6
fix(update): require volta's full tools/image layout for the undotted…
clay-good Jul 28, 2026
2ca3354
fix(adapters): escape control characters in Gemini multiline prompts
clay-good Jul 28, 2026
a52b64d
fix(completion): finish the $ZSH_CUSTOM support and isolate it in tests
clay-good Jul 28, 2026
e033a4d
chore(release): correct the hermes and zcode changeset wording
clay-good Jul 28, 2026
40657aa
test(feedback): pin the manual fallback on a non-label gh failure
clay-good Jul 28, 2026
3de75e0
fix(completion): match fpath entries as literal strings in the OMZ gu…
clay-good Jul 28, 2026
2f7352f
fix(adapters): never emit a bare carriage return in Gemini TOML prompts
clay-good Jul 28, 2026
6049821
build(nix): update the pnpm deps hash for the smol-toml devDependency
clay-good Jul 28, 2026
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 5 additions & 0 deletions .changeset/add-codearts-tool.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
"@fission-ai/openspec": minor
---

Add CodeArts Agent skills support: `openspec init --tools codeartsagent` installs the workflow skills.
5 changes: 5 additions & 0 deletions .changeset/add-hermes-tool.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
"@fission-ai/openspec": minor
---

Add Hermes Agent as a supported AI tool: `openspec init --tools hermes` installs the workflow skills (Hermes is skills-only and invokes them directly).
5 changes: 5 additions & 0 deletions .changeset/add-zcode-tool.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
"@fission-ai/openspec": minor
---

Add ZCode as a supported AI tool: `openspec init --tools zcode` generates its skills and `/opsx:*` commands.
5 changes: 5 additions & 0 deletions .changeset/avoid-npx-profile-changes.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
"@fission-ai/openspec": patch
---

Apply profile changes with the installed CLI instead of shelling out to `npx`, which could run a different version.
5 changes: 5 additions & 0 deletions .changeset/bom-delta-parsing.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
"@fission-ai/openspec": patch
---

Delta and main-spec parsers strip a UTF-8 BOM, so files saved by Windows editors or PowerShell redirects no longer fail with "No delta sections found".
5 changes: 5 additions & 0 deletions .changeset/change-name-length.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
"@fission-ai/openspec": patch
---

`openspec new change` rejects names over 200 characters with a validation message instead of surfacing a raw ENAMETOOLONG filesystem error.
5 changes: 5 additions & 0 deletions .changeset/codex-skills-only.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
"@fission-ai/openspec": minor
---

Codex is now skills-only: workflows install as `$openspec-*` skills and previously managed custom prompts are retired (existing ones are cleaned up on update).
5 changes: 5 additions & 0 deletions .changeset/doctor-store-drift.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
"@fission-ai/openspec": patch
---

`openspec doctor` now notes when a store checkout is behind its upstream ref.
5 changes: 5 additions & 0 deletions .changeset/drift-check-multiplicity.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
"@fission-ai/openspec": patch
---

Make the archive scenario-drift check multiplicity-aware: a MODIFIED block that keeps only one of two same-named scenarios no longer silently drops the other.
5 changes: 5 additions & 0 deletions .changeset/feedback-manual-fallback.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
"@fission-ai/openspec": patch
---

`openspec feedback` shows the formatted text and a pre-filled submission URL on any gh failure (issues disabled, network, rate limit), not only when gh is missing or unauthenticated.
5 changes: 5 additions & 0 deletions .changeset/fence-aware-drift-check.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
"@fission-ai/openspec": patch
---

The archive scenario-drift check now ignores `#### Scenario:` lines inside fenced code blocks, matching validate: a fenced example no longer false-aborts an archive, and a fenced name no longer masks a genuinely dropped scenario.
5 changes: 5 additions & 0 deletions .changeset/gemini-toml-escaping.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
"@fission-ai/openspec": patch
---

Gemini command files escape TOML-active characters (quotes, backslashes, control characters) in the description and prompt, so a template value containing them can no longer produce an invalid `.toml` file.
5 changes: 5 additions & 0 deletions .changeset/kimi-cli-to-kimi-code.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
"@fission-ai/openspec": patch
---

Follow the Kimi CLI rename to Kimi Code: new install paths with automatic migration of existing `.kimi` setups.
5 changes: 5 additions & 0 deletions .changeset/local-dates-cli.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
"@fission-ai/openspec": patch
---

Use local dates for CLI date-only values (archive names, timestamps) instead of UTC, so late-evening archives no longer get tomorrow's date.
5 changes: 5 additions & 0 deletions .changeset/missing-core-workflows-warning.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
"@fission-ai/openspec": patch
---

`openspec update` warns when a custom profile is missing core workflows instead of silently generating a partial install.
5 changes: 5 additions & 0 deletions .changeset/modified-noop-counting.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
"@fission-ai/openspec": patch
---

Archive treats a MODIFIED delta whose content already matches the main spec as a no-op: a fully early-synced change now reports "Specs already in sync" instead of rewriting the file and claiming modifications.
5 changes: 5 additions & 0 deletions .changeset/multiselect-checkbox-markers.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
"@fission-ai/openspec": patch
---

Render multi-select prompts with `[x]`/`[ ]` checkbox markers instead of radio-button icons.
5 changes: 5 additions & 0 deletions .changeset/nested-spec-discovery.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
"@fission-ai/openspec": patch
---

Discover nested spec paths like `specs/<area>/<capability>/spec.md` recursively and consistently across parse, apply, and archive.
5 changes: 5 additions & 0 deletions .changeset/renamed-near-miss-guard.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
"@fission-ai/openspec": patch
---

An already-synced RENAMED delta aborts when a case/whitespace variant of the source requirement still exists — the same typo guard REMOVED deltas have.
5 changes: 5 additions & 0 deletions .changeset/resolve-open-questions.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
"@fission-ai/openspec": patch
---

Proposal guidance now resolves blocking open questions with the user instead of deferring them to design.md.
5 changes: 5 additions & 0 deletions .changeset/skills-sh-distribution.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
"@fission-ai/openspec": minor
---

Publish the workflow skills as static `skills/<name>/SKILL.md` files so `npx skills add Fission-AI/OpenSpec` works.
5 changes: 5 additions & 0 deletions .changeset/spec-content-guidance.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
"@fission-ai/openspec": patch
---

Specs instructions include the spec content guidance from the concepts docs, so generated specs follow the requirement/scenario format.
5 changes: 5 additions & 0 deletions .changeset/static-welcome-waits.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
"@fission-ai/openspec": patch
---

The static welcome screen (reduced motion, `--no-animation`, narrow terminals) now waits for the Enter it asks for instead of letting the keystroke submit the tool picker unseen.
5 changes: 5 additions & 0 deletions .changeset/store-aware-main-specs.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
"@fission-ai/openspec": patch
---

Sync and archive workflows resolve main specs through the store-aware root instead of assuming `openspec/specs` in the repo.
5 changes: 5 additions & 0 deletions .changeset/symlinked-schema-dirs.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
"@fission-ai/openspec": minor
---

Resolve symlinked schema directories so schemas shared via symlink (e.g. from a dotfiles repo) are discovered.
5 changes: 5 additions & 0 deletions .changeset/update-check-detection-hardening.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
"@fission-ai/openspec": patch
---

The stale-CLI check hardens its install detection: a directory merely named `volta` no longer changes the upgrade hint, the Windows npm-ownership check corroborates against the `openspec.cmd` shim npm actually writes, and a registry redirect from https to plain http is no longer followed.
5 changes: 5 additions & 0 deletions .changeset/update-check-redirect-teardown.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
"@fission-ai/openspec": patch
---

The stale-CLI check tears down a redirected registry connection when its time budget expires instead of leaving the socket open.
5 changes: 5 additions & 0 deletions .changeset/update-zero-artifact-notice.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
"@fission-ai/openspec": patch
---

`openspec update` with `delivery: commands` prints the same configuration correction as init when it removes the skills of a tool that supports only skills, instead of deleting them silently.
5 changes: 5 additions & 0 deletions .changeset/validator-unreadable-specs.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
"@fission-ai/openspec": patch
---

`openspec validate` reports an unreadable specs/ directory as the error it is instead of misdiagnosing it as "no deltas found".
5 changes: 5 additions & 0 deletions .changeset/windows-welcome-input.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
"@fission-ai/openspec": patch
---

Preserve keyboard input on Windows after the welcome screen instead of dropping the first keystrokes.
5 changes: 5 additions & 0 deletions .changeset/zsh-completions-custom-omz.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
"@fission-ai/openspec": patch
---

zsh completion install honors `$ZSH` and `$ZSH_CUSTOM`, so Oh My Zsh setups at custom locations get the completion where their shell actually loads it.
18 changes: 14 additions & 4 deletions src/commands/feedback.ts
Original file line number Diff line number Diff line change
Expand Up @@ -131,16 +131,26 @@ function isMissingLabelError(error: any): boolean {
}

/**
* Report a gh CLI failure and exit, preserving gh's exit code
* Report a gh CLI failure and exit, preserving gh's exit code.
*
* gh failed after the user already typed their feedback (issues disabled,
* 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): void {
function reportGhFailure(error: any, title: string, body: string): void {
// Display the error output from gh CLI
if (error.stderr) {
console.error(error.stderr.toString());
} else if (error.message) {
console.error(error.message);
}

displayFormattedFeedback(title, body);

const manualUrl = generateManualSubmissionUrl(title, body);
console.log('Please submit your feedback manually:');
console.log(manualUrl);

// Exit with the same code as gh CLI
process.exit(error.status ?? 1);
}
Expand Down Expand Up @@ -181,7 +191,7 @@ function submitViaGhCli(title: string, body: string): void {
issueUrl = createIssue(title, body, ['feedback']);
} catch (error: any) {
if (!isMissingLabelError(error)) {
reportGhFailure(error);
reportGhFailure(error, title, body);
return;
}

Expand All @@ -191,7 +201,7 @@ function submitViaGhCli(title: string, body: string): void {
issueUrl = createIssue(title, body, []);
labelApplied = false;
} catch (retryError: any) {
reportGhFailure(retryError);
reportGhFailure(retryError, title, body);
return;
}
}
Expand Down
39 changes: 37 additions & 2 deletions src/core/command-generation/adapters/gemini.ts
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,41 @@
import path from 'path';
import type { CommandContent, ToolCommandAdapter } from '../types.js';

/**
* Control characters (C0 except tab/newline/carriage return, plus DEL) are
* invalid inside TOML strings and must be written as escapes.
*/
const TOML_CONTROL_CHARS = new RegExp('[\\u0000-\\u0008\\u000b\\u000c\\u000e-\\u001f\\u007f]', 'g');

/**
* TOML basic strings are escape-active: a backslash or double quote in the
* value breaks the file if written raw. Newlines cannot appear in a
* single-line basic string at all, so they are escaped too.
*/
function escapeTomlBasicString(value: string): string {
return value
.replace(/\\/g, '\\\\')
.replace(/"/g, '\\"')
.replace(/\n/g, '\\n')
.replace(/\r/g, '\\r')
.replace(/\t/g, '\\t')
.replace(TOML_CONTROL_CHARS, (c) => `\\u${c.charCodeAt(0).toString(16).padStart(4, '0')}`);
}

/**
* Multiline basic strings keep raw newlines and tabs, but backslashes are
* still escape-active, any run of three quotes would end the string, and the
* same control characters are invalid as in single-line basic strings.
* Control chars are escaped last so their introduced backslashes are not
* re-doubled.
*/
function escapeTomlMultilineBasicString(value: string): string {
return value
.replace(/\\/g, '\\\\')
.replace(/"""/g, '""\\"')
.replace(TOML_CONTROL_CHARS, (c) => `\\u${c.charCodeAt(0).toString(16).padStart(4, '0')}`);
}

/**
* Gemini adapter for command generation.
* File path: .gemini/commands/opsx/<id>.toml
Expand All @@ -20,10 +55,10 @@ export const geminiAdapter: ToolCommandAdapter = {
},

formatFile(content: CommandContent): string {
return `description = "${content.description}"
return `description = "${escapeTomlBasicString(content.description)}"

prompt = """
${content.body}
${escapeTomlMultilineBasicString(content.body)}
"""
`;
},
Expand Down
1 change: 1 addition & 0 deletions src/core/command-generation/adapters/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -31,3 +31,4 @@ export { lingmaAdapter } from './lingma.js';
export { qwenAdapter } from './qwen.js';
export { roocodeAdapter } from './roocode.js';
export { traeAdapter } from './trae.js';
export { zcodeAdapter } from './zcode.js';
2 changes: 1 addition & 1 deletion src/core/completions/command-registry.ts
Original file line number Diff line number Diff line change
Expand Up @@ -184,7 +184,7 @@ export const COMMAND_REGISTRY: CommandDefinition[] = [
},
{
name: 'instructions',
description: 'Output enriched instructions for creating an artifact or applying tasks',
description: 'Output enriched instructions for artifacts, apply, or archive',
acceptsPositional: true,
positionals: [{ name: 'artifact', optional: true }],
flags: [
Expand Down
25 changes: 20 additions & 5 deletions src/core/completions/installers/zsh-installer.ts
Original file line number Diff line number Diff line change
Expand Up @@ -35,16 +35,29 @@ export class ZshInstaller {
}

// Fall back to checking for ~/.oh-my-zsh directory
const ohMyZshPath = path.join(this.homeDir, '.oh-my-zsh');

try {
const stat = await fs.stat(ohMyZshPath);
const stat = await fs.stat(this.ohMyZshRoot());
return stat.isDirectory();
} catch {
return false;
}
}

/**
* Oh My Zsh exports its root as $ZSH; honor a custom location, or the
* completion lands in a ~/.oh-my-zsh tree that nothing ever loads.
*/
private ohMyZshRoot(): string {
return process.env.ZSH || path.join(this.homeDir, '.oh-my-zsh');
}

/**
* The custom dir is separately relocatable via $ZSH_CUSTOM.
*/
private ohMyZshCustomDir(): string {
return process.env.ZSH_CUSTOM || path.join(this.ohMyZshRoot(), 'custom');
Comment thread
coderabbitai[bot] marked this conversation as resolved.
}

/**
* Get the appropriate installation path for the completion script
*
Expand All @@ -56,7 +69,7 @@ export class ZshInstaller {
if (isOhMyZsh) {
// Oh My Zsh custom completions directory
return {
path: path.join(this.homeDir, '.oh-my-zsh', 'custom', 'completions', '_openspec'),
path: path.join(this.ohMyZshCustomDir(), 'completions', '_openspec'),
isOhMyZsh: true,
};
} else {
Expand Down Expand Up @@ -330,7 +343,9 @@ export class ZshInstaller {
return [
'Note: Oh My Zsh typically auto-loads completions from custom/completions.',
`Verify that ${completionsDir} is in your fpath by running:`,
' echo $fpath | grep "custom/completions"',
// Grep for the actual directory: a relocated $ZSH_CUSTOM need not
// contain the literal "custom/completions".
` echo $fpath | grep "${completionsDir}"`,
Comment thread
coderabbitai[bot] marked this conversation as resolved.
Outdated
'',
'If not found, completions may not work. Restart your shell to ensure changes take effect.',
];
Expand Down
3 changes: 2 additions & 1 deletion src/core/parsers/markdown-parser.ts
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,8 @@ export class MarkdownParser {
}

protected static normalizeContent(content: string): string {
return content.replace(/\r\n?/g, '\n');
// Strip a UTF-8 BOM so a header on the first line still matches.
return content.replace(/^/, '').replace(/\r\n?/g, '\n');
}

parseSpec(name: string): Spec {
Expand Down
4 changes: 3 additions & 1 deletion src/core/parsers/requirement-blocks.ts
Original file line number Diff line number Diff line change
Expand Up @@ -141,7 +141,9 @@ export interface DeltaPlan {
}

function normalizeLineEndings(content: string): string {
return content.replace(/\r\n?/g, '\n');
// Strip a UTF-8 BOM: Windows editors and PowerShell redirects prepend one,
// and it would keep the first line's `## ADDED Requirements` from matching.
return content.replace(/^/, '').replace(/\r\n?/g, '\n');
}

/**
Expand Down
Loading
Loading