fix(#3010): post-install message and docs use /gsd-update --reapply (#3012)

* fix(#3010): post-install message and docs use /gsd-update --reapply

PR #2824 consolidated 86 skills into ~58, removing the standalone
/gsd-reapply-patches command and folding it into a flag on /gsd-update
(/gsd-update --reapply). The 1.39.1 hotfix (#2954) updated help.md
but missed three other surfaces that still recommended the dead form:

1. bin/install.js reportLocalPatches() — runtime emitter shown after
   every install with backed-up patches. All branches updated:
   - claude/opencode/kilo/copilot: /gsd-update --reapply
   - gemini: /gsd:update --reapply
   - codex: $gsd-update --reapply
   - cursor: gsd-update --reapply (mention the skill name)

2. get-shit-done/workflows/update.md — Step 4 prose and the
   check_local_patches block both referenced /gsd-reapply-patches.
   Replaced with /gsd-update --reapply (with backticks around the
   command per CR feedback for copy/paste UX).

3. Localized docs (en/ja-JP/ko-KR/zh-CN) — 14 files across
   ARCHITECTURE.md / COMMANDS.md / FEATURES.md / INVENTORY.md /
   USER-GUIDE.md / manual-update.md still listed the removed command.

Tests:
- bug-3010-reapply-patches-references.test.cjs (4 tests): scans
  bin/install.js's reportLocalPatches body, every workflow file, and
  every doc (excluding CHANGELOG history and help.md's deprecation
  notice) for the removed command form, and verifies each runtime
  branch emits the consolidated form via captured console output.
- tests/copilot-install.test.cjs:1081-1115 — stale assertions that
  hard-coded the removed string updated to assert /gsd-update --reapply.

Verification: 115/115 pass across both files.

Co-authored-by: Patrick Clery <patrick@patrickclery.com>
Closes #3010

* test(#3010): broaden dead-command scan + tighten runtime exact-match

CodeRabbit follow-up findings on #3012:

1. Workflow + docs scans only matched "/gsd-reapply-patches", missing
   the gemini ("/gsd:reapply-patches") and codex ("$gsd-reapply-patches")
   spellings. A regression that re-introduced either form in localized
   docs would have passed silently. Extracted a DEAD_COMMAND_PATTERNS
   array + findDeadCommands() helper used by both scans, so all three
   removed forms are checked uniformly. Match output also reports which
   spellings hit, for faster diagnosis.

2. reportLocalPatches runtime test asserted output.includes('update --reapply'),
   which is too loose — a malformed prefix like '/gsd:update --reapply' on
   the claude branch would have passed. Replaced with an exact
   {runtime → expected token} map covering all 7 branches:
     claude/opencode/kilo/copilot → /gsd-update --reapply
     gemini → /gsd:update --reapply
     codex → $gsd-update --reapply
     cursor → gsd-update --reapply
   Negative assertion also runs DEAD_COMMAND_PATTERNS against output for
   every runtime, so dead forms can't slip in regardless of branch.

Verification: 4/4 pass on bug-3010-reapply-patches-references.test.cjs.

* test(#3010): add prefix-absence guard for cursor runtime (CR follow-up)

CodeRabbit (Minor): the cursor expected token "gsd-update --reapply" is
a substring of every prefixed form ("/gsd-update --reapply" for claude/
opencode/kilo/copilot, "\$gsd-update --reapply" for codex). The positive
output.includes(expectedToken) check therefore can't distinguish correct
cursor output from a regression where the installer emits a prefixed
form for cursor — both pass the substring check.

Add an explicit prefix-absence assertion for cursor that fails if any
of /, \$, or : appears immediately before "gsd-update --reapply" in
output. The gemini form ("/gsd:update --reapply") doesn't share the
substring (gsd:update vs gsd-update) so it's already caught by the
positive includes failing on cursor's expected bare token.

Verification: 4/4 pass.

---------

Co-authored-by: Patrick Clery <patrick@patrickclery.com>
This commit is contained in:
Tom Boucher
2026-05-02 09:38:34 -04:00
committed by GitHub
parent a4e5cc7c24
commit f2decefede
19 changed files with 300 additions and 35 deletions

View File

@@ -0,0 +1,5 @@
---
type: Fixed
pr: 3012
---
**Post-install message and update.md no longer recommend the removed `/gsd-reapply-patches` command** — after PR #2824 consolidated 86 skills into ~58, `/gsd-reapply-patches` was folded into a flag (`/gsd-update --reapply`). The 1.39.1 hotfix (#2954) updated `help.md` but missed `bin/install.js`'s `reportLocalPatches` runtime emitter, `get-shit-done/workflows/update.md` Step 4, and the English + zh-CN/ja-JP/ko-KR doc set. Users hit "Unknown command" after every install with backed-up patches. All five runtime branches in `reportLocalPatches` (claude, opencode, kilo, copilot, gemini, codex, cursor) now emit the consolidated form. Regression: `tests/bug-3010-reapply-patches-references.test.cjs` scans `bin/install.js`, every workflow file, and every doc (excluding CHANGELOG history and help.md's deprecation notice) for stale recommendations. See #3010.

View File

@@ -7158,14 +7158,14 @@ function reportLocalPatches(configDir, runtime = 'claude') {
if (meta.files && meta.files.length > 0) {
const reapplyCommand = (runtime === 'opencode' || runtime === 'kilo' || runtime === 'copilot')
? '/gsd-reapply-patches'
? '/gsd-update --reapply'
: runtime === 'gemini'
? '/gsd:reapply-patches'
? '/gsd:update --reapply'
: runtime === 'codex'
? '$gsd-reapply-patches'
? '$gsd-update --reapply'
: runtime === 'cursor'
? 'gsd-reapply-patches (mention the skill name)'
: '/gsd-reapply-patches';
? 'gsd-update --reapply (mention the skill name)'
: '/gsd-update --reapply';
console.log('');
console.log(' ' + yellow + 'Local patches detected' + reset + ' (from v' + meta.from_version + '):');
for (const f of meta.files) {

View File

@@ -579,7 +579,7 @@ The installer (`bin/install.js`, ~3,000 lines) handles:
- Augment Code: Skills-first with full skill conversion and config management
5. **Path normalization** — Replaces `~/.claude/` paths with runtime-specific paths
6. **Settings integration** — Registers hooks in runtime's `settings.json`
7. **Patch backup** — Since v1.17, backs up locally modified files to `gsd-local-patches/` for `/gsd-reapply-patches`
7. **Patch backup** — Since v1.17, backs up locally modified files to `gsd-local-patches/` for `/gsd-update --reapply`
8. **Manifest tracking** — Writes `gsd-file-manifest.json` for clean uninstall
9. **Uninstall mode** — `--uninstall` removes all GSD files, hooks, and settings

View File

@@ -902,7 +902,7 @@ continues. Drift detection cannot fail verification.
- REQ-UPDATE-02: System MUST display changelog for new version before updating
- REQ-UPDATE-03: System MUST be runtime-aware and target the correct directory
- REQ-UPDATE-04: System MUST back up locally modified files to `gsd-local-patches/`
- REQ-UPDATE-05: `/gsd-reapply-patches` MUST restore local modifications after update
- REQ-UPDATE-05: `/gsd-update --reapply` MUST restore local modifications after update
---
@@ -2255,7 +2255,7 @@ Test suite that scans all agent, workflow, and command files for embedded inject
### 103. Post-Merge Hunk Verification
**Command:** `/gsd-reapply-patches`
**Command:** `/gsd-update --reapply`
**Purpose:** After applying local patches post-update, verify that all hunks were actually applied by comparing the expected patch content against the live filesystem. Surface any dropped or partial hunks immediately rather than silently accepting incomplete merges.

View File

@@ -224,7 +224,7 @@ Full roster at `get-shit-done/workflows/*.md`. Workflows are thin orchestrators
| `profile-user.md` | Orchestrate the full developer profiling flow — consent, session scan, profile generation. | `/gsd-profile-user` |
| `progress.md` | Progress rendering — project context, position, and next-action routing. | `/gsd-progress` |
| `quick.md` | Quick-task execution with GSD guarantees (atomic commits, state tracking). | `/gsd-quick` |
| `reapply-patches.md` | Reapply local modifications after a GSD update. | `/gsd-reapply-patches` |
| `reapply-patches.md` | Reapply local modifications after a GSD update. | `/gsd-update --reapply` |
| `remove-phase.md` | Remove a future phase from the roadmap and renumber subsequent phases. | `/gsd-remove-phase` |
| `remove-workspace.md` | Remove a GSD workspace and clean up worktrees. | `/gsd-remove-workspace` |
| `research-phase.md` | Standalone phase research workflow (usually invoked via `plan-phase`). | `/gsd-research-phase` |

View File

@@ -1090,7 +1090,7 @@ Set `commit_docs: false` during `/gsd-new-project` or via `/gsd-settings`. Add `
### GSD Update Overwrote My Local Changes
Since v1.17, the installer backs up locally modified files to `gsd-local-patches/`. Run `/gsd-reapply-patches` to merge your changes back.
Since v1.17, the installer backs up locally modified files to `gsd-local-patches/`. Run `/gsd-update --reapply` to merge your changes back.
### Cannot Update via npm
@@ -1249,7 +1249,7 @@ If the installer crashes with `EPERM: operation not permitted, scandir` on Windo
| Quick targeted fix | `/gsd-quick` |
| Plan doesn't match your vision | `/gsd-discuss-phase [N]` then re-plan |
| Costs running high | `/gsd-set-profile budget` and `/gsd-settings` to toggle agents off |
| Update broke local changes | `/gsd-reapply-patches` |
| Update broke local changes | `/gsd-update --reapply` |
| Want session summary for stakeholder | `/gsd-session-report` |
| Don't know what step is next | `/gsd-next` |
| Parallel execution build errors | Update GSD or set `parallelization.enabled: false` |

View File

@@ -439,7 +439,7 @@ UI-SPEC.md (per phase) ───────────────────
- Antigravity: Googleモデル同等品によるスキルファースト
5. **パス正規化** — `~/.claude/` パスをランタイム固有のパスに置換
6. **設定統合** — ランタイムの `settings.json` にフックを登録
7. **パッチバックアップ** — v1.17以降、ローカルで変更されたファイルを `/gsd-reapply-patches` 用に `gsd-local-patches/` へバックアップ
7. **パッチバックアップ** — v1.17以降、ローカルで変更されたファイルを `/gsd-update --reapply` 用に `gsd-local-patches/` へバックアップ
8. **マニフェスト追跡** — クリーンアンインストールのために `gsd-file-manifest.json` を書き込み
9. **アンインストールモード** — `--uninstall` ですべてのGSDファイル、フック、設定を削除

View File

@@ -794,12 +794,12 @@ Claude Codeのセッション分析から8つの次元(コミュニケーシ
/gsd-update # アップデートを確認してインストール
```
### `/gsd-reapply-patches`
### `/gsd-update --reapply`
GSDアップデート後にローカルの変更を復元します。
```bash
/gsd-reapply-patches # ローカルの変更をマージバック
/gsd-update --reapply # ローカルの変更をマージバック
```
---

View File

@@ -800,7 +800,7 @@
- REQ-UPDATE-02: システムは更新前に新しいバージョンのチェンジログを表示しなければならない
- REQ-UPDATE-03: システムはランタイムを認識し、正しいディレクトリを対象としなければならない
- REQ-UPDATE-04: システムはローカルで変更されたファイルを `gsd-local-patches/` にバックアップしなければならない
- REQ-UPDATE-05: `/gsd-reapply-patches` は更新後にローカルの変更を復元しなければならない
- REQ-UPDATE-05: `/gsd-update --reapply` は更新後にローカルの変更を復元しなければならない
---

View File

@@ -432,7 +432,7 @@ GSD はマークダウンファイルを生成し、それが LLM のシステ
| `/gsd-check-todos` | 保留中の TODO を一覧表示 | 記録したアイデアのレビュー |
| `/gsd-settings` | ワークフロートグルとモデルプロファイルを設定 | モデル変更、エージェントのトグル |
| `/gsd-set-profile <profile>` | クイックプロファイル切り替え | コスト/品質トレードオフの変更 |
| `/gsd-reapply-patches` | アップデート後にローカル変更を復元 | ローカル編集がある場合の `/gsd-update` 後 |
| `/gsd-update --reapply` | アップデート後にローカル変更を復元 | ローカル編集がある場合の `/gsd-update` 後 |
### コード品質とレビュー
@@ -754,7 +754,7 @@ GSD サブエージェントが Anthropic モデルを呼び出し、OpenRouter
### GSD アップデートがローカル変更を上書きした
v1.17 以降、インストーラーはローカルで変更されたファイルを `gsd-local-patches/` にバックアップします。`/gsd-reapply-patches` を実行して変更をマージし直してください。
v1.17 以降、インストーラーはローカルで変更されたファイルを `gsd-local-patches/` にバックアップします。`/gsd-update --reapply` を実行して変更をマージし直してください。
### ワークフロー診断 (`/gsd-forensics`)
@@ -801,7 +801,7 @@ Windows でインストーラーが `EPERM: operation not permitted, scandir`
| ターゲットを絞った修正 | `/gsd-quick` |
| プランがビジョンに合わない | `/gsd-discuss-phase [N]` で再プランニング |
| コストが高い | `/gsd-set-profile budget` と `/gsd-settings` でエージェントをオフ |
| アップデートがローカル変更を壊した | `/gsd-reapply-patches` |
| アップデートがローカル変更を壊した | `/gsd-update --reapply` |
| ステークホルダー向けセッションサマリーが欲しい | `/gsd-session-report` |
| 次のステップがわからない | `/gsd-next` |
| 並列実行でビルドエラー | GSD を更新するか `parallelization.enabled: false` を設定 |

View File

@@ -439,7 +439,7 @@ UI-SPEC.md (per phase) ───────────────────
- Antigravity: Google 모델 등가물을 사용한 skills-first 방식
5. **경로 정규화** — `~/.claude/` 경로를 런타임별 경로로 교체
6. **설정 통합** — 런타임의 `settings.json`에 훅 등록
7. **패치 백업** — v1.17부터 로컬 수정 파일을 `gsd-local-patches/`에 백업하여 `/gsd-reapply-patches`에 사용
7. **패치 백업** — v1.17부터 로컬 수정 파일을 `gsd-local-patches/`에 백업하여 `/gsd-update --reapply`에 사용
8. **매니페스트 추적** — 깔끔한 제거를 위해 `gsd-file-manifest.json` 작성
9. **제거 모드** — `--uninstall`로 모든 GSD 파일, 훅, 설정 제거

View File

@@ -794,12 +794,12 @@ Claude Code 세션 분석을 통해 8개 차원(커뮤니케이션 스타일,
/gsd-update # 업데이트 확인 및 설치
```
### `/gsd-reapply-patches`
### `/gsd-update --reapply`
GSD 업데이트 후 로컬 수정사항을 복원합니다.
```bash
/gsd-reapply-patches # 로컬 변경사항 병합
/gsd-update --reapply # 로컬 변경사항 병합
```
---

View File

@@ -800,7 +800,7 @@
- REQ-UPDATE-02: 업데이트 전에 새 버전의 변경 로그를 표시해야 합니다.
- REQ-UPDATE-03: 런타임을 인식하고 올바른 디렉토리를 대상으로 해야 합니다.
- REQ-UPDATE-04: 로컬에서 수정된 파일을 `gsd-local-patches/`에 백업해야 합니다.
- REQ-UPDATE-05: `/gsd-reapply-patches`는 업데이트 후 로컬 수정사항을 복원해야 합니다.
- REQ-UPDATE-05: `/gsd-update --reapply`는 업데이트 후 로컬 수정사항을 복원해야 합니다.
---

View File

@@ -432,7 +432,7 @@ GSD는 LLM 시스템 프롬프트가 되는 마크다운 파일을 생성합니
| `/gsd-check-todos` | 보류 중인 할 일 목록 | 캡처된 아이디어 검토 시 |
| `/gsd-settings` | 워크플로우 토글 및 모델 프로필 설정 | 모델 변경, 에이전트 토글 시 |
| `/gsd-set-profile <profile>` | 빠른 프로필 전환 | 비용/품질 트레이드오프 변경 시 |
| `/gsd-reapply-patches` | 업데이트 후 로컬 수정사항 복원 | 로컬 편집이 있는 상태에서 `/gsd-update` 이후 |
| `/gsd-update --reapply` | 업데이트 후 로컬 수정사항 복원 | 로컬 편집이 있는 상태에서 `/gsd-update` 이후 |
### 코드 품질 및 리뷰
@@ -754,7 +754,7 @@ GSD 서브에이전트가 Anthropic 모델을 호출하는데 OpenRouter나 로
### GSD 업데이트가 로컬 변경사항을 덮어쓴 경우
v1.17부터 설치 프로그램이 로컬로 수정된 파일을 `gsd-local-patches/`에 백업합니다. 변경사항을 다시 병합하려면 `/gsd-reapply-patches`를 실행하세요.
v1.17부터 설치 프로그램이 로컬로 수정된 파일을 `gsd-local-patches/`에 백업합니다. 변경사항을 다시 병합하려면 `/gsd-update --reapply`를 실행하세요.
### 워크플로우 진단 (`/gsd-forensics`)
@@ -801,7 +801,7 @@ Windows에서 설치 프로그램이 `EPERM: operation not permitted, scandir`
| 빠른 목표 수정 | `/gsd-quick` |
| 계획이 비전과 맞지 않음 | `/gsd-discuss-phase [N]` 후 재계획 |
| 비용이 높아짐 | `/gsd-set-profile budget` 및 `/gsd-settings`에서 에이전트 비활성화 |
| 업데이트가 로컬 변경사항 파괴 | `/gsd-reapply-patches` |
| 업데이트가 로컬 변경사항 파괴 | `/gsd-update --reapply` |
| 이해관계자를 위한 세션 요약 필요 | `/gsd-session-report` |
| 다음 단계를 모르겠음 | `/gsd-next` |
| 병렬 실행 빌드 오류 | GSD 업데이트 또는 `parallelization.enabled: false` 설정 |

View File

@@ -59,4 +59,4 @@ The installer performs a clean wipe-and-replace of GSD-managed directories only:
- Your `CLAUDE.md` files
- Custom hooks
Locally modified GSD files are automatically backed up to `gsd-local-patches/` before the install. Run `/gsd-reapply-patches` after updating to merge your modifications back in.
Locally modified GSD files are automatically backed up to `gsd-local-patches/` before the install. Run `/gsd-update --reapply` after updating to merge your modifications back in.

View File

@@ -234,7 +234,7 @@
| `/gsd-check-todos` | 列出待处理事项 | 查看捕获的想法 |
| `/gsd-settings` | 配置工作流开关和模型配置 | 更改模型、切换代理 |
| `/gsd-set-profile <profile>` | 快速切换配置 | 更改成本/质量权衡 |
| `/gsd-reapply-patches` | 更新后恢复本地修改 | 如果你有本地编辑,在 `/gsd-update` 后 |
| `/gsd-update --reapply` | 更新后恢复本地修改 | 如果你有本地编辑,在 `/gsd-update` 后 |
---
@@ -466,7 +466,7 @@ node gsd-tools.cjs state sync # 从磁盘重建 STATE.md
### GSD 更新覆盖了我的本地更改
从 v1.17 开始,安装程序将本地修改的文件备份到 `gsd-local-patches/`。运行 `/gsd-reapply-patches` 将你的更改合并回来。
从 v1.17 开始,安装程序将本地修改的文件备份到 `gsd-local-patches/`。运行 `/gsd-update --reapply` 将你的更改合并回来。
### 子代理似乎失败但工作已完成
@@ -487,7 +487,7 @@ node gsd-tools.cjs state sync # 从磁盘重建 STATE.md
| 快速针对性修复 | `/gsd-quick` |
| 计划与你的愿景不符 | `/gsd-discuss-phase [N]` 然后重新规划 |
| 成本过高 | `/gsd-set-profile budget` 和 `/gsd-settings` 关闭代理 |
| 更新破坏了本地更改 | `/gsd-reapply-patches` |
| 更新破坏了本地更改 | `/gsd-update --reapply` |
---

View File

@@ -406,7 +406,7 @@ Your custom files in other locations are preserved:
- Custom hooks ✓
- Your CLAUDE.md files ✓
If you've modified any GSD files directly, they'll be automatically backed up to `gsd-local-patches/` and can be reapplied with `/gsd-reapply-patches` after the update.
If you've modified any GSD files directly, they'll be automatically backed up to `gsd-local-patches/` and can be reapplied with `/gsd-update --reapply` after the update.
```
@@ -614,7 +614,7 @@ Check for gsd-local-patches/backup-meta.json in the config directory.
```
Local patches were backed up before the update.
Run /gsd-update --reapply to merge your modifications into the new version.
Run `/gsd-update --reapply` to merge your modifications into the new version.
```
**If no patches:** Continue normally.

View File

@@ -0,0 +1,255 @@
// allow-test-rule: source-text-is-the-product
// Reads .md and .js product files whose deployed text IS what the user
// sees — testing text content tests the deployed contract.
/**
* Regression test for bug #3010
*
* After PR #2824 consolidated 86 skills into ~58, the standalone slash
* command `/gsd-reapply-patches` was removed and folded into a flag on
* `/gsd-update` (i.e. `/gsd-update --reapply`). The 1.39.1 hotfix (#2954)
* fixed `help.md` to reflect the consolidated commands, but missed two
* other surfaces that still printed/recommended the removed command:
*
* 1. `bin/install.js` — the post-install message (`reportLocalPatches`)
* told every runtime to "Run /gsd-reapply-patches", which is no
* longer a registered command and prints "Unknown command".
* 2. `get-shit-done/workflows/update.md` Step 4 — the auto-commit text
* appended at the end of the `/gsd-update` flow recommended the
* same dead command.
* 3. English `docs/USER-GUIDE.md`, `docs/manual-update.md`,
* `docs/ARCHITECTURE.md`, `docs/FEATURES.md`, `docs/INVENTORY.md`
* and the translated docs under `docs/{zh-CN,ja-JP,ko-KR}/` carried
* stale references in the same recommendation positions.
*
* Fix: every user-facing recommendation now points at `/gsd-update --reapply`.
*
* This test verifies the user-facing contract:
* 1. `bin/install.js` source emits the consolidated form for every runtime.
* 2. No file under `get-shit-done/workflows/` recommends running
* `/gsd-reapply-patches` (the historical "replaces the former" mention
* in `help.md` is allowed because it's the deprecation notice itself).
* 3. No file under `docs/` recommends running `/gsd-reapply-patches`
* (CHANGELOG history references are excluded — they document the
* past and must not be rewritten).
*
* Defensive scope: the workflow file `reapply-patches.md` and code
* comments naming the workflow file (`scripts/verify-reapply-patches.cjs`,
* comments in `bin/install.js`) are NOT user-facing recommendations —
* those reference the workflow's *implementation name*, which is
* unchanged. Only strings that prompt the user to *run* the command
* are in scope here.
*/
'use strict';
process.env.GSD_TEST_MODE = '1';
const { describe, test } = require('node:test');
const assert = require('node:assert/strict');
const fs = require('node:fs');
const path = require('node:path');
const ROOT = path.join(__dirname, '..');
const INSTALL_JS = path.join(ROOT, 'bin', 'install.js');
const WORKFLOWS_DIR = path.join(ROOT, 'get-shit-done', 'workflows');
const DOCS_DIR = path.join(ROOT, 'docs');
// Files that are allowed to mention the dead command for legitimate reasons:
// - help.md — explicitly documents that --reapply *replaces* the former
// standalone command. Removing this would erase the deprecation
// trail for users who still type the old form.
// - CHANGELOG.md — historical entries describing past bugs/fixes referencing
// the old command name. Rewriting history would falsify
// release notes.
const ALLOWED_HISTORICAL_MENTIONS = new Set([
path.join(WORKFLOWS_DIR, 'help.md'),
path.join(ROOT, 'CHANGELOG.md'),
]);
function walkMd(dir) {
const files = [];
if (!fs.existsSync(dir)) return files;
for (const entry of fs.readdirSync(dir, { withFileTypes: true })) {
const full = path.join(dir, entry.name);
if (entry.isDirectory()) files.push(...walkMd(full));
else if (entry.name.endsWith('.md')) files.push(full);
}
return files;
}
describe('bug-3010: post-install message and docs recommend /gsd-update --reapply', () => {
test('bin/install.js emits /gsd-update --reapply (no /gsd-reapply-patches recommendations)', () => {
const src = fs.readFileSync(INSTALL_JS, 'utf-8');
// Locate the reportLocalPatches function — that is the runtime emitter
// a user sees after every install. Scope the assertion to that function
// body only so historical doc-comments (e.g. JSDoc explaining the
// verifier history) are not flagged. The function body runs from the
// declaration line to the next top-level `function ` declaration.
const fnStart = src.indexOf('function reportLocalPatches');
assert.ok(fnStart >= 0, 'reportLocalPatches function must exist in bin/install.js');
const afterFn = src.indexOf('\nfunction ', fnStart + 1);
const fnBody = afterFn > 0 ? src.slice(fnStart, afterFn) : src.slice(fnStart);
// The body must reference the consolidated command for every runtime
// path. The negative assertion is what catches drift — adding a
// forgotten `/gsd-reapply-patches` literal here regresses #3010.
assert.ok(
fnBody.includes('/gsd-update --reapply'),
'reportLocalPatches must emit the consolidated /gsd-update --reapply form',
);
assert.ok(
!fnBody.includes('/gsd-reapply-patches'),
'reportLocalPatches must NOT emit the removed /gsd-reapply-patches command',
);
assert.ok(
!fnBody.includes('/gsd:reapply-patches'),
'reportLocalPatches must NOT emit the removed /gsd:reapply-patches Gemini-style command',
);
assert.ok(
!fnBody.includes('$gsd-reapply-patches'),
'reportLocalPatches must NOT emit the removed $gsd-reapply-patches Codex-style command',
);
});
// All three legacy spellings of the removed command. The slash/dollar
// prefix is the slash-command marker — bare "reapply-patches" without a
// prefix is not a user-typable command and is allowed (file path refs,
// workflow filename, verify-reapply-patches.cjs script).
// /gsd-reapply-patches — claude/opencode/kilo/copilot
// /gsd:reapply-patches — gemini namespace
// $gsd-reapply-patches — codex prefix
const DEAD_COMMAND_PATTERNS = [
/\/gsd-reapply-patches\b/g,
/\/gsd:reapply-patches\b/g,
/\$gsd-reapply-patches\b/g,
];
function findDeadCommands(stripped) {
const matches = [];
for (const re of DEAD_COMMAND_PATTERNS) {
const m = stripped.match(re);
if (m) matches.push(...m);
}
return matches;
}
test('no workflow file recommends a removed reapply-patches command', () => {
const workflowFiles = walkMd(WORKFLOWS_DIR);
assert.ok(workflowFiles.length > 0, `expected workflow markdown files under ${WORKFLOWS_DIR}`);
const offenders = [];
for (const file of workflowFiles) {
if (ALLOWED_HISTORICAL_MENTIONS.has(file)) continue;
const src = fs.readFileSync(file, 'utf-8');
// Strip HTML comments to avoid matching commented-out examples
// and prose that quotes the old command for context.
const stripped = src.replace(/<!--[\s\S]*?-->/g, '');
const matches = findDeadCommands(stripped);
if (matches.length) offenders.push(`${path.relative(ROOT, file)}: ${matches.length} mention(s) [${[...new Set(matches)].join(', ')}]`);
}
assert.deepStrictEqual(
offenders,
[],
'workflow files must not recommend any removed reapply-patches command form:\n ' +
offenders.join('\n '),
);
});
test('no doc under docs/ recommends a removed reapply-patches command (excluding CHANGELOG history)', () => {
const docFiles = walkMd(DOCS_DIR);
assert.ok(docFiles.length > 0, `expected docs under ${DOCS_DIR}`);
const offenders = [];
for (const file of docFiles) {
if (ALLOWED_HISTORICAL_MENTIONS.has(file)) continue;
const src = fs.readFileSync(file, 'utf-8');
const stripped = src.replace(/<!--[\s\S]*?-->/g, '');
const matches = findDeadCommands(stripped);
if (matches.length) offenders.push(`${path.relative(ROOT, file)}: ${matches.length} mention(s) [${[...new Set(matches)].join(', ')}]`);
}
assert.deepStrictEqual(
offenders,
[],
'docs must not recommend any removed reapply-patches command form:\n ' +
offenders.join('\n '),
);
});
test('reportLocalPatches output text includes the consolidated form for every runtime branch', () => {
// Functional check: dynamically require the installer, capture
// console.log, and assert each runtime branch emits the new form.
// This guards against future refactors that could re-introduce a
// runtime-specific stale string the static text scan would miss.
const { reportLocalPatches } = require(INSTALL_JS);
assert.ok(typeof reportLocalPatches === 'function', 'reportLocalPatches must be exported');
const tmpDir = fs.mkdtempSync(path.join(require('os').tmpdir(), 'gsd-bug-3010-'));
try {
const patchesDir = path.join(tmpDir, 'gsd-local-patches');
fs.mkdirSync(patchesDir, { recursive: true });
fs.writeFileSync(
path.join(patchesDir, 'backup-meta.json'),
JSON.stringify({ from_version: '1.0', files: ['skills/gsd-test/SKILL.md'] }),
);
// Cover every runtime branch in the conditional with the EXACT token
// each branch is contractually required to emit. A loose substring
// like 'update --reapply' would let a malformed prefix slip through
// (e.g. emitting '/gsd-update --reapply' for the gemini branch when
// it should be '/gsd:update --reapply').
const expectedByRuntime = {
claude: '/gsd-update --reapply',
opencode: '/gsd-update --reapply',
kilo: '/gsd-update --reapply',
copilot: '/gsd-update --reapply',
gemini: '/gsd:update --reapply',
codex: '$gsd-update --reapply',
cursor: 'gsd-update --reapply',
};
for (const [runtime, expectedToken] of Object.entries(expectedByRuntime)) {
const logs = [];
const originalLog = console.log;
console.log = (...args) => logs.push(args.join(' '));
try {
reportLocalPatches(tmpDir, runtime);
} finally {
console.log = originalLog;
}
const output = logs.join('\n');
assert.ok(
output.includes(expectedToken),
`runtime ${runtime}: output must include exact token "${expectedToken}", got:\n${output}`,
);
// The cursor runtime expects a BARE token without slash/dollar/colon
// prefix. The bare form is a substring of every prefixed variant, so
// the positive substring check above can't tell correct cursor output
// from a regression that wrongly emitted '/gsd-update --reapply'
// (claude form) or '$gsd-update --reapply' (codex form) for cursor.
// Add an explicit prefix-absence guard for cursor so that regression
// is caught.
if (runtime === 'cursor') {
assert.ok(
!/[/$:]gsd-update --reapply/.test(output),
`runtime cursor: output must use bare "gsd-update --reapply" without slash/dollar/colon prefix, got:\n${output}`,
);
}
// Negative: none of the dead command forms may appear, regardless of runtime.
for (const re of DEAD_COMMAND_PATTERNS) {
assert.ok(
!re.test(output),
`runtime ${runtime}: output must not reference removed command (matched ${re.source}), got:\n${output}`,
);
re.lastIndex = 0; // reset stateful global regex
}
}
} finally {
fs.rmSync(tmpDir, { recursive: true, force: true });
}
});
});

View File

@@ -1078,7 +1078,7 @@ describe('Copilot manifest and patches fixes', () => {
console.log = originalLog;
});
test('reportLocalPatches shows /gsd-reapply-patches for Copilot', () => {
test('reportLocalPatches shows /gsd-update --reapply for Copilot', () => {
// Create patches directory with metadata
const patchesDir = path.join(tmpDir, 'gsd-local-patches');
fs.mkdirSync(patchesDir, { recursive: true });
@@ -1091,11 +1091,15 @@ describe('Copilot manifest and patches fixes', () => {
assert.ok(result.length > 0, 'returns patched files list');
const output = logs.join('\n');
assert.ok(output.includes('/gsd-reapply-patches'), 'uses dash format for Copilot');
// Asserts the consolidated form. /gsd-reapply-patches was removed in
// 1.39 (PR #2824) and folded into a flag on /gsd-update — see #3010.
// Negative assertion guards against regression to the dead command.
assert.ok(output.includes('/gsd-update --reapply'), 'uses consolidated /gsd-update --reapply form for Copilot');
assert.ok(!output.includes('/gsd-reapply-patches'), 'does not reference removed /gsd-reapply-patches command');
assert.ok(!output.includes('/gsd:reapply-patches'), 'does not use colon format');
});
test('reportLocalPatches shows /gsd-reapply-patches for Claude', () => {
test('reportLocalPatches shows /gsd-update --reapply for Claude', () => {
// Create patches directory with metadata
const patchesDir = path.join(tmpDir, 'gsd-local-patches');
fs.mkdirSync(patchesDir, { recursive: true });
@@ -1108,7 +1112,8 @@ describe('Copilot manifest and patches fixes', () => {
assert.ok(result.length > 0, 'returns patched files list');
const output = logs.join('\n');
assert.ok(output.includes('/gsd-reapply-patches'), 'uses hyphen format for Claude');
assert.ok(output.includes('/gsd-update --reapply'), 'uses consolidated /gsd-update --reapply form for Claude');
assert.ok(!output.includes('/gsd-reapply-patches'), 'does not reference removed /gsd-reapply-patches command');
assert.ok(!output.includes('/gsd:reapply-patches'), 'does not use colon format for Claude');
});
});