@opengsd/gsd-core 1.18.0 → 1.19.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 (257) hide show
  1. package/.claude-plugin/marketplace.json +1 -1
  2. package/.claude-plugin/plugin.json +2 -1
  3. package/.opencode/plugins/gsd-core.js +2 -3
  4. package/agents/gsd-code-fixer.compact.md +2 -2
  5. package/agents/gsd-code-fixer.md +4 -3
  6. package/agents/gsd-verifier.md +1 -1
  7. package/bin/install.js +422 -103
  8. package/gsd-core/bin/gsd-tools.cjs +106 -7
  9. package/gsd-core/bin/lib/agent-install-check.cjs +29 -3
  10. package/gsd-core/bin/lib/args-terminator.cjs +114 -0
  11. package/gsd-core/bin/lib/broken-windows.cjs +59 -1
  12. package/gsd-core/bin/lib/capability-lock.cjs +13 -2
  13. package/gsd-core/bin/lib/capability-registry.cjs +156 -74
  14. package/gsd-core/bin/lib/capability-state.cjs +14 -8
  15. package/gsd-core/bin/lib/capability-validator.cjs +17 -0
  16. package/gsd-core/bin/lib/config-loader.cjs +10 -0
  17. package/gsd-core/bin/lib/config.cjs +256 -58
  18. package/gsd-core/bin/lib/core-utils.cjs +34 -0
  19. package/gsd-core/bin/lib/decision-coverage-support.cjs +74 -0
  20. package/gsd-core/bin/lib/gate-context-drift.cjs +8 -3
  21. package/gsd-core/bin/lib/gate-decision-coverage-plan.cjs +6 -1
  22. package/gsd-core/bin/lib/gate-decision-coverage-verify.cjs +6 -1
  23. package/gsd-core/bin/lib/gate-evaluation-scope.cjs +134 -13
  24. package/gsd-core/bin/lib/gate-tdd-review-checkpoint.cjs +63 -22
  25. package/gsd-core/bin/lib/gate-ui-safety.cjs +23 -5
  26. package/gsd-core/bin/lib/git-info-exclude.cjs +57 -0
  27. package/gsd-core/bin/lib/health-diagnostic-rules/phase-structure.cjs +18 -3
  28. package/gsd-core/bin/lib/health-diagnostic-types.cjs +4 -0
  29. package/gsd-core/bin/lib/health-diagnostic.cjs +52 -1
  30. package/gsd-core/bin/lib/host-integration.cjs +13 -6
  31. package/gsd-core/bin/lib/init.cjs +65 -1
  32. package/gsd-core/bin/lib/install-engine.cjs +246 -32
  33. package/gsd-core/bin/lib/install-profiles.cjs +9 -4
  34. package/gsd-core/bin/lib/installer-migration-report.cjs +2 -0
  35. package/gsd-core/bin/lib/markdown-line-diff.cjs +147 -0
  36. package/gsd-core/bin/lib/markdown-sectionizer.cjs +113 -0
  37. package/gsd-core/bin/lib/milestone-lock.cjs +8 -1
  38. package/gsd-core/bin/lib/milestone-release-map.cjs +244 -0
  39. package/gsd-core/bin/lib/milestone-release-tags.cjs +440 -0
  40. package/gsd-core/bin/lib/milestone.cjs +43 -1
  41. package/gsd-core/bin/lib/model-catalog.cjs +8 -0
  42. package/gsd-core/bin/lib/normalize-test-command.cjs +67 -0
  43. package/gsd-core/bin/lib/phase-id.cjs +21 -4
  44. package/gsd-core/bin/lib/phase-lifecycle.cjs +37 -4
  45. package/gsd-core/bin/lib/phase.cjs +115 -26
  46. package/gsd-core/bin/lib/planning-snapshot.cjs +3 -1
  47. package/gsd-core/bin/lib/planning-workspace.cjs +18 -0
  48. package/gsd-core/bin/lib/pristine-baseline.cjs +11 -3
  49. package/gsd-core/bin/lib/read-only-git-env.cjs +58 -0
  50. package/gsd-core/bin/lib/review-lane-descriptor.cjs +35 -2
  51. package/gsd-core/bin/lib/review-lane-runner.cjs +33 -4
  52. package/gsd-core/bin/lib/roadmap-parser.cjs +12 -0
  53. package/gsd-core/bin/lib/roadmap-upgrade.cjs +39 -2
  54. package/gsd-core/bin/lib/roadmap.cjs +33 -25
  55. package/gsd-core/bin/lib/runtime-artifact-conversion.cjs +381 -14
  56. package/gsd-core/bin/lib/runtime-artifact-layout.cjs +8 -4
  57. package/gsd-core/bin/lib/runtime-hooks-surface.cjs +60 -16
  58. package/gsd-core/bin/lib/shell-command-projection.cjs +121 -16
  59. package/gsd-core/bin/lib/smart-entry.cjs +13 -0
  60. package/gsd-core/bin/lib/state-contract.cjs +29 -9
  61. package/gsd-core/bin/lib/state-document.cjs +12 -4
  62. package/gsd-core/bin/lib/state-transition.cjs +119 -24
  63. package/gsd-core/bin/lib/state.cjs +50 -28
  64. package/gsd-core/bin/lib/surface.cjs +9 -0
  65. package/gsd-core/bin/lib/task-command-router.cjs +5 -25
  66. package/gsd-core/bin/lib/task-content-resolution.cjs +46 -1
  67. package/gsd-core/bin/lib/verification.cjs +96 -9
  68. package/gsd-core/bin/lib/verify.cjs +2 -0
  69. package/gsd-core/bin/lib/worktree-safety.cjs +22 -1
  70. package/gsd-core/bin/shared/config-defaults.manifest.json +1 -0
  71. package/gsd-core/bin/shared/config-schema.manifest.json +1 -0
  72. package/gsd-core/references/git-integration.md +11 -0
  73. package/gsd-core/references/loop-hook-dispatch.md +13 -6
  74. package/gsd-core/references/planning-config.md +2 -0
  75. package/gsd-core/references/tdd.md +2 -2
  76. package/gsd-core/templates/DEBUG.md +16 -0
  77. package/gsd-core/templates/config.json +2 -1
  78. package/gsd-core/templates/verification-report.md +1 -1
  79. package/gsd-core/workflows/audit-fix.md +2 -1
  80. package/gsd-core/workflows/code-review/steps/structural-pre-pass.md +10 -1
  81. package/gsd-core/workflows/code-review.md +61 -7
  82. package/gsd-core/workflows/complete-milestone/steps/project-md-evolution-review.md +145 -0
  83. package/gsd-core/workflows/complete-milestone/steps/release-map.md +80 -0
  84. package/gsd-core/workflows/complete-milestone.md +37 -150
  85. package/gsd-core/workflows/diagnose-issues.md +29 -5
  86. package/gsd-core/workflows/discuss-phase/modes/power.md +1 -1
  87. package/gsd-core/workflows/discuss-phase-assumptions.md +33 -2
  88. package/gsd-core/workflows/discuss-phase-power.md +19 -2
  89. package/gsd-core/workflows/discuss-phase.md +6 -10
  90. package/gsd-core/workflows/execute-phase/steps/per-plan-worktree-gate.md +13 -7
  91. package/gsd-core/workflows/execute-phase/steps/post-merge-gate.md +7 -2
  92. package/gsd-core/workflows/execute-phase/steps/regression-gate-run.md +2 -1
  93. package/gsd-core/workflows/execute-phase/steps/verify-phase-goal.md +2 -2
  94. package/gsd-core/workflows/execute-phase/steps/wave-post-gate-hooks.md +11 -2
  95. package/gsd-core/workflows/execute-phase.md +9 -5
  96. package/gsd-core/workflows/health.md +2 -1
  97. package/gsd-core/workflows/import.md +1 -1
  98. package/gsd-core/workflows/plan-phase/steps/chunked-planning-mode.md +2 -2
  99. package/gsd-core/workflows/plan-phase.md +13 -13
  100. package/gsd-core/workflows/quick/steps/plan-checker-loop.md +1 -1
  101. package/gsd-core/workflows/quick/steps/worktree-pre-dispatch-commit.md +8 -1
  102. package/gsd-core/workflows/quick-batch/steps/plan-checker-loop.md +2 -1
  103. package/gsd-core/workflows/quick.md +1 -1
  104. package/gsd-core/workflows/review.md +16 -13
  105. package/gsd-core/workflows/section-manifest.json +5 -0
  106. package/gsd-core/workflows/settings.md +1 -0
  107. package/gsd-core/workflows/ship.md +3 -4
  108. package/gsd-core/workflows/spec-phase.md +3 -6
  109. package/gsd-core/workflows/verify-work.md +4 -3
  110. package/hooks/dist/gsd-agent-isolation-guard.js +11 -1
  111. package/hooks/dist/gsd-cursor-subagent-start.js +6 -2
  112. package/hooks/dist/gsd-path-export.js +95 -0
  113. package/hooks/dist/gsd-secret-read-guard.js +172 -39
  114. package/hooks/dist/gsd-statusline.js +35 -2
  115. package/hooks/dist/gsd-windsurf-pre-write.js +28 -1
  116. package/hooks/dist/gsd-workflow-guard.js +1 -1
  117. package/hooks/dist/gsd-worktree-path-guard.js +28 -1
  118. package/hooks/dist/lib/isolation-sentinel.js +44 -0
  119. package/hooks/dist/managed-hooks-registry.cjs +1 -0
  120. package/hooks/gsd-agent-isolation-guard.js +11 -1
  121. package/hooks/gsd-cursor-subagent-start.js +6 -2
  122. package/hooks/gsd-path-export.js +95 -0
  123. package/hooks/gsd-secret-read-guard.js +172 -39
  124. package/hooks/gsd-statusline.js +35 -2
  125. package/hooks/gsd-windsurf-pre-write.js +28 -1
  126. package/hooks/gsd-workflow-guard.js +1 -1
  127. package/hooks/gsd-worktree-path-guard.js +28 -1
  128. package/hooks/hooks.json +1 -0
  129. package/hooks/lib/isolation-sentinel.js +44 -0
  130. package/hooks/managed-hooks-registry.cjs +1 -0
  131. package/package.json +13 -6
  132. package/pi/gsd.cjs +1047 -10
  133. package/pi/package-agents/gsd-advisor-researcher.md +113 -0
  134. package/pi/package-agents/gsd-ai-researcher.md +111 -0
  135. package/pi/package-agents/gsd-assumptions-analyzer.md +110 -0
  136. package/pi/package-agents/gsd-code-fixer.md +769 -0
  137. package/pi/package-agents/gsd-code-reviewer.md +402 -0
  138. package/pi/package-agents/gsd-codebase-mapper.md +850 -0
  139. package/pi/package-agents/gsd-debug-session-manager.md +416 -0
  140. package/pi/package-agents/gsd-debugger.md +1274 -0
  141. package/pi/package-agents/gsd-doc-classifier.md +269 -0
  142. package/pi/package-agents/gsd-doc-synthesizer.md +259 -0
  143. package/pi/package-agents/gsd-doc-verifier.md +212 -0
  144. package/pi/package-agents/gsd-doc-writer.md +626 -0
  145. package/pi/package-agents/gsd-dom-verifier.md +164 -0
  146. package/pi/package-agents/gsd-domain-researcher.md +144 -0
  147. package/pi/package-agents/gsd-eval-auditor.md +185 -0
  148. package/pi/package-agents/gsd-eval-planner.md +149 -0
  149. package/pi/package-agents/gsd-executor.md +894 -0
  150. package/pi/package-agents/gsd-framework-selector.md +159 -0
  151. package/pi/package-agents/gsd-integration-checker.md +476 -0
  152. package/pi/package-agents/gsd-intel-updater.md +338 -0
  153. package/pi/package-agents/gsd-mempalace-curator.md +49 -0
  154. package/pi/package-agents/gsd-nyquist-auditor.md +200 -0
  155. package/pi/package-agents/gsd-pattern-mapper.md +340 -0
  156. package/pi/package-agents/gsd-phase-researcher.md +897 -0
  157. package/pi/package-agents/gsd-plan-checker.md +1077 -0
  158. package/pi/package-agents/gsd-planner.md +1003 -0
  159. package/pi/package-agents/gsd-project-researcher.md +611 -0
  160. package/pi/package-agents/gsd-research-synthesizer.md +259 -0
  161. package/pi/package-agents/gsd-roadmapper.md +779 -0
  162. package/pi/package-agents/gsd-security-auditor.md +171 -0
  163. package/pi/package-agents/gsd-ui-auditor.md +581 -0
  164. package/pi/package-agents/gsd-ui-checker.md +417 -0
  165. package/pi/package-agents/gsd-ui-researcher.md +469 -0
  166. package/pi/package-agents/gsd-user-profiler.md +174 -0
  167. package/pi/package-agents/gsd-verifier.md +963 -0
  168. package/pi/package-agents/manifest.json +39 -0
  169. package/pi/package-skills/gsd-add-tests/SKILL.md +39 -0
  170. package/pi/package-skills/gsd-ai-integration-phase/SKILL.md +36 -0
  171. package/pi/package-skills/gsd-audit-fix/SKILL.md +34 -0
  172. package/pi/package-skills/gsd-audit-milestone/SKILL.md +40 -0
  173. package/pi/package-skills/gsd-audit-uat/SKILL.md +26 -0
  174. package/pi/package-skills/gsd-autonomous/SKILL.md +52 -0
  175. package/pi/package-skills/gsd-capture/SKILL.md +68 -0
  176. package/pi/package-skills/gsd-cleanup/SKILL.md +25 -0
  177. package/pi/package-skills/gsd-code-review/SKILL.md +71 -0
  178. package/pi/package-skills/gsd-complete-milestone/SKILL.md +147 -0
  179. package/pi/package-skills/gsd-config/SKILL.md +60 -0
  180. package/pi/package-skills/gsd-debug/SKILL.md +64 -0
  181. package/pi/package-skills/gsd-discuss-phase/SKILL.md +74 -0
  182. package/pi/package-skills/gsd-docs-update/SKILL.md +50 -0
  183. package/pi/package-skills/gsd-eval-review/SKILL.md +35 -0
  184. package/pi/package-skills/gsd-execute-phase/SKILL.md +62 -0
  185. package/pi/package-skills/gsd-explore/SKILL.md +26 -0
  186. package/pi/package-skills/gsd-extract-learnings/SKILL.md +25 -0
  187. package/pi/package-skills/gsd-fast/SKILL.md +33 -0
  188. package/pi/package-skills/gsd-forensics/SKILL.md +59 -0
  189. package/pi/package-skills/gsd-graphify/SKILL.md +210 -0
  190. package/pi/package-skills/gsd-health/SKILL.md +36 -0
  191. package/pi/package-skills/gsd-help/SKILL.md +37 -0
  192. package/pi/package-skills/gsd-import/SKILL.md +46 -0
  193. package/pi/package-skills/gsd-inbox/SKILL.md +42 -0
  194. package/pi/package-skills/gsd-ingest-docs/SKILL.md +43 -0
  195. package/pi/package-skills/gsd-manager/SKILL.md +46 -0
  196. package/pi/package-skills/gsd-map-codebase/SKILL.md +85 -0
  197. package/pi/package-skills/gsd-mempalace-capture/SKILL.md +111 -0
  198. package/pi/package-skills/gsd-mempalace-recall/SKILL.md +111 -0
  199. package/pi/package-skills/gsd-milestone-summary/SKILL.md +54 -0
  200. package/pi/package-skills/gsd-mvp-phase/SKILL.md +45 -0
  201. package/pi/package-skills/gsd-new-milestone/SKILL.md +49 -0
  202. package/pi/package-skills/gsd-new-project/SKILL.md +49 -0
  203. package/pi/package-skills/gsd-next/SKILL.md +34 -0
  204. package/pi/package-skills/gsd-ns-context/SKILL.md +31 -0
  205. package/pi/package-skills/gsd-ns-ideate/SKILL.md +30 -0
  206. package/pi/package-skills/gsd-ns-manage/SKILL.md +42 -0
  207. package/pi/package-skills/gsd-ns-project/SKILL.md +34 -0
  208. package/pi/package-skills/gsd-ns-review/SKILL.md +35 -0
  209. package/pi/package-skills/gsd-ns-workflow/SKILL.md +44 -0
  210. package/pi/package-skills/gsd-onboard/SKILL.md +46 -0
  211. package/pi/package-skills/gsd-pause-work/SKILL.md +48 -0
  212. package/pi/package-skills/gsd-phase/SKILL.md +61 -0
  213. package/pi/package-skills/gsd-plan-phase/SKILL.md +62 -0
  214. package/pi/package-skills/gsd-plan-review-convergence/SKILL.md +63 -0
  215. package/pi/package-skills/gsd-pr-branch/SKILL.md +31 -0
  216. package/pi/package-skills/gsd-profile-user/SKILL.md +48 -0
  217. package/pi/package-skills/gsd-progress/SKILL.md +51 -0
  218. package/pi/package-skills/gsd-quick/SKILL.md +179 -0
  219. package/pi/package-skills/gsd-quick-batch/SKILL.md +114 -0
  220. package/pi/package-skills/gsd-resume-work/SKILL.md +31 -0
  221. package/pi/package-skills/gsd-review/SKILL.md +50 -0
  222. package/pi/package-skills/gsd-review-backlog/SKILL.md +65 -0
  223. package/pi/package-skills/gsd-secure-phase/SKILL.md +37 -0
  224. package/pi/package-skills/gsd-settings/SKILL.md +30 -0
  225. package/pi/package-skills/gsd-ship/SKILL.md +27 -0
  226. package/pi/package-skills/gsd-sketch/SKILL.md +56 -0
  227. package/pi/package-skills/gsd-spec-phase/SKILL.md +63 -0
  228. package/pi/package-skills/gsd-spike/SKILL.md +53 -0
  229. package/pi/package-skills/gsd-stats/SKILL.md +22 -0
  230. package/pi/package-skills/gsd-surface/SKILL.md +177 -0
  231. package/pi/package-skills/gsd-thread/SKILL.md +29 -0
  232. package/pi/package-skills/gsd-ui-phase/SKILL.md +35 -0
  233. package/pi/package-skills/gsd-ui-review/SKILL.md +35 -0
  234. package/pi/package-skills/gsd-ultraplan-phase/SKILL.md +38 -0
  235. package/pi/package-skills/gsd-undo/SKILL.md +38 -0
  236. package/pi/package-skills/gsd-update/SKILL.md +51 -0
  237. package/pi/package-skills/gsd-validate-phase/SKILL.md +37 -0
  238. package/pi/package-skills/gsd-verify-work/SKILL.md +41 -0
  239. package/pi/package-skills/gsd-workspace/SKILL.md +57 -0
  240. package/pi/package-skills/gsd-workstreams/SKILL.md +73 -0
  241. package/scripts/build-hooks.js +4 -0
  242. package/scripts/command-contract-helpers.cjs +1 -1
  243. package/scripts/docs-guard-registry.cjs +20 -7
  244. package/scripts/gen-health-docs.cjs +13 -1
  245. package/scripts/gen-pi-package-skills.cjs +217 -0
  246. package/scripts/lib/macos-conformance-tier.generated.cjs +11 -1
  247. package/scripts/lib/platform-conformance-tier.generated.cjs +26 -1
  248. package/scripts/lint-docs-guard-registration.exempt-baseline.cjs +3 -3
  249. package/scripts/lint-retired-runtime-name.cjs +5 -0
  250. package/scripts/npm-audit-baseline.cjs +41 -7
  251. package/scripts/reviewed-or-maintainer-policy.cjs +181 -0
  252. package/scripts/select-docs-guards.cjs +1 -1
  253. package/scripts/self-test-report-policy.cjs +350 -0
  254. package/vscode/package.json +1 -1
  255. package/scripts/diagnose-validate-commit-in-tier.cjs +0 -270
  256. package/scripts/diagnose-validate-commit-phases.cjs +0 -348
  257. package/scripts/xtrace-phase-timing.cjs +0 -205
@@ -25,6 +25,9 @@ exports.updateBullet = updateBullet;
25
25
  exports.updateHeading = updateHeading;
26
26
  exports.extractTaggedBlocks = extractTaggedBlocks;
27
27
  exports.stripTaggedBlocks = stripTaggedBlocks;
28
+ exports.editOutsideTaggedBlocks = editOutsideTaggedBlocks;
29
+ exports.maskFences = maskFences;
30
+ exports.editOutsideFences = editOutsideFences;
28
31
  exports.replaceSection = replaceSection;
29
32
  exports.withSection = withSection;
30
33
  exports.deleteSection = deleteSection;
@@ -850,6 +853,116 @@ function stripTaggedBlocks(content, tagName, allowAttributes = false, caseSensit
850
853
  return content;
851
854
  return content.replace(taggedBlockPattern(tagName, caseSensitive ? 'g' : 'gi', allowAttributes), '');
852
855
  }
856
+ /**
857
+ * Run `edit` over `content` with every `<tagName>…</tagName>` block hidden from
858
+ * it, then put the blocks back byte-for-byte (#5543). The blocks are swapped for
859
+ * one-line placeholder comments, so `edit` sees the rest of the document in its
860
+ * original order and cannot touch, or be captured by, a block's contents.
861
+ *
862
+ * Same hardened block pattern as `stripTaggedBlocks` (exact `<tag>` opener, so
863
+ * `<details open>` stays visible to `edit`). If `edit` returns a non-string, or
864
+ * drops a placeholder, the original `content` is returned unchanged rather than
865
+ * a document with a block missing.
866
+ *
867
+ * Nested blocks are NOT supported, exactly as for `extractTaggedBlocks` and
868
+ * `stripTaggedBlocks`: the first `</tag>` closes the block. Every ROADMAP reader
869
+ * that strips shipped milestones shares that grammar, so this writer sees the
870
+ * same blocks those readers do.
871
+ */
872
+ function editOutsideTaggedBlocks(content, tagName, edit, allowAttributes = false, caseSensitive = false) {
873
+ if (typeof content !== 'string' || content.length === 0)
874
+ return content;
875
+ if (typeof tagName !== 'string' || tagName.length === 0)
876
+ return edit(content);
877
+ const blocks = [];
878
+ let nonce = 0;
879
+ while (content.includes(`<!-- gsd-masked-${tagName}-${nonce}-`))
880
+ nonce += 1;
881
+ const placeholder = (i) => `<!-- gsd-masked-${tagName}-${nonce}-${i} -->`;
882
+ const masked = content.replace(taggedBlockPattern(tagName, caseSensitive ? 'g' : 'gi', allowAttributes), (block) => {
883
+ blocks.push(block);
884
+ return placeholder(blocks.length - 1);
885
+ });
886
+ if (blocks.length === 0)
887
+ return edit(content);
888
+ const edited = edit(masked);
889
+ if (typeof edited !== 'string')
890
+ return content;
891
+ let restored = edited;
892
+ for (let i = 0; i < blocks.length; i++) {
893
+ const marker = placeholder(i);
894
+ const at = restored.indexOf(marker);
895
+ if (at === -1 || restored.indexOf(marker, at + marker.length) !== -1)
896
+ return content;
897
+ restored = restored.slice(0, at) + blocks[i] + restored.slice(at + marker.length);
898
+ }
899
+ return restored;
900
+ }
901
+ /**
902
+ * Hide every fenced code block from a line-oriented edit. State-transition writers locate their
903
+ * target with `/^Field:.*$/m`-style patterns; those match a fenced EXAMPLE of the field as readily
904
+ * as the live line, so the example got the new value and the live line stayed stale. Masking each
905
+ * block to a single `<!-- gsd-masked-fence-… -->` line lets the same pattern see only live lines;
906
+ * `restore` splices the blocks back byte-for-byte (CRLF included — the original line text is kept).
907
+ * The fence grammar is `scanFencedBlocks`' (the one every reader here uses), and an unterminated
908
+ * fence masks to the end of the content, as `stripFencedCode` treats it.
909
+ */
910
+ function maskFences(content) {
911
+ const identity = { masked: content, restore: (edited) => edited };
912
+ if (typeof content !== 'string' || content.length === 0)
913
+ return identity;
914
+ const lines = content.split('\n');
915
+ const blocks = scanFencedBlocks(lines);
916
+ if (blocks.length === 0)
917
+ return identity;
918
+ let nonce = 0;
919
+ while (content.includes(`<!-- gsd-masked-fence-${nonce}-`))
920
+ nonce += 1;
921
+ const placeholder = (i) => `<!-- gsd-masked-fence-${nonce}-${i} -->`;
922
+ const kept = [];
923
+ const originals = [];
924
+ let cursor = 0;
925
+ for (const block of blocks) {
926
+ const end = block.closeLineIdx === -1 ? lines.length - 1 : block.closeLineIdx;
927
+ for (; cursor < block.openLineIdx; cursor++)
928
+ kept.push(lines[cursor]);
929
+ originals.push(lines.slice(block.openLineIdx, end + 1).join('\n'));
930
+ kept.push(placeholder(originals.length - 1));
931
+ cursor = end + 1;
932
+ }
933
+ for (; cursor < lines.length; cursor++)
934
+ kept.push(lines[cursor]);
935
+ return {
936
+ masked: kept.join('\n'),
937
+ restore(edited) {
938
+ if (typeof edited !== 'string')
939
+ return null;
940
+ let restored = edited;
941
+ for (let i = 0; i < originals.length; i++) {
942
+ const marker = placeholder(i);
943
+ const at = restored.indexOf(marker);
944
+ if (at === -1 || restored.indexOf(marker, at + marker.length) !== -1)
945
+ return null;
946
+ restored = restored.slice(0, at) + originals[i] + restored.slice(at + marker.length);
947
+ }
948
+ return restored;
949
+ },
950
+ };
951
+ }
952
+ /**
953
+ * Run `edit` over `content` with every fenced code block hidden from it (see `maskFences`).
954
+ * Content without a fence is edited as given. An edit that returns a non-string, or that loses a
955
+ * placeholder, returns the original content unchanged.
956
+ */
957
+ function editOutsideFences(content, edit) {
958
+ if (typeof content !== 'string' || content.length === 0)
959
+ return content;
960
+ const mask = maskFences(content);
961
+ const edited = edit(mask.masked);
962
+ if (typeof edited !== 'string')
963
+ return content;
964
+ return mask.restore(edited) ?? content;
965
+ }
853
966
  // ─── replaceSection ───────────────────────────────────────────────────────────
854
967
  /**
855
968
  * Splice `newBody` in place of a section's body and return the resulting
@@ -57,6 +57,7 @@ exports.releaseMilestonePhase = releaseMilestonePhase;
57
57
  const node_fs_1 = __importDefault(require("node:fs"));
58
58
  const node_path_1 = __importDefault(require("node:path"));
59
59
  const clock_cjs_1 = require("./clock.cjs");
60
+ const git_info_exclude_cjs_1 = require("./git-info-exclude.cjs");
60
61
  // eslint-disable-next-line @typescript-eslint/no-require-imports -- planning-workspace.cjs is an export= CommonJS module
61
62
  const planningWorkspace = require("./planning-workspace.cjs");
62
63
  // eslint-disable-next-line @typescript-eslint/no-require-imports -- active-workstream-store.cjs is an export= CommonJS module
@@ -122,7 +123,13 @@ function readClaim(cwd) {
122
123
  }
123
124
  function writeClaim(cwd, claim) {
124
125
  ensureClaimDir(cwd);
125
- node_fs_1.default.writeFileSync(milestoneLockPath(cwd), JSON.stringify(claim, null, 2) + '\n');
126
+ const lockPath = milestoneLockPath(cwd);
127
+ node_fs_1.default.writeFileSync(lockPath, JSON.stringify(claim, null, 2) + '\n');
128
+ // #5551: the claim is per-machine state, never project content. Keep it out
129
+ // of `git status` (the ship preflight refuses a dirty tree). Every write, not
130
+ // only the first, so a claim file that predates this fix is covered too; the
131
+ // helper is idempotent and best-effort.
132
+ (0, git_info_exclude_cjs_1.excludeFromGit)(node_path_1.default.dirname(lockPath), exports.MILESTONE_LOCK_FILENAME);
126
133
  }
127
134
  // planningWorkspace.planningDir targets .planning (or the workstream-scoped
128
135
  // variant); the parent is created by every GSD command that gets this far, but
@@ -0,0 +1,244 @@
1
+ "use strict";
2
+ /**
3
+ * Milestone Release Map — validation, rendering and archive read-back for the
4
+ * per-repository release record `milestone complete --release-map` persists
5
+ * (#5706, phase 2 of epic #3628, docs/adr/3628-milestone-release-mapping.md).
6
+ *
7
+ * A release map is the caller-supplied record of what each repository shipped
8
+ * for a milestone. It is data: this module never resolves a repository to a
9
+ * directory, never consults `planning.sub_repos`, and never creates a tag. The
10
+ * map is archived by recorded value, so a repository that later leaves
11
+ * `planning.sub_repos` does not change an already-archived map.
12
+ */
13
+ var __importDefault = (this && this.__importDefault) || function (mod) {
14
+ return (mod && mod.__esModule) ? mod : { "default": mod };
15
+ };
16
+ Object.defineProperty(exports, "__esModule", { value: true });
17
+ exports.RELEASE_MAP_TAG_STATES = exports.RELEASE_MAP_STATUSES = exports.MAX_RELEASE_MAP_FILE_BYTES = exports.MAX_RELEASE_MAP_FIELD_LENGTH = exports.MAX_RELEASE_MAP_ENTRIES = void 0;
18
+ exports.validateReleaseMap = validateReleaseMap;
19
+ exports.renderReleaseMapMarkdown = renderReleaseMapMarkdown;
20
+ exports.releaseMapArchivePath = releaseMapArchivePath;
21
+ exports.serializeReleaseMapArchive = serializeReleaseMapArchive;
22
+ exports.loadReleaseMapFile = loadReleaseMapFile;
23
+ exports.readArchivedReleaseMap = readArchivedReleaseMap;
24
+ const node_fs_1 = __importDefault(require("node:fs"));
25
+ const node_path_1 = __importDefault(require("node:path"));
26
+ const security_cjs_1 = require("./security.cjs");
27
+ /** Maximum number of entries a single release map may carry. */
28
+ exports.MAX_RELEASE_MAP_ENTRIES = 256;
29
+ /** Maximum length (UTF-16 code units) of any single string field in an entry. */
30
+ exports.MAX_RELEASE_MAP_FIELD_LENGTH = 200;
31
+ /** Maximum size in bytes of a `--release-map` input file. */
32
+ exports.MAX_RELEASE_MAP_FILE_BYTES = 1048576;
33
+ /** Allowed values of an entry's `status`. */
34
+ exports.RELEASE_MAP_STATUSES = Object.freeze(['released', 'deployed', 'unreleased']);
35
+ /** Allowed values of an entry's `tag`. */
36
+ exports.RELEASE_MAP_TAG_STATES = Object.freeze(['created', 'already_tagged', 'skipped', 'none']);
37
+ const OPTIONAL_STRING_FIELDS = ['release', 'reference', 'environment'];
38
+ const ENTRY_FIELDS = Object.freeze(['repo', ...OPTIONAL_STRING_FIELDS, 'status', 'tag']);
39
+ function isPlainObject(value) {
40
+ return typeof value === 'object' && value !== null && !Array.isArray(value);
41
+ }
42
+ function describeType(value) {
43
+ if (value === null)
44
+ return 'null';
45
+ if (Array.isArray(value))
46
+ return 'array';
47
+ return typeof value;
48
+ }
49
+ /**
50
+ * Validate a release map (an array of entries) and return the normalized
51
+ * entries. Every problem is reported in one pass so a caller fixes the file
52
+ * once. Strict on shape: unknown fields are an error, not silently dropped.
53
+ */
54
+ function validateReleaseMap(input) {
55
+ if (!Array.isArray(input)) {
56
+ return { ok: false, errors: [`release map must be an array of entries (got ${describeType(input)})`] };
57
+ }
58
+ const errors = [];
59
+ if (input.length > exports.MAX_RELEASE_MAP_ENTRIES) {
60
+ errors.push(`release map has ${input.length} entries; the maximum is ${exports.MAX_RELEASE_MAP_ENTRIES}`);
61
+ }
62
+ const map = [];
63
+ const seenRepos = new Set();
64
+ const limit = Math.min(input.length, exports.MAX_RELEASE_MAP_ENTRIES);
65
+ for (let i = 0; i < limit; i++) {
66
+ const raw = input[i];
67
+ const label = `entries[${i}]`;
68
+ if (!isPlainObject(raw)) {
69
+ errors.push(`${label} must be an object (got ${describeType(raw)})`);
70
+ continue;
71
+ }
72
+ let entryOk = true;
73
+ for (const key of Object.keys(raw)) {
74
+ if (!ENTRY_FIELDS.includes(key)) {
75
+ errors.push(`${label} has unknown field "${key.slice(0, exports.MAX_RELEASE_MAP_FIELD_LENGTH)}"`);
76
+ entryOk = false;
77
+ }
78
+ }
79
+ const repo = raw['repo'];
80
+ if (typeof repo !== 'string' || repo.trim() === '') {
81
+ errors.push(`${label}.repo must be a non-blank string`);
82
+ entryOk = false;
83
+ }
84
+ else if (repo.length > exports.MAX_RELEASE_MAP_FIELD_LENGTH) {
85
+ errors.push(`${label}.repo exceeds ${exports.MAX_RELEASE_MAP_FIELD_LENGTH} characters`);
86
+ entryOk = false;
87
+ }
88
+ else if (seenRepos.has(repo)) {
89
+ errors.push(`${label}.repo is a duplicate of an earlier entry`);
90
+ entryOk = false;
91
+ }
92
+ else {
93
+ seenRepos.add(repo);
94
+ }
95
+ const optional = {};
96
+ for (const field of OPTIONAL_STRING_FIELDS) {
97
+ const value = raw[field];
98
+ if (value === undefined || value === null) {
99
+ optional[field] = null;
100
+ }
101
+ else if (typeof value !== 'string') {
102
+ errors.push(`${label}.${field} must be a string or null (got ${describeType(value)})`);
103
+ entryOk = false;
104
+ }
105
+ else if (value.length > exports.MAX_RELEASE_MAP_FIELD_LENGTH) {
106
+ errors.push(`${label}.${field} exceeds ${exports.MAX_RELEASE_MAP_FIELD_LENGTH} characters`);
107
+ entryOk = false;
108
+ }
109
+ else {
110
+ optional[field] = value;
111
+ }
112
+ }
113
+ const status = raw['status'];
114
+ if (typeof status !== 'string' || !exports.RELEASE_MAP_STATUSES.includes(status)) {
115
+ errors.push(`${label}.status must be one of: ${exports.RELEASE_MAP_STATUSES.join(', ')}`);
116
+ entryOk = false;
117
+ }
118
+ const tag = raw['tag'];
119
+ if (typeof tag !== 'string' || !exports.RELEASE_MAP_TAG_STATES.includes(tag)) {
120
+ errors.push(`${label}.tag must be one of: ${exports.RELEASE_MAP_TAG_STATES.join(', ')}`);
121
+ entryOk = false;
122
+ }
123
+ if (entryOk) {
124
+ map.push({
125
+ repo: repo,
126
+ release: optional['release'] ?? null,
127
+ reference: optional['reference'] ?? null,
128
+ environment: optional['environment'] ?? null,
129
+ status: status,
130
+ tag: tag,
131
+ });
132
+ }
133
+ }
134
+ if (errors.length > 0)
135
+ return { ok: false, errors };
136
+ return { ok: true, map };
137
+ }
138
+ /**
139
+ * Escape one value for a GFM table cell: a pipe, newline, backtick, angle
140
+ * bracket or other markdown control character in recorded data can neither
141
+ * end the cell, start a new row, nor inject structure (headings, HTML).
142
+ */
143
+ function escapeCell(value) {
144
+ if (value === null)
145
+ return '-';
146
+ const flattened = value.replace(/[\u0000-\u001f\u007f\u2028\u2029]+/g, ' ').trim();
147
+ if (flattened === '')
148
+ return '-';
149
+ const escaped = flattened.replace(/[\\|`*_[\]<>&~#!]/g, (ch) => {
150
+ if (ch === '<')
151
+ return '&lt;';
152
+ if (ch === '>')
153
+ return '&gt;';
154
+ if (ch === '&')
155
+ return '&amp;';
156
+ return `\\${ch}`;
157
+ });
158
+ return escaped;
159
+ }
160
+ /**
161
+ * Render a validated release map as the `**Release mapping:**` markdown block
162
+ * appended to a MILESTONES.md entry. One table row per entry, always six cells.
163
+ */
164
+ function renderReleaseMapMarkdown(map) {
165
+ const lines = [
166
+ '**Release mapping:**',
167
+ '',
168
+ '| Repository | Release | Reference | Environment | Status | Tag |',
169
+ '| --- | --- | --- | --- | --- | --- |',
170
+ ];
171
+ for (const entry of map) {
172
+ lines.push(`| ${escapeCell(entry.repo)} | ${escapeCell(entry.release)} | ${escapeCell(entry.reference)} | ${escapeCell(entry.environment)} | ${escapeCell(entry.status)} | ${escapeCell(entry.tag)} |`);
173
+ }
174
+ return `${lines.join('\n')}\n`;
175
+ }
176
+ /** Path of the archived release map for a milestone version. */
177
+ function releaseMapArchivePath(milestonesDir, version) {
178
+ return node_path_1.default.join(milestonesDir, `${version}-RELEASE-MAP.json`);
179
+ }
180
+ /** Serialize the archive document `{ version, entries }`. */
181
+ function serializeReleaseMapArchive(version, map) {
182
+ return `${JSON.stringify({ version, entries: map }, null, 2)}\n`;
183
+ }
184
+ /**
185
+ * Read and validate a caller-supplied `--release-map` file. Refuses anything
186
+ * that is not a regular file (directory, FIFO, device), anything over 1 MiB and
187
+ * anything unreadable or unparseable, without ever opening a non-regular file.
188
+ */
189
+ function loadReleaseMapFile(filePath) {
190
+ let stat;
191
+ try {
192
+ stat = node_fs_1.default.statSync(filePath);
193
+ }
194
+ catch (err) {
195
+ const code = err.code ?? 'unknown';
196
+ return { ok: false, errors: [`release map file cannot be read (${code}): ${node_path_1.default.basename(filePath)}`] };
197
+ }
198
+ if (!stat.isFile()) {
199
+ return { ok: false, errors: [`release map path is not a regular file: ${node_path_1.default.basename(filePath)}`] };
200
+ }
201
+ if (stat.size > exports.MAX_RELEASE_MAP_FILE_BYTES) {
202
+ return { ok: false, errors: [`release map file is ${stat.size} bytes; the maximum is ${exports.MAX_RELEASE_MAP_FILE_BYTES}`] };
203
+ }
204
+ let text;
205
+ try {
206
+ text = node_fs_1.default.readFileSync(filePath, 'utf-8');
207
+ }
208
+ catch (err) {
209
+ const code = err.code ?? 'unknown';
210
+ return { ok: false, errors: [`release map file cannot be read (${code}): ${node_path_1.default.basename(filePath)}`] };
211
+ }
212
+ const parsed = (0, security_cjs_1.safeJsonParse)(text, { maxLength: exports.MAX_RELEASE_MAP_FILE_BYTES, label: 'release map' });
213
+ if (!parsed.ok) {
214
+ return { ok: false, errors: [parsed.error ?? 'release map: invalid JSON'] };
215
+ }
216
+ return validateReleaseMap(parsed.value);
217
+ }
218
+ /**
219
+ * Read a milestone's archived release map back by recorded value. A milestone
220
+ * archived without a map yields `{ ok: true, map: null }` ("no map"), never an
221
+ * error; a present-but-corrupt archive is reported, not guessed at.
222
+ */
223
+ function readArchivedReleaseMap(milestonesDir, version) {
224
+ const file = releaseMapArchivePath(milestonesDir, version);
225
+ let text;
226
+ try {
227
+ text = node_fs_1.default.readFileSync(file, 'utf-8');
228
+ }
229
+ catch (err) {
230
+ if (err.code === 'ENOENT')
231
+ return { ok: true, map: null };
232
+ return { ok: false, error: `release map archive cannot be read: ${node_path_1.default.basename(file)}` };
233
+ }
234
+ const parsed = (0, security_cjs_1.safeJsonParse)(text, { maxLength: exports.MAX_RELEASE_MAP_FILE_BYTES, label: 'release map archive' });
235
+ if (!parsed.ok)
236
+ return { ok: false, error: parsed.error ?? 'release map archive: invalid JSON' };
237
+ const doc = parsed.value;
238
+ if (!isPlainObject(doc))
239
+ return { ok: false, error: 'release map archive: expected an object' };
240
+ const validated = validateReleaseMap(doc['entries']);
241
+ if (!validated.ok)
242
+ return { ok: false, error: validated.errors.join('; ') };
243
+ return { ok: true, map: validated.map };
244
+ }