@opengsd/gsd-core 1.19.0 → 1.20.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (106) hide show
  1. package/.claude-plugin/marketplace.json +1 -1
  2. package/.claude-plugin/plugin.json +1 -1
  3. package/agents/gsd-code-fixer.compact.md +5 -5
  4. package/agents/gsd-code-fixer.md +9 -4
  5. package/agents/gsd-code-reviewer.compact.md +49 -8
  6. package/agents/gsd-code-reviewer.md +53 -14
  7. package/agents/gsd-executor.md +8 -4
  8. package/commands/gsd/update.md +5 -2
  9. package/gsd-core/bin/gsd-tools.cjs +73 -23
  10. package/gsd-core/bin/lib/capability-lifecycle.cjs +1 -12
  11. package/gsd-core/bin/lib/capability-registry.cjs +284 -93
  12. package/gsd-core/bin/lib/capability-validator.cjs +15 -0
  13. package/gsd-core/bin/lib/commands.cjs +27 -17
  14. package/gsd-core/bin/lib/config-loader.cjs +3 -3
  15. package/gsd-core/bin/lib/core-utils.cjs +11 -0
  16. package/gsd-core/bin/lib/frontmatter.cjs +21 -3
  17. package/gsd-core/bin/lib/gate-api-coverage-verify-pre.cjs +2 -2
  18. package/gsd-core/bin/lib/gate-context-drift.cjs +1 -1
  19. package/gsd-core/bin/lib/gate-phase-context.cjs +7 -8
  20. package/gsd-core/bin/lib/gate-schema-drift.cjs +1 -1
  21. package/gsd-core/bin/lib/git-info-exclude.cjs +1 -6
  22. package/gsd-core/bin/lib/health-diagnostic-rules/phase-structure.cjs +13 -9
  23. package/gsd-core/bin/lib/health-diagnostic-rules/roadmap-disk-consistency.cjs +9 -7
  24. package/gsd-core/bin/lib/health-diagnostic-rules/state-consistency.cjs +3 -3
  25. package/gsd-core/bin/lib/init.cjs +180 -67
  26. package/gsd-core/bin/lib/io.cjs +6 -0
  27. package/gsd-core/bin/lib/markdown-sectionizer.cjs +34 -1
  28. package/gsd-core/bin/lib/markdown-table.cjs +23 -0
  29. package/gsd-core/bin/lib/mcp-server.cjs +8 -5
  30. package/gsd-core/bin/lib/milestone.cjs +18 -50
  31. package/gsd-core/bin/lib/phase-entry-grammar.cjs +119 -0
  32. package/gsd-core/bin/lib/phase-id-display.cjs +2 -0
  33. package/gsd-core/bin/lib/phase-id.cjs +609 -35
  34. package/gsd-core/bin/lib/phase-lifecycle.cjs +9 -74
  35. package/gsd-core/bin/lib/phase-locator.cjs +328 -19
  36. package/gsd-core/bin/lib/phase.cjs +2990 -666
  37. package/gsd-core/bin/lib/planning-document.cjs +26 -5
  38. package/gsd-core/bin/lib/planning-inspect.cjs +27 -35
  39. package/gsd-core/bin/lib/planning-snapshot.cjs +12 -7
  40. package/gsd-core/bin/lib/planning-workspace.cjs +3 -3
  41. package/gsd-core/bin/lib/read-only-git-env.cjs +23 -2
  42. package/gsd-core/bin/lib/refactor-trigger-command-router.cjs +2 -2
  43. package/gsd-core/bin/lib/review-lane-descriptor.cjs +22 -0
  44. package/gsd-core/bin/lib/review-lane-invocation.cjs +125 -18
  45. package/gsd-core/bin/lib/reviewer-step-dispatch.cjs +11 -9
  46. package/gsd-core/bin/lib/roadmap-parser.cjs +730 -144
  47. package/gsd-core/bin/lib/roadmap-progress.cjs +136 -0
  48. package/gsd-core/bin/lib/roadmap-upgrade.cjs +8 -5
  49. package/gsd-core/bin/lib/roadmap.cjs +358 -106
  50. package/gsd-core/bin/lib/shell-command-projection.cjs +36 -9
  51. package/gsd-core/bin/lib/smart-entry.cjs +22 -11
  52. package/gsd-core/bin/lib/state-contract.cjs +11 -7
  53. package/gsd-core/bin/lib/state-document.cjs +332 -23
  54. package/gsd-core/bin/lib/state-transition.cjs +39 -10
  55. package/gsd-core/bin/lib/state.cjs +498 -179
  56. package/gsd-core/bin/lib/template.cjs +2 -2
  57. package/gsd-core/bin/lib/validate.cjs +3 -2
  58. package/gsd-core/bin/lib/vendor/re2js.cjs +863 -577
  59. package/gsd-core/bin/lib/vendor/re2js.d.cts +284 -219
  60. package/gsd-core/bin/lib/verify.cjs +2 -2
  61. package/gsd-core/bin/lib/workstream-inventory.cjs +26 -20
  62. package/gsd-core/bin/verify-reapply-patches.cjs +411 -10
  63. package/gsd-core/references/cicd-review-patterns.md +45 -0
  64. package/gsd-core/references/necessity-review.md +48 -0
  65. package/gsd-core/workflows/code-review-fix.md +46 -5
  66. package/gsd-core/workflows/code-review.md +3 -0
  67. package/gsd-core/workflows/execute-phase.md +1 -1
  68. package/gsd-core/workflows/execute-plan.md +1 -1
  69. package/gsd-core/workflows/plan-phase/steps/stall-detection-helpers.md +13 -9
  70. package/gsd-core/workflows/plan-phase.md +1 -1
  71. package/gsd-core/workflows/pr-branch.md +2 -0
  72. package/gsd-core/workflows/reapply-patches.md +104 -0
  73. package/gsd-core/workflows/review.md +54 -141
  74. package/gsd-core/workflows/secure-phase.md +1 -1
  75. package/gsd-core/workflows/settings-advanced.md +57 -9
  76. package/gsd-core/workflows/ship.md +12 -0
  77. package/hooks/dist/gsd-graphify-update.sh +6 -0
  78. package/hooks/dist/gsd-statusline.js +3 -2
  79. package/hooks/dist/gsd-windsurf-pre-write.js +4 -4
  80. package/hooks/dist/gsd-workflow-guard.js +2 -1
  81. package/hooks/dist/gsd-worktree-path-guard.js +4 -4
  82. package/hooks/dist/lib/git-probe.js +16 -0
  83. package/hooks/gsd-graphify-update.sh +6 -0
  84. package/hooks/gsd-statusline.js +3 -2
  85. package/hooks/gsd-windsurf-pre-write.js +4 -4
  86. package/hooks/gsd-workflow-guard.js +2 -1
  87. package/hooks/gsd-worktree-path-guard.js +4 -4
  88. package/hooks/lib/git-probe.js +16 -0
  89. package/package.json +3 -3
  90. package/pi/package-agents/gsd-code-fixer.md +9 -4
  91. package/pi/package-agents/gsd-code-reviewer.md +53 -14
  92. package/pi/package-agents/gsd-executor.md +8 -4
  93. package/pi/package-agents/manifest.json +3 -3
  94. package/pi/package-skills/gsd-update/SKILL.md +4 -1
  95. package/scripts/ci-next-health.cjs +246 -23
  96. package/scripts/docs-guard-registry.cjs +2 -0
  97. package/scripts/gen-features.cjs +14 -2
  98. package/scripts/lib/drift-scan.cjs +27 -0
  99. package/scripts/lib/macos-conformance-tier.generated.cjs +6 -0
  100. package/scripts/lib/platform-conformance-tier.generated.cjs +11 -0
  101. package/scripts/lint-phase-enumeration-drift.cjs +8 -4
  102. package/scripts/lint-roadmap-progress-drift.cjs +421 -0
  103. package/scripts/mutation-matrix.cjs +23 -1
  104. package/scripts/sync-next-version.cjs +38 -6
  105. package/skills/gsd-update/SKILL.md +5 -2
  106. package/vscode/package.json +1 -1
@@ -22,7 +22,7 @@ const path = require('path');
22
22
  const { spawnSync } = require('child_process');
23
23
  const { tokenize, skipToSubcommand } = require('./lib/git-cmd.js');
24
24
  const { HOOK_ON_CRASH, allow, deny, crash } = require('./lib/hook-exit.js');
25
- const { reportIfUndetermined, BLOCKING_GUARD_PROBE_TIMEOUT_MS } = require('./lib/git-probe.js');
25
+ const { reportIfUndetermined, gitEnv, BLOCKING_GUARD_PROBE_TIMEOUT_MS } = require('./lib/git-probe.js');
26
26
 
27
27
  // This guard is almost entirely advisory (fail open — a broken advisory must
28
28
  // never wedge every tool call), with ONE hard block (#3504 force-add-on-
@@ -83,6 +83,7 @@ function forceGitAddCwds(command, defaultCwd) {
83
83
  function currentBranch(cwd) {
84
84
  const result = spawnSync('git', ['branch', '--show-current'], { // allow-index-refresh: reads HEAD only, never opens the index
85
85
  cwd,
86
+ env: gitEnv(),
86
87
  encoding: 'utf8',
87
88
  stdio: ['ignore', 'pipe', 'ignore'],
88
89
  windowsHide: true,
@@ -18,7 +18,7 @@ const fs = require('fs');
18
18
  const path = require('path');
19
19
  const { spawnSync } = require('child_process');
20
20
  const { HOOK_ON_CRASH, allow, deny, crash } = require('./lib/hook-exit.js');
21
- const { reportIfUndetermined, BLOCKING_GUARD_PROBE_TIMEOUT_MS } = require('./lib/git-probe.js');
21
+ const { reportIfUndetermined, gitEnv, BLOCKING_GUARD_PROBE_TIMEOUT_MS } = require('./lib/git-probe.js');
22
22
 
23
23
  // This guard's outer catch has always exited 0 (fail open): a path guard that
24
24
  // cannot resolve the worktree must not block the user's edit — its whole job
@@ -49,10 +49,10 @@ const SPAWNOPT = { encoding: 'utf8', stdio: ['ignore', 'pipe', 'ignore'], timeou
49
49
  // Parity: tests/git-optional-locks-parity.test.cjs.
50
50
  // A raw plugin-marketplace or git-clone install never ran `npm run build:lib`
51
51
  // (#3582), so the compiled owner can be absent. This guard only runs
52
- // `git rev-parse`, which never opens the index, so the unmodified environment
52
+ // `git rev-parse`, which never opens the index, so the scrubbed gitEnv() base
53
53
  // loses nothing; requiring the artifact unguarded would throw at load and the
54
54
  // containment guard would silently stop running.
55
- let readOnlyGitEnv = () => process.env;
55
+ let readOnlyGitEnv = (base = process.env) => base;
56
56
  try {
57
57
  const { ensureRuntimeBuild } = require('../gsd-core/bin/ensure-runtime-build.cjs');
58
58
  ensureRuntimeBuild();
@@ -62,7 +62,7 @@ try {
62
62
  }
63
63
 
64
64
  function git(args, cwd) {
65
- return spawnSync('git', args, { ...SPAWNOPT, cwd, env: readOnlyGitEnv() });
65
+ return spawnSync('git', args, { ...SPAWNOPT, cwd, env: readOnlyGitEnv(gitEnv()) });
66
66
  }
67
67
 
68
68
  // Walk up from `start` to find the nearest existing directory.
@@ -92,7 +92,23 @@ function reportIfUndetermined(hookName, probeLabel, result) {
92
92
  const BLOCKING_GUARD_PROBE_TIMEOUT_MS = 5000;
93
93
  const BLOCKING_GUARD_MAX_SEQUENTIAL_PROBES = 3;
94
94
 
95
+ // #5548: repo-location vars git sets in hooks; inherited, they override the
96
+ // probe's cwd/-C. Parity-tested copy of GIT_LOCATION_ENV_VARS in
97
+ // src/shell-command-projection.cts (hooks ship standalone and cannot import it).
98
+ const GIT_LOCATION_ENV_VARS = [
99
+ 'GIT_ALTERNATE_OBJECT_DIRECTORIES', 'GIT_OBJECT_DIRECTORY', 'GIT_DIR', 'GIT_WORK_TREE',
100
+ 'GIT_IMPLICIT_WORK_TREE', 'GIT_GRAFT_FILE', 'GIT_INDEX_FILE', 'GIT_NO_REPLACE_OBJECTS',
101
+ 'GIT_REPLACE_REF_BASE', 'GIT_PREFIX', 'GIT_SHALLOW_FILE', 'GIT_COMMON_DIR',
102
+ ];
103
+
104
+ /** `env` (default process.env) minus the repo-location vars (case-insensitive, for Windows). */
105
+ function gitEnv(env = process.env) {
106
+ return Object.fromEntries(Object.entries(env).filter(([k]) => !GIT_LOCATION_ENV_VARS.includes(k.toUpperCase())));
107
+ }
108
+
95
109
  module.exports = {
110
+ GIT_LOCATION_ENV_VARS,
111
+ gitEnv,
96
112
  classifyGitProbe,
97
113
  reportIfUndetermined,
98
114
  BLOCKING_GUARD_PROBE_TIMEOUT_MS,
@@ -81,6 +81,12 @@ case "$COMMAND" in
81
81
  *) exit 0 ;;
82
82
  esac
83
83
 
84
+ # #5548: inherited repo-location vars (GIT_DIR, GIT_INDEX_FILE, ...) would override
85
+ # cwd for every git call below. Drop git's own list minus the config-injection vars.
86
+ for v in $(git rev-parse --local-env-vars 2>/dev/null); do
87
+ case "$v" in GIT_CONFIG|GIT_CONFIG_PARAMETERS|GIT_CONFIG_COUNT) ;; *) unset "$v" ;; esac
88
+ done
89
+
84
90
  # Gate 3 — inside git repo
85
91
  git rev-parse --git-dir >/dev/null 2>&1 || exit 0
86
92
 
@@ -18,6 +18,7 @@ const os = require('os');
18
18
  // monkeypatching childProcess.execFileSync.
19
19
  const childProcess = require('child_process');
20
20
  const { HOOK_ON_CRASH, allow, crash } = require('./lib/hook-exit.js');
21
+ const { gitEnv } = require('./lib/git-probe.js');
21
22
 
22
23
  // This hook's build-seam outer catch (require.main guard just below) has
23
24
  // always exited 0 (fail open — the statusline renders on EVERY prompt, so a
@@ -934,7 +935,7 @@ function readGitStatus(dir) {
934
935
  // or untracked files; overflow still degrades safely to segment-absent via
935
936
  // the catch below.
936
937
  return childProcess.execFileSync('git', ['-C', dir, 'status', '--porcelain=v2', '--branch'],
937
- { encoding: 'utf8', timeout: GIT_STATUS_TIMEOUT_MS, maxBuffer: 8 * 1024 * 1024, stdio: ['ignore', 'pipe', 'ignore'], windowsHide: true, env: readOnlyGitEnv() });
938
+ { encoding: 'utf8', timeout: GIT_STATUS_TIMEOUT_MS, maxBuffer: 8 * 1024 * 1024, stdio: ['ignore', 'pipe', 'ignore'], windowsHide: true, env: readOnlyGitEnv(gitEnv()) });
938
939
  } catch (e) {
939
940
  return null;
940
941
  }
@@ -1029,7 +1030,7 @@ function readStateHeadCommits(root, stamp) {
1029
1030
  try {
1030
1031
  return childProcess.execFileSync('git', // allow-index-refresh: rev-list walks the commit graph and never opens the index
1031
1032
  ['-C', root, 'rev-list', '--left-right', '--count', `${stamp}...HEAD`],
1032
- { encoding: 'utf8', timeout: STATE_FRESHNESS_GIT_TIMEOUT_MS,
1033
+ { env: gitEnv(), encoding: 'utf8', timeout: STATE_FRESHNESS_GIT_TIMEOUT_MS,
1033
1034
  stdio: ['ignore', 'pipe', 'ignore'], windowsHide: true });
1034
1035
  } catch (e) {
1035
1036
  return null;
@@ -31,7 +31,7 @@ const fs = require('fs');
31
31
  const path = require('path');
32
32
  const { spawnSync } = require('child_process');
33
33
  const { allow, deny } = require('./lib/hook-exit.js');
34
- const { reportIfUndetermined, BLOCKING_GUARD_PROBE_TIMEOUT_MS } = require('./lib/git-probe.js');
34
+ const { reportIfUndetermined, gitEnv, BLOCKING_GUARD_PROBE_TIMEOUT_MS } = require('./lib/git-probe.js');
35
35
 
36
36
  // #3911 (ADR-3889 Phase 7): the exit(2) call site (block(), below) is
37
37
  // migrated to hook-exit.js's deny(undefined, reason) — see
@@ -64,10 +64,10 @@ const SPAWNOPT = { encoding: 'utf8', stdio: ['ignore', 'pipe', 'ignore'], timeou
64
64
  // Parity: tests/git-optional-locks-parity.test.cjs.
65
65
  // A raw plugin-marketplace or git-clone install never ran `npm run build:lib`
66
66
  // (#3582), so the compiled owner can be absent. This guard only runs
67
- // `git rev-parse`, which never opens the index, so the unmodified environment
67
+ // `git rev-parse`, which never opens the index, so the scrubbed gitEnv() base
68
68
  // loses nothing; requiring the artifact unguarded would throw at load and the
69
69
  // containment guard would silently stop running.
70
- let readOnlyGitEnv = () => process.env;
70
+ let readOnlyGitEnv = (base = process.env) => base;
71
71
  try {
72
72
  const { ensureRuntimeBuild } = require('../gsd-core/bin/ensure-runtime-build.cjs');
73
73
  ensureRuntimeBuild();
@@ -77,7 +77,7 @@ try {
77
77
  }
78
78
 
79
79
  function git(args, cwd) {
80
- return spawnSync('git', args, { ...SPAWNOPT, cwd, env: readOnlyGitEnv() });
80
+ return spawnSync('git', args, { ...SPAWNOPT, cwd, env: readOnlyGitEnv(gitEnv()) });
81
81
  }
82
82
 
83
83
  // Walk up from `start` to find the nearest existing DIRECTORY (not merely an
@@ -22,7 +22,7 @@ const path = require('path');
22
22
  const { spawnSync } = require('child_process');
23
23
  const { tokenize, skipToSubcommand } = require('./lib/git-cmd.js');
24
24
  const { HOOK_ON_CRASH, allow, deny, crash } = require('./lib/hook-exit.js');
25
- const { reportIfUndetermined, BLOCKING_GUARD_PROBE_TIMEOUT_MS } = require('./lib/git-probe.js');
25
+ const { reportIfUndetermined, gitEnv, BLOCKING_GUARD_PROBE_TIMEOUT_MS } = require('./lib/git-probe.js');
26
26
 
27
27
  // This guard is almost entirely advisory (fail open — a broken advisory must
28
28
  // never wedge every tool call), with ONE hard block (#3504 force-add-on-
@@ -83,6 +83,7 @@ function forceGitAddCwds(command, defaultCwd) {
83
83
  function currentBranch(cwd) {
84
84
  const result = spawnSync('git', ['branch', '--show-current'], { // allow-index-refresh: reads HEAD only, never opens the index
85
85
  cwd,
86
+ env: gitEnv(),
86
87
  encoding: 'utf8',
87
88
  stdio: ['ignore', 'pipe', 'ignore'],
88
89
  windowsHide: true,
@@ -18,7 +18,7 @@ const fs = require('fs');
18
18
  const path = require('path');
19
19
  const { spawnSync } = require('child_process');
20
20
  const { HOOK_ON_CRASH, allow, deny, crash } = require('./lib/hook-exit.js');
21
- const { reportIfUndetermined, BLOCKING_GUARD_PROBE_TIMEOUT_MS } = require('./lib/git-probe.js');
21
+ const { reportIfUndetermined, gitEnv, BLOCKING_GUARD_PROBE_TIMEOUT_MS } = require('./lib/git-probe.js');
22
22
 
23
23
  // This guard's outer catch has always exited 0 (fail open): a path guard that
24
24
  // cannot resolve the worktree must not block the user's edit — its whole job
@@ -49,10 +49,10 @@ const SPAWNOPT = { encoding: 'utf8', stdio: ['ignore', 'pipe', 'ignore'], timeou
49
49
  // Parity: tests/git-optional-locks-parity.test.cjs.
50
50
  // A raw plugin-marketplace or git-clone install never ran `npm run build:lib`
51
51
  // (#3582), so the compiled owner can be absent. This guard only runs
52
- // `git rev-parse`, which never opens the index, so the unmodified environment
52
+ // `git rev-parse`, which never opens the index, so the scrubbed gitEnv() base
53
53
  // loses nothing; requiring the artifact unguarded would throw at load and the
54
54
  // containment guard would silently stop running.
55
- let readOnlyGitEnv = () => process.env;
55
+ let readOnlyGitEnv = (base = process.env) => base;
56
56
  try {
57
57
  const { ensureRuntimeBuild } = require('../gsd-core/bin/ensure-runtime-build.cjs');
58
58
  ensureRuntimeBuild();
@@ -62,7 +62,7 @@ try {
62
62
  }
63
63
 
64
64
  function git(args, cwd) {
65
- return spawnSync('git', args, { ...SPAWNOPT, cwd, env: readOnlyGitEnv() });
65
+ return spawnSync('git', args, { ...SPAWNOPT, cwd, env: readOnlyGitEnv(gitEnv()) });
66
66
  }
67
67
 
68
68
  // Walk up from `start` to find the nearest existing directory.
@@ -92,7 +92,23 @@ function reportIfUndetermined(hookName, probeLabel, result) {
92
92
  const BLOCKING_GUARD_PROBE_TIMEOUT_MS = 5000;
93
93
  const BLOCKING_GUARD_MAX_SEQUENTIAL_PROBES = 3;
94
94
 
95
+ // #5548: repo-location vars git sets in hooks; inherited, they override the
96
+ // probe's cwd/-C. Parity-tested copy of GIT_LOCATION_ENV_VARS in
97
+ // src/shell-command-projection.cts (hooks ship standalone and cannot import it).
98
+ const GIT_LOCATION_ENV_VARS = [
99
+ 'GIT_ALTERNATE_OBJECT_DIRECTORIES', 'GIT_OBJECT_DIRECTORY', 'GIT_DIR', 'GIT_WORK_TREE',
100
+ 'GIT_IMPLICIT_WORK_TREE', 'GIT_GRAFT_FILE', 'GIT_INDEX_FILE', 'GIT_NO_REPLACE_OBJECTS',
101
+ 'GIT_REPLACE_REF_BASE', 'GIT_PREFIX', 'GIT_SHALLOW_FILE', 'GIT_COMMON_DIR',
102
+ ];
103
+
104
+ /** `env` (default process.env) minus the repo-location vars (case-insensitive, for Windows). */
105
+ function gitEnv(env = process.env) {
106
+ return Object.fromEntries(Object.entries(env).filter(([k]) => !GIT_LOCATION_ENV_VARS.includes(k.toUpperCase())));
107
+ }
108
+
95
109
  module.exports = {
110
+ GIT_LOCATION_ENV_VARS,
111
+ gitEnv,
96
112
  classifyGitProbe,
97
113
  reportIfUndetermined,
98
114
  BLOCKING_GUARD_PROBE_TIMEOUT_MS,
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@opengsd/gsd-core",
3
- "version": "1.19.0",
3
+ "version": "1.20.0",
4
4
  "description": "GSD Core is a meta-prompting, context engineering, and spec-driven development system for AI coding agents.",
5
5
  "main": ".opencode/plugins/gsd-core.js",
6
6
  "bin": {
@@ -91,7 +91,7 @@
91
91
  "gpt-tokenizer": "4.0.0",
92
92
  "js-yaml": "^4.3.2",
93
93
  "mutation-testing-metrics": "^3.7.3",
94
- "re2js": "^2.8.6",
94
+ "re2js": "^2.8.7",
95
95
  "saxes": "6.0.0",
96
96
  "tap-parser": "18.3.4",
97
97
  "typescript": "^6.0.3",
@@ -146,7 +146,7 @@
146
146
  "lint:gate-positive-control": "node scripts/lint-gate-positive-control.cjs",
147
147
  "lint:frontmatter-fence-drift": "node scripts/lint-frontmatter-fence-drift.cjs",
148
148
  "lint:response-language": "node scripts/lint-response-language-coverage.cjs",
149
- "lint:ci": "npm run lint && npm run lint:skill-deps && npm run lint:generated-sync && node scripts/lint-test-file-count.cjs && node scripts/lint-command-contract.cjs && node scripts/lint-pr-check-project-dir.cjs && npm run lint:legacy-name && npm run lint:retired-runtime-name && node scripts/lint-regression-test-names.cjs && node scripts/lint-allow-test-rule-refs.cjs && node scripts/lint-resolution-provenance.cjs && node scripts/lint-portable-timeout.cjs && node scripts/lint-portable-grep.cjs && node scripts/lint-allowed-tools-parity.cjs && node scripts/lint-phase-arg-assignment.cjs && node scripts/validate-registry.cjs && node scripts/lint-table-schema-drift.cjs && node scripts/lint-planning-document-positive-control.cjs && node scripts/lint-pr-branch-pattern-drift.cjs && node scripts/lint-fix-has-regression-tests.cjs && node scripts/lint-example-parser-parity.cjs && node scripts/lint-docs-command-form.cjs && node scripts/lint-plan-count-drift.cjs && node scripts/lint-milestone-window-drift.cjs && node scripts/lint-phase-enumeration-drift.cjs && node scripts/lint-planning-prompt-drift.cjs && node scripts/lint-unreachable-guard-drift.cjs && node scripts/lint-completion-ratio-drift.cjs && node scripts/lint-slug-derivation-drift.cjs && node scripts/lint-state-field-drift.cjs && node scripts/lint-state-write-path-drift.cjs && node scripts/lint-completion-predicate-drift.cjs && node scripts/lint-planning-snapshot-bypass-drift.cjs && node scripts/lint-health-diagnostic-rule-table.cjs && node scripts/lint-planning-artifact-writer-drift.cjs && node scripts/lint-frontmatter-scalar-broad-grep.cjs && node scripts/lint-removed-but-needed.cjs && node scripts/lint-no-adhoc-regex-escape.cjs && node scripts/lint-vendored-deps.cjs && node scripts/lint-docs-guard-registration.cjs && node scripts/lint-source-test-name-collision.cjs && npm run lint:hooks-runtime-build-seam && node scripts/check-contract-drift.cjs && node scripts/lint-mutation-test-derivation-drift.cjs && node scripts/lint-seam-enforcement.cjs && node scripts/lint-workflow-shellcheck.cjs && npm run lint:response-language && node scripts/lint-verify-lifecycle-writes.cjs && node scripts/lint-frontmatter-fence-drift.cjs && node scripts/lint-evaluation-scope-drift.cjs && node scripts/lint-gate-evidence-drift.cjs && node scripts/lint-gate-positive-control.cjs",
149
+ "lint:ci": "npm run lint && npm run lint:skill-deps && npm run lint:generated-sync && node scripts/lint-test-file-count.cjs && node scripts/lint-command-contract.cjs && node scripts/lint-pr-check-project-dir.cjs && npm run lint:legacy-name && npm run lint:retired-runtime-name && node scripts/lint-regression-test-names.cjs && node scripts/lint-allow-test-rule-refs.cjs && node scripts/lint-resolution-provenance.cjs && node scripts/lint-portable-timeout.cjs && node scripts/lint-portable-grep.cjs && node scripts/lint-allowed-tools-parity.cjs && node scripts/lint-phase-arg-assignment.cjs && node scripts/validate-registry.cjs && node scripts/lint-table-schema-drift.cjs && node scripts/lint-planning-document-positive-control.cjs && node scripts/lint-pr-branch-pattern-drift.cjs && node scripts/lint-fix-has-regression-tests.cjs && node scripts/lint-example-parser-parity.cjs && node scripts/lint-docs-command-form.cjs && node scripts/lint-plan-count-drift.cjs && node scripts/lint-milestone-window-drift.cjs && node scripts/lint-phase-enumeration-drift.cjs && node scripts/lint-planning-prompt-drift.cjs && node scripts/lint-unreachable-guard-drift.cjs && node scripts/lint-completion-ratio-drift.cjs && node scripts/lint-slug-derivation-drift.cjs && node scripts/lint-state-field-drift.cjs && node scripts/lint-state-write-path-drift.cjs && node scripts/lint-completion-predicate-drift.cjs && node scripts/lint-planning-snapshot-bypass-drift.cjs && node scripts/lint-health-diagnostic-rule-table.cjs && node scripts/lint-planning-artifact-writer-drift.cjs && node scripts/lint-frontmatter-scalar-broad-grep.cjs && node scripts/lint-removed-but-needed.cjs && node scripts/lint-no-adhoc-regex-escape.cjs && node scripts/lint-vendored-deps.cjs && node scripts/lint-docs-guard-registration.cjs && node scripts/lint-source-test-name-collision.cjs && npm run lint:hooks-runtime-build-seam && node scripts/check-contract-drift.cjs && node scripts/lint-mutation-test-derivation-drift.cjs && node scripts/lint-seam-enforcement.cjs && node scripts/lint-workflow-shellcheck.cjs && npm run lint:response-language && node scripts/lint-verify-lifecycle-writes.cjs && node scripts/lint-frontmatter-fence-drift.cjs && node scripts/lint-evaluation-scope-drift.cjs && node scripts/lint-gate-evidence-drift.cjs && node scripts/lint-gate-positive-control.cjs && node scripts/lint-roadmap-progress-drift.cjs",
150
150
  "lint:allow-test-rule-refs": "node scripts/lint-allow-test-rule-refs.cjs",
151
151
  "lint:regression-names": "node scripts/lint-regression-test-names.cjs",
152
152
  "lint:descriptions": "node scripts/lint-descriptions.cjs",
@@ -157,6 +157,8 @@ Each finding starts with:
157
157
 
158
158
  Where ID matches: `CR-\d+` or `BL-\d+` (Critical-tier-equivalent), `WR-\d+` (Warning), or `IN-\d+` (Info)
159
159
 
160
+ A finding whose body carries a `**Category:** necessity` line is a **necessity finding** (#4100): code the reviewer judged unnecessary, not broken. Its prefix still gives its severity.
161
+
160
162
  **Required Fields:**
161
163
 
162
164
  - **File:** line contains primary file path
@@ -438,7 +440,7 @@ This cleanup is unconditional when a worktree was created — register it mental
438
440
  - `phase_dir`: Path to phase directory (e.g., `.planning/phases/02-code-review-command`)
439
441
  - `padded_phase`: Zero-padded phase number (e.g., "02")
440
442
  - `review_path`: Full path to REVIEW.md (e.g., `.planning/phases/02-code-review-command/02-REVIEW.md`)
441
- - `fix_scope`: "critical_warning" (default) or "all" (includes Info findings)
443
+ - `fix_scope`: "critical_warning" (default) or "all" (includes Info and necessity findings)
442
444
  - `fix_report_path`: Full path for REVIEW-FIX.md output (e.g., `.planning/phases/02-code-review-command/02-REVIEW-FIX.md`)
443
445
 
444
446
  **3. Read REVIEW.md:**
@@ -464,6 +466,7 @@ Read `./CLAUDE.md` and check for `.claude/skills/` or `.agents/skills/` (as desc
464
466
  For each finding, extract:
465
467
  - `id`: Finding identifier (e.g., CR-01, WR-03, IN-12)
466
468
  - `severity`: Critical (CR-* or BL-*), Warning (WR-*), Info (IN-*)
469
+ - `category`: `necessity` when the finding carries a `**Category:** necessity` line, otherwise none
467
470
  - `title`: Issue title from `### ` heading
468
471
  - `file`: Primary file path from **File:** line
469
472
  - `files`: ALL file paths referenced in finding (including in Fix section) — for multi-file fixes
@@ -472,15 +475,17 @@ For each finding, extract:
472
475
  - `fix`: Full fix content from **Fix:** section (may be multi-line, may contain code fences)
473
476
 
474
477
  **2. Filter by fix_scope:**
475
- - If `fix_scope == "critical_warning"`: include only CR-*, BL-*, and WR-* findings
476
- - If `fix_scope == "all"`: include CR-*, BL-*, WR-*, and IN-* findings
478
+ - If `fix_scope == "critical_warning"`: include only CR-*, BL-*, and WR-* findings; `critical_warning` excludes necessity findings (removing code is a scope decision for the human, #4100). The marker overrides the prefix: a WR-* finding carrying `**Category:** necessity` is OUT of scope here, and is skipped without applying any edit
479
+ - If `fix_scope == "all"`: include CR-*, BL-*, WR-*, and IN-* findings; `all` includes necessity findings, ordered last. A necessity fix removes code, so record it as `fixed: requires human verification`
477
480
 
478
481
  **3. Sort findings by severity:**
479
- - Critical (CR-* and BL-*) first, then Warning, then Info
482
+ - Critical (CR-* and BL-*) first, then Warning, then Info, then necessity findings
480
483
  - Within same severity, maintain document order
481
484
 
482
485
  **4. Count findings in scope:**
483
486
  Record `findings_in_scope` for REVIEW-FIX.md frontmatter.
487
+
488
+ If `findings_in_scope` is 0, apply nothing: write REVIEW-FIX.md with `fixed: 0`, `skipped: 0`, `status: all_fixed`, and a body stating that no finding was in scope and the out-of-scope findings remain in REVIEW.md.
484
489
  </step>
485
490
 
486
491
  <step name="apply_fixes">
@@ -60,6 +60,8 @@ This ensures project-specific patterns, conventions, and best practices are appl
60
60
 
61
61
  **3. Code Quality** — Dead code, unused imports/variables, poor naming conventions, missing error handling, inconsistent patterns, overly complex functions (high cyclomatic complexity), code duplication, magic numbers, commented-out code
62
62
 
63
+ **4. Necessity** (#4100) — reachable code whose guarded-against problem cannot occur in this stack: impossible-state guard, already-defended-elsewhere, cargo-culted pattern, phantom requirement, generality without instantiation. At `standard`/`deep`, Read `__GSD_PI_ENGINE_ROOT__/gsd-core/references/necessity-review.md` before classifying findings; if it cannot be read, say so in the Summary and omit `necessity:`. `quick` does not assess necessity. Warning when provable from the repo, Info when it rests on judgment, Never Critical; each finding cites the evidence that the state cannot occur and carries a `**Category:** necessity` line.
64
+
63
65
  **Out of Scope (v1):** Performance issues (O(n²) algorithms, memory leaks, inefficient queries) are NOT in scope for v1. Focus on correctness, security, and maintainability.
64
66
 
65
67
  </review_scope>
@@ -70,12 +72,17 @@ This ensures project-specific patterns, conventions, and best practices are appl
70
72
 
71
73
  **quick** — Pattern-matching only. Use grep/regex to scan for common anti-patterns without reading full file contents. Target: under 2 minutes.
72
74
 
73
- Patterns checked:
74
- - Hardcoded secrets: `(password|secret|api_key|token|apikey|api-key)\s*[=:]\s*['"][^'"]+['"]`
75
- - Dangerous functions: `eval\(|innerHTML|dangerouslySetInnerHTML|exec\(|system\(|shell_exec|passthru`
76
- - Debug artifacts: `console\.log|debugger;|TODO|FIXME|XXX|HACK`
77
- - Empty catch blocks: `catch\s*\([^)]*\)\s*\{\s*\}`
78
- - Commented-out code: `^\s*//.*[{};]|^\s*#.*:|^\s*/\*`
75
+ Checks (commands in `review_by_depth`):
76
+ - Hardcoded secrets
77
+ - Dangerous functions
78
+ - Debug artifacts
79
+ - Empty catch blocks
80
+ - Commented-out code
81
+ - Unpinned actions
82
+ - Non-idempotent install
83
+ - Missing job timeout
84
+
85
+ The last three: GitHub Actions workflows only.
79
86
 
80
87
  **standard** (default) — Read each changed file. Check for bugs, security issues, and quality problems in context. Cross-reference imports and exports. Target: 5-15 minutes.
81
88
 
@@ -85,6 +92,7 @@ Language-aware checks:
85
92
  - **Go**: Unchecked error returns, goroutine leaks, context not passed, `defer` in loops, race conditions
86
93
  - **C/C++**: Buffer overflow patterns, use-after-free indicators, null pointer dereferences, missing bounds checks, memory leaks
87
94
  - **Shell**: Unquoted variables, `eval` usage, missing `set -e`, command injection via interpolation
95
+ - **CI/CD pipelines** (by path: `.github/workflows/*.yml`, `Dockerfile*`, `docker-compose*.yml`, deploy scripts): Read `__GSD_PI_ENGINE_ROOT__/gsd-core/references/cicd-review-patterns.md`; if it cannot be read, say so in the Summary and check: non-idempotent steps (`npm install` vs `npm ci`, unlocked `pip install`, unguarded `mkdir`/`rm`), no job `timeout-minutes`, deploy without `concurrency` + `cancel-in-progress`, per-environment rebuild (not build-once), no health check or rollback, load-bearing `continue-on-error`, static credentials where OIDC works, unpinned actions
88
96
 
89
97
  **deep** — All of standard, plus cross-file analysis. Trace function call chains across imports. Target: 15-30 minutes.
90
98
 
@@ -173,6 +181,7 @@ NOTE: Do NOT exclude all `.md` files — commands, workflows, and agents are sou
173
181
  - Go: `.go`
174
182
  - C/C++: `.c`, `.cpp`, `.h`, `.hpp`
175
183
  - Shell: `.sh`, `.bash`
184
+ - CI/CD (by path): `.github/workflows/*.yml`/`*.yaml`, `Dockerfile*`, `docker-compose*.yml`, `deploy*.sh` (also Shell)
176
185
  - Other: Review generically
177
186
 
178
187
  **3. Exit early if empty:** If no source files remain after filtering, create REVIEW.md with:
@@ -193,22 +202,48 @@ NOTE: `status: clean` means "reviewed and found no issues." `status: skipped` me
193
202
  Branch on depth level:
194
203
 
195
204
  **For depth=quick:**
196
- Run grep patterns (from `<depth_levels>` quick section) against all files:
205
+ Run every check from `<depth_levels>` quick section:
197
206
  ```bash
207
+ # Set FILE per scoped path with: while IFS= read -r FILE (never paste a name into code).
208
+
198
209
  # Hardcoded secrets
199
- grep -n -E "(password|secret|api_key|token|apikey|api-key)\s*[=:]\s*['\"]\w+['\"]" file
210
+ grep -n -E -- "(password|secret|api_key|token|apikey|api-key)\s*[=:]\s*['\"][^'\"]+['\"]" "$FILE"
200
211
 
201
212
  # Dangerous functions
202
- grep -n -E "eval\(|innerHTML|dangerouslySetInnerHTML|exec\(|system\(|shell_exec" file
213
+ grep -n -E -- "eval\(|innerHTML|dangerouslySetInnerHTML|exec\(|system\(|shell_exec|passthru" "$FILE"
203
214
 
204
215
  # Debug artifacts
205
- grep -n -E "console\.log|debugger;|TODO|FIXME|XXX|HACK" file
206
-
207
- # Empty catch
208
- grep -n -E "catch\s*\([^)]*\)\s*\{\s*\}" file
216
+ grep -n -E -- "console\.log|debugger;|TODO|FIXME|XXX|HACK" "$FILE"
217
+
218
+ # Empty catch blocks
219
+ grep -n -E -- "catch\s*\([^)]*\)\s*\{\s*\}" "$FILE"
220
+
221
+ # Commented-out code
222
+ grep -n -E -- "^\s*//.*[{};]|^\s*#[[:space:]]*(if|for|while|def|class|elif|else|try|except|with)[[:space:](].*:[[:space:]]*$|^\s*/\*[^*].*[{};]" "$FILE"
223
+
224
+ # Unpinned actions
225
+ grep -n -E -- "^[[:space:]]*(-[[:space:]]+)?uses:[[:space:]]*['\"]?[^[:space:]'\"]+@(main|master|latest)['\"]?([[:space:]]|$)" "$FILE"
226
+
227
+ # Non-idempotent install
228
+ [ -f package-lock.json ] && grep -n -E -- "(^|[^[:alnum:]_-])npm[[:space:]]+(install|i)([[:space:]]|$)" "$FILE" | grep -v -E "^[0-9]+:[[:space:]]*#|[[:space:]](-g|--global)([[:space:]]|$)"
229
+
230
+ # Missing job timeout
231
+ awk '
232
+ function flush() { if (job != "" && runs && !timed) print line ": " job ": no job-level timeout-minutes"; job = "" }
233
+ { sub(/\r$/, "") }
234
+ /^[^[:space:]#]/ { flush(); injobs = ($0 ~ /^jobs:[[:space:]]*(#.*)?$/); jind = -1; next }
235
+ !injobs || /^[[:space:]]*(#.*)?$/ { next }
236
+ { ind = match($0, /[^ ]/) - 1 }
237
+ jind < 0 { jind = ind }
238
+ ind == jind { flush(); job = $0; sub(/^ +/, "", job); sub(/:.*/, "", job); line = NR; kind = -1; runs = 0; timed = 0; next }
239
+ kind < 0 { kind = ind }
240
+ ind == kind && /^ *runs-on:/ { runs = 1 }
241
+ ind == kind && /^ *timeout-minutes:/ { timed = 1 }
242
+ END { flush() }
243
+ ' "$FILE"
209
244
  ```
210
245
 
211
- Record findings with severity: secrets/dangerous=Critical, debug=Info, empty catch=Warning
246
+ Record findings with severity: secrets/dangerous=Critical, debug and commented-out code=Info, empty catch and the three CI/CD checks=Warning
212
247
 
213
248
  **For depth=standard:**
214
249
  For each file:
@@ -252,6 +287,7 @@ For each finding, assign severity:
252
287
  - Type coercion issues (`==` vs `===`)
253
288
  - Unhandled promise rejections
254
289
  - Dead code paths that indicate logic errors
290
+ - CI/CD pipeline findings: every check in the CI/CD entry
255
291
 
256
292
  **Info** — Style issues, naming improvements, dead code, unused imports, suggestions:
257
293
  - Unused imports/variables
@@ -286,10 +322,13 @@ findings:
286
322
  warning: N
287
323
  info: N
288
324
  total: N
325
+ necessity: N
289
326
  status: clean | issues_found
290
327
  ---
291
328
  ```
292
329
 
330
+ Emit `necessity:` only at standard/deep depth. It counts the necessity findings among `warning` + `info` and is never added to `total`.
331
+
293
332
  **3. Body sections (required order):**
294
333
  1) `## Structural Findings (fallow)` — only when structural findings were provided; list normalized items first.
295
334
  2) `## Narrative Findings (AI reviewer)` — your adversarial findings from direct code review, including any external-reviewer claim you independently verified (`(external: {slug})`, see `load_context` step 5).
@@ -761,7 +761,14 @@ Do NOT skip. Do NOT proceed to state updates if self-check fails.
761
761
  </self_check>
762
762
 
763
763
  <state_updates>
764
- After SUMMARY.md, update STATE.md using `gsd-tools query` state handlers (named flags):
764
+ After SUMMARY.md, update ROADMAP.md progress FIRST, then STATE.md. The ROADMAP write is the step that can decline (`missing_phase_details`), and a decline stops the plan before any STATE.md write, so STATE.md never advances past a plan the ROADMAP did not record:
765
+
766
+ ```bash
767
+ # Update ROADMAP.md progress for this phase (plan counts, status). A decline stops here, before STATE.md moves.
768
+ PROGRESS=$(gsd_run query roadmap.update-plan-progress "${PHASE_NUMBER}") && REASON=$(jq -er '.reason//""' <<<"$PROGRESS") && [ "$REASON" != missing_phase_details ] || { echo "ERROR: ROADMAP update-plan-progress did not complete for ${PHASE_NUMBER}-${PLAN} (missing_phase_details decline, failed query, or jq not installed). STOP: skip this plan's completion steps." >&2; exit 1; }
769
+ ```
770
+
771
+ Then update STATE.md using `gsd-tools query` state handlers (named flags):
765
772
 
766
773
  ```bash
767
774
  # Advance plan counter (handles edge cases automatically)
@@ -788,9 +795,6 @@ gsd_run query state.record-session \
788
795
  ```
789
796
 
790
797
  ```bash
791
- # Update ROADMAP.md progress for this phase (plan counts, status)
792
- gsd_run query roadmap.update-plan-progress "${PHASE_NUMBER}"
793
-
794
798
  # Mark completed requirements from PLAN.md frontmatter
795
799
  # Extract the `requirements` array from the plan's frontmatter, then mark each complete
796
800
  gsd_run query requirements.mark-complete ${REQ_IDS}
@@ -3,8 +3,8 @@
3
3
  "gsd-advisor-researcher.md": "6988b236b53568bbcea7886d11ca9f0badf9aedb3f650d42ca894211d91aafe4",
4
4
  "gsd-ai-researcher.md": "bdc567357f7361d7e427a2730bdac17c0b28ecb1bcbbc687df68d573f0ee9f3e",
5
5
  "gsd-assumptions-analyzer.md": "56b243f4c50403cbc133ad8c91ae40aa1aeceadd6624d0fd26406ebf8c113205",
6
- "gsd-code-fixer.md": "11a76631ffe74bf5deb707d69b8412bf4251fe8ae87e03cdd59bc28fc6e3904d",
7
- "gsd-code-reviewer.md": "de2a37a6e76bf1d70ff16cb16881326b60a7236194d6126abe5a40d0fd9a98f5",
6
+ "gsd-code-fixer.md": "2d806b6bae3797a7757b4872a93790fe33a4f22e90f6e71f6344b52df3048d52",
7
+ "gsd-code-reviewer.md": "459b38676fd7e6b7bf147a9c3d88243630ff289366a359c7c489536d38b2bd21",
8
8
  "gsd-codebase-mapper.md": "5901db6721a6f2365be52819a41ab1c1590a7cbbbdc6d992dce2a7b99f7d12fb",
9
9
  "gsd-debug-session-manager.md": "7b68386a6ab0169eb4c1b44afa921b8196db1eac1af3a0dd07a87d5ae2b208ef",
10
10
  "gsd-debugger.md": "7328920b7fbee1b76af7259a14b96290562544d97c6ced1291e1e1f3529e1598",
@@ -16,7 +16,7 @@
16
16
  "gsd-domain-researcher.md": "f1b07d9c79dcb89c90e5af7f61adfa5dc73ebe8429783d8cb281d7ae2bdc682b",
17
17
  "gsd-eval-auditor.md": "9d77e7329c52741e7e62185b566231eb872c6b34afa6c869960a922af573b3d5",
18
18
  "gsd-eval-planner.md": "efc12a2822d77a5989db2e3efe83b7fda9f0509bb203910d7e26eb9369f89949",
19
- "gsd-executor.md": "2302fb8b45608b422eb956db27d449e7d5f5c2d41c772188d675ad4f260c342e",
19
+ "gsd-executor.md": "45e9540f6380b1883ad5b144411b21447eef20f310cbd0ab7c72c2bdaf5f8c47",
20
20
  "gsd-framework-selector.md": "96d52931468372ee88c4f1d551c13c97d2be8079053b55d992e0ea1720d3d5c9",
21
21
  "gsd-integration-checker.md": "a233ab8c5c97b6099c649d1e9a09e88ff1ed1bf7df7c1b13bc766c33d58e09b9",
22
22
  "gsd-intel-updater.md": "26c70b81580cc5bf713959bdcc84a16a35569861d2e79c07c71258ccd05ba9bc",
@@ -33,14 +33,17 @@ Routes to the update workflow which handles:
33
33
  <flags>
34
34
  - **--sync**: Sync managed GSD skills across runtime roots so multi-runtime users stay aligned after an update. Runs the sync-skills workflow (--from, --to, --dry-run, --apply flags supported).
35
35
  - **--reapply**: Reapply local modifications after a GSD update. Uses three-way comparison (pristine baseline, user-modified backup, newly installed version) to merge user customizations back. Runs the reapply-patches workflow.
36
+ - **--interactive[=exceptions|all]** (with `--reapply` only): show each user edit next to the stock text with a recommended disposition and wait for a keep / retire / rewrite decision per edit. `all` shows every edit; `exceptions` (bare `--interactive`) shows only edits upstream adopted, partly adopted, reworded around, or orphaned, and keeps the rest. Without it, `--reapply` behaves exactly as before.
36
37
  - **--next** (alias **--rc**): Target the `@next` RC dist-tag instead of `@latest` so you can install or refresh a release candidate (e.g. `1.4.0-rc.1`) through the normal update flow — scope/runtime detection, changelog preview, custom-file backup, and cache clearing all still apply. Omitting it keeps targeting `@latest` (no change). See ADR #660 for the RC channel.
37
38
  - **(no flag)**: Standard update — check for new version, show changelog, install.
38
39
  </flags>
39
40
 
40
41
  <process>
42
+ If the arguments contain `--interactive` (bare or with a value) but not `--reapply`, STOP with: `--interactive applies only to --reapply (for example: /skill:gsd-update --reapply --interactive=all)`. Do nothing else.
43
+
41
44
  Parse the first token of the `<arguments>` block:
42
45
  - If it is `--sync`: strip the flag, execute the sync-skills workflow (passing remaining args for --from/--to/--dry-run/--apply).
43
- - If it is `--reapply`: strip the flag, execute the reapply-patches workflow.
46
+ - If it is `--reapply`: strip the flag, execute the reapply-patches workflow, passing the remaining arguments (for example `--interactive=all`) to it.
44
47
  - Otherwise (including `--next` / `--rc`): execute the update workflow end-to-end, passing the `<arguments>` block through so the workflow's parse_update_channel step can select the release channel.
45
48
 
46
49
  </process>