Commit Graph

2292 Commits

Author SHA1 Message Date
Tom Boucher
eefef2ec19 feat(#787): elevate Cline — .clinerules/ dir form, PreToolUse hook, AGENTS.md (#803)
* feat(#787): elevate Cline — .clinerules/ dir form, PreToolUse hook, AGENTS.md

Migrate the installer's Cline output from a single-file .clinerules to the
.clinerules/ directory form (.clinerules/gsd.md), which is the prerequisite for
Cline's v3.36 hooks (a path cannot be both a file and a directory). Add a
.clinerules/hooks/PreToolUse lifecycle hook implementing Cline's JSON stdin ->
{cancel,errorMessage,contextModification} protocol; it guards .planning/
artifacts and fails open. On global installs, merge GSD instructions into the
cross-tool ~/.agents/AGENTS.md target (marker-delimited, merge-safe). A legacy
single-file .clinerules is migrated in place; --uninstall removes the new
artifacts and strips the AGENTS.md GSD block.

Also fixes the uninstall targetDir for Cline local installs (it pointed at
./.cline instead of the project root) and re-runs writeManifest after the
Cline artifacts are written so they are hash-tracked.

Self-contained: implemented independently of the #782 Cline skills work.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>

* fix(#787): address review findings

- Scope PreToolUse hook path-walk to PATH_KEY fields only (eliminates false
  positive when doc body content mentions .planning/)
- Use lstatSync + isSymbolicLink() for migration guard so GSD never writes
  through a user's symlinked .clinerules into an external directory
- Add regression tests for both cases

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>

* chore(#787): set changeset pr: 803

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>

---------

Co-authored-by: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-07 15:20:55 -04:00
Tom Boucher
74a818308e feat(#785): write .cursor/commands/ Cursor 1.6 slash-command surface (#805)
* feat(#785): write .cursor/commands/ as Cursor 1.6 slash-command surface

Cursor 1.6 (released 2025-09-12) introduced plain-markdown slash commands
in `.cursor/commands/<name>.md` — no frontmatter, invocable via `/` in the
Agent input. GSD previously emitted only `~/.cursor/skills/` for Cursor.

This PR wires a second artifact kind for `cursor` in
`runtime-artifact-layout.cts`: `convertedCommandsKind('commands', 'gsd-',
'convertClaudeCommandToCursorCommand', configDir)`. The new kind applies the
same `convertClaudeToCursorMarkdown` transforms (tool renames, brand
substitution, slash-command normalisation) and then strips YAML frontmatter
so the output is plain prose. Skills output is unchanged.

`stageCommandsForRuntimeFlat` in `install-profiles.cts` stages each source
`.md` as a flat `<stem>.md` in a temp dir; the existing `_copyStaged` commands
path then prefixes and copies to `<configDir>/commands/`.

`.cursor/mcp.json` is explicitly OUT OF SCOPE: GSD ships no MCP server; the
`mcpServers` schema cannot be usefully populated by the installer.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>

* refactor(#785): address review nit

---------

Co-authored-by: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-06-07 15:07:14 -04:00
Tom Boucher
c480e04188 fix(#783): resolve Kilo global skills base to ~/.kilo/skills (#806)
* fix(#783): resolve Kilo global skills base to ~/.kilo/skills

getGlobalSkillsBase('kilo') returned ~/.config/kilo/skills (the XDG config
dir), but Kilo Code discovers global skills from ~/.kilo/skills/ (the .kilo
dir in HOME), independent of the kilo.jsonc config dir. Add a HOME-relative
special case so the resolver matches Kilo's actual discovery path.

The config dir (~/.config/kilo) and the installer's command/ path are
correct and unchanged. This corrects the path used by doctor/status and
agent-skills-block resolution; the installer writes commands (not skills)
for Kilo, so no files were being written to the wrong location.

Closes #783

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>

* chore(#783): set changeset pr to 806

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>

---------

Co-authored-by: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-07 14:59:33 -04:00
Tom Boucher
5e852611e3 feat(#786): elevate GitHub Copilot installer — lifecycle hook + AGENTS.md (#804)
* feat(#786): elevate Copilot installer with lifecycle hook + AGENTS.md

Emit a self-contained sessionStart hook config (.github/hooks/gsd-session.json
local, ~/.copilot/hooks/gsd-session.json global) and write AGENTS.md at the repo
root (Copilot CLI reads it as primary instructions) alongside
copilot-instructions.md. The hook is an inline `command` hook (no separate hook
script), so it cannot dangle. Uninstall removes both and preserves user content.

Verified against GitHub Copilot CLI primary docs: hooks-configuration (camelCase
events, version+hooks shape, inline bash/powershell command hooks) and
add-custom-instructions (AGENTS.md read at repo root as primary instructions).

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>

* chore(#786): set changeset pr number to 804

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>

---------

Co-authored-by: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-07 14:53:32 -04:00
Viktorplus
64328bd2ae Merge remote-tracking branch 'upstream/next' into kimi-runtime-support
# Conflicts:
#	bin/install.js
2026-06-07 19:21:53 +02:00
Tom Boucher
7e1112c28f refactor(#56): retire legacy runtime directory helpers into runtime-homes projection (#802)
* refactor(#56): retire legacy runtime directory helpers into runtime-homes projection

Consolidate per-runtime global config-dir resolution onto the single
canonical projection runtime-homes:getGlobalConfigDir. Extend it with the
explicitDir override (CLI --config-dir) and the opencode/kilo
OPENCODE_CONFIG/KILO_CONFIG file-path precedence the installer helpers had,
making it byte-for-behavior equivalent to the old getGlobalDir across all
15 install runtimes.

Delete bin/install.js's getGlobalDir/getOpencodeGlobalDir/getKiloGlobalDir
(and the orphaned local expandTilde), repoint all 9 call-sites, and remove
getGlobalDir from module.exports (net -242 lines in the installer). Migrate
the 5 test importers to the canonical projection; harden default/XDG
assertions against ambient *_CONFIG env vars. getAgentsDir now respects
OPENCODE_CONFIG/KILO_CONFIG consistently with the installer (intentional
convergence). Update CONTEXT.md Installer Module entry.

Completes the installer-refactor chain #58 -> #60 -> #56.

Closes #56

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>

* chore(#56): add changeset for runtime directory helper retirement

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>

---------

Co-authored-by: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-07 12:24:04 -04:00
Tom Boucher
f66c4a082c feat(#766): distribute gsd-core as a native Claude Code plugin (#797)
* feat(#766): distribute gsd-core as a native Claude Code plugin

Add an additive .claude-plugin/plugin.json manifest plus hooks/hooks.json so gsd-core can be installed as a first-class Claude Code plugin (marketplace or zero-friction @skills-dir), with /gsd-core: namespaced commands and lifecycle management — alongside the unchanged npm/file-copy installer.

- .claude-plugin/plugin.json: validated with 'claude plugin validate --strict'
- hooks/hooks.json: mirrors the installer's always-on Claude hook wiring via ${CLAUDE_PLUGIN_ROOT}
- package.json: ship .claude-plugin in the npm tarball
- tests/issue-766-plugin-manifest.test.cjs: manifest + always-on-hook-contract drift guards
- docs: install-on-your-runtime.md + FEATURES.md

Closes #766

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>

* docs(#766): add ADR-766 + glossary entry for Claude Code Plugin Manifest Module

Record the plugin manifest as the Seam projecting gsd-core's artifact surfaces onto the Claude Code plugin contract (sibling of the Runtime Artifact Layout Module, ADR-3660), with the defined kind->field mapping and the always-on hook projection rule.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>

---------

Co-authored-by: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-07 12:02:39 -04:00
Tom Boucher
571d7b5a1c feat(#764): skip cross-platform test matrix for docs-only and inert-CI PRs (#798)
test.yml had no paths filter and the ci-test-scope classifier treated docs/
and every .github/workflows/* as code_changed, so documentation edits and
product-irrelevant automation tweaks still spun up the full Linux/Windows/macOS
matrix. Narrow the heavy matrix to changes that can actually affect the product
or the test pipeline.

- ci-test-scope.cjs: drop docs/ from code_changed (docs-only -> full skip; the
  required-tests fan-in still reports green). Add src/ to code_changed (it was
  missing -> a source-only PR previously skipped all tests). Add INERT_WORKFLOWS
  allowlist + isInertCi() + an "inert CI" rule, and a product_changed output that
  gates the heavy test/coverage jobs. Fail-safe: any workflow not on the inert
  allowlist defaults to the full matrix. A module-load assertion throws if a
  PROTECTED_WORKFLOWS entry (test/install-smoke/mutation/security-scan/release)
  is ever added to the inert set, so a weakening edit fails CI loudly.
- test.yml: keep the static 3-lane matrix (so the H1 shell-policy linter can
  still statically verify the Windows lane), gate test/coverage on
  product_changed, add a lightweight ubuntu-only test-inert job, and branch the
  required-tests fan-in on product_changed.
- docs-required.yml: run docs-parity-live-registry (gated on docs/ changes) so
  pure-docs PRs still catch live-registry drift without the matrix.
- tests: cover docs-only, inert-only, src/, pipeline, unknown-workflow fail-safe,
  mixed escalation, the code_changed=false -> no-lanes invariant, and protected-
  workflow tamper-evidence.

Closes #764

Co-authored-by: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-07 11:46:48 -04:00
Tom Boucher
2f07443119 refactor(#60): make runtime config adapter registry explicit (#795)
* refactor(#60): make runtime config adapter registry explicit

Replace scattered inline `runtime === '...'` config-mutation branching in
bin/install.js with an explicit, typed adapter registry. The new
src/runtime-config-adapter-registry.cts maps each of the 15 supported
runtimes to a config intent { installSurface, writesSharedSettings,
finishPermissionWriter }; install()/finishInstall() dispatch by resolved
intent instead of runtime-name checks (cursor/windsurf/trae collapse to one
profile-marker-only branch).

Behavior-preserving: the same config files are written for the same runtimes
(opencode still writes both settings.json and its permissions; kilo writes
only its permissions; codex minimal-mode and opencode GSD_TEST_MODE guards
unchanged). Unknown runtimes fail loudly via TypeError, with an Object.hasOwn
barrier so prototype-chain keys (__proto__/constructor) also throw rather than
returning a bogus intent. Leads the installer-refactor chain (#58 -> #60 ->
#56), building on ADR-58.

Closes #60

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>

* chore(#60): add changeset for runtime config adapter registry

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>

* docs(#60): register Runtime Config Adapter Registry in CONTEXT.md glossary

Per docs/contributor-standards.md, every new Module/seam must get a
`### <Name>` entry under the domain glossary. Adds the entry for the
runtime-config-adapter-registry seam introduced in this PR (interface,
policy boundary, source file, ADR-58 / #60 cross-references).

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>

---------

Co-authored-by: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-07 11:16:03 -04:00
Joe
8951f2907e test(#339): regression guard for gsd-sdk refs in runtime surfaces (#691)
* test(#339): add regression guard for gsd-sdk refs in runtime surfaces

Lock in the already-clean runtime surface so a retired `gsd-sdk`/`GSD_SDK`
reference cannot creep back into a shipped prompt or hook. Scans
gsd-core/workflows, gsd-core/references, commands/gsd, agents, and hooks
(excluding the gitignored dist/ build artifact).

Complements gsd-tools-path-refs.test.cjs, which only catches the
`gsd-sdk query` binary form; this catches any runtime reference. A second
test guards against an empty sweep so a future dir rename can't silently
turn the guard into a no-op.

Intentionally does NOT touch bin/install.js (live stale-package-detection
mechanics per #339 triage) or CI/lint scripts (legitimate stale detection).

Refs #339

* test(#339): split file content on /\r?\n/ for Windows CRLF parity

The windows-test-parity-guard lint requires test files that readFileSync +
split to use /\r?\n/, not '\n', so CRLF files don't leave a trailing \r.
New test files are not in the PR #3649 allowlist.

* test(#339): cover .sh/.json runtime files in workflows surface

Address #691 review: the gsd-core/workflows surface scanned only .md,
silently skipping two deployed runtime files — _runtime-launcher.snippet.sh
(synced into every hook) and discuss-phase/templates/checkpoint.json. Add
.sh/.json so the guard covers all 290 deployed runtime files (was 288/290),
not just the .md subset.

* test(#339): cover templates/contexts surfaces + per-ext empty-sweep guard

Address PR #691 review (trek-e):
- Add gsd-core/templates and gsd-core/contexts to RUNTIME_SURFACES —
  both are deep-copied by the installer and runtime-loaded via
  @~/.claude/gsd-core/templates/*.md anchors, so a reintroduced gsd-sdk
  ref there would have slipped past the guard. (major)
- Reword the bin/install.js exclusion rationale: it has zero gsd-sdk refs
  today (subsystem removed in #515, shim retired in #522); the real reason
  it is excluded is that it is installer code, not a deployed prompt/hook
  surface. (minor)
- Make the empty-sweep guard assert coverage per configured extension, not
  per surface — .md files alone kept gsd-core/workflows green even if
  .sh/.json were dropped, silently un-covering _runtime-launcher.snippet.sh
  and discuss-phase/templates/*.json. (low)

---------

Co-authored-by: Tom Boucher <trekkie@nomorestars.com>
2026-06-07 10:53:00 -04:00
Viktorplus
170b434a67 Merge remote-tracking branch 'upstream/next' into kimi-runtime-support 2026-06-07 16:47:38 +02:00
Viktorplus
50ac0fcba8 test: cover kimi agent staging cleanup tracking 2026-06-07 16:41:29 +02:00
Viktorplus
4e51bc32bd Merge remote-tracking branch 'upstream/next' into kimi-runtime-support 2026-06-07 16:38:21 +02:00
Solvely-Colin
dd75400801 fix discord release changelog announcements 2026-06-07 10:27:20 -04:00
Tom Boucher
60e2e5e5f3 feat(#761): add scheduled base-context sweep to close SHA-branch-evading draft PRs (#765)
close-draft-prs.yml (on pull_request_target after #760) cannot close fork draft
PRs whose head branch name looks like a Git SHA — GitHub never dispatches
pull_request_target for such branches, and a pull_request run from a fork gets a
read-only token. So a draft PR on a SHA-named fork branch evades the auto-close.

Add close-draft-prs-sweep.yml: a schedule (every 6h) + workflow_dispatch sweep
running in base-repo context with pull-requests: write that paginates open PRs,
filters to non-OWNER/MEMBER/COLLABORATOR drafts, and closes + comments them with
the identical policy/message as the event-driven workflow. Re-fetches each
candidate before mutating (TOCTOU guard), closes before commenting so
enforcement is never gated on the explanatory comment, and core.setFailed on
partial failures. The per-PR workflow remains the fast path; this is the
safety net for the documented residual bypass.

Extends tests/workflow-maintainer-skip.test.cjs with structural guards locking
the triggers, write permission, maintainer carve-out, pagination, and message.

Co-authored-by: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-07 10:11:54 -04:00
Tom Boucher
5237fae537 feat(#759): non-destructive CHANGELOG preview in the rc release job (#763)
The rc action publishes a release candidate to @next for testing but
never surfaces the curated CHANGELOG section for the version under test —
render only runs destructively at finalize (#715), so there was no safe
way to preview the upcoming notes during the RC window.

Add a --preview mode to scripts/changeset/cli.cjs cmdRender: it renders
the dated release section to stdout via the existing renderChangelog/
serializeChangelog path (with priorChangelog: null, so only the new
section is emitted), reuses the shared injectEmptyPlaceholder helper for
zero-fragment releases, and returns WITHOUT writing CHANGELOG.md or
deleting any .changeset fragment. Wire a "Preview CHANGELOG" step into
the rc job that renders to a file (standalone command, so a malformed
fragment fails the step) and cats it to the job summary and log.

Closes #759

Co-authored-by: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-07 10:05:21 -04:00
Tom Boucher
f44605093e fix(#758): trigger draft-PR auto-close on pull_request_target (#760)
Bare `pull_request` hands fork PRs a read-only GITHUB_TOKEN, so the
close/comment API calls 403 and a first-time/external contributor's draft
PR survives — bypassing the auto-close for exactly the population the job
targets. Switch to `pull_request_target`, which runs in the base-repo
context with a write-capable token even for fork PRs. Safe because the job
never checks out or executes PR-supplied code; it only reads event metadata
and calls the GitHub API. The minimal `permissions: pull-requests: write`
block still constrains the token.

Add a regression guard in tests/workflow-maintainer-skip.test.cjs asserting
the workflow triggers on pull_request_target and not bare pull_request.

Co-authored-by: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-07 09:45:18 -04:00
Viktorplus
2d9e44c6a6 fix: align kimi install roots with docs 2026-06-07 12:05:13 +02:00
Viktorplus
4e12967683 Merge remote-tracking branch 'upstream/next' into kimi-runtime-support 2026-06-07 11:56:18 +02:00
Tom Boucher
4056d830bc refactor(#651): consolidate verification-status routing into one queryable seam (#755)
* refactor(#651): consolidate verification-status routing into one queryable seam

The passed/gaps_found/human_needed verification status was re-encoded as
bare strings across three prose surfaces (gsd-verifier emits, execute-phase
routes, ship gates), each independently deciding the per-status next action
with no parity coupling — the DEFECT.GENERATIVE-FIX class.

Give the enum one home: src/verification.cts (-> bin/lib/verification.cjs)
exposing `gsd_run query verification.status <phaseDir>` returning a typed
{status, next_action, next_command}. ship.md and execute-phase.md now consume
the query instead of re-deriving the routing in prose; gsd-verifier.md points
at the shared vocabulary as the single emitter (values unchanged).

Also fixes the latent broad-grep status misread (DEFECT.FRONTMATTER-SCALAR-
BROAD-GREP): execute-phase.md read `grep "^status:"` over the whole report, so
a body `status:` line could misroute a valid phase. Extraction is now
frontmatter-scoped in one place. A parity test fails if a verifier status
gains no route. Lands the two CONTEXT.md DEFECT entries captured on the issue.

Closes #651

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>

* chore(#651): set changeset pr to 755

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>

---------

Co-authored-by: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-07 01:26:56 -04:00
Tom Boucher
f7e902f1cf feat(#52): add agent_skills_security.trusted_global_roots allowlist for global skills (#754)
* feat(#52): add agent_skills_security.trusted_global_roots allowlist

Opt-in allowlist so a global: agent skill whose SKILL.md realpath resolves
outside the default global skills base (e.g. ~/.claude/skills) is accepted
when its real target lies under a user-declared trusted root. Default [] is
byte-identical to prior behavior; the symlink-escape guard is preserved and
simply re-applied against each declared root.

- src/security.cts: loadTrustedGlobalRoots — tilde-expand (~ and ~/), reject
  project-relative and dangerously broad roots (filesystem/UNC root, homedir),
  realpath-canonicalize each root every run and drop non-existent ones.
- src/init.cts: on base-check failure the guard consults the trusted roots
  (hoisted out of the loop); emits a stderr NOTE when a skill is accepted via
  a trusted root so the widened boundary is visible.
- src/core.cts: thread agent_skills_security through loadConfig.
- config-schema.manifest.json: allow the new key path.
- docs/CONFIGURATION.md: document the option and its security model.
- tests/agent-skills.test.cjs: unit + end-to-end CLI coverage (regression,
  feature, negative, broad-root hardening, stderr NOTE).

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>

* docs(#52): add changeset fragment for trusted_global_roots (#754)

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>

---------

Co-authored-by: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-07 01:06:41 -04:00
Tom Boucher
ad1203f9d4 feat(#25): scope gsd-verifier Step 7b to enumerate-or-single-test; forbid full-suite re-runs (#753)
* feat(#25): scope gsd-verifier Step 7b to enumerate-or-single-test; forbid full-suite re-runs

Step 7b's lone test example (`npm test -- --grep "$PHASE_TEST_PATTERN"`) is
mocha/vitest/jest-specific, where `--grep` filters which tests *execute*. Models
generalized it to `cargo test --workspace 2>&1 | grep X` (runs the whole suite,
filters only *output*) and repeated it once per must-have, adding minutes per
verification with no new evidence after the first run.

Replace the example with language-agnostic guidance: prove a test EXISTS via
enumeration (`cargo test -- --list` / `pytest --collect-only` / `npx vitest
list` / `go test -list`), and prove it PASSES via a single named test
(`cargo test <name> -- --exact` / `pytest -k` / `npx vitest run -t`). Add a
Spot-check constraint forbidding more than one full-suite run per verification
or piping a full run through grep per must-have, while still permitting one
saved run + grep when a full run is genuinely required. docs/AGENTS.md gains a
one-line Key-behaviors note, and a new test asserts the Step 7b content.

Scoped per the maintainer decision on the issue: folded into Step 7b (no new
top-level Step 7a) with no VERIFICATION.md label changes.

Closes #25

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>

* chore(#25): add Changed changeset fragment for PR #753

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>

---------

Co-authored-by: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-07 00:46:27 -04:00
Tom Boucher
612e3e60e8 fix(#751): recognise config-set prototype-pollution guard in CodeQL + test dynamic-key vectors (#752)
CodeQL alert #26 (js/prototype-pollution-utility) kept firing on setConfigValue
because its dataflow does not trace the #663 Set-based, pre-loop keys.some(...)
forbidden-key check as a sanitising barrier on the write site.

- src/config.cts: replace the Set + pre-loop check with inline literal
  comparisons (key === '__proto__' || 'prototype' || 'constructor') on the exact
  key used to index `current`, immediately before each write (intermediate keys
  in the descent loop, plus the final key). Same forbidden set, same error
  message and ERROR_REASON.CONFIG_PARSE_FAILED — behaviour unchanged from #663,
  but the barrier is now CodeQL-recognised.
- tests/config.test.cjs: add regression tests for schema-valid dynamic-prefix
  keys (agent_skills.__proto__, agent_skills.constructor, agent_skills.prototype,
  features.__proto__, review.models.constructor) that pass the isValidConfigKey
  schema gate and reach the guard. Each asserts the guard's own message fires
  (not the schema gate's "Unknown config key") and Object.prototype is not
  polluted. The prior #663 tests never reached the guard — their keys are
  rejected by the schema gate first — so the guard's real attack surface was
  untested.

Co-authored-by: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-07 00:18:00 -04:00
Tom Boucher
7e76f1a736 feat(#703): add --granularity override flag to /gsd:plan-phase (#750)
* feat(#703): add --granularity override flag to /gsd:plan-phase

Add a `--granularity <coarse|standard|fine>` flag to /gsd:plan-phase that
overrides the configured planning granularity for a single invocation.

The override is a new highest-priority tier above the existing precedence
chain (granularities[phaseType] -> granularity -> planning.granularity ->
'standard') in resolveGranularityInternal; when the flag is absent, resolution
is byte-for-byte unchanged. cmdInitPlanPhase now resolves with phaseType
'planning' so granularities.planning participates, and emits the resolved
value in the init JSON, which the plan-phase workflow forwards to the planner
prompt. Invalid values are rejected at the CLI boundary via a shared
assertValidGranularityOverride helper.

Closes #703

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>

* chore(#703): set changeset pr to 750

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>

---------

Co-authored-by: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-06 23:56:53 -04:00
Tom Boucher
cf8bd3cd5e fix(#683): auto-degrade phase execution to sequential on worktree base mismatch (#749)
* fix(#683): auto-degrade phase execution to sequential on worktree base mismatch

Claude Code forks worktree-isolated executors off the repository default
branch (origin/HEAD), not the orchestrator's HEAD. Running /gsd-execute-phase
on a branch diverged from the default (unmerged milestone/feature branch) left
every executor without the phase's plan files and tripped the
worktree-branch-check guard with `exit 42` — 100% reproducible, all OSes.

- New module src/worktree-base-ref.cts: HEAD-vs-fork-base drift detection
  (origin/HEAD with symbolic-ref fallback) and no-clobber worktree.baseRef
  management, exposed as `worktree base-check` / `worktree set-baseref`.
- execute-phase.md: pre-dispatch, for Claude Code with worktrees enabled,
  auto-degrades the run to sequential on the main tree when a base mismatch
  is detected, recommending worktree.baseRef:"head". The exit-42 guard stays
  as a backstop.
- Installer: fresh local Claude installs set worktree.baseRef:"head" in
  .claude/settings.local.json (no-clobber, respecting an explicit shared
  settings.json value); upgrades print an opt-in notice pointing at
  `gsd-tools worktree set-baseref`.
- Docs: how-to guide, CLI/config reference, planning-config cross-ref.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>

* fix(#683): auto-apply worktree.baseRef on upgrade; gate fresh+upgrade on use_worktrees

Per maintainer direction: on a local Claude Code UPGRADE, set
worktree.baseRef:"head" automatically (no opt-in notice) when the project's
workflow.use_worktrees is enabled, instead of merely printing a remediation
notice. For consistency the FRESH path is now gated the same way: both paths
compute worktrees-enabled once (bounded walk-up read of .planning/config.json,
default enabled unless workflow.use_worktrees === false) and apply the
no-clobber baseRef only when enabled — never overwriting an explicit value in
settings.local.json or a shared settings.json. gsd-tools worktree set-baseref
remains for manual use. Docs + changeset updated; tests hardened (file-exists
assertions, fresh+disabled case, upgrade idempotency).

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>

* fix(#683): measure workflow byte-budget on LF, fixing Windows-only CI failure

The workflow-size-budget test failed only on Windows: git checks out the .md
files as CRLF (no eol=lf in .gitattributes) and byteCount used
fs.statSync().size (raw on-disk bytes), counting an extra \r per line. That
inflated execute-phase.md — the XL high-water-mark file pinned near its ceiling
by the tighten-only ratchet — from 88492 LF bytes to ~90245 on Windows, over
the 90000 XL ceiling, while passing on the LF-checkout Mac/Linux runners.

The ceilings are explicitly "calibrated against raw `wc -c`" on an LF checkout,
so the measurement should be LF-based on every platform. byteCount now reads the
file and counts Buffer.byteLength after stripping CR, making the budget
platform-independent (a no-op on LF checkouts; verified statSync === normalized
for all 88 workflow files). No ceilings changed. Added a regression test
asserting CRLF and LF content of the same file count identically.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>

* fix(#683): make worktree-base-ref test path mocks Windows-safe (path.join)

tests/worktree-base-ref.test.cjs keyed its injected readFile/writeFile mocks
(and a few expected `file` values) with forward-slash template literals like
`${claudeDir}/settings.local.json`. The module composes those paths with
path.join(), which emits backslashes on Windows, so the mock keys never matched
the module's lookup → readFile returned null → resolveEffectiveBaseRef /
cmdWorktreeBaseCheck / cmdWorktreeSetBaseRef (and the JSONC variants) failed on
the Windows full-test runner only (they passed on Mac/Linux, and the install
tests passed because they use the real filesystem). The module is correct;
only the test fixtures hardcoded '/'.

All mock keys and path assertions now use path.join(base, ...) mirroring the
module, so they match on every platform (no-op on POSIX). 19 path references
across 16 lines.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>

---------

Co-authored-by: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-06 23:40:24 -04:00
Tom Boucher
edd3c6c332 fix(#730): scope current-milestone Phase Details section in roadmap parser (#748)
`extractCurrentMilestone()` scoped the current-milestone window to its
`## Phases` checklist subsection and terminated at the milestone's own
`## Milestone … (Phase Details)` heading, so the `### Phase N:` detail
headers fell outside scope. Every parser-backed command — `init.phase-op`
(and thus `/gsd:discuss-phase`, `/gsd:plan-phase`), `state`, `roadmap list`,
and `validate health` (W006) — therefore could not resolve phases of any
milestone after the first until a `.planning/phases/` directory already
existed, blocking discuss/plan.

The parser now additionally includes the current milestone's `(Phase Details)`
section in scope, located via the already-computed version matches and anchored
(boundary-aware) to the selected milestone's version token so sibling
sub-milestones sharing a version prefix do not cross-pollinate. The existing
heading selection and primary window are unchanged.

Adds tests/bug-730-milestone-phase-details-scope.test.cjs covering the
two-milestone reproduction, first-milestone non-regression, direct
getRoadmapPhaseInternal resolution, validate-health W006 visibility, a
three-milestone roadmap, and the closed-sibling sub-milestone case.

Co-authored-by: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-06 22:13:20 -04:00
Tom Boucher
1c98d0c5aa refactor(#712): replace Codex slash-command denylist lookbehind with positive-boundary match (#747)
* refactor(#712): replace Codex slash-command denylist lookbehind with positive-boundary match

The hyphen-style /gsd-<cmd> -> $gsd-<cmd> conversion in
convertSlashCommandsToCodexSkillMentions used a negative-lookbehind DENYLIST
enumerating characters that must NOT precede a real mention. #637 -> #704 showed
this is an unbounded treadmill: each new unanticipated preceding char (/, ., word
chars, then }, )) leaked the same path-corruption bug class, and a backtick-wrapped
path (`/gsd-core/workflows/update.md`) still leaked through.

Replace it with a POSITIVE two-boundary definition of a mention:
  1. Left: opens at start-of-string, whitespace, or an inline-prose delimiter
     (backtick/quote/paren/bracket).
  2. Right: the command token is not followed by a path separator `/` (a path
     continues, a command does not). The (?![a-z0-9/-]) lookahead also blocks
     regex backtracking to a shorter command.

This closes the whole class by construction (no preceding-char denylist to
maintain) and fixes the backtick-wrapped-path corruption the #704 test
documented as a pre-existing gap, while preserving conversion of legitimate
backtick-wrapped mentions (e.g. CONTEXT.md's `/gsd-execute-phase` lists).

The colon-style /gsd: replace is intentionally left unguarded (it never appears
as a filesystem path segment) and is annotated as such.

Tests assert the regex directly (function now exported) across a convert/
don't-convert matrix plus one end-to-end pipeline assertion for the headline
backtick-path case.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>

* chore(#712): add changeset fragment for PR #747

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>

---------

Co-authored-by: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-06 19:40:52 -04:00
Viktorplus
40c3fec397 fix: align kimi runtime support with review 2026-06-07 01:27:12 +02:00
Viktorplus
86836ebd2a Merge branch 'open-gsd:next' into kimi-runtime-support 2026-06-07 00:53:20 +02:00
Tom Boucher
1bea220d58 refactor(#720): lazy-load MVP-only reference bodies on non-MVP runs (#746)
* refactor(#720): lazy-load MVP-only reference bodies (eager @-import → gated Read)

Convert eager @-imports of MVP-only reference bodies into lazy "Read" instructions
gated on MVP_MODE / WALKING_SKELETON / MVP+TDD, so non-MVP planning/execution runs
no longer pull MVP guidance into context. Covers both the workflow files and the
planner/executor agent definitions (the dominant context-cost path):

- workflows/plan-phase.md: planner-mvp-mode.md + skeleton-template.md (L146/936/937/941)
- workflows/execute-phase.md: execute-mvp-tdd.md halt-report ref, now gated on gate-trip (L191)
- agents/gsd-planner.md: planner-mvp-mode.md, user-story-template.md, skeleton-template.md
- agents/gsd-executor.md: execute-mvp-tdd.md

The dedicated always-MVP mvp-phase workflow keeps its eager imports (intentional).
Behaviour is unchanged; non-MVP runs simply carry less loaded context. Adds a
regression guard mirroring the discuss-phase lazy-load test, and documents the
conformance in docs/ARCHITECTURE.md.

Refs #720

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>

* docs(#720): add changeset fragment (pr #746)

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>

---------

Co-authored-by: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-06 18:23:17 -04:00
Viktorplus
ca0370a7b5 fix(04-02): run Kimi install migrations before artifacts
- move Kimi global install after migration gate
- align migration integration assertions with Kimi skills and agents contract
2026-06-06 22:16:21 +02:00
Viktorplus
de5c57c685 test(03-02): add failing Kimi agent install tests
- assert Kimi global layout stages agent YAML and prompts
- expect global install to write agents/gsd.yaml while local guard stays no-op
2026-06-06 22:16:20 +02:00
Viktorplus
16553f3c36 test(03-03): assert Kimi agent tool mapper wiring
- verify generated YAML emits Kimi module paths
- assert raw Claude and MCP tool names stay excluded
2026-06-06 22:16:20 +02:00
Viktorplus
260f276287 test(03-03): add failing Kimi tool mapper tests
- cover Kimi module path mappings
- assert stable dedupe and exclusion diagnostics
2026-06-06 22:16:20 +02:00
Viktorplus
3541e4877d test(03-01): add failing Kimi agent contract tests
- cover root Kimi custom agent artifact shape
- cover subagent prompt separation and diagnostics
2026-06-06 22:16:20 +02:00
Viktorplus
cc447fcd66 feat(02-02): install Kimi global skills only
- Wire Kimi global layout to convertClaudeCommandToKimiSkill
- Keep --kimi --local guarded as a no-op
- Add Kimi self-invocation hint without agent or tool artifacts
2026-06-06 22:16:20 +02:00
Viktorplus
da1b6111e7 test(02-02): add failing Kimi global skill install tests
- Expect Kimi global layout to stage skills/gsd-*/SKILL.md
- Assert --kimi --global writes gsd-new-project SKILL.md
- Preserve explicit --kimi --local no-op guard
2026-06-06 22:16:20 +02:00
Viktorplus
6a6243331e test(02-01): add failing Kimi skill converter tests
- Cover Kimi SKILL.md frontmatter shape
- Cover /skill:gsd-new-project invocation rewrites
- Assert Phase 3 agent/tool artifacts are absent
2026-06-06 22:16:19 +02:00
Viktorplus
d008c83ec9 feat(01-02): add Kimi runtime name policy
- Register canonical kimi in runtime alias manifest and fallback policy
- Add focused canonicalization coverage without extra aliases
2026-06-06 22:16:19 +02:00
Viktorplus
2a8745b21a fix(01-03): guard Kimi global artifact writes
- Keep Kimi global installs as a Phase 1 no-op skeleton
- Assert no unconverted Kimi skills, agents, hooks, or payload files are written
2026-06-06 22:16:19 +02:00
Viktorplus
58626283b2 feat(01-03): implement Kimi paths layout and local guard
- Resolve Kimi global skills under the generic agents path
- Add empty Kimi layout placeholder and local install no-op guard
- Keep selection/path tests aligned without Kimi skill conversion
2026-06-06 22:16:19 +02:00
Viktorplus
0d456d4bb0 test(01-03): add failing Kimi path and guard tests
- Assert Kimi canonical skills path uses generic agents root
- Assert Phase 1 layout placeholder and local install guard behavior
2026-06-06 22:16:19 +02:00
Viktorplus
8c541b1f3a test(01-02): add failing Kimi runtime selection coverage
- Expect Kimi in interactive runtime option 11
- Move all-runtimes shortcut to option 17
- Cover Kimi in runtimeMap, allRuntimes and prompt text
2026-06-06 22:16:18 +02:00
Tom Boucher
f729101eec refactor(scripts): replace process.exit() with ExitError + runMain handler (#739) (#740)
Part 1 of 2 of the n/no-process-exit cleanup (umbrella #738): convert every
process.exit() call in standalone scripts/** CLIs to the rule-compliant pattern.

- New shared helper scripts/lib/cli-exit.cjs: ExitError(code,message) + runMain()
  which translates a thrown ExitError / returned number into process.exitCode
  (never process.exit()), flushing output and still firing process.on('exit').
- main()-based entrypoints: throw new ExitError(code) for errors, return <code>
  for verdicts; invoked via runMain(main). Child exit codes preserved via return.
- top-level-only scripts: imperative body extracted into main() so mid-flow
  aborts (throw ExitError) actually halt; pure consts/helpers stay at module scope.
- diff-touches-shipped-paths.cjs: stdin event handling restructured to an async
  read so the whole flow runs under runMain; uncaughtException/unhandledRejection
  nets replaced by an in-band catch that preserves EXIT_ERROR=2.

Exit codes verified unchanged for every converted script (success/error/help and
the 0/1/2 semantic codes in diff-touches). Rule stays warn here; flipped to error
in part 2 (#738) once gsd-core/bin/** is also clean.

Refs #739

Co-authored-by: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-06 16:13:13 -04:00
Tom Boucher
dfbb684843 fix(#706): skip rescue of already-committed SUMMARY to avoid worktree cleanup merge_failed (#709)
* fix(#706): skip rescueSummaryArtifacts when SUMMARY is already committed

rescueSummaryArtifacts now probes `git cat-file -e HEAD:<path>` before
copying a SUMMARY.md into the main checkout.  When the file is already
committed on the worktree branch, copying it as an untracked file causes
`git merge --no-ff` to abort with "untracked working tree files would be
overwritten by merge" — a permanent merge_failed cleanup-wave failure.

Fail-closed on timeout: if cat-file is unreliable we skip rescue (the
merge will surface the collision as it did before, which is recoverable).

Adds 4 new test cases in worktree-safety.test.cjs covering:
- committed SUMMARY skipped, merge succeeds (#706 regression case)
- committed SUMMARY skipped even when timeout (fail-closed)
- uncommitted SUMMARY still rescued (existing contract preserved)
- rescue failure on ENOSPC still propagates (unchanged)

Closes #706

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>

* chore: add changeset for #706

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>

* fix(#706): treat cat-file exit 128 as uncertain — skip rescue (fail-closed)

The previous guard skipped rescue only when `exitCode === 0` (committed) or
`timedOut`. Any other non-zero exit, including `128` (fatal git error: corrupt
object store, unborn HEAD, missing repo), fell through and PROCEEDED with
rescue — potentially re-creating the #706 untracked-file merge collision.

Fix: rescue ONLY when `exitCode === 1` (cat-file definitively reports the
object absent). All other outcomes — 0 (committed), 128 (fatal), null/SIGTERM
(timeout), or any other code — are treated as "uncertain → skip rescue".

Also corrects the JSDoc bullet that still referenced `git ls-files
--error-unmatch` (the old mechanism); updated to `git cat-file -e HEAD:<relPath>`.

Regression test added: asserts rescue is SKIPPED when cat-file returns exit 128,
leaving the merge to surface the issue safely rather than silently copying an
already-committed file.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>

* chore: link changeset to PR #709

---------

Co-authored-by: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-06-06 12:40:28 -04:00
Tom Boucher
4b50a686e5 fix(#704): exclude } and ) from Codex path-rewrite lookbehind (no literal $gsd-core in installs) (#710)
* fix(#704): exclude } and ) from Codex path-rewrite lookbehind

Shell variable expressions like \${VAR}/gsd-core/ and command-substitution
paths like \$(cmd)/gsd-local-patches were being rewritten to \$gsd-core and
\$gsd-local-patches respectively because the negative lookbehind in
convertSlashCommandsToCodexSkillMentions did not include } or ).

Add both characters to the lookbehind set:
  (?<![a-zA-Z0-9./})])

Also adds regression test:
  tests/bug-704-codex-launcher-path-corruption.test.cjs

Closes #704

* chore: add changeset for #704

* test: use RUNTIME_ROOT_PATH in assertion to eliminate dead-code lint warning

Replace the partial hard-coded fragment '}/gsd-core/bin/' with the
existing RUNTIME_ROOT_PATH const so the assertion both compiles clean
(no unused variable) and self-documents which canonical launcher path
must survive Codex conversion intact (#704).

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>

* chore: link changeset to PR #710

---------

Co-authored-by: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-06-06 12:40:06 -04:00
Tom Boucher
b4e817cfa0 chore(lint): refactor magic-sleep tests to async waits + ratchet rules to error (#735)
Replace raw setTimeout/Atomics.wait synchronization sleeps in 4 test files
with a shared async delay()/waitFor() poll-for-condition helper in
tests/helpers.cjs, then flip local/no-magic-sleep-in-tests and
no-restricted-syntax from warn to error so the debt can't regrow.

- tests/helpers.cjs: add delay(ms) + waitFor(predicate, opts), exported
- bug-1974: setTimeout backoff -> await delay()
- config.test: drop Atomics.wait sleep(); async retry via await delay()
- graphify: waitForBuildStatus/cleanupHookRepo async via await delay()
- locking-bugs: 3 Atomics.wait poll loops -> await waitFor()
- eslint.config.mjs: ratchet both rules warn -> error

Refs #733

Co-authored-by: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-06 12:04:38 -04:00
Tom Boucher
bf48127c13 chore(lint): justify intentional no-control-regex (ANSI strip) + ratchet to error (#737)
The 5 no-control-regex warnings are all the same intentional ANSI-color-strip
pattern /\x1b\[[0-9;]*m/g across 5 test files. The \x1b (ESC) control char is
the required leading byte of an ANSI SGR sequence, so matching it is the whole
point of stripping color codes from captured CLI/console output. Add an inline
eslint-disable-next-line with justification at each site (not a refactor — the
control char is essential, not accidental), then flip no-control-regex from
warn to error so the debt can't regrow.

Refs #736

Co-authored-by: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-06 12:04:18 -04:00
Tom Boucher
ba231ecbfc chore: clean up clear-cut ESLint warnings (#732) (#734)
Pay down pre-existing error→warn lint debt. Removes dead imports/vars, unused functions, redundant regex/string escapes, and stale eslint-disable directives; converts unused `catch (_e)` to optional catch binding (src/*.cts).

No behavior change. Lint 345→125 warnings (0 errors); deferred categories (n/no-process-exit, test-sleeps, control-regex) tracked in #732 for follow-up. Full test suite green (0 failures); code-review verified all removals unused and all escape fixes semantics-preserving.

Closes #732

Co-authored-by: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-06 11:24:48 -04:00
Tom Boucher
42b74100f1 feat(#159): auto-use existing RESEARCH.md in /gsd:plan-phase --research-phase (#718)
* feat(#159): auto-use existing RESEARCH.md in /gsd:plan-phase --research-phase

When RESEARCH.md already exists in research-only mode and neither --research
nor --view is passed, emit a one-line notice and exit cleanly instead of
prompting update/view/skip. This matches the promptless auto-use of standard
/gsd:plan-phase <N> (§5.1) and removes the §5.0/§5.1 inconsistency, making
AI-agent and CLI invocations non-interactive in the common case. The two
explicit-flag escape hatches (--research to refresh, --view to print) cover
any deviation.

Closes #159

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>

* chore(#159): point changeset fragment at PR #718

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>

* docs(#159): tighten research-phase reference register (Diataxis)

Make the 'no modifier' research-phase entries descriptive rather than
imperative and drop the trailing 'pass --research/--view' clauses, which
duplicated the adjacent --research/--view documentation. Reference docs
describe; the recovery flags are documented in their own entries. The
emitted runtime notice in the workflow keeps naming the flags (in-band
recovery), unchanged.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>

---------

Co-authored-by: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-06 11:09:54 -04:00