@opengsd/gsd-core 1.19.0 → 1.20.1

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 (131) 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/bin/install.js +36 -40
  9. package/commands/gsd/new-milestone.md +2 -2
  10. package/commands/gsd/new-project.md +2 -2
  11. package/commands/gsd/update.md +5 -2
  12. package/gsd-core/bin/gsd-tools.cjs +177 -93
  13. package/gsd-core/bin/lib/capability-lifecycle.cjs +1 -12
  14. package/gsd-core/bin/lib/capability-registry.cjs +284 -93
  15. package/gsd-core/bin/lib/capability-validator.cjs +15 -0
  16. package/gsd-core/bin/lib/commands.cjs +46 -17
  17. package/gsd-core/bin/lib/config-loader.cjs +3 -3
  18. package/gsd-core/bin/lib/core-utils.cjs +11 -0
  19. package/gsd-core/bin/lib/frontmatter.cjs +21 -3
  20. package/gsd-core/bin/lib/gate-api-coverage-verify-pre.cjs +2 -2
  21. package/gsd-core/bin/lib/gate-context-drift.cjs +1 -1
  22. package/gsd-core/bin/lib/gate-phase-context.cjs +7 -8
  23. package/gsd-core/bin/lib/gate-schema-drift.cjs +1 -1
  24. package/gsd-core/bin/lib/git-info-exclude.cjs +1 -6
  25. package/gsd-core/bin/lib/gsd-tool-dir.cjs +134 -0
  26. package/gsd-core/bin/lib/health-diagnostic-rules/phase-structure.cjs +13 -9
  27. package/gsd-core/bin/lib/health-diagnostic-rules/roadmap-disk-consistency.cjs +9 -7
  28. package/gsd-core/bin/lib/health-diagnostic-rules/state-consistency.cjs +3 -3
  29. package/gsd-core/bin/lib/init.cjs +181 -68
  30. package/gsd-core/bin/lib/install-engine.cjs +9 -9
  31. package/gsd-core/bin/lib/install-manifest-record.cjs +86 -0
  32. package/gsd-core/bin/lib/install-profiles.cjs +4 -2
  33. package/gsd-core/bin/lib/io.cjs +6 -0
  34. package/gsd-core/bin/lib/markdown-sectionizer.cjs +34 -1
  35. package/gsd-core/bin/lib/markdown-table.cjs +23 -0
  36. package/gsd-core/bin/lib/mcp-server.cjs +8 -5
  37. package/gsd-core/bin/lib/milestone.cjs +18 -50
  38. package/gsd-core/bin/lib/phase-entry-grammar.cjs +119 -0
  39. package/gsd-core/bin/lib/phase-id-display.cjs +2 -0
  40. package/gsd-core/bin/lib/phase-id.cjs +627 -36
  41. package/gsd-core/bin/lib/phase-lifecycle.cjs +9 -74
  42. package/gsd-core/bin/lib/phase-locator.cjs +328 -19
  43. package/gsd-core/bin/lib/phase.cjs +3039 -666
  44. package/gsd-core/bin/lib/planning-document.cjs +26 -5
  45. package/gsd-core/bin/lib/planning-inspect.cjs +27 -35
  46. package/gsd-core/bin/lib/planning-snapshot.cjs +12 -7
  47. package/gsd-core/bin/lib/planning-workspace.cjs +3 -3
  48. package/gsd-core/bin/lib/read-only-git-env.cjs +23 -2
  49. package/gsd-core/bin/lib/refactor-trigger-command-router.cjs +2 -2
  50. package/gsd-core/bin/lib/review-lane-descriptor.cjs +22 -0
  51. package/gsd-core/bin/lib/review-lane-invocation.cjs +125 -18
  52. package/gsd-core/bin/lib/reviewer-step-dispatch.cjs +11 -9
  53. package/gsd-core/bin/lib/roadmap-parser.cjs +759 -144
  54. package/gsd-core/bin/lib/roadmap-progress.cjs +136 -0
  55. package/gsd-core/bin/lib/roadmap-upgrade.cjs +8 -5
  56. package/gsd-core/bin/lib/roadmap.cjs +358 -106
  57. package/gsd-core/bin/lib/runtime-artifact-conversion.cjs +310 -130
  58. package/gsd-core/bin/lib/runtime-artifact-layout.cjs +6 -0
  59. package/gsd-core/bin/lib/shell-command-projection.cjs +36 -9
  60. package/gsd-core/bin/lib/smart-entry.cjs +22 -11
  61. package/gsd-core/bin/lib/state-contract.cjs +11 -7
  62. package/gsd-core/bin/lib/state-document.cjs +332 -23
  63. package/gsd-core/bin/lib/state-transition.cjs +51 -15
  64. package/gsd-core/bin/lib/state.cjs +499 -180
  65. package/gsd-core/bin/lib/template.cjs +2 -2
  66. package/gsd-core/bin/lib/validate.cjs +3 -2
  67. package/gsd-core/bin/lib/vendor/re2js.cjs +863 -577
  68. package/gsd-core/bin/lib/vendor/re2js.d.cts +284 -219
  69. package/gsd-core/bin/lib/verify.cjs +2 -2
  70. package/gsd-core/bin/lib/workstream-inventory.cjs +27 -21
  71. package/gsd-core/bin/lib/worktree-base-ref.cjs +32 -2
  72. package/gsd-core/bin/verify-reapply-patches.cjs +411 -10
  73. package/gsd-core/references/cicd-review-patterns.md +45 -0
  74. package/gsd-core/references/necessity-review.md +48 -0
  75. package/gsd-core/workflows/code-review-fix.md +46 -5
  76. package/gsd-core/workflows/code-review.md +3 -0
  77. package/gsd-core/workflows/execute-phase.md +1 -1
  78. package/gsd-core/workflows/execute-plan.md +1 -1
  79. package/gsd-core/workflows/plan-phase/steps/stall-detection-helpers.md +13 -9
  80. package/gsd-core/workflows/plan-phase.md +1 -1
  81. package/gsd-core/workflows/pr-branch.md +2 -0
  82. package/gsd-core/workflows/reapply-patches.md +104 -0
  83. package/gsd-core/workflows/review.md +54 -141
  84. package/gsd-core/workflows/secure-phase.md +1 -1
  85. package/gsd-core/workflows/settings-advanced.md +57 -9
  86. package/gsd-core/workflows/ship.md +12 -0
  87. package/hooks/dist/gsd-agent-isolation-guard.js +1 -1
  88. package/hooks/dist/gsd-cursor-subagent-start.js +1 -1
  89. package/hooks/dist/gsd-graphify-update.sh +6 -0
  90. package/hooks/dist/gsd-statusline.js +3 -2
  91. package/hooks/dist/gsd-windsurf-pre-write.js +4 -4
  92. package/hooks/dist/gsd-workflow-guard.js +2 -1
  93. package/hooks/dist/gsd-worktree-path-guard.js +4 -4
  94. package/hooks/dist/lib/git-probe.js +16 -0
  95. package/hooks/dist/lib/isolation-sentinel.js +77 -2
  96. package/hooks/gsd-agent-isolation-guard.js +1 -1
  97. package/hooks/gsd-cursor-subagent-start.js +1 -1
  98. package/hooks/gsd-graphify-update.sh +6 -0
  99. package/hooks/gsd-statusline.js +3 -2
  100. package/hooks/gsd-windsurf-pre-write.js +4 -4
  101. package/hooks/gsd-workflow-guard.js +2 -1
  102. package/hooks/gsd-worktree-path-guard.js +4 -4
  103. package/hooks/lib/git-probe.js +16 -0
  104. package/hooks/lib/isolation-sentinel.js +77 -2
  105. package/package.json +3 -3
  106. package/pi/package-agents/gsd-code-fixer.md +9 -4
  107. package/pi/package-agents/gsd-code-reviewer.md +53 -14
  108. package/pi/package-agents/gsd-executor.md +8 -4
  109. package/pi/package-agents/manifest.json +3 -3
  110. package/pi/package-skills/gsd-new-milestone/SKILL.md +1 -1
  111. package/pi/package-skills/gsd-new-project/SKILL.md +1 -1
  112. package/pi/package-skills/gsd-update/SKILL.md +4 -1
  113. package/scripts/changeset/lint.cjs +28 -3
  114. package/scripts/ci-next-health.cjs +246 -23
  115. package/scripts/docs-guard-registry.cjs +2 -0
  116. package/scripts/gen-features.cjs +14 -2
  117. package/scripts/lib/drift-scan.cjs +27 -0
  118. package/scripts/lib/macos-conformance-tier.generated.cjs +10 -0
  119. package/scripts/lib/platform-conformance-tier.generated.cjs +15 -0
  120. package/scripts/lint-phase-enumeration-drift.cjs +8 -4
  121. package/scripts/lint-roadmap-progress-drift.cjs +421 -0
  122. package/scripts/merge-commit-pr.cjs +186 -0
  123. package/scripts/mutation-matrix.cjs +23 -1
  124. package/scripts/release-ancestry.cjs +86 -0
  125. package/scripts/release-pipeline-pr.cjs +92 -0
  126. package/scripts/require-issue-link-policy.cjs +18 -9
  127. package/scripts/sync-next-version.cjs +48 -7
  128. package/skills/gsd-new-milestone/SKILL.md +1 -1
  129. package/skills/gsd-new-project/SKILL.md +1 -1
  130. package/skills/gsd-update/SKILL.md +5 -2
  131. package/vscode/package.json +1 -1
@@ -25,12 +25,12 @@ const { readSubdirectories } = coreUtilsMod;
25
25
  const planScan = require("./plan-scan.cjs");
26
26
  // eslint-disable-next-line @typescript-eslint/no-require-imports
27
27
  const planningWorkspace = require("./planning-workspace.cjs");
28
- const { planningPaths, planningRoot, getActiveWorkstream } = planningWorkspace;
28
+ const { planningPaths, planningRoot, getActiveWorkstream, resolvePhaseIdConvention } = planningWorkspace;
29
29
  const state_document_cjs_1 = require("./state-document.cjs");
30
30
  // eslint-disable-next-line @typescript-eslint/no-require-imports -- frontmatter.cjs is an export= CommonJS module
31
31
  const frontmatterMod = require("./frontmatter.cjs");
32
32
  const { extractFrontmatter, stripFrontmatter } = frontmatterMod;
33
- const markdown_table_cjs_1 = require("./markdown-table.cjs");
33
+ const roadmap_progress_cjs_1 = require("./roadmap-progress.cjs");
34
34
  // eslint-disable-next-line @typescript-eslint/no-require-imports -- verification.cjs is an export= CommonJS module
35
35
  const verificationMod = require("./verification.cjs");
36
36
  const { isPhaseComplete, isVerificationStatus, VERIFICATION_STATUS } = verificationMod;
@@ -41,6 +41,9 @@ const { phaseKeyFromDir, phaseKeyFromProse, parentPhaseKey } = phaseIdMod;
41
41
  const roadmapParserMod = require("./roadmap-parser.cjs");
42
42
  const { getMilestonePhaseFilter, isMilestoneShippedInRoadmap } = roadmapParserMod;
43
43
  const workstream_inventory_builder_cjs_1 = require("./workstream-inventory-builder.cjs");
44
+ // eslint-disable-next-line @typescript-eslint/no-require-imports
45
+ const phaseLocatorMod = require("./phase-locator.cjs");
46
+ const { phaseDirectoryOptions } = phaseLocatorMod;
44
47
  // ─── Implementation ───────────────────────────────────────────────────────────
45
48
  function workstreamsRoot(cwd) {
46
49
  return node_path_1.default.join(planningRoot(cwd), 'workstreams');
@@ -75,7 +78,7 @@ function countRoadmapPhases(roadmapPath, fallbackCount, cwd, ws, versionOverride
75
78
  // `null` -> 0 (fallback), `undefined` -> 3. A non-bracket workstream
76
79
  // resolves to a value that compiles the same base grammar `null` did, so
77
80
  // its count is unchanged (verified against a legacy control).
78
- const filter = getMilestonePhaseFilter(cwd, versionOverride ?? null, undefined, ws ?? null);
81
+ const filter = getMilestonePhaseFilter(cwd, versionOverride ?? null, undefined, ws ?? null, phaseDirectoryOptions(cwd, undefined, undefined, ws ?? null));
79
82
  // A pass-all degrade (phaseCount 0) means the window declared no phases —
80
83
  // fall back rather than reporting a confident zero.
81
84
  return filter.phaseCount > 0 ? filter.phaseCount : fallbackCount;
@@ -98,25 +101,22 @@ function countRoadmapPhases(roadmapPath, fallbackCount, cwd, ws, versionOverride
98
101
  * `phaseKeyFromDir` uses for directories, so a `| 01. … |` row and a `1-slug`
99
102
  * directory cannot land in different key spaces (the padding-asymmetry defect).
100
103
  */
101
- function parseRoadmapProgressRows(roadmapPath) {
102
- let content;
103
- try {
104
- content = node_fs_1.default.readFileSync(roadmapPath, 'utf-8');
105
- }
106
- catch {
107
- return []; /* no roadmap */
108
- }
104
+ function parseRoadmapProgressRows(content, directoryContext, convention) {
109
105
  // Milestone-ATTRIBUTING shape first. Both shapes carry `Plans Complete`, so
110
106
  // probing that column first would pick a flat table appearing earlier in the
111
107
  // document over a milestone-grouped one later — every row would come back
112
108
  // unattributed and be treated as current-milestone, silently over-including.
113
- const table = (0, markdown_table_cjs_1.findTableWithColumns)(content, ['Phase', 'Milestone'])
114
- ?? (0, markdown_table_cjs_1.findTableWithColumns)(content, ['Phase', 'Plans Complete']);
115
- if (!table)
116
- return [];
109
+ // #5761: the roadmap-progress owner picks the table (heading-scoped, so a
110
+ // decoy table elsewhere cannot be mistaken for it); this reader only supplies
111
+ // the probe order. A table outside a table-less `## Progress` section has
112
+ // always been read here (#5692: no behavior change), hence `sectionMiss`.
113
+ const { rows: tableRows } = (0, roadmap_progress_cjs_1.locateRoadmapProgress)(content, {
114
+ columns: [['Phase', 'Milestone'], ['Phase', 'Plans Complete']],
115
+ sectionMiss: 'document',
116
+ });
117
117
  const rows = [];
118
- for (const row of table.rows) {
119
- const key = phaseKeyFromProse(row['Phase']);
118
+ for (const row of tableRows) {
119
+ const key = phaseKeyFromProse(row['Phase'], directoryContext, convention);
120
120
  if (key === null)
121
121
  continue;
122
122
  const cell = (row['Milestone'] ?? '').trim();
@@ -428,12 +428,18 @@ function inspectWorkstream(cwd, name, options = {}) {
428
428
  const writeDiagnostic = options.writeDiagnostic ?? ((message) => process.stderr.write(message));
429
429
  const p = planningPaths(cwd, name);
430
430
  const phaseDirNames = readSubdirectories(p.phases);
431
+ let roadmap = '';
432
+ try {
433
+ roadmap = node_fs_1.default.readFileSync(p.roadmap, 'utf8');
434
+ }
435
+ catch { /* no roadmap */ }
436
+ const directoryContext = phaseDirectoryOptions(cwd, undefined, roadmap, name);
431
437
  // #2562: scope progress to the CURRENT milestone. Membership and the
432
438
  // denominator are derived in ONE key space (`phaseKeyFromDir` /
433
439
  // `phaseKeyFromProse`, both from the phase-id owner module) so the two sides
434
440
  // of the rollup cannot disagree.
435
441
  const currentVersion = readCurrentMilestoneVersion(p.state, p.roadmap);
436
- const progressRows = parseRoadmapProgressRows(p.roadmap);
442
+ const progressRows = parseRoadmapProgressRows(roadmap, directoryContext, resolvePhaseIdConvention(cwd, name));
437
443
  // Phase keys the ROADMAP attributes to the current milestone. A row whose
438
444
  // Milestone cell is blank or malformed is INCLUDED rather than dropped: a
439
445
  // phase we cannot attribute must still be visible to the rollup. Dropping it
@@ -458,7 +464,7 @@ function inspectWorkstream(cwd, name, options = {}) {
458
464
  // it is exactly the caller that cannot set `GSD_WORKSTREAM`; spelling `null`
459
465
  // pinned every workstream to the legacy grammar and made `headingScoped`
460
466
  // unreachable (`phaseCount > 0` never held) on a bracket workstream.
461
- const headingFilter = getMilestonePhaseFilter(cwd, currentVersion, undefined, name);
467
+ const headingFilter = getMilestonePhaseFilter(cwd, currentVersion, undefined, name, directoryContext);
462
468
  const headingScoped = headingFilter.versionScoped && headingFilter.phaseCount > 0;
463
469
  // Phase keys the ROADMAP attributes to some OTHER milestone. A row carrying an
464
470
  // explicit version that is not the current one is a positive claim by a prior
@@ -507,7 +513,7 @@ function inspectWorkstream(cwd, name, options = {}) {
507
513
  const isDirInCurrentMilestone = (dir) => {
508
514
  if (!scoped)
509
515
  return true;
510
- const key = phaseKeyFromDir(dir);
516
+ const key = phaseKeyFromDir(dir, undefined, directoryContext);
511
517
  if (currentMilestoneKeys.has(key))
512
518
  return true;
513
519
  const parent = parentPhaseKey(key);
@@ -563,7 +569,7 @@ function inspectWorkstream(cwd, name, options = {}) {
563
569
  }
564
570
  return {
565
571
  directory: dir,
566
- phaseKey: phaseKeyFromDir(dir),
572
+ phaseKey: phaseKeyFromDir(dir, undefined, directoryContext),
567
573
  mtimeMs: phaseDirMtime(phaseDir),
568
574
  planCount: counts.planCount,
569
575
  summaryCount: counts.summaryCount,
@@ -24,8 +24,10 @@ exports.cmdWorktreeSetBaseRef = cmdWorktreeSetBaseRef;
24
24
  exports.classifyGitHead = classifyGitHead;
25
25
  exports.observeHarnessForkFromHead = observeHarnessForkFromHead;
26
26
  exports.evaluateWorktreeBaseDegrade = evaluateWorktreeBaseDegrade;
27
+ const node_crypto_1 = __importDefault(require("node:crypto"));
27
28
  const node_fs_1 = __importDefault(require("node:fs"));
28
29
  const node_path_1 = __importDefault(require("node:path"));
30
+ const gsd_tool_dir_cjs_1 = require("./gsd-tool-dir.cjs");
29
31
  const shell_command_projection_cjs_1 = require("./shell-command-projection.cjs");
30
32
  const runtime_homes_cjs_1 = require("./runtime-homes.cjs");
31
33
  // ─── Internal helpers ─────────────────────────────────────────────────────────
@@ -572,13 +574,41 @@ function observeHarnessForkFromHead(deps) {
572
574
  }
573
575
  });
574
576
  const stateWrite = deps.stateWrite ?? ((file, content) => {
577
+ let tmpPath = null;
575
578
  try {
576
- node_fs_1.default.mkdirSync(node_path_1.default.dirname(file), { recursive: true });
577
- node_fs_1.default.writeFileSync(file, content, 'utf8');
579
+ // #5757: the shared owner refuses a symlinked/escaping .gsd and keeps the
580
+ // directory git-ignored; the write is an exclusive random tmp + rename so
581
+ // a planted cache-file symlink is replaced, never followed.
582
+ const dir = (0, gsd_tool_dir_cjs_1.ensureToolOwnedGsdDir)(cwd, {
583
+ marker: '# gsd-core harness fork probe',
584
+ rules: ['/harness-fork-probe.json', '/harness-fork-probe.json.tmp-*', '/.gitignore'],
585
+ });
586
+ const target = node_path_1.default.join(dir, node_path_1.default.basename(file));
587
+ // `tmpPath` is assigned only once the exclusive create succeeded, so an
588
+ // EEXIST never unlinks an entry this process did not create.
589
+ const candidateTmp = `${target}.tmp-${process.pid}-${node_crypto_1.default.randomBytes(6).toString('hex')}`;
590
+ const tmpFd = node_fs_1.default.openSync(candidateTmp, 'wx');
591
+ tmpPath = candidateTmp;
592
+ try {
593
+ node_fs_1.default.writeFileSync(tmpFd, content, 'utf8');
594
+ }
595
+ finally {
596
+ node_fs_1.default.closeSync(tmpFd);
597
+ }
598
+ (0, shell_command_projection_cjs_1.retryRenameSync)(tmpPath, target);
599
+ tmpPath = null;
578
600
  }
579
601
  catch {
580
602
  // A cache write must never break the check that produced the verdict.
581
603
  }
604
+ finally {
605
+ if (tmpPath) {
606
+ try {
607
+ node_fs_1.default.unlinkSync(tmpPath);
608
+ }
609
+ catch { /* best-effort cleanup */ }
610
+ }
611
+ }
582
612
  });
583
613
  // 1. Cache — trusted only while the orchestrator HEAD is unchanged.
584
614
  try {