@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
@@ -0,0 +1,136 @@
1
+ "use strict";
2
+ /**
3
+ * Roadmap Progress Module (#5761, Phase 1 of epic #5692): the single owner of
4
+ * "which table is this ROADMAP's Progress table, and what is its milestone
5
+ * slice".
6
+ *
7
+ * Before this module the question was answered by six locators/scopers that
8
+ * disagreed on `## Progress Tracker`, on a `## Progress` inside a fenced block,
9
+ * and on the headingless fallback. Every READER now asks `locateRoadmapProgress`
10
+ * (or, for the "does this span hold the Progress section" probe,
11
+ * `findProgressSection`); a second hand-written `## Progress` regex or
12
+ * table-locating call in a reader is the DEFECT.GENERATIVE-FIX shape.
13
+ *
14
+ * The rule, defined once:
15
+ * - The Progress section is the first level-2 ATX heading, outside fenced
16
+ * code, whose text starts with the word `Progress` (`## Progress`,
17
+ * `## Progress Tracker`). It runs to the next level-1/2 heading outside
18
+ * fenced code.
19
+ * - The table is the first table in that section whose header is a SUPERSET
20
+ * of the requested column set (ADR-2143 §3: addressed by NAME, never
21
+ * ordinal). A section with no matching table yields no table; it does not
22
+ * fall through to the rest of the document (#2012 decoy avoidance).
23
+ * - A document with no Progress heading is scanned whole (a headingless
24
+ * milestone slice, #1445, keeps resolving).
25
+ * - Two readers had a wider pre-owner reach and ask for it by name, so the
26
+ * rule stays single (#5692: no behavior change for existing ROADMAP.md
27
+ * files): `levels: [1, 2]` for the archive-span probe, which always took a
28
+ * level-1 `# Progress`, and `sectionMiss: 'document'` for the workstream
29
+ * inventory, which always read a table outside a table-less section.
30
+ * - The milestone slice (#5165): when the caller names a milestone and the
31
+ * table carries a `Milestone` column, only that milestone's rows.
32
+ *
33
+ * The writers (`roadmap.cts` update-plan-progress / phase add, `phase.cts`
34
+ * complete / remove) cut their slice with `editRoadmapProgress` (#5762), the
35
+ * write side of the same rule, so a writer edits only the section the readers
36
+ * read. `phase.cts`'s `cmdPhaseComplete` already-complete check still slices
37
+ * the Milestone column itself, with a blank-milestone-matches-nothing rule this
38
+ * owner deliberately does not share.
39
+ */
40
+ Object.defineProperty(exports, "__esModule", { value: true });
41
+ exports.isProgressHeadingTitle = isProgressHeadingTitle;
42
+ exports.normalizeMilestoneToken = normalizeMilestoneToken;
43
+ exports.milestoneVersionOf = milestoneVersionOf;
44
+ exports.findProgressSection = findProgressSection;
45
+ exports.editRoadmapProgress = editRoadmapProgress;
46
+ exports.locateRoadmapProgress = locateRoadmapProgress;
47
+ const markdown_table_cjs_1 = require("./markdown-table.cjs");
48
+ const markdown_sectionizer_cjs_1 = require("./markdown-sectionizer.cjs");
49
+ /** The canonical column set both RoadmapProgress variants carry. */
50
+ const CANONICAL_COLUMNS = ['Phase', 'Plans Complete', 'Status', 'Completed'];
51
+ /**
52
+ * The ONE textual expression of "this heading is titled Progress": the title
53
+ * starts with the word `Progress`, any suffix admitted (`Progress`,
54
+ * `Progress Tracker`, `Progress (v2.1)`), case-insensitive. `findProgressSection`
55
+ * adds the level-2 anchor; the bracket writers (`phase.cts`) reuse the title
56
+ * test at their own levels (#4304).
57
+ */
58
+ function isProgressHeadingTitle(text) {
59
+ return /^Progress\b/i.test(text.trim());
60
+ }
61
+ /**
62
+ * #5165: a milestone token compared by identity, not spelling — `v1.1`, `V1.1`
63
+ * and ` 1.1 ` name one milestone. A hand-decorated Progress cell
64
+ * (`**v1.1**`, `` `v1.1` ``, `v1.1 (current)`) still names its first
65
+ * whitespace-separated token. Absent/blank tokens normalize to `''`.
66
+ */
67
+ function normalizeMilestoneToken(value) {
68
+ // `[v1.1](ROADMAP.md#v11)` names its link text.
69
+ const unlinked = (milestoneVersionOf(value) ?? '').replace(/\[([^\]]*)\]\([^)]*\)/g, '$1');
70
+ const first = unlinked.split(/\s+/)[0] ?? '';
71
+ return first.replace(/^[*_`]+|[*_`:,;]+$/g, '').replace(/^v/i, '').toLowerCase();
72
+ }
73
+ /**
74
+ * #5165: a STATE.md `milestone:` frontmatter value as a trimmed version string,
75
+ * or null when it is absent, blank or not scalar text.
76
+ */
77
+ function milestoneVersionOf(value) {
78
+ if (typeof value !== 'string' && typeof value !== 'number')
79
+ return null;
80
+ const text = String(value).trim();
81
+ return text === '' ? null : text;
82
+ }
83
+ /**
84
+ * The Progress section of `content`: the first level-2 heading (`levels` widens
85
+ * this) outside fenced code whose text starts with the word `Progress`, up to
86
+ * the next level-1/2 heading. Null when the document has no such heading.
87
+ */
88
+ function findProgressSection(content, options = {}) {
89
+ const headings = (0, markdown_sectionizer_cjs_1.tokenizeHeadings)(content);
90
+ const { active, exclude, levels = [2] } = options;
91
+ const isProgress = (h) => levels.includes(h.level) && isProgressHeadingTitle(h.text) && !(exclude && exclude(h.offset));
92
+ // #5553: with an active-milestone range, the first Progress heading INSIDE it wins over an
93
+ // earlier one (a shipped plain section's own table); with none in range, the first overall.
94
+ const inActive = active ? headings.findIndex((h) => isProgress(h) && h.offset >= active.start && h.offset < active.end) : -1;
95
+ const index = inActive !== -1 ? inActive : headings.findIndex(isProgress);
96
+ if (index === -1)
97
+ return null;
98
+ return (0, markdown_sectionizer_cjs_1.headingSliceRange)(content, headings, index, headings[index].level);
99
+ }
100
+ /**
101
+ * The WRITE side of the same rule (#5762): run `edit` on the Progress section
102
+ * `findProgressSection` locates and splice the result back byte-exact, so a
103
+ * writer can only ever edit the section the readers read.
104
+ *
105
+ * A document with no Progress heading is what the readers scan whole;
106
+ * `headingless: 'whole'` (default) gives `edit` that same whole document,
107
+ * `'skip'` leaves it untouched (a destructive writer that must not guess a
108
+ * table by column names alone).
109
+ */
110
+ function editRoadmapProgress(content, edit, options = {}) {
111
+ const section = findProgressSection(content, { active: options.active });
112
+ if (!section)
113
+ return options.headingless === 'skip' ? content : edit(content);
114
+ return (0, markdown_sectionizer_cjs_1.editHeadingSlice)(content, section, edit);
115
+ }
116
+ /** Which table is the Progress table, and what is its milestone slice. */
117
+ function locateRoadmapProgress(content, options = {}) {
118
+ const section = findProgressSection(content);
119
+ const scoped = section ? content.slice(section.start, section.end) : content;
120
+ // `sectionMiss: 'document'` widens each column set's probe from the section to the whole
121
+ // document, set by set, so an earlier set found anywhere still beats a later set in the section.
122
+ const widen = section !== null && options.sectionMiss === 'document';
123
+ let table = null;
124
+ for (const columns of options.columns ?? [CANONICAL_COLUMNS]) {
125
+ table = (0, markdown_table_cjs_1.findTableWithColumns)(scoped, [...columns]) ?? (widen ? (0, markdown_table_cjs_1.findTableWithColumns)(content, [...columns]) : null);
126
+ if (table)
127
+ break;
128
+ }
129
+ if (!table)
130
+ return { table: null, rows: [], section };
131
+ const want = normalizeMilestoneToken(options.milestone);
132
+ const rows = want !== '' && table.columns.includes('Milestone')
133
+ ? table.rows.filter((r) => normalizeMilestoneToken(r['Milestone']) === want)
134
+ : table.rows;
135
+ return { table, rows, section };
136
+ }
@@ -13,6 +13,7 @@ var __importDefault = (this && this.__importDefault) || function (mod) {
13
13
  const node_fs_1 = __importDefault(require("node:fs"));
14
14
  const node_path_1 = __importDefault(require("node:path"));
15
15
  const shell_command_projection_cjs_1 = require("./shell-command-projection.cjs");
16
+ const phase_entry_grammar_cjs_1 = require("./phase-entry-grammar.cjs");
16
17
  // eslint-disable-next-line @typescript-eslint/no-require-imports
17
18
  const planningWorkspace = require("./planning-workspace.cjs");
18
19
  // eslint-disable-next-line @typescript-eslint/no-require-imports
@@ -1397,10 +1398,11 @@ function computeBracketPlan(cwd) {
1397
1398
  // bullet stayed legacy after an otherwise-done migration, so the
1398
1399
  // bracket-mode milestone id set carried the stale legacy token alongside
1399
1400
  // the bracket ones.
1400
- const CHECKLIST_BULLET_PREFIX_SRC = '[-*]\\s*\\[[ x]\\]\\s*\\*{0,2}';
1401
+ // #5764: the prefix is the phase-entry grammar's (line-anchored, indent included).
1402
+ const CHECKLIST_BULLET_PREFIX_SRC = (0, phase_entry_grammar_cjs_1.phaseEntryPrefixSrc)();
1401
1403
  const CHECKLIST_PHASE_INTRO_SRC = phaseHeadingPrefixSrcFor(PHASE_HEADING_BASELINE.LABEL_ONLY, undefined, true);
1402
- const mnnChecklistRe = new RegExp(`^(\\s*${CHECKLIST_BULLET_PREFIX_SRC})${CHECKLIST_PHASE_INTRO_SRC}(${MNN_SOURCE_TOKEN_SOURCE})(${OPTIONAL_PHASE_TAG_SOURCE})`, 'i');
1403
- const legacyChecklistRe = new RegExp(`^(\\s*${CHECKLIST_BULLET_PREFIX_SRC})${CHECKLIST_PHASE_INTRO_SRC}(${PHASE_NUMBER_TOKEN_SOURCE})(${OPTIONAL_PHASE_TAG_SOURCE})`, 'i');
1404
+ const mnnChecklistRe = new RegExp(`(${CHECKLIST_BULLET_PREFIX_SRC})${CHECKLIST_PHASE_INTRO_SRC}(${MNN_SOURCE_TOKEN_SOURCE})(${OPTIONAL_PHASE_TAG_SOURCE})`, 'i');
1405
+ const legacyChecklistRe = new RegExp(`(${CHECKLIST_BULLET_PREFIX_SRC})${CHECKLIST_PHASE_INTRO_SRC}(${PHASE_NUMBER_TOKEN_SOURCE})(${OPTIONAL_PHASE_TAG_SOURCE})`, 'i');
1404
1406
  // #4144 round 8 I1: `legacyChecklistRe` above requires nothing at all
1405
1407
  // after the number/tag — deliberately, since Tier 1 (reader-recognized,
1406
1408
  // `isReaderRecognizedBullet` below) must match exactly what the readers'
@@ -1752,7 +1754,8 @@ function computeMigrationPlan(cwd, options = {}) {
1752
1754
  if (roadmapEdits.some(e => e.lineIndex === i))
1753
1755
  continue;
1754
1756
  // Match checklist items: "- [ ] **Phase N:**" or "- [x] Phase N:" (also decimal)
1755
- const checklistMatch = line.match(new RegExp(`^(\\s*-\\s*\\[[ x]\\]\\s*\\*{0,2}Phase\\s+)(${PHASE_NUMBER_TOKEN_SOURCE})(\\s*[:\\s*])`, 'i'));
1757
+ // The entry prefix (marker, box, bold token) is the phase-entry grammar's (#5764).
1758
+ const checklistMatch = line.match(new RegExp(`(${(0, phase_entry_grammar_cjs_1.phaseEntryPrefixSrc)()}Phase[ \\t]+)(${PHASE_NUMBER_TOKEN_SOURCE})(\\s*[:\\s*])`, 'i'));
1756
1759
  if (checklistMatch) {
1757
1760
  const legacyNum = checklistMatch[2];
1758
1761
  const cIntPart = parseInt(legacyNum, 10);
@@ -1778,7 +1781,7 @@ function computeMigrationPlan(cwd, options = {}) {
1778
1781
  newId = found.newId;
1779
1782
  }
1780
1783
  if (newId) {
1781
- const newLine = line.replace(new RegExp(`^(\\s*-\\s*\\[[ x]\\]\\s*\\*{0,2}Phase\\s+)${PHASE_NUMBER_TOKEN_SOURCE}(\\s*[:\\s*])`, 'i'), `$1${newId}$2`);
1784
+ const newLine = line.replace(new RegExp(`(${(0, phase_entry_grammar_cjs_1.phaseEntryPrefixSrc)()}Phase[ \\t]+)${PHASE_NUMBER_TOKEN_SOURCE}(\\s*[:\\s*])`, 'i'), `$1${newId}$2`);
1782
1785
  if (newLine !== line) {
1783
1786
  roadmapEdits.push({ lineIndex: i, from: line, to: newLine });
1784
1787
  }