@opengsd/gsd-core 1.17.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.
- package/.claude-plugin/marketplace.json +1 -1
- package/.claude-plugin/plugin.json +2 -1
- package/.opencode/plugins/gsd-core.js +2 -3
- package/agents/gsd-advisor-researcher.compact.md +1 -1
- package/agents/gsd-advisor-researcher.md +1 -1
- package/agents/gsd-assumptions-analyzer.compact.md +1 -1
- package/agents/gsd-assumptions-analyzer.md +1 -1
- package/agents/gsd-code-fixer.compact.md +3 -3
- package/agents/gsd-code-fixer.md +5 -4
- package/agents/gsd-code-reviewer.compact.md +1 -1
- package/agents/gsd-code-reviewer.md +1 -1
- package/agents/gsd-codebase-mapper.compact.md +1 -1
- package/agents/gsd-codebase-mapper.md +1 -1
- package/agents/gsd-debugger.md +1 -1
- package/agents/gsd-doc-verifier.compact.md +4 -4
- package/agents/gsd-doc-verifier.md +4 -3
- package/agents/gsd-doc-writer.compact.md +23 -5
- package/agents/gsd-doc-writer.md +19 -6
- package/agents/gsd-eval-auditor.compact.md +1 -1
- package/agents/gsd-eval-auditor.md +1 -1
- package/agents/gsd-executor.md +7 -3
- package/agents/gsd-integration-checker.compact.md +1 -1
- package/agents/gsd-integration-checker.md +1 -1
- package/agents/gsd-nyquist-auditor.compact.md +1 -1
- package/agents/gsd-nyquist-auditor.md +1 -1
- package/agents/gsd-phase-researcher.md +1 -1
- package/agents/gsd-plan-checker.md +2 -1
- package/agents/gsd-planner.md +5 -4
- package/agents/gsd-project-researcher.compact.md +1 -1
- package/agents/gsd-project-researcher.md +1 -1
- package/agents/gsd-research-synthesizer.compact.md +5 -5
- package/agents/gsd-research-synthesizer.md +5 -5
- package/agents/gsd-roadmapper.compact.md +1 -1
- package/agents/gsd-roadmapper.md +1 -1
- package/agents/gsd-security-auditor.compact.md +1 -1
- package/agents/gsd-security-auditor.md +1 -1
- package/agents/gsd-ui-auditor.compact.md +1 -1
- package/agents/gsd-ui-auditor.md +1 -1
- package/agents/gsd-ui-checker.compact.md +1 -1
- package/agents/gsd-ui-checker.md +1 -1
- package/agents/gsd-ui-researcher.compact.md +13 -1
- package/agents/gsd-ui-researcher.md +28 -1
- package/agents/gsd-verifier.md +2 -2
- package/bin/install.js +460 -105
- package/commands/gsd/code-review.md +8 -0
- package/commands/gsd/pause-work.md +1 -1
- package/gsd-core/bin/gsd-tools.cjs +124 -18
- package/gsd-core/bin/lib/agent-install-check.cjs +29 -3
- package/gsd-core/bin/lib/args-terminator.cjs +114 -0
- package/gsd-core/bin/lib/broken-windows.cjs +59 -1
- package/gsd-core/bin/lib/capability-ledger.cjs +25 -0
- package/gsd-core/bin/lib/capability-lock.cjs +41 -8
- package/gsd-core/bin/lib/capability-registry.cjs +162 -74
- package/gsd-core/bin/lib/capability-source.cjs +3 -1
- package/gsd-core/bin/lib/capability-state.cjs +20 -11
- package/gsd-core/bin/lib/capability-validator.cjs +23 -4
- package/gsd-core/bin/lib/commands.cjs +5 -1
- package/gsd-core/bin/lib/config-loader.cjs +13 -3
- package/gsd-core/bin/lib/config.cjs +256 -58
- package/gsd-core/bin/lib/core-utils.cjs +34 -0
- package/gsd-core/bin/lib/decision-coverage-support.cjs +93 -0
- package/gsd-core/bin/lib/gate-context-drift.cjs +8 -3
- package/gsd-core/bin/lib/gate-decision-coverage-plan.cjs +21 -1
- package/gsd-core/bin/lib/gate-decision-coverage-verify.cjs +19 -1
- package/gsd-core/bin/lib/gate-evaluation-scope.cjs +134 -13
- package/gsd-core/bin/lib/gate-predicate-evaluator.cjs +23 -3
- package/gsd-core/bin/lib/gate-predicate.cjs +8 -12
- package/gsd-core/bin/lib/gate-tdd-review-checkpoint.cjs +114 -18
- package/gsd-core/bin/lib/gate-ui-safety.cjs +23 -5
- package/gsd-core/bin/lib/git-info-exclude.cjs +57 -0
- package/gsd-core/bin/lib/health-diagnostic-rules/phase-structure.cjs +18 -3
- package/gsd-core/bin/lib/health-diagnostic-types.cjs +4 -0
- package/gsd-core/bin/lib/health-diagnostic.cjs +52 -1
- package/gsd-core/bin/lib/host-integration.cjs +13 -6
- package/gsd-core/bin/lib/init.cjs +76 -5
- package/gsd-core/bin/lib/install-effort-resolver.cjs +2 -2
- package/gsd-core/bin/lib/install-engine.cjs +246 -32
- package/gsd-core/bin/lib/install-model-override-resolver.cjs +4 -3
- package/gsd-core/bin/lib/install-profiles.cjs +14 -7
- package/gsd-core/bin/lib/installer-migration-report.cjs +2 -0
- package/gsd-core/bin/lib/markdown-line-diff.cjs +147 -0
- package/gsd-core/bin/lib/markdown-sectionizer.cjs +113 -0
- package/gsd-core/bin/lib/milestone-lock.cjs +8 -1
- package/gsd-core/bin/lib/milestone-release-map.cjs +244 -0
- package/gsd-core/bin/lib/milestone-release-tags.cjs +440 -0
- package/gsd-core/bin/lib/milestone.cjs +43 -1
- package/gsd-core/bin/lib/model-catalog.cjs +8 -0
- package/gsd-core/bin/lib/normalize-test-command.cjs +67 -0
- package/gsd-core/bin/lib/phase-id.cjs +21 -4
- package/gsd-core/bin/lib/phase-lifecycle.cjs +37 -4
- package/gsd-core/bin/lib/phase.cjs +235 -37
- package/gsd-core/bin/lib/planning-snapshot.cjs +3 -1
- package/gsd-core/bin/lib/planning-workspace.cjs +18 -0
- package/gsd-core/bin/lib/pristine-baseline.cjs +11 -3
- package/gsd-core/bin/lib/read-only-git-env.cjs +58 -0
- package/gsd-core/bin/lib/report-parser.cjs +57 -1
- package/gsd-core/bin/lib/review-lane-descriptor.cjs +35 -2
- package/gsd-core/bin/lib/review-lane-invocation.cjs +40 -0
- package/gsd-core/bin/lib/review-lane-runner.cjs +33 -4
- package/gsd-core/bin/lib/review-reviewer-selection.cjs +20 -1
- package/gsd-core/bin/lib/roadmap-parser.cjs +12 -0
- package/gsd-core/bin/lib/roadmap-upgrade.cjs +39 -2
- package/gsd-core/bin/lib/roadmap.cjs +89 -30
- package/gsd-core/bin/lib/runtime-artifact-conversion.cjs +428 -16
- package/gsd-core/bin/lib/runtime-artifact-layout.cjs +8 -4
- package/gsd-core/bin/lib/runtime-homes.cjs +18 -0
- package/gsd-core/bin/lib/runtime-hooks-surface.cjs +60 -16
- package/gsd-core/bin/lib/shell-command-projection.cjs +123 -16
- package/gsd-core/bin/lib/smart-entry.cjs +37 -16
- package/gsd-core/bin/lib/state-contract.cjs +29 -9
- package/gsd-core/bin/lib/state-document.cjs +47 -5
- package/gsd-core/bin/lib/state-transition.cjs +150 -34
- package/gsd-core/bin/lib/state.cjs +140 -88
- package/gsd-core/bin/lib/surface.cjs +9 -0
- package/gsd-core/bin/lib/task-command-router.cjs +7 -28
- package/gsd-core/bin/lib/task-content-resolution.cjs +46 -1
- package/gsd-core/bin/lib/tdd-red-evidence.cjs +5 -0
- package/gsd-core/bin/lib/tdd-test-files.cjs +42 -0
- package/gsd-core/bin/lib/verification.cjs +96 -9
- package/gsd-core/bin/lib/verify.cjs +38 -1
- package/gsd-core/bin/lib/worktree-safety.cjs +22 -1
- package/gsd-core/bin/shared/config-defaults.manifest.json +1 -0
- package/gsd-core/bin/shared/config-schema.manifest.json +1 -0
- package/gsd-core/references/agent-contracts.md +1 -1
- package/gsd-core/references/agent-skills-bootstrap.md +26 -7
- package/gsd-core/references/git-integration.md +14 -3
- package/gsd-core/references/loop-hook-dispatch.md +13 -6
- package/gsd-core/references/planner-reversibility.md +18 -4
- package/gsd-core/references/planner-verify-command-grounding.md +18 -0
- package/gsd-core/references/planning-config.md +2 -0
- package/gsd-core/references/tdd.md +5 -4
- package/gsd-core/references/verifier-phase-gates.md +8 -1
- package/gsd-core/templates/DEBUG.md +16 -0
- package/gsd-core/templates/config.json +2 -1
- package/gsd-core/templates/verification-report.md +1 -1
- package/gsd-core/workflows/add-backlog.md +10 -2
- package/gsd-core/workflows/add-phase.md +5 -1
- package/gsd-core/workflows/add-tests.md +5 -1
- package/gsd-core/workflows/add-todo.md +5 -1
- package/gsd-core/workflows/ai-integration-phase.md +5 -1
- package/gsd-core/workflows/audit-fix.md +2 -1
- package/gsd-core/workflows/audit-milestone.md +21 -9
- package/gsd-core/workflows/autonomous/templates/minimal-context.md +41 -0
- package/gsd-core/workflows/autonomous.md +52 -64
- package/gsd-core/workflows/check-todos.md +5 -1
- package/gsd-core/workflows/code-review/steps/structural-pre-pass.md +10 -1
- package/gsd-core/workflows/code-review-fix.md +5 -1
- package/gsd-core/workflows/code-review.md +66 -8
- package/gsd-core/workflows/complete-milestone/detail/elaboration.md +5 -1
- package/gsd-core/workflows/complete-milestone/steps/project-md-evolution-review.md +145 -0
- package/gsd-core/workflows/complete-milestone/steps/release-map.md +80 -0
- package/gsd-core/workflows/complete-milestone.md +64 -157
- package/gsd-core/workflows/debug.md +5 -1
- package/gsd-core/workflows/diagnose-issues.md +34 -6
- package/gsd-core/workflows/discuss-phase/modes/chain.md +5 -1
- package/gsd-core/workflows/discuss-phase/modes/power.md +1 -1
- package/gsd-core/workflows/discuss-phase-assumptions.md +44 -5
- package/gsd-core/workflows/discuss-phase-power.md +19 -2
- package/gsd-core/workflows/discuss-phase.md +11 -11
- package/gsd-core/workflows/docs-update/detail/elaboration.md +7 -6
- package/gsd-core/workflows/docs-update.md +27 -5
- package/gsd-core/workflows/edit-phase.md +5 -1
- package/gsd-core/workflows/eval-review.md +11 -3
- package/gsd-core/workflows/execute-phase/steps/codebase-drift-gate.md +5 -1
- package/gsd-core/workflows/execute-phase/steps/per-plan-worktree-gate.md +13 -7
- package/gsd-core/workflows/execute-phase/steps/post-merge-gate.md +19 -8
- package/gsd-core/workflows/execute-phase/steps/regression-gate-run.md +21 -3
- package/gsd-core/workflows/execute-phase/steps/regression-gate.md +18 -1
- package/gsd-core/workflows/execute-phase/steps/verify-phase-goal.md +7 -3
- package/gsd-core/workflows/execute-phase/steps/wave-post-gate-hooks.md +11 -2
- package/gsd-core/workflows/execute-phase.md +46 -11
- package/gsd-core/workflows/execute-plan.md +8 -4
- package/gsd-core/workflows/extract-learnings.md +6 -2
- package/gsd-core/workflows/forensics.md +5 -1
- package/gsd-core/workflows/health.md +2 -1
- package/gsd-core/workflows/import.md +6 -2
- package/gsd-core/workflows/insert-phase.md +5 -1
- package/gsd-core/workflows/manager.md +10 -2
- package/gsd-core/workflows/map-codebase.md +5 -1
- package/gsd-core/workflows/milestone-summary.md +10 -2
- package/gsd-core/workflows/new-milestone.md +13 -5
- package/gsd-core/workflows/new-project.md +9 -5
- package/gsd-core/workflows/next.md +8 -4
- package/gsd-core/workflows/pause-work.md +3 -3
- package/gsd-core/workflows/plan-phase/steps/auto-advance-check.md +70 -0
- package/gsd-core/workflows/plan-phase/steps/chunked-planning-mode.md +7 -3
- package/gsd-core/workflows/plan-phase/steps/closed-phase-gate.md +6 -2
- package/gsd-core/workflows/plan-phase.md +54 -86
- package/gsd-core/workflows/plan-review-convergence.md +17 -5
- package/gsd-core/workflows/plant-seed.md +8 -4
- package/gsd-core/workflows/progress.md +6 -2
- package/gsd-core/workflows/quick/steps/plan-checker-loop.md +1 -1
- package/gsd-core/workflows/quick/steps/worktree-pre-dispatch-commit.md +8 -1
- package/gsd-core/workflows/quick-batch/steps/plan-checker-loop.md +2 -1
- package/gsd-core/workflows/quick-batch.md +5 -1
- package/gsd-core/workflows/quick.md +23 -8
- package/gsd-core/workflows/remove-phase.md +5 -1
- package/gsd-core/workflows/resume-project.md +5 -1
- package/gsd-core/workflows/review.md +22 -15
- package/gsd-core/workflows/scan.md +5 -1
- package/gsd-core/workflows/section-manifest.json +5 -0
- package/gsd-core/workflows/secure-phase.md +6 -2
- package/gsd-core/workflows/settings.md +4 -3
- package/gsd-core/workflows/ship.md +16 -14
- package/gsd-core/workflows/spec-phase.md +3 -6
- package/gsd-core/workflows/thread.md +10 -2
- package/gsd-core/workflows/transition.md +5 -1
- package/gsd-core/workflows/ui-phase.md +5 -1
- package/gsd-core/workflows/ui-review.md +6 -2
- package/gsd-core/workflows/ultraplan-phase.md +5 -1
- package/gsd-core/workflows/update.md +5 -1
- package/gsd-core/workflows/validate-phase.md +6 -2
- package/gsd-core/workflows/verify-work/detail/elaboration.md +1 -1
- package/gsd-core/workflows/verify-work/steps/mvp-uat-framing.md +5 -1
- package/gsd-core/workflows/verify-work.md +15 -6
- package/hooks/dist/gsd-agent-isolation-guard.js +14 -2
- package/hooks/dist/gsd-cursor-subagent-start.js +8 -3
- package/hooks/dist/gsd-path-export.js +95 -0
- package/hooks/dist/gsd-secret-read-guard.js +244 -53
- package/hooks/dist/gsd-statusline.js +35 -2
- package/hooks/dist/gsd-validate-commit.sh +27 -5
- package/hooks/dist/gsd-windsurf-pre-write.js +28 -1
- package/hooks/dist/gsd-workflow-guard.js +1 -1
- package/hooks/dist/gsd-worktree-path-guard.js +28 -1
- package/hooks/dist/lib/isolation-sentinel.js +44 -0
- package/hooks/dist/managed-hooks-registry.cjs +1 -0
- package/hooks/gsd-agent-isolation-guard.js +14 -2
- package/hooks/gsd-cursor-subagent-start.js +8 -3
- package/hooks/gsd-path-export.js +95 -0
- package/hooks/gsd-secret-read-guard.js +244 -53
- package/hooks/gsd-statusline.js +35 -2
- package/hooks/gsd-validate-commit.sh +27 -5
- package/hooks/gsd-windsurf-pre-write.js +28 -1
- package/hooks/gsd-workflow-guard.js +1 -1
- package/hooks/gsd-worktree-path-guard.js +28 -1
- package/hooks/hooks.json +1 -0
- package/hooks/lib/isolation-sentinel.js +44 -0
- package/hooks/managed-hooks-registry.cjs +1 -0
- package/package.json +13 -6
- package/pi/gsd.cjs +1047 -10
- package/pi/package-agents/gsd-advisor-researcher.md +113 -0
- package/pi/package-agents/gsd-ai-researcher.md +111 -0
- package/pi/package-agents/gsd-assumptions-analyzer.md +110 -0
- package/pi/package-agents/gsd-code-fixer.md +769 -0
- package/pi/package-agents/gsd-code-reviewer.md +402 -0
- package/pi/package-agents/gsd-codebase-mapper.md +850 -0
- package/pi/package-agents/gsd-debug-session-manager.md +416 -0
- package/pi/package-agents/gsd-debugger.md +1274 -0
- package/pi/package-agents/gsd-doc-classifier.md +269 -0
- package/pi/package-agents/gsd-doc-synthesizer.md +259 -0
- package/pi/package-agents/gsd-doc-verifier.md +212 -0
- package/pi/package-agents/gsd-doc-writer.md +626 -0
- package/pi/package-agents/gsd-dom-verifier.md +164 -0
- package/pi/package-agents/gsd-domain-researcher.md +144 -0
- package/pi/package-agents/gsd-eval-auditor.md +185 -0
- package/pi/package-agents/gsd-eval-planner.md +149 -0
- package/pi/package-agents/gsd-executor.md +894 -0
- package/pi/package-agents/gsd-framework-selector.md +159 -0
- package/pi/package-agents/gsd-integration-checker.md +476 -0
- package/pi/package-agents/gsd-intel-updater.md +338 -0
- package/pi/package-agents/gsd-mempalace-curator.md +49 -0
- package/pi/package-agents/gsd-nyquist-auditor.md +200 -0
- package/pi/package-agents/gsd-pattern-mapper.md +340 -0
- package/pi/package-agents/gsd-phase-researcher.md +897 -0
- package/pi/package-agents/gsd-plan-checker.md +1077 -0
- package/pi/package-agents/gsd-planner.md +1003 -0
- package/pi/package-agents/gsd-project-researcher.md +611 -0
- package/pi/package-agents/gsd-research-synthesizer.md +259 -0
- package/pi/package-agents/gsd-roadmapper.md +779 -0
- package/pi/package-agents/gsd-security-auditor.md +171 -0
- package/pi/package-agents/gsd-ui-auditor.md +581 -0
- package/pi/package-agents/gsd-ui-checker.md +417 -0
- package/pi/package-agents/gsd-ui-researcher.md +469 -0
- package/pi/package-agents/gsd-user-profiler.md +174 -0
- package/pi/package-agents/gsd-verifier.md +963 -0
- package/pi/package-agents/manifest.json +39 -0
- package/pi/package-skills/gsd-add-tests/SKILL.md +39 -0
- package/pi/package-skills/gsd-ai-integration-phase/SKILL.md +36 -0
- package/pi/package-skills/gsd-audit-fix/SKILL.md +34 -0
- package/pi/package-skills/gsd-audit-milestone/SKILL.md +40 -0
- package/pi/package-skills/gsd-audit-uat/SKILL.md +26 -0
- package/pi/package-skills/gsd-autonomous/SKILL.md +52 -0
- package/pi/package-skills/gsd-capture/SKILL.md +68 -0
- package/pi/package-skills/gsd-cleanup/SKILL.md +25 -0
- package/pi/package-skills/gsd-code-review/SKILL.md +71 -0
- package/pi/package-skills/gsd-complete-milestone/SKILL.md +147 -0
- package/pi/package-skills/gsd-config/SKILL.md +60 -0
- package/pi/package-skills/gsd-debug/SKILL.md +64 -0
- package/pi/package-skills/gsd-discuss-phase/SKILL.md +74 -0
- package/pi/package-skills/gsd-docs-update/SKILL.md +50 -0
- package/pi/package-skills/gsd-eval-review/SKILL.md +35 -0
- package/pi/package-skills/gsd-execute-phase/SKILL.md +62 -0
- package/pi/package-skills/gsd-explore/SKILL.md +26 -0
- package/pi/package-skills/gsd-extract-learnings/SKILL.md +25 -0
- package/pi/package-skills/gsd-fast/SKILL.md +33 -0
- package/pi/package-skills/gsd-forensics/SKILL.md +59 -0
- package/pi/package-skills/gsd-graphify/SKILL.md +210 -0
- package/pi/package-skills/gsd-health/SKILL.md +36 -0
- package/pi/package-skills/gsd-help/SKILL.md +37 -0
- package/pi/package-skills/gsd-import/SKILL.md +46 -0
- package/pi/package-skills/gsd-inbox/SKILL.md +42 -0
- package/pi/package-skills/gsd-ingest-docs/SKILL.md +43 -0
- package/pi/package-skills/gsd-manager/SKILL.md +46 -0
- package/pi/package-skills/gsd-map-codebase/SKILL.md +85 -0
- package/pi/package-skills/gsd-mempalace-capture/SKILL.md +111 -0
- package/pi/package-skills/gsd-mempalace-recall/SKILL.md +111 -0
- package/pi/package-skills/gsd-milestone-summary/SKILL.md +54 -0
- package/pi/package-skills/gsd-mvp-phase/SKILL.md +45 -0
- package/pi/package-skills/gsd-new-milestone/SKILL.md +49 -0
- package/pi/package-skills/gsd-new-project/SKILL.md +49 -0
- package/pi/package-skills/gsd-next/SKILL.md +34 -0
- package/pi/package-skills/gsd-ns-context/SKILL.md +31 -0
- package/pi/package-skills/gsd-ns-ideate/SKILL.md +30 -0
- package/pi/package-skills/gsd-ns-manage/SKILL.md +42 -0
- package/pi/package-skills/gsd-ns-project/SKILL.md +34 -0
- package/pi/package-skills/gsd-ns-review/SKILL.md +35 -0
- package/pi/package-skills/gsd-ns-workflow/SKILL.md +44 -0
- package/pi/package-skills/gsd-onboard/SKILL.md +46 -0
- package/pi/package-skills/gsd-pause-work/SKILL.md +48 -0
- package/pi/package-skills/gsd-phase/SKILL.md +61 -0
- package/pi/package-skills/gsd-plan-phase/SKILL.md +62 -0
- package/pi/package-skills/gsd-plan-review-convergence/SKILL.md +63 -0
- package/pi/package-skills/gsd-pr-branch/SKILL.md +31 -0
- package/pi/package-skills/gsd-profile-user/SKILL.md +48 -0
- package/pi/package-skills/gsd-progress/SKILL.md +51 -0
- package/pi/package-skills/gsd-quick/SKILL.md +179 -0
- package/pi/package-skills/gsd-quick-batch/SKILL.md +114 -0
- package/pi/package-skills/gsd-resume-work/SKILL.md +31 -0
- package/pi/package-skills/gsd-review/SKILL.md +50 -0
- package/pi/package-skills/gsd-review-backlog/SKILL.md +65 -0
- package/pi/package-skills/gsd-secure-phase/SKILL.md +37 -0
- package/pi/package-skills/gsd-settings/SKILL.md +30 -0
- package/pi/package-skills/gsd-ship/SKILL.md +27 -0
- package/pi/package-skills/gsd-sketch/SKILL.md +56 -0
- package/pi/package-skills/gsd-spec-phase/SKILL.md +63 -0
- package/pi/package-skills/gsd-spike/SKILL.md +53 -0
- package/pi/package-skills/gsd-stats/SKILL.md +22 -0
- package/pi/package-skills/gsd-surface/SKILL.md +177 -0
- package/pi/package-skills/gsd-thread/SKILL.md +29 -0
- package/pi/package-skills/gsd-ui-phase/SKILL.md +35 -0
- package/pi/package-skills/gsd-ui-review/SKILL.md +35 -0
- package/pi/package-skills/gsd-ultraplan-phase/SKILL.md +38 -0
- package/pi/package-skills/gsd-undo/SKILL.md +38 -0
- package/pi/package-skills/gsd-update/SKILL.md +51 -0
- package/pi/package-skills/gsd-validate-phase/SKILL.md +37 -0
- package/pi/package-skills/gsd-verify-work/SKILL.md +41 -0
- package/pi/package-skills/gsd-workspace/SKILL.md +57 -0
- package/pi/package-skills/gsd-workstreams/SKILL.md +73 -0
- package/scripts/audit-workflow-script-paths.cjs +1 -1
- package/scripts/build-hooks.js +4 -0
- package/scripts/command-contract-helpers.cjs +1 -1
- package/scripts/diagnose-windows-spawn-watcher.cjs +317 -0
- package/scripts/docs-guard-registry.cjs +22 -7
- package/scripts/gen-health-docs.cjs +13 -1
- package/scripts/gen-pi-package-skills.cjs +217 -0
- package/scripts/lib/macos-conformance-tier.generated.cjs +19 -0
- package/scripts/lib/platform-conformance-tier.generated.cjs +41 -0
- package/scripts/lint-docs-guard-registration.exempt-baseline.cjs +3 -3
- package/scripts/lint-phase-arg-assignment.cjs +2 -2
- package/scripts/lint-retired-runtime-name.cjs +5 -0
- package/scripts/lint-workflow-shellcheck-baseline.json +16 -16
- package/scripts/npm-audit-baseline.cjs +41 -7
- package/scripts/release-tarball-smoke.cjs +1 -0
- package/scripts/reviewed-or-maintainer-policy.cjs +181 -0
- package/scripts/select-docs-guards.cjs +1 -1
- package/scripts/self-test-report-policy.cjs +350 -0
- package/skills/gsd-code-review/SKILL.md +8 -0
- package/skills/gsd-pause-work/SKILL.md +1 -1
- package/vscode/package.json +1 -1
|
@@ -0,0 +1,147 @@
|
|
|
1
|
+
'use strict';
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.MAX_SCOPED_EDIT_DISTANCE = void 0;
|
|
4
|
+
exports.matchLines = matchLines;
|
|
5
|
+
exports.writeScope = writeScope;
|
|
6
|
+
/**
|
|
7
|
+
* Line alignment for scoped Markdown normalization (#5165).
|
|
8
|
+
*
|
|
9
|
+
* `platformWriteSync` re-spaces a `.md` document on every write. Given the
|
|
10
|
+
* document the write replaces, only the line junctions the write itself made
|
|
11
|
+
* need that treatment; everything else is published byte-for-byte. This module
|
|
12
|
+
* is the pure half of that: it aligns the new lines against the prior lines and
|
|
13
|
+
* says which junctions the write left alone. It performs no I/O.
|
|
14
|
+
*/
|
|
15
|
+
/**
|
|
16
|
+
* Largest line-edit distance `matchLines` will search for. A write that changed
|
|
17
|
+
* more than this many lines is a rewrite, not an edit: it falls back to
|
|
18
|
+
* whole-document normalization (the pre-#5165 behavior), which also bounds the
|
|
19
|
+
* diff's time and memory.
|
|
20
|
+
*/
|
|
21
|
+
exports.MAX_SCOPED_EDIT_DISTANCE = 400;
|
|
22
|
+
/**
|
|
23
|
+
* Align the lines of the document being written (`next`) against the document
|
|
24
|
+
* it replaces (`prev`). Returns, per `next` line, the index of its counterpart
|
|
25
|
+
* in `prev` (a longest-common-subsequence match, Myers' O(ND) greedy diff after
|
|
26
|
+
* trimming the common prefix and suffix), or -1 for a line the write added or
|
|
27
|
+
* changed. Null when the documents differ by more than MAX_SCOPED_EDIT_DISTANCE
|
|
28
|
+
* edits.
|
|
29
|
+
*/
|
|
30
|
+
function matchLines(prev, next) {
|
|
31
|
+
const match = new Int32Array(next.length).fill(-1);
|
|
32
|
+
const common = Math.min(prev.length, next.length);
|
|
33
|
+
let lo = 0;
|
|
34
|
+
while (lo < common && prev[lo] === next[lo]) {
|
|
35
|
+
match[lo] = lo;
|
|
36
|
+
lo++;
|
|
37
|
+
}
|
|
38
|
+
let prevEnd = prev.length;
|
|
39
|
+
let nextEnd = next.length;
|
|
40
|
+
while (prevEnd > lo && nextEnd > lo && prev[prevEnd - 1] === next[nextEnd - 1]) {
|
|
41
|
+
prevEnd--;
|
|
42
|
+
nextEnd--;
|
|
43
|
+
match[nextEnd] = prevEnd;
|
|
44
|
+
}
|
|
45
|
+
const a = prev.slice(lo, prevEnd);
|
|
46
|
+
const b = next.slice(lo, nextEnd);
|
|
47
|
+
const n = a.length;
|
|
48
|
+
const m = b.length;
|
|
49
|
+
if (n === 0 || m === 0)
|
|
50
|
+
return match;
|
|
51
|
+
const max = Math.min(n + m, exports.MAX_SCOPED_EDIT_DISTANCE);
|
|
52
|
+
const off = max + 1;
|
|
53
|
+
const v = new Int32Array(2 * max + 3);
|
|
54
|
+
const trace = [];
|
|
55
|
+
let found = -1;
|
|
56
|
+
for (let d = 0; d <= max && found < 0; d++) {
|
|
57
|
+
trace.push(v.slice()); // the furthest-reaching points after d-1 edits
|
|
58
|
+
for (let k = -d; k <= d; k += 2) {
|
|
59
|
+
let x = (k === -d || (k !== d && v[off + k - 1] < v[off + k + 1])) ? v[off + k + 1] : v[off + k - 1] + 1;
|
|
60
|
+
let y = x - k;
|
|
61
|
+
while (x < n && y < m && a[x] === b[y]) {
|
|
62
|
+
x++;
|
|
63
|
+
y++;
|
|
64
|
+
}
|
|
65
|
+
v[off + k] = x;
|
|
66
|
+
if (x >= n && y >= m) {
|
|
67
|
+
found = d;
|
|
68
|
+
break;
|
|
69
|
+
}
|
|
70
|
+
}
|
|
71
|
+
}
|
|
72
|
+
if (found < 0)
|
|
73
|
+
return null;
|
|
74
|
+
let x = n;
|
|
75
|
+
let y = m;
|
|
76
|
+
for (let d = found; d > 0; d--) {
|
|
77
|
+
const before = trace[d];
|
|
78
|
+
const k = x - y;
|
|
79
|
+
const prevK = (k === -d || (k !== d && before[off + k - 1] < before[off + k + 1])) ? k + 1 : k - 1;
|
|
80
|
+
const prevX = before[off + prevK];
|
|
81
|
+
const prevY = prevX - prevK;
|
|
82
|
+
while (x > prevX && y > prevY) {
|
|
83
|
+
x--;
|
|
84
|
+
y--;
|
|
85
|
+
match[lo + y] = lo + x;
|
|
86
|
+
}
|
|
87
|
+
x = prevX;
|
|
88
|
+
y = prevY;
|
|
89
|
+
}
|
|
90
|
+
while (x > 0 && y > 0) {
|
|
91
|
+
x--;
|
|
92
|
+
y--;
|
|
93
|
+
match[lo + y] = lo + x;
|
|
94
|
+
}
|
|
95
|
+
return match;
|
|
96
|
+
}
|
|
97
|
+
/**
|
|
98
|
+
* Derive a {@link WriteScope} from a line alignment. A junction is the write's
|
|
99
|
+
* own — and so is normalized — when it is wholly inside text the write added,
|
|
100
|
+
* or when a deletion between two kept lines made them newly adjacent. The
|
|
101
|
+
* boundary of an edit that replaced lines inherits the surrounding spacing:
|
|
102
|
+
* replacing a line in place must not add blank lines around it.
|
|
103
|
+
*/
|
|
104
|
+
function writeScope(match, prevLength) {
|
|
105
|
+
// For each unmatched line: how many prior lines its run of changed lines replaced.
|
|
106
|
+
const replaced = new Int32Array(match.length);
|
|
107
|
+
const priorIndex = new Int32Array(match.length).fill(-1);
|
|
108
|
+
for (let i = 0; i < match.length;) {
|
|
109
|
+
if (match[i] >= 0) {
|
|
110
|
+
i++;
|
|
111
|
+
continue;
|
|
112
|
+
}
|
|
113
|
+
let end = i;
|
|
114
|
+
while (end + 1 < match.length && match[end + 1] < 0)
|
|
115
|
+
end++;
|
|
116
|
+
const before = i > 0 ? match[i - 1] : -1;
|
|
117
|
+
const after = end + 1 < match.length ? match[end + 1] : prevLength;
|
|
118
|
+
const dropped = after - before - 1;
|
|
119
|
+
for (let j = i; j <= end; j++) {
|
|
120
|
+
replaced[j] = dropped;
|
|
121
|
+
if (j - i < dropped)
|
|
122
|
+
priorIndex[j] = before + 1 + (j - i);
|
|
123
|
+
}
|
|
124
|
+
i = end + 1;
|
|
125
|
+
}
|
|
126
|
+
return {
|
|
127
|
+
junctionKept(i) {
|
|
128
|
+
const left = match[i] >= 0;
|
|
129
|
+
const right = match[i + 1] >= 0;
|
|
130
|
+
// Two kept lines are one junction only if nothing sat between them in the
|
|
131
|
+
// prior document; a deletion made them adjacent, so the write owns it.
|
|
132
|
+
if (left && right)
|
|
133
|
+
return match[i + 1] - match[i] === 1;
|
|
134
|
+
if (left)
|
|
135
|
+
return replaced[i + 1] >= 1;
|
|
136
|
+
if (right)
|
|
137
|
+
return replaced[i] >= 1;
|
|
138
|
+
return false;
|
|
139
|
+
},
|
|
140
|
+
lineWritten(i) {
|
|
141
|
+
return match[i] < 0;
|
|
142
|
+
},
|
|
143
|
+
replacedPriorIndex(i) {
|
|
144
|
+
return priorIndex[i] ?? -1;
|
|
145
|
+
},
|
|
146
|
+
};
|
|
147
|
+
}
|
|
@@ -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
|
-
|
|
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 '<';
|
|
152
|
+
if (ch === '>')
|
|
153
|
+
return '>';
|
|
154
|
+
if (ch === '&')
|
|
155
|
+
return '&';
|
|
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
|
+
}
|