@opengsd/gsd-core 1.7.0-rc.1 → 1.7.0-rc.3

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 (191) hide show
  1. package/.claude-plugin/marketplace.json +20 -0
  2. package/.claude-plugin/plugin.json +1 -1
  3. package/.opencode/plugins/gsd-core.js +711 -0
  4. package/GEMINI.md +25 -23
  5. package/README.md +3 -3
  6. package/agents/gsd-advisor-researcher.md +3 -1
  7. package/agents/gsd-ai-researcher.md +2 -2
  8. package/agents/gsd-assumptions-analyzer.md +2 -0
  9. package/agents/gsd-code-fixer.md +3 -1
  10. package/agents/gsd-code-reviewer.md +2 -0
  11. package/agents/gsd-codebase-mapper.md +2 -0
  12. package/agents/gsd-debug-session-manager.md +1 -1
  13. package/agents/gsd-debugger.md +3 -1
  14. package/agents/gsd-doc-writer.md +2 -0
  15. package/agents/gsd-domain-researcher.md +1 -1
  16. package/agents/gsd-eval-auditor.md +3 -1
  17. package/agents/gsd-executor.md +12 -9
  18. package/agents/gsd-integration-checker.md +2 -0
  19. package/agents/gsd-intel-updater.md +1 -1
  20. package/agents/gsd-mempalace-curator.md +1 -1
  21. package/agents/gsd-nyquist-auditor.md +2 -0
  22. package/agents/gsd-phase-researcher.md +4 -2
  23. package/agents/gsd-plan-checker.md +3 -1
  24. package/agents/gsd-planner.md +4 -2
  25. package/agents/gsd-project-researcher.md +4 -2
  26. package/agents/gsd-research-synthesizer.md +3 -1
  27. package/agents/gsd-roadmapper.md +2 -0
  28. package/agents/gsd-security-auditor.md +2 -0
  29. package/agents/gsd-ui-auditor.md +2 -0
  30. package/agents/gsd-ui-checker.md +2 -0
  31. package/agents/gsd-ui-researcher.md +4 -2
  32. package/agents/gsd-verifier.md +6 -4
  33. package/bin/install.js +226 -459
  34. package/commands/gsd/mempalace-capture.md +1 -1
  35. package/commands/gsd/mempalace-recall.md +9 -5
  36. package/commands/gsd/next.md +29 -0
  37. package/commands/gsd/ns-workflow.md +5 -2
  38. package/gsd-core/bin/ensure-runtime-build.cjs +246 -0
  39. package/gsd-core/bin/gsd-tools.cjs +43 -8
  40. package/gsd-core/bin/lib/capability-loader.cjs +27 -9
  41. package/gsd-core/bin/lib/capability-registry.cjs +209 -205
  42. package/gsd-core/bin/lib/capability-source.cjs +22 -7
  43. package/gsd-core/bin/lib/capability-validator.cjs +24 -2
  44. package/gsd-core/bin/lib/check-command-router.cjs +111 -1
  45. package/gsd-core/bin/lib/clusters.cjs +1 -0
  46. package/gsd-core/bin/lib/commands.cjs +2 -1
  47. package/gsd-core/bin/lib/config.cjs +19 -1
  48. package/gsd-core/bin/lib/core-utils.cjs +41 -0
  49. package/gsd-core/bin/lib/external-job.cjs +287 -0
  50. package/gsd-core/bin/lib/frontmatter.cjs +53 -6
  51. package/gsd-core/bin/lib/gate-predicate-evaluator.cjs +151 -0
  52. package/gsd-core/bin/lib/graphify.cjs +41 -9
  53. package/gsd-core/bin/lib/handshake-serialized.cjs +70 -0
  54. package/gsd-core/bin/lib/host-integration-sdk.cjs +53 -0
  55. package/gsd-core/bin/lib/host-integration.cjs +61 -0
  56. package/gsd-core/bin/lib/init.cjs +34 -6
  57. package/gsd-core/bin/lib/milestone.cjs +56 -11
  58. package/gsd-core/bin/lib/phase-id.cjs +18 -0
  59. package/gsd-core/bin/lib/phase.cjs +51 -29
  60. package/gsd-core/bin/lib/phases-command-router.cjs +4 -3
  61. package/gsd-core/bin/lib/plan-scan.cjs +9 -1
  62. package/gsd-core/bin/lib/probe-core.cjs +44 -4
  63. package/gsd-core/bin/lib/profile-output.cjs +1 -1
  64. package/gsd-core/bin/lib/prohibition-enforcement.cjs +21 -19
  65. package/gsd-core/bin/lib/roadmap-command-router.cjs +3 -2
  66. package/gsd-core/bin/lib/roadmap-parser.cjs +21 -11
  67. package/gsd-core/bin/lib/roadmap.cjs +45 -22
  68. package/gsd-core/bin/lib/runtime-hooks-surface.cjs +18 -13
  69. package/gsd-core/bin/lib/runtime-name-policy.cjs +8 -10
  70. package/gsd-core/bin/lib/shell-command-projection.cjs +12 -8
  71. package/gsd-core/bin/lib/smart-entry.cjs +521 -0
  72. package/gsd-core/bin/lib/state-transition.cjs +15 -0
  73. package/gsd-core/bin/lib/state.cjs +27 -8
  74. package/gsd-core/bin/lib/surface.cjs +18 -9
  75. package/gsd-core/bin/lib/update-context.cjs +0 -5
  76. package/gsd-core/bin/lib/validate.cjs +2 -1
  77. package/gsd-core/bin/lib/verify.cjs +6 -4
  78. package/gsd-core/bin/lib/workstream-inventory-builder.cjs +12 -2
  79. package/gsd-core/bin/lib/workstream-inventory.cjs +28 -0
  80. package/gsd-core/bin/shared/config-schema.manifest.json +1 -0
  81. package/gsd-core/bin/shared/model-catalog.json +8 -13
  82. package/gsd-core/bin/shared/runtime-aliases.manifest.json +0 -5
  83. package/gsd-core/references/agent-skills-bootstrap.md +60 -0
  84. package/gsd-core/references/gates.md +1 -1
  85. package/gsd-core/references/model-profiles.md +27 -0
  86. package/gsd-core/references/planning-config.md +1 -1
  87. package/gsd-core/workflows/_runtime-launcher.snippet.sh +1 -1
  88. package/gsd-core/workflows/add-backlog.md +1 -1
  89. package/gsd-core/workflows/add-phase.md +1 -1
  90. package/gsd-core/workflows/add-tests.md +1 -1
  91. package/gsd-core/workflows/add-todo.md +1 -1
  92. package/gsd-core/workflows/ai-integration-phase.md +1 -1
  93. package/gsd-core/workflows/audit-fix.md +1 -1
  94. package/gsd-core/workflows/audit-milestone.md +1 -1
  95. package/gsd-core/workflows/audit-uat.md +1 -1
  96. package/gsd-core/workflows/autonomous.md +23 -25
  97. package/gsd-core/workflows/check-todos.md +1 -1
  98. package/gsd-core/workflows/cleanup.md +1 -1
  99. package/gsd-core/workflows/code-review-fix.md +1 -1
  100. package/gsd-core/workflows/code-review.md +1 -1
  101. package/gsd-core/workflows/complete-milestone.md +7 -11
  102. package/gsd-core/workflows/debug.md +1 -1
  103. package/gsd-core/workflows/diagnose-issues.md +1 -1
  104. package/gsd-core/workflows/discuss-phase/modes/advisor.md +1 -1
  105. package/gsd-core/workflows/discuss-phase/modes/auto.md +1 -1
  106. package/gsd-core/workflows/discuss-phase/modes/chain.md +1 -1
  107. package/gsd-core/workflows/discuss-phase-assumptions.md +1 -1
  108. package/gsd-core/workflows/discuss-phase.md +2 -2
  109. package/gsd-core/workflows/do.md +1 -1
  110. package/gsd-core/workflows/docs-update.md +1 -1
  111. package/gsd-core/workflows/edit-phase.md +1 -1
  112. package/gsd-core/workflows/eval-review.md +1 -1
  113. package/gsd-core/workflows/execute-phase/steps/codebase-drift-gate.md +1 -1
  114. package/gsd-core/workflows/execute-phase/steps/post-merge-gate.md +1 -1
  115. package/gsd-core/workflows/execute-phase.md +5 -6
  116. package/gsd-core/workflows/execute-plan.md +1 -1
  117. package/gsd-core/workflows/explore.md +1 -1
  118. package/gsd-core/workflows/extract-learnings.md +1 -1
  119. package/gsd-core/workflows/forensics.md +4 -4
  120. package/gsd-core/workflows/graduation.md +1 -1
  121. package/gsd-core/workflows/health.md +1 -1
  122. package/gsd-core/workflows/help/modes/full.md +15 -1
  123. package/gsd-core/workflows/help/modes/topic.md +1 -0
  124. package/gsd-core/workflows/import.md +1 -1
  125. package/gsd-core/workflows/ingest-docs.md +1 -1
  126. package/gsd-core/workflows/insert-phase.md +1 -1
  127. package/gsd-core/workflows/list-seeds.md +1 -1
  128. package/gsd-core/workflows/list-workspaces.md +1 -1
  129. package/gsd-core/workflows/manager.md +1 -1
  130. package/gsd-core/workflows/map-codebase.md +1 -1
  131. package/gsd-core/workflows/milestone-summary.md +4 -4
  132. package/gsd-core/workflows/mvp-phase.md +1 -1
  133. package/gsd-core/workflows/new-milestone.md +7 -1
  134. package/gsd-core/workflows/new-project.md +1 -1
  135. package/gsd-core/workflows/new-workspace.md +1 -1
  136. package/gsd-core/workflows/next.md +1 -1
  137. package/gsd-core/workflows/pause-work.md +1 -1
  138. package/gsd-core/workflows/plan-milestone-gaps.md +1 -1
  139. package/gsd-core/workflows/plan-phase/steps/closed-phase-gate.md +42 -0
  140. package/gsd-core/workflows/plan-phase/steps/prd-express-path.md +102 -0
  141. package/gsd-core/workflows/plan-phase/steps/windows-troubleshooting.md +23 -0
  142. package/gsd-core/workflows/plan-phase.md +12 -160
  143. package/gsd-core/workflows/plan-review-convergence.md +1 -1
  144. package/gsd-core/workflows/plant-seed.md +1 -1
  145. package/gsd-core/workflows/pr-branch.md +1 -1
  146. package/gsd-core/workflows/profile-user.md +1 -1
  147. package/gsd-core/workflows/progress.md +1 -1
  148. package/gsd-core/workflows/quick.md +1 -1
  149. package/gsd-core/workflows/reapply-patches.md +1 -1
  150. package/gsd-core/workflows/remove-phase.md +1 -1
  151. package/gsd-core/workflows/remove-workspace.md +1 -1
  152. package/gsd-core/workflows/resume-project.md +1 -1
  153. package/gsd-core/workflows/review.md +50 -9
  154. package/gsd-core/workflows/scan.md +1 -1
  155. package/gsd-core/workflows/secure-phase.md +1 -1
  156. package/gsd-core/workflows/settings-advanced.md +13 -11
  157. package/gsd-core/workflows/settings-integrations.md +1 -1
  158. package/gsd-core/workflows/settings.md +1 -1
  159. package/gsd-core/workflows/ship.md +1 -1
  160. package/gsd-core/workflows/sketch-wrap-up.md +1 -1
  161. package/gsd-core/workflows/sketch.md +1 -1
  162. package/gsd-core/workflows/smart-entry.md +120 -0
  163. package/gsd-core/workflows/spec-phase.md +8 -6
  164. package/gsd-core/workflows/spike-wrap-up.md +1 -1
  165. package/gsd-core/workflows/spike.md +1 -1
  166. package/gsd-core/workflows/stats.md +1 -1
  167. package/gsd-core/workflows/thread.md +1 -1
  168. package/gsd-core/workflows/transition.md +1 -1
  169. package/gsd-core/workflows/ui-phase.md +1 -1
  170. package/gsd-core/workflows/ui-review.md +1 -1
  171. package/gsd-core/workflows/ultraplan-phase.md +1 -1
  172. package/gsd-core/workflows/update.md +3 -6
  173. package/gsd-core/workflows/validate-phase.md +1 -1
  174. package/gsd-core/workflows/verify-phase.md +1 -1
  175. package/gsd-core/workflows/verify-work.md +63 -6
  176. package/package.json +5 -5
  177. package/scripts/ci-test-scope.cjs +19 -1
  178. package/scripts/generate-package-identity.cjs +27 -2
  179. package/scripts/lint-allow-test-rule-refs.allowlist.json +0 -148
  180. package/scripts/lint-regression-test-names.allowlist.json +1 -273
  181. package/scripts/lint-test-file-count.allowlist.json +7 -91
  182. package/scripts/research-profiles.cjs +6 -6
  183. package/scripts/run-tests.cjs +67 -4
  184. package/scripts/slurm-adapter.cjs +269 -0
  185. package/scripts/sync-manifest-versions.cjs +67 -14
  186. package/skills/gsd-mempalace-capture/SKILL.md +1 -1
  187. package/skills/gsd-mempalace-recall/SKILL.md +9 -5
  188. package/skills/gsd-next/SKILL.md +30 -0
  189. package/skills/gsd-ns-workflow/SKILL.md +4 -1
  190. package/skills/gsd-review/SKILL.md +6 -0
  191. package/gemini-extension.json +0 -6
@@ -22,7 +22,7 @@ const node_fs_1 = __importDefault(require("node:fs"));
22
22
  const node_path_1 = __importDefault(require("node:path"));
23
23
  // eslint-disable-next-line @typescript-eslint/no-require-imports
24
24
  const phaseIdModule = require("./phase-id.cjs");
25
- const { escapeRegex, phaseMarkdownRegexSource, phaseMarkdownRegexSourceExact, stripProjectCodePrefix, OPTIONAL_PROJECT_CODE_PREFIX_SOURCE, } = phaseIdModule;
25
+ const { escapeRegex, phaseMarkdownRegexSource, phaseMarkdownRegexSourceExact, stripProjectCodePrefix, OPTIONAL_PROJECT_CODE_PREFIX_SOURCE, OPTIONAL_PHASE_TAG_SOURCE, } = phaseIdModule;
26
26
  // eslint-disable-next-line @typescript-eslint/no-require-imports
27
27
  const planningWorkspace = require("./planning-workspace.cjs");
28
28
  const { planningDir } = planningWorkspace;
@@ -82,7 +82,8 @@ function extractCurrentMilestone(content, cwd) {
82
82
  const preambleCutoff = firstMilestoneMatch ? firstMilestoneMatch.index : detailsOpenIdx;
83
83
  const preamble = content.slice(0, preambleCutoff)
84
84
  .replace(/<details>[\s\S]*?<\/details>/gi, '')
85
- .replace(/^#{2,4}\s*Phase\s+[\w][\w.-]*\s*:[^\n]*(?:\n(?!#{1,6}\s)[^\n]*)*\n?/gim, '')
85
+ // #1729: `(?:\s*\([^)\n]*\))?` tolerates a pre-colon ( ) tag (literal mirror of OPTIONAL_PHASE_TAG_SOURCE).
86
+ .replace(/^#{2,4}\s*Phase\s+[\w][\w.-]*(?:\s*\([^)\n]*\))?\s*:[^\n]*(?:\n(?!#{1,6}\s)[^\n]*)*\n?/gim, '')
86
87
  .replace(/^#{1,4}\s*Phase Details\b[^\n]*\n?/gim, '');
87
88
  return preamble + content.slice(detailsOpenIdx, detailsEnd);
88
89
  }
@@ -151,7 +152,8 @@ function extractCurrentMilestone(content, cwd) {
151
152
  }
152
153
  const preamble = beforeMilestones
153
154
  .replace(/<details>[\s\S]*?<\/details>/gi, '')
154
- .replace(/^#{2,4}\s*Phase\s+[\w][\w.-]*\s*:[^\n]*(?:\n(?!#{1,6}\s)[^\n]*)*\n?/gim, '')
155
+ // #1729: `(?:\s*\([^)\n]*\))?` tolerates a pre-colon ( ) tag (literal mirror of OPTIONAL_PHASE_TAG_SOURCE).
156
+ .replace(/^#{2,4}\s*Phase\s+[\w][\w.-]*(?:\s*\([^)\n]*\))?\s*:[^\n]*(?:\n(?!#{1,6}\s)[^\n]*)*\n?/gim, '')
155
157
  .replace(/^#{1,4}\s*Phase Details\b[^\n]*\n?/gim, '');
156
158
  return detailsSection
157
159
  ? preamble + currentSection + '\n' + detailsSection
@@ -171,16 +173,20 @@ function replaceInCurrentMilestone(content, pattern, replacement) {
171
173
  return before + after.replace(pattern, replacement);
172
174
  }
173
175
  function findRoadmapPhaseInContent(content, phaseNum, phaseSource) {
174
- const phasePattern = new RegExp(`#{2,4}\\s*(?:\\[[^\\]]+\\]\\s*)?Phase\\s+${phaseSource ?? phaseMarkdownRegexSource(phaseNum)}:\\s*([^\\n]+)`, 'i');
175
- const headerMatch = content.match(phasePattern);
176
+ // #1729: OPTIONAL_PHASE_TAG_SOURCE after the number tolerates a pre-colon ( ) tag.
177
+ const headingPattern = new RegExp(`^(?:\\[[^\\]]+\\]\\s*)?Phase\\s+${phaseSource ?? phaseMarkdownRegexSource(phaseNum)}${OPTIONAL_PHASE_TAG_SOURCE}:\\s*(.+)$`, 'i');
178
+ const headings = (0, markdown_sectionizer_cjs_1.tokenizeHeadings)(content);
179
+ const headingIndex = headings.findIndex((heading) => headingPattern.test(heading.text));
180
+ if (headingIndex === -1)
181
+ return null;
182
+ const heading = headings[headingIndex];
183
+ const headerMatch = heading.text.match(headingPattern);
176
184
  if (!headerMatch)
177
185
  return null;
178
186
  const phaseName = headerMatch[1].trim();
179
- const headerIndex = headerMatch.index;
180
- const restOfContent = content.slice(headerIndex);
181
- const nextHeaderMatch = restOfContent.match(/\n#{2,4}\s+(?:\[[^\]]+\]\s*)?Phase\s+[\w]/i);
182
- const sectionEnd = nextHeaderMatch ? headerIndex + nextHeaderMatch.index : content.length;
183
- const section = content.slice(headerIndex, sectionEnd).trim();
187
+ const nextHeading = headings.slice(headingIndex + 1).find((candidate) => candidate.level <= heading.level);
188
+ const sectionEnd = nextHeading ? nextHeading.offset : content.length;
189
+ const section = content.slice(heading.offset, sectionEnd).trim();
184
190
  const goalMatch = section.match(/\*\*Goal(?:\*\*:|\*?\*?:\*\*)\s*([^\n]+)/i);
185
191
  const goal = goalMatch ? goalMatch[1].trim() : null;
186
192
  return {
@@ -209,6 +215,9 @@ function roadmapPhaseLookupSources(phaseNum) {
209
215
  function getRoadmapPhaseInternal(cwd, phaseNum) {
210
216
  if (!phaseNum)
211
217
  return null;
218
+ const normalizedPhase = stripProjectCodePrefix(phaseNum);
219
+ if (/^999(?:\.|$)/.test(normalizedPhase))
220
+ return null;
212
221
  const roadmapPath = node_path_1.default.join(planningDir(cwd), 'ROADMAP.md');
213
222
  if (!node_fs_1.default.existsSync(roadmapPath))
214
223
  return null;
@@ -369,7 +378,8 @@ function getMilestonePhaseFilter(cwd, versionOverride, phaseIdConvention) {
369
378
  }
370
379
  // Use tokenizeHeadings (fence-aware) instead of stripFencedLines + regex.
371
380
  // T4 seam migration: phase headings inside fences are excluded automatically.
372
- const phaseHeadingPattern = /^(?:\[[^\]]+\]\s*)?Phase\s+([\w][\w.-]*)\s*:/i;
381
+ // #1729: `(?:\s*\([^)\n]*\))?` tolerates a pre-colon ( ) tag (literal mirror of OPTIONAL_PHASE_TAG_SOURCE).
382
+ const phaseHeadingPattern = /^(?:\[[^\]]+\]\s*)?Phase\s+([\w][\w.-]*)(?:\s*\([^)\n]*\))?\s*:/i;
373
383
  for (const h of (0, markdown_sectionizer_cjs_1.tokenizeHeadings)(roadmap)) {
374
384
  if (h.level < 2 || h.level > 4)
375
385
  continue;
@@ -17,13 +17,14 @@ const ioMod = require("./io.cjs");
17
17
  const { output, error } = ioMod;
18
18
  // eslint-disable-next-line @typescript-eslint/no-require-imports
19
19
  const phaseIdMod = require("./phase-id.cjs");
20
- const { escapeRegex, normalizePhaseName, phaseMarkdownRegexSource, phaseMarkdownRegexSourceExact, phaseTokenMatches } = phaseIdMod;
20
+ const { escapeRegex, normalizePhaseName, phaseMarkdownRegexSource, phaseMarkdownRegexSourceExact, phaseTokenMatches, stripProjectCodePrefix, OPTIONAL_PHASE_TAG_SOURCE } = phaseIdMod;
21
21
  // eslint-disable-next-line @typescript-eslint/no-require-imports
22
22
  const phaseLocatorMod = require("./phase-locator.cjs");
23
23
  const { findPhaseInternal } = phaseLocatorMod;
24
24
  // eslint-disable-next-line @typescript-eslint/no-require-imports
25
25
  const roadmapParserModule = require("./roadmap-parser.cjs");
26
26
  const { stripShippedMilestones, extractCurrentMilestone, replaceInCurrentMilestone } = roadmapParserModule;
27
+ const markdown_sectionizer_cjs_1 = require("./markdown-sectionizer.cjs");
27
28
  const shell_command_projection_cjs_1 = require("./shell-command-projection.cjs");
28
29
  // eslint-disable-next-line @typescript-eslint/no-require-imports
29
30
  const planningWorkspace = require("./planning-workspace.cjs");
@@ -31,8 +32,14 @@ const { planningPaths, withPlanningLock, findContextMdIn } = planningWorkspace;
31
32
  // eslint-disable-next-line @typescript-eslint/no-require-imports
32
33
  const scanPhasePlans = require("./plan-scan.cjs");
33
34
  // eslint-disable-next-line @typescript-eslint/no-require-imports
35
+ const coreUtils = require("./core-utils.cjs");
36
+ const { countMatchedSummaries } = coreUtils;
37
+ // eslint-disable-next-line @typescript-eslint/no-require-imports
34
38
  const frontmatter = require("./frontmatter.cjs");
35
39
  const { extractFrontmatter, parseMustHavesBlock } = frontmatter;
40
+ // eslint-disable-next-line @typescript-eslint/no-require-imports
41
+ const verificationMod = require("./verification.cjs");
42
+ const { readVerificationStatus } = verificationMod;
36
43
  // ─── coerceTruthToString ──────────────────────────────────────────────────────
37
44
  /**
38
45
  * Coerce an arbitrary YAML scalar/object into a string for cross-cutting
@@ -94,12 +101,14 @@ function countPhasePlansAndSummaries(phaseDir) {
94
101
  * checklist-only match), or null if the phase is not present at all.
95
102
  */
96
103
  function searchPhaseInContent(content, escapedPhase, phaseNum) {
97
- // Match "## Phase X:", "### Phase X:", or "#### Phase X:" with optional name
98
- const phasePattern = new RegExp(`#{2,4}\\s*(?:\\[[^\\]]+\\]\\s*)?Phase\\s+${escapedPhase}:\\s*([^\\n]+)`, 'i');
99
- const headerMatch = content.match(phasePattern);
104
+ // #1729: OPTIONAL_PHASE_TAG_SOURCE after the number tolerates a pre-colon ( ) tag.
105
+ const headingPattern = new RegExp(`^(?:\\[[^\\]]+\\]\\s*)?Phase\\s+${escapedPhase}${OPTIONAL_PHASE_TAG_SOURCE}:\\s*(.+)$`, 'i');
106
+ const headings = (0, markdown_sectionizer_cjs_1.tokenizeHeadings)(content);
107
+ const headingIndex = headings.findIndex((heading) => headingPattern.test(heading.text));
108
+ const headerMatch = headingIndex === -1 ? null : headings[headingIndex].text.match(headingPattern);
100
109
  if (!headerMatch) {
101
110
  // Fallback: check if phase exists in summary list but missing detail section
102
- const checklistPattern = new RegExp(`-\\s*\\[[ x]\\]\\s*\\*\\*Phase\\s+${escapedPhase}:\\s*([^*]+)\\*\\*`, 'i');
111
+ const checklistPattern = new RegExp(`-\\s*\\[[ x]\\]\\s*\\*\\*Phase\\s+${escapedPhase}${OPTIONAL_PHASE_TAG_SOURCE}:\\s*([^*]+)\\*\\*`, 'i');
103
112
  const checklistMatch = content.match(checklistPattern);
104
113
  if (checklistMatch) {
105
114
  return {
@@ -113,14 +122,12 @@ function searchPhaseInContent(content, escapedPhase, phaseNum) {
113
122
  return null;
114
123
  }
115
124
  const phaseName = headerMatch[1].trim();
116
- const headerIndex = headerMatch.index;
117
- // Find the end of this section (next ## or ### phase header, or end of file).
118
- // Also matches bracket-prefixed headings like ### [GSD] Phase 2-01:.
119
- const restOfContent = content.slice(headerIndex);
120
- const nextHeaderMatch = restOfContent.match(/\n#{2,4}\s+(?:\[[^\]]+\]\s*)?Phase\s+[\w][\w.-]*/i);
121
- const sectionEnd = nextHeaderMatch
122
- ? headerIndex + nextHeaderMatch.index
123
- : content.length;
125
+ const headerIndex = headings[headingIndex].offset;
126
+ const currentHeading = headings[headingIndex];
127
+ const nextHeading = headings
128
+ .slice(headingIndex + 1)
129
+ .find((candidate) => candidate.level <= currentHeading.level);
130
+ const sectionEnd = nextHeading ? nextHeading.offset : content.length;
124
131
  const section = content.slice(headerIndex, sectionEnd).trim();
125
132
  // Extract goal if present (supports both **Goal:** and **Goal**: formats)
126
133
  const goalMatch = section.match(/\*\*Goal(?::\*\*|\*\*:)\s*([^\n]+)/i);
@@ -159,6 +166,8 @@ function searchPhaseInContent(content, escapedPhase, phaseNum) {
159
166
  * phase resolution as `roadmap.get-phase` — not a milestone-only subset.
160
167
  */
161
168
  function getRoadmapPhaseWithFallback(cwd, phaseNum) {
169
+ if (/^999(?:\.|$)/.test(stripProjectCodePrefix(phaseNum)))
170
+ return null;
162
171
  const roadmapPath = planningPaths(cwd).roadmap;
163
172
  if (!node_fs_1.default.existsSync(roadmapPath))
164
173
  return null;
@@ -185,6 +194,10 @@ function getRoadmapPhaseWithFallback(cwd, phaseNum) {
185
194
  }
186
195
  // ─── cmdRoadmapGetPhase ───────────────────────────────────────────────────────
187
196
  function cmdRoadmapGetPhase(cwd, phaseNum, raw) {
197
+ if (/^999(?:\.|$)/.test(stripProjectCodePrefix(phaseNum))) {
198
+ output({ found: false, phase_number: phaseNum }, raw, '');
199
+ return;
200
+ }
188
201
  const roadmapPath = planningPaths(cwd).roadmap;
189
202
  if (!node_fs_1.default.existsSync(roadmapPath)) {
190
203
  output({ found: false, error: 'ROADMAP.md not found' }, raw, '');
@@ -249,7 +262,8 @@ function cmdRoadmapAnalyze(cwd, raw) {
249
262
  const content = extractCurrentMilestone(rawContent, cwd);
250
263
  const phasesDir = planningPaths(cwd).phases;
251
264
  // Extract all phase headings: ## Phase N: Name or ### Phase N: Name
252
- const phasePattern = /#{2,4}\s*(?:\[[^\]]+\]\s*)?Phase\s+(\d+[A-Z]?(?:[.-]\d+)*)\s*:\s*([^\n]+)/gi;
265
+ // #1729: `(?:\s*\([^)\n]*\))?` tolerates a pre-colon ( ) tag (literal mirror of OPTIONAL_PHASE_TAG_SOURCE).
266
+ const phasePattern = /#{2,4}\s*(?:\[[^\]]+\]\s*)?Phase\s+(\d+[A-Z]?(?:[.-]\d+)*)(?:\s*\([^)\n]*\))?\s*:\s*([^\n]+)/gi;
253
267
  const phases = [];
254
268
  let match;
255
269
  // Phase 0 (pre-milestone) and Phase 999 (backlog) are sentinels, not real
@@ -325,7 +339,7 @@ function cmdRoadmapAnalyze(cwd, raw) {
325
339
  // #3537: padding-tolerant fragment — the heading discovered above may use
326
340
  // a different padding than the summary-bullet checkbox below it (mixed
327
341
  // padding inside one ROADMAP is legal and seen in real projects).
328
- const checkboxPattern = new RegExp(`-\\s*\\[(x| )\\]\\s*.*Phase\\s+${phaseMarkdownRegexSource(phaseNum)}[:\\s]`, 'i');
342
+ const checkboxPattern = new RegExp(`-\\s*\\[(x| )\\]\\s*.*Phase\\s+${phaseMarkdownRegexSource(phaseNum)}${OPTIONAL_PHASE_TAG_SOURCE}[:\\s]`, 'i');
329
343
  const checkboxMatch = content.match(checkboxPattern);
330
344
  const roadmapComplete = checkboxMatch ? checkboxMatch[1] === 'x' : false;
331
345
  // If roadmap marks phase complete, trust that over disk file structure.
@@ -402,12 +416,21 @@ function cmdRoadmapUpdatePlanProgress(cwd, phaseNum, raw) {
402
416
  error(`Phase ${phaseNum} not found`);
403
417
  }
404
418
  const planCount = phaseInfo.plans.length;
405
- const summaryCount = phaseInfo.summaries.length;
419
+ // Count only summaries that pair with a real plan (#1988): stray non-plan
420
+ // summaries (30-FIX-CR02-SUMMARY.md, 30-GAPCLOSURE-SUMMARY.md, …) must not
421
+ // inflate summary_count and silently flip the phase to Complete.
422
+ const summaryCount = countMatchedSummaries(phaseInfo.plans, phaseInfo.summaries);
406
423
  if (planCount === 0) {
407
424
  output({ updated: false, reason: 'No plans found', plan_count: 0, summary_count: 0 }, raw, 'no plans');
408
425
  return;
409
426
  }
410
- const isComplete = summaryCount >= planCount;
427
+ // Verification gate (#2022): do NOT check the phase checkbox or stamp a
428
+ // completion date until the phase's verification status is 'passed', matching
429
+ // cmdPhaseComplete's gate (phase.cts:1436). Previously the checkbox fired the
430
+ // moment the last plan summary landed — before gsd-verifier had verified.
431
+ const phaseDir = node_path_1.default.join(cwd, phaseInfo.directory);
432
+ const verificationPassed = readVerificationStatus(phaseDir).status === 'passed';
433
+ const isComplete = summaryCount >= planCount && verificationPassed;
411
434
  const status = isComplete ? 'Complete' : summaryCount > 0 ? 'In Progress' : 'Planned';
412
435
  const today = clock_cjs_1.realClock.today();
413
436
  if (!node_fs_1.default.existsSync(roadmapPath)) {
@@ -450,14 +473,14 @@ function cmdRoadmapUpdatePlanProgress(cwd, phaseNum, raw) {
450
473
  // `**Plans**: N plans` — bold word + outer colon (gsd-core/templates/roadmap.md)
451
474
  // `**Plans:** N plans` — bold "Plans:" (colon inside bold)
452
475
  // `Plans: N plans` — plain text header
453
- const planCountPattern = new RegExp(`(#{2,4}\\s*Phase\\s+${phasePattern}(?=[:\\s])[\\s\\S]*?(?:\\*\\*Plans\\*\\*:|\\*\\*Plans:\\*\\*|(?:^|\\n)Plans:)\\s*)[^\\n]+`, 'i');
476
+ const planCountPattern = new RegExp(`(#{2,4}\\s*Phase\\s+${phasePattern}${OPTIONAL_PHASE_TAG_SOURCE}(?=[:\\s])[\\s\\S]*?(?:\\*\\*Plans\\*\\*:|\\*\\*Plans:\\*\\*|(?:^|\\n)Plans:)\\s*)[^\\n]+`, 'i');
454
477
  const planCountText = isComplete
455
478
  ? `${summaryCount}/${planCount} plans complete`
456
479
  : `${summaryCount}/${planCount} plans executed`;
457
480
  roadmapContent = replaceInCurrentMilestone(roadmapContent, planCountPattern, `$1${planCountText}`);
458
481
  // If complete: check checkbox
459
482
  if (isComplete) {
460
- const checkboxPattern = new RegExp(`(-\\s*\\[)[ ](\\]\\s*.*Phase\\s+${phasePattern}[:\\s][^\\n]*)`, 'i');
483
+ const checkboxPattern = new RegExp(`(-\\s*\\[)[ ](\\]\\s*.*Phase\\s+${phasePattern}${OPTIONAL_PHASE_TAG_SOURCE}[:\\s][^\\n]*)`, 'i');
461
484
  roadmapContent = replaceInCurrentMilestone(roadmapContent, checkboxPattern, `$1x$2 (completed ${today})`);
462
485
  }
463
486
  // Mark completed plan checkboxes (e.g. "- [ ] 50-01-PLAN.md", "- [ ] 50-01:", or "- [ ] **50-01**")
@@ -507,8 +530,8 @@ function cmdRoadmapUpdatePlanProgress(cwd, phaseNum, raw) {
507
530
  //
508
531
  // Pattern A: anchor to bare `Plans:` header (preferred).
509
532
  // Pattern B: fallback to bold summary when no bare header exists.
510
- const insertRowsPatternA = new RegExp(`(#{2,4}\\s*Phase\\s+${phasePattern}(?=[:\\s])[\\s\\S]*?(?:^|\\n)(?:Plans:)[^\\n]*)`, 'i');
511
- const insertRowsPatternB = new RegExp(`(#{2,4}\\s*Phase\\s+${phasePattern}(?=[:\\s])[\\s\\S]*?(?:\\*\\*Plans\\*\\*:|\\*\\*Plans:\\*\\*)[^\\n]*)`, 'i');
533
+ const insertRowsPatternA = new RegExp(`(#{2,4}\\s*Phase\\s+${phasePattern}${OPTIONAL_PHASE_TAG_SOURCE}(?=[:\\s])[\\s\\S]*?(?:^|\\n)(?:Plans:)[^\\n]*)`, 'i');
534
+ const insertRowsPatternB = new RegExp(`(#{2,4}\\s*Phase\\s+${phasePattern}${OPTIONAL_PHASE_TAG_SOURCE}(?=[:\\s])[\\s\\S]*?(?:\\*\\*Plans\\*\\*:|\\*\\*Plans:\\*\\*)[^\\n]*)`, 'i');
512
535
  const sortedMissing = [...missingPlans].sort();
513
536
  const newRows = sortedMissing.map(p => `- [ ] ${p}`).join('\n');
514
537
  const inserter = (match) => `${match}\n${newRows}`;
@@ -645,7 +668,7 @@ function cmdRoadmapAnnotateDependencies(cwd, phaseNum, raw) {
645
668
  // #3537: padding-tolerant fragment so the caller's resolved padded id
646
669
  // matches un-padded ROADMAP headings.
647
670
  const phaseEscaped = phaseMarkdownRegexSource(phaseNum);
648
- const phaseHeaderPattern = new RegExp(`(#{2,4}\\s*Phase\\s+${phaseEscaped}:[^\\n]*)`, 'i');
671
+ const phaseHeaderPattern = new RegExp(`(#{2,4}\\s*Phase\\s+${phaseEscaped}${OPTIONAL_PHASE_TAG_SOURCE}:[^\\n]*)`, 'i');
649
672
  const phaseMatch = content.match(phaseHeaderPattern);
650
673
  if (!phaseMatch)
651
674
  return;
@@ -1039,7 +1039,7 @@ function applySettingsJsonHooks(settings, opts) {
1039
1039
  // Configure PreToolUse hook for prompt injection detection
1040
1040
  // ADR-857 phase 5f-2: drive dialect from opts.hookEvents (registry descriptor).
1041
1041
  // hookEvents='gemini' → BeforeTool; all others → PreToolUse.
1042
- // Equivalence: hookEvents='gemini' iff runtime∈{gemini,antigravity} (same as old check).
1042
+ // Equivalence: hookEvents='gemini' iff runtime===antigravity (same as old check).
1043
1043
  const preToolEvent = hookEvents === 'gemini' ? 'BeforeTool' : 'PreToolUse';
1044
1044
  if (!settings.hooks[preToolEvent]) {
1045
1045
  settings.hooks[preToolEvent] = [];
@@ -1312,9 +1312,14 @@ function applySettingsJsonHooks(settings, opts) {
1312
1312
  }
1313
1313
  }
1314
1314
  // ── end SubagentStop / Stop / PreCompact events ────────────────────────────
1315
- // ── Gemini-only extended hook events (#776) ───────────────────────────────
1316
- // Gemini CLI exposes several hook events beyond BeforeTool/AfterTool that
1317
- // gsd previously did not register. Three high-value events are added here:
1315
+ // ── Extended hook events (#776; Gemini runtime removed #1928) ──────────────
1316
+ // The Gemini-3-backend dialect exposes several hook events beyond
1317
+ // BeforeTool/AfterTool. These were added for the now-removed Gemini CLI
1318
+ // runtime (#776). No currently supported runtime declares them —
1319
+ // Antigravity's descriptor carries `extendedHookEvents: []` — so this loop
1320
+ // is an inert, descriptor-driven seam: it no-ops for every present runtime
1321
+ // and re-activates automatically if a future runtime declares any of them.
1322
+ // Three high-value events would be wired here:
1318
1323
  //
1319
1324
  // BeforeAgent — fires after user submits a prompt, before the agent
1320
1325
  // plans. Wire gsd-context-monitor for context headroom
@@ -1335,15 +1340,15 @@ function applySettingsJsonHooks(settings, opts) {
1335
1340
  // gsd hook use case at this time; deferred to a follow-on issue.
1336
1341
  //
1337
1342
  // Guard is now descriptor-driven: only events present in extendedEvents are wired.
1338
- for (const geminiEvent of ['BeforeAgent', 'AfterAgent', 'BeforeModel']) {
1339
- if (!extendedEvents.includes(geminiEvent))
1343
+ for (const extendedEvent of ['BeforeAgent', 'AfterAgent', 'BeforeModel']) {
1344
+ if (!extendedEvents.includes(extendedEvent))
1340
1345
  continue;
1341
- if (!Array.isArray(settings.hooks[geminiEvent])) {
1342
- settings.hooks[geminiEvent] = [];
1346
+ if (!Array.isArray(settings.hooks[extendedEvent])) {
1347
+ settings.hooks[extendedEvent] = [];
1343
1348
  }
1344
- const alreadyHasContextMonitor = settings.hooks[geminiEvent].some((entry) => entry.hooks && entry.hooks.some((h) => referencesHook(h, 'gsd-context-monitor')));
1349
+ const alreadyHasContextMonitor = settings.hooks[extendedEvent].some((entry) => entry.hooks && entry.hooks.some((h) => referencesHook(h, 'gsd-context-monitor')));
1345
1350
  if (!alreadyHasContextMonitor && node_fs_1.default.existsSync(contextMonitorFile) && contextMonitorCommand) {
1346
- settings.hooks[geminiEvent].push({
1351
+ settings.hooks[extendedEvent].push({
1347
1352
  hooks: [
1348
1353
  {
1349
1354
  type: 'command',
@@ -1352,13 +1357,13 @@ function applySettingsJsonHooks(settings, opts) {
1352
1357
  }
1353
1358
  ]
1354
1359
  });
1355
- console.log(` ${green}✓${reset} Configured ${geminiEvent} context monitor hook (Gemini)`);
1360
+ console.log(` ${green}✓${reset} Configured ${extendedEvent} context monitor hook`);
1356
1361
  }
1357
1362
  else if (!alreadyHasContextMonitor && !node_fs_1.default.existsSync(contextMonitorFile)) {
1358
- console.warn(` ${yellow}⚠${reset} Skipped ${geminiEvent} hook — gsd-context-monitor.js not found at target`);
1363
+ console.warn(` ${yellow}⚠${reset} Skipped ${extendedEvent} hook — gsd-context-monitor.js not found at target`);
1359
1364
  }
1360
1365
  }
1361
- // ── end Gemini-only extended hook events ──────────────────────────────────
1366
+ // ── end Antigravity-only extended hook events ──────────────────────────────
1362
1367
  // ── FileChanged hook: hot-reload gsd config on .planning/config.json edits ─
1363
1368
  // Claude Code fires FileChanged when a watched file changes on disk. Wire
1364
1369
  // gsd-config-reload.js to reload the gsd config context whenever the user
@@ -29,7 +29,6 @@ const FALLBACK_ALIASES = {
29
29
  claude: ['claude', 'claude-code', 'claude-cli'],
30
30
  opencode: ['opencode', 'open-code', 'opencode-cli'],
31
31
  kilo: ['kilo', 'kilo-cli'],
32
- gemini: ['gemini', 'gemini-cli', 'gemini-code'],
33
32
  codex: ['codex', 'codex-app', 'codex-cli', 'codex_desktop', 'codex-desktop'],
34
33
  copilot: ['copilot', 'copilot-cli', 'github-copilot'],
35
34
  antigravity: ['antigravity', 'antigravity-cli', 'antigravity-agent'],
@@ -114,7 +113,7 @@ function resolveRuntimeNameFromCandidates(...candidates) {
114
113
  * claude → .claude/CLAUDE.md
115
114
  * codex, opencode, kilo, kimi → AGENTS.md
116
115
  * copilot → .github/copilot-instructions.md
117
- * antigravity, gemini → GEMINI.md
116
+ * antigravity → GEMINI.md
118
117
  * unknown / future runtimes → AGENTS.md (safe cross-agent default)
119
118
  *
120
119
  * Source-of-truth references for each runtime's read path:
@@ -126,7 +125,9 @@ function resolveRuntimeNameFromCandidates(...candidates) {
126
125
  * 'copilot-instructions' writes the same `.github/copilot-instructions.md`.)
127
126
  * - codex/opencode/kilo/kimi: AGENTS.md is the documented cross-agent
128
127
  * instruction file (agentsmd/agents.md convention).
129
- * - antigravity/gemini: GEMINI.md is Gemini CLI's contextFileName.
128
+ * - antigravity: GEMINI.md is Antigravity CLI's contextFileName (the Gemini
129
+ * CLI runtime that historically shared this file was removed — #1928;
130
+ * Google sunset Gemini CLI 2026-06-18 and Antigravity CLI is its successor).
130
131
  *
131
132
  * Aliases are normalized via `canonicalizeRuntimeName` first, so inputs like
132
133
  * `codex-cli` resolve to `codex` → `AGENTS.md`. Replaces the prior codex-only
@@ -139,7 +140,7 @@ function getProjectInstructionFile(runtime) {
139
140
  return '.claude/CLAUDE.md';
140
141
  if (canonical === 'copilot')
141
142
  return '.github/copilot-instructions.md';
142
- if (canonical === 'antigravity' || canonical === 'gemini')
143
+ if (canonical === 'antigravity')
143
144
  return 'GEMINI.md';
144
145
  // codex, opencode, kilo, kimi, AND unknown/future runtimes all default to
145
146
  // root AGENTS.md (the safe cross-agent instruction file).
@@ -180,7 +181,7 @@ function getDirName(runtime) {
180
181
  *
181
182
  * Voice: these are the SHORT UI labels, intentionally distinct from the
182
183
  * descriptor `title` (the long product name — e.g. "OpenAI Codex CLI",
183
- * "GitHub Copilot", "Gemini CLI") which serves documentation/registry display,
184
+ * "GitHub Copilot") which serves documentation/registry display,
184
185
  * not the install console. A future slice may relocate this to a
185
186
  * `runtime.label` descriptor field; until then this table is the source.
186
187
  *
@@ -195,7 +196,6 @@ function getDirName(runtime) {
195
196
  const RUNTIME_LABELS = {
196
197
  claude: 'Claude Code',
197
198
  opencode: 'OpenCode',
198
- gemini: 'Gemini',
199
199
  kilo: 'Kilo',
200
200
  codex: 'Codex',
201
201
  copilot: 'Copilot',
@@ -231,7 +231,7 @@ function getRuntimeLabel(runtime) {
231
231
  * bin/install.js (ADR-1239 Phase B / #1679, AC2 slice 2) — the add-a-host tax:
232
232
  * a new runtime meant remembering to add a branch here. Values are preserved
233
233
  * BYTE-FOR-BYTE from the prior chain; golden install parity asserts generated
234
- * hook output is unchanged across all 16 runtimes.
234
+ * hook output is unchanged across all 15 runtimes.
235
235
  *
236
236
  * Two runtimes are intentionally absent (handled by the caller, NOT this table):
237
237
  * - `claude` → the default; falls through to `DEFAULT_FRAGMENT`.
@@ -244,7 +244,6 @@ const DEFAULT_CONFIG_HOME_FRAGMENT = "'.claude'";
244
244
  const GLOBAL_CONFIG_HOME_FRAGMENTS = {
245
245
  copilot: "'.copilot'",
246
246
  opencode: "'.config', 'opencode'",
247
- gemini: "'.gemini'",
248
247
  kilo: "'.config', 'kilo'",
249
248
  codex: "'.codex'",
250
249
  cursor: "'.cursor'",
@@ -277,7 +276,7 @@ function getGlobalConfigHomeFragment(runtime) {
277
276
  * removes).
278
277
  */
279
278
  const RUNTIME_FLAG_IDS = Object.freeze([
280
- 'opencode', 'kilo', 'gemini', 'codex', 'copilot', 'antigravity', 'cursor',
279
+ 'opencode', 'kilo', 'codex', 'copilot', 'antigravity', 'cursor',
281
280
  'windsurf', 'augment', 'trae', 'qwen', 'hermes', 'codebuddy', 'cline', 'kimi',
282
281
  ]);
283
282
  /**
@@ -303,7 +302,6 @@ function runtimeFlags(runtime) {
303
302
  */
304
303
  const DEFAULT_NEW_PROJECT_COMMAND = '/gsd-new-project';
305
304
  const RUNTIME_NEW_PROJECT_COMMANDS = {
306
- gemini: '/gsd:new-project',
307
305
  codex: '$gsd-new-project',
308
306
  cursor: 'gsd-new-project (mention the skill name)',
309
307
  kimi: '/skill:gsd-new-project',
@@ -56,16 +56,20 @@ const node_child_process_1 = __importDefault(require("node:child_process"));
56
56
  * runtime/shell combination.
57
57
  *
58
58
  * Current evidence-backed policy:
59
- * - Gemini on Windows requires `& ` for quoted node/bash runners.
60
- * - Claude Code on Windows does NOT: its hook commands execute under bash/Git
61
- * Bash and `& ` breaks there (#3413).
59
+ * - Claude Code on Windows does NOT need it: its hook commands execute under
60
+ * bash/Git Bash and `& ` breaks there (#3413).
61
+ * - #1928: Gemini CLI — the ONLY runtime with a verified need for the `& `
62
+ * prefix on Windows — was removed (Google sunset it 2026-06-18). No currently
63
+ * supported runtime has a verified need, so this seam is now inert. It is
64
+ * retained (not deleted) so a future runtime with a verified need is a
65
+ * one-line re-enable, per the conservative policy below. Note: Antigravity —
66
+ * the Gemini-backend successor — never matched the old `runtime === 'gemini'`
67
+ * check, so its behavior (no prefix) is unchanged.
62
68
  *
63
69
  * Keep the policy conservative until another runtime has a verified need.
64
70
  */
65
- function hookCommandNeedsPowerShellCallOperator(opts = {}) {
66
- const platform = opts.platform || process.platform;
67
- const runtime = opts.runtime || 'generic';
68
- return platform === 'win32' && runtime === 'gemini';
71
+ function hookCommandNeedsPowerShellCallOperator(_opts = {}) {
72
+ return false;
69
73
  }
70
74
  /**
71
75
  * Project a fully-assembled hook command string for the target runtime.
@@ -121,7 +125,7 @@ function formatManagedHookScriptToken(scriptPath, opts = {}) {
121
125
  function projectLocalHookPrefix({ runtime = 'claude', dirName }) {
122
126
  if (!dirName)
123
127
  return dirName;
124
- return (runtime === 'gemini' || runtime === 'antigravity')
128
+ return (runtime === 'antigravity')
125
129
  ? dirName
126
130
  : `"$CLAUDE_PROJECT_DIR"/${dirName}`;
127
131
  }