@opengsd/gsd-core 1.14.0 → 1.16.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 +1 -1
- package/.opencode/plugins/gsd-core.js +85 -5
- package/README.ja-JP.md +3 -3
- package/README.ko-KR.md +3 -3
- package/README.pt-BR.md +3 -3
- package/README.zh-CN.md +3 -3
- package/agents/gsd-code-fixer.compact.md +7 -6
- package/agents/gsd-code-fixer.md +9 -8
- package/agents/gsd-code-reviewer.compact.md +5 -3
- package/agents/gsd-code-reviewer.md +8 -6
- package/agents/gsd-debug-session-manager.compact.md +17 -2
- package/agents/gsd-debug-session-manager.md +17 -2
- package/agents/gsd-debugger.md +3 -3
- package/agents/gsd-eval-auditor.compact.md +1 -1
- package/agents/gsd-eval-auditor.md +1 -1
- package/agents/gsd-executor.md +17 -12
- package/agents/gsd-intel-updater.compact.md +1 -1
- package/agents/gsd-intel-updater.md +1 -1
- package/agents/gsd-mempalace-curator.md +2 -2
- package/agents/gsd-phase-researcher.md +19 -11
- package/agents/gsd-plan-checker.md +15 -9
- package/agents/gsd-planner.md +15 -11
- package/agents/gsd-project-researcher.compact.md +1 -1
- package/agents/gsd-project-researcher.md +1 -1
- package/agents/gsd-research-synthesizer.compact.md +1 -1
- package/agents/gsd-research-synthesizer.md +1 -1
- package/agents/gsd-ui-auditor.compact.md +21 -30
- package/agents/gsd-ui-auditor.md +166 -37
- package/agents/gsd-ui-researcher.compact.md +1 -1
- package/agents/gsd-ui-researcher.md +1 -1
- package/agents/gsd-verifier.md +37 -14
- package/bin/install.js +764 -287
- package/commands/gsd/add-tests.md +6 -1
- package/commands/gsd/ai-integration-phase.md +6 -1
- package/commands/gsd/audit-fix.md +5 -0
- package/commands/gsd/audit-milestone.md +6 -1
- package/commands/gsd/autonomous.md +7 -2
- package/commands/gsd/capture.md +9 -5
- package/commands/gsd/code-review.md +7 -2
- package/commands/gsd/complete-milestone.md +4 -0
- package/commands/gsd/config.md +7 -3
- package/commands/gsd/debug.md +11 -7
- package/commands/gsd/discuss-phase.md +7 -3
- package/commands/gsd/docs-update.md +12 -7
- package/commands/gsd/eval-review.md +6 -1
- package/commands/gsd/execute-phase.md +12 -7
- package/commands/gsd/extract-learnings.md +5 -0
- package/commands/gsd/fast.md +4 -0
- package/commands/gsd/forensics.md +5 -1
- package/commands/gsd/graphify.md +10 -6
- package/commands/gsd/health.md +5 -0
- package/commands/gsd/help.md +7 -2
- package/commands/gsd/import.md +7 -3
- package/commands/gsd/inbox.md +5 -0
- package/commands/gsd/ingest-docs.md +5 -1
- package/commands/gsd/manager.md +6 -1
- package/commands/gsd/map-codebase.md +7 -3
- package/commands/gsd/mempalace-capture.md +12 -4
- package/commands/gsd/mempalace-recall.md +5 -1
- package/commands/gsd/milestone-summary.md +5 -1
- package/commands/gsd/mvp-phase.md +8 -3
- package/commands/gsd/new-milestone.md +6 -1
- package/commands/gsd/new-project.md +5 -0
- package/commands/gsd/next.md +6 -1
- package/commands/gsd/ns-context.md +4 -0
- package/commands/gsd/ns-ideate.md +4 -0
- package/commands/gsd/ns-manage.md +4 -0
- package/commands/gsd/ns-project.md +4 -0
- package/commands/gsd/ns-review.md +4 -0
- package/commands/gsd/ns-workflow.md +4 -0
- package/commands/gsd/onboard.md +6 -1
- package/commands/gsd/pause-work.md +5 -1
- package/commands/gsd/phase.md +8 -4
- package/commands/gsd/plan-phase.md +6 -1
- package/commands/gsd/plan-review-convergence.md +11 -7
- package/commands/gsd/pr-branch.md +4 -0
- package/commands/gsd/profile-user.md +5 -1
- package/commands/gsd/progress.md +7 -2
- package/commands/gsd/quick-batch.md +21 -9
- package/commands/gsd/quick.md +12 -7
- package/commands/gsd/review.md +7 -4
- package/commands/gsd/secure-phase.md +6 -1
- package/commands/gsd/ship.md +5 -0
- package/commands/gsd/sketch.md +7 -2
- package/commands/gsd/spec-phase.md +5 -1
- package/commands/gsd/spike.md +8 -3
- package/commands/gsd/surface.md +5 -1
- package/commands/gsd/thread.md +4 -0
- package/commands/gsd/ui-phase.md +6 -1
- package/commands/gsd/ui-review.md +6 -1
- package/commands/gsd/ultraplan-phase.md +5 -1
- package/commands/gsd/undo.md +5 -1
- package/commands/gsd/update.md +6 -2
- package/commands/gsd/validate-phase.md +6 -1
- package/commands/gsd/verify-work.md +6 -1
- package/commands/gsd/workspace.md +7 -3
- package/gsd-core/bin/gsd-tools.cjs +477 -78
- package/gsd-core/bin/lib/active-workstream-store.cjs +15 -0
- package/gsd-core/bin/lib/adr-parser.cjs +3 -1
- package/gsd-core/bin/lib/agent-install-check.cjs +4 -1
- package/gsd-core/bin/lib/audit.cjs +144 -42
- package/gsd-core/bin/lib/broken-windows.cjs +13 -13
- package/gsd-core/bin/lib/capability-activation.cjs +9 -4
- package/gsd-core/bin/lib/capability-registry.cjs +197 -222
- package/gsd-core/bin/lib/capability-validator.cjs +16 -1
- package/gsd-core/bin/lib/check-auto-mode.cjs +35 -0
- package/gsd-core/bin/lib/check-command-router.cjs +164 -1625
- package/gsd-core/bin/lib/cjs-command-router-adapter.cjs +13 -2
- package/gsd-core/bin/lib/cli-exit.cjs +12 -0
- package/gsd-core/bin/lib/codex-agent-toml.cjs +32 -33
- package/gsd-core/bin/lib/command-aliases.cjs +7 -0
- package/gsd-core/bin/lib/command-routing-hub.cjs +48 -1
- package/gsd-core/bin/lib/commands.cjs +343 -207
- package/gsd-core/bin/lib/complexity-trigger.cjs +8 -7
- package/gsd-core/bin/lib/config-loader.cjs +65 -4
- package/gsd-core/bin/lib/config.cjs +76 -19
- package/gsd-core/bin/lib/core-utils.cjs +6 -1
- package/gsd-core/bin/lib/coverage.cjs +4 -8
- package/gsd-core/bin/lib/decision-coverage-support.cjs +259 -0
- package/gsd-core/bin/lib/decisions.cjs +30 -14
- package/gsd-core/bin/lib/drift.cjs +177 -42
- package/gsd-core/bin/lib/frontmatter-fence.cjs +90 -0
- package/gsd-core/bin/lib/frontmatter-splice.cjs +494 -0
- package/gsd-core/bin/lib/frontmatter.cjs +426 -234
- package/gsd-core/bin/lib/gap-checker.cjs +72 -29
- package/gsd-core/bin/lib/gate-api-coverage-verify-pre.cjs +381 -0
- package/gsd-core/bin/lib/gate-args.cjs +53 -0
- package/gsd-core/bin/lib/gate-codebase-drift.cjs +285 -0
- package/gsd-core/bin/lib/gate-config.cjs +46 -0
- package/gsd-core/bin/lib/gate-context-drift.cjs +141 -0
- package/gsd-core/bin/lib/gate-decision-coverage-plan.cjs +169 -0
- package/gsd-core/bin/lib/gate-decision-coverage-verify.cjs +126 -0
- package/gsd-core/bin/lib/gate-evaluation-scope.cjs +555 -0
- package/gsd-core/bin/lib/gate-evidence.cjs +138 -0
- package/gsd-core/bin/lib/gate-exit.cjs +27 -0
- package/gsd-core/bin/lib/gate-gap-analysis-plan-post.cjs +61 -0
- package/gsd-core/bin/lib/gate-phase-context.cjs +170 -0
- package/gsd-core/bin/lib/gate-predicate-evaluator.cjs +1 -1
- package/gsd-core/bin/lib/gate-predicate.cjs +165 -0
- package/gsd-core/bin/lib/gate-prohibition-enforcement.cjs +94 -0
- package/gsd-core/bin/lib/gate-schema-drift.cjs +165 -0
- package/gsd-core/bin/lib/gate-tdd-red-evidence.cjs +100 -0
- package/gsd-core/bin/lib/gate-tdd-review-checkpoint.cjs +182 -0
- package/gsd-core/bin/lib/gate-ui-plan.cjs +86 -0
- package/gsd-core/bin/lib/gate-ui-safety.cjs +80 -0
- package/gsd-core/bin/lib/gate-verdict.cjs +64 -0
- package/gsd-core/bin/lib/gate-verify-command-paths.cjs +78 -0
- package/gsd-core/bin/lib/gate-verify-failure-directions.cjs +41 -0
- package/gsd-core/bin/lib/graphify.cjs +10 -2
- package/gsd-core/bin/lib/health-diagnostic-rules/phase-structure.cjs +43 -47
- package/gsd-core/bin/lib/health-diagnostic.cjs +45 -8
- package/gsd-core/bin/lib/host-runtime-detection.cjs +9 -0
- package/gsd-core/bin/lib/init.cjs +364 -132
- package/gsd-core/bin/lib/install-engine.cjs +30 -33
- package/gsd-core/bin/lib/install-profiles.cjs +7 -4
- package/gsd-core/bin/lib/installer-migrations.cjs +8 -1
- package/gsd-core/bin/lib/io.cjs +122 -3
- package/gsd-core/bin/lib/loop-resolver.cjs +95 -0
- package/gsd-core/bin/lib/markdown-sectionizer.cjs +75 -1
- package/gsd-core/bin/lib/milestone.cjs +37 -6
- package/gsd-core/bin/lib/model-resolver.cjs +171 -62
- package/gsd-core/bin/lib/observability/event.cjs +1 -1
- package/gsd-core/bin/lib/observability/logger.cjs +46 -1
- package/gsd-core/bin/lib/pattern.cjs +10 -0
- package/gsd-core/bin/lib/phase-command-router.cjs +20 -5
- package/gsd-core/bin/lib/phase-estimation.cjs +5 -4
- package/gsd-core/bin/lib/phase-id-card.cjs +32 -0
- package/gsd-core/bin/lib/phase-id-display.cjs +78 -0
- package/gsd-core/bin/lib/phase-id.cjs +110 -8
- package/gsd-core/bin/lib/phase-lifecycle.cjs +9 -2
- package/gsd-core/bin/lib/phase-locator.cjs +29 -10
- package/gsd-core/bin/lib/phase-status.cjs +360 -0
- package/gsd-core/bin/lib/phase.cjs +489 -88
- package/gsd-core/bin/lib/plan-document.cjs +142 -20
- package/gsd-core/bin/lib/plan-drift-guard.cjs +5 -0
- package/gsd-core/bin/lib/planning-document.cjs +692 -0
- package/gsd-core/bin/lib/planning-inspect.cjs +60 -9
- package/gsd-core/bin/lib/planning-snapshot.cjs +18 -0
- package/gsd-core/bin/lib/planning-workspace.cjs +83 -55
- package/gsd-core/bin/lib/pr-branch-patterns.cjs +57 -0
- package/gsd-core/bin/lib/pristine-baseline.cjs +10 -0
- package/gsd-core/bin/lib/probe-core.cjs +7 -1
- package/gsd-core/bin/lib/profile-output.cjs +6 -3
- package/gsd-core/bin/lib/prohibition-enforcement.cjs +0 -55
- package/gsd-core/bin/lib/project-root.cjs +41 -2
- package/gsd-core/bin/lib/quick-batch-command-router.cjs +35 -9
- package/gsd-core/bin/lib/quick-batch-dispatch.cjs +11 -8
- package/gsd-core/bin/lib/real-home-guard.cjs +9 -1
- package/gsd-core/bin/lib/report-parser.cjs +269 -0
- package/gsd-core/bin/lib/review-lane-descriptor.cjs +10 -30
- package/gsd-core/bin/lib/review-reviewer-selection.cjs +2 -2
- package/gsd-core/bin/lib/roadmap-command-router.cjs +25 -19
- package/gsd-core/bin/lib/roadmap-parser.cjs +242 -15
- package/gsd-core/bin/lib/roadmap-upgrade.cjs +1653 -65
- package/gsd-core/bin/lib/roadmap.cjs +405 -88
- package/gsd-core/bin/lib/runtime-artifact-conversion.cjs +373 -187
- package/gsd-core/bin/lib/runtime-artifact-install-plan.cjs +5 -2
- package/gsd-core/bin/lib/runtime-artifact-layout.cjs +57 -1
- package/gsd-core/bin/lib/runtime-homes.cjs +14 -7
- package/gsd-core/bin/lib/runtime-hooks-surface.cjs +522 -624
- package/gsd-core/bin/lib/runtime-name-policy.cjs +246 -21
- package/gsd-core/bin/lib/runtime-slash.cjs +47 -30
- package/gsd-core/bin/lib/shell-command-projection.cjs +47 -8
- package/gsd-core/bin/lib/smart-entry.cjs +19 -3
- package/gsd-core/bin/lib/stale-bake-guard.cjs +32 -48
- package/gsd-core/bin/lib/state-contract.cjs +15 -18
- package/gsd-core/bin/lib/state-document.cjs +100 -22
- package/gsd-core/bin/lib/state-transition.cjs +39 -2
- package/gsd-core/bin/lib/state.cjs +256 -105
- package/gsd-core/bin/lib/surface.cjs +19 -2
- package/gsd-core/bin/lib/tdd-red-evidence.cjs +48 -79
- package/gsd-core/bin/lib/uat-predicate.cjs +359 -38
- package/gsd-core/bin/lib/uat.cjs +432 -7
- package/gsd-core/bin/lib/ui-consideration-probe.cjs +15 -2
- package/gsd-core/bin/lib/ui-frontend-evidence.cjs +167 -40
- package/gsd-core/bin/lib/undo-commit-selection.cjs +131 -0
- package/gsd-core/bin/lib/vendor/README.md +31 -9
- package/gsd-core/bin/lib/vendor/saxes.cjs +1934 -0
- package/gsd-core/bin/lib/vendor/saxes.cjs.LICENSE.txt +92 -0
- package/gsd-core/bin/lib/vendor/tap-parser.cjs +8927 -0
- package/gsd-core/bin/lib/vendor/tap-parser.cjs.LICENSE.txt +152 -0
- package/gsd-core/bin/lib/verification-command-router.cjs +2 -1
- package/gsd-core/bin/lib/verification.cjs +1378 -274
- package/gsd-core/bin/lib/verify-command-grounding.cjs +46 -2
- package/gsd-core/bin/lib/verify-command-router.cjs +18 -7
- package/gsd-core/bin/lib/verify.cjs +357 -531
- package/gsd-core/bin/lib/workstream-inventory-builder.cjs +14 -6
- package/gsd-core/bin/lib/workstream-inventory.cjs +31 -21
- package/gsd-core/bin/lib/workstream-name-policy.cjs +31 -1
- package/gsd-core/bin/lib/workstream.cjs +11 -2
- package/gsd-core/bin/lib/worktree-base-ref.cjs +482 -73
- package/gsd-core/bin/lib/worktree-safety.cjs +784 -51
- package/gsd-core/bin/shared/config-defaults.manifest.json +8 -0
- package/gsd-core/bin/shared/config-schema.manifest.json +5 -0
- package/gsd-core/references/autonomous-smart-discuss.md +2 -1
- package/gsd-core/references/autonomous-ui-design-contract.md +3 -3
- package/gsd-core/references/checkpoints.md +5 -3
- package/gsd-core/references/edge-probe-fixtures/01-round-half-even/expected-coverage.json +28 -3
- package/gsd-core/references/edge-probe-fixtures/02-merge-intervals/expected-coverage.json +37 -4
- package/gsd-core/references/edge-probe-fixtures/03-truncate-graphemes/expected-coverage.json +28 -3
- package/gsd-core/references/edge-probe-fixtures/04-money-rounding/expected-coverage.json +28 -3
- package/gsd-core/references/edge-probe-fixtures/05-list-dedupe/expected-coverage.json +37 -4
- package/gsd-core/references/edge-probe-fixtures/06-resolved-mixed/expected-coverage.json +37 -4
- package/gsd-core/references/edge-probe.md +195 -21
- package/gsd-core/references/execute-mvp-tdd.md +5 -10
- package/gsd-core/references/execute-phase-between-wave-reset.md +10 -6
- package/gsd-core/references/execute-phase-response-language.md +1 -1
- package/gsd-core/references/execute-phase-wave-guard.md +22 -11
- package/gsd-core/references/gsd-run-resolver.md +1 -1
- package/gsd-core/references/loop-hook-dispatch.md +7 -1
- package/gsd-core/references/model-profiles.md +1 -1
- package/gsd-core/references/offer-next.md +1 -1
- package/gsd-core/references/phase-argument-parsing.md +9 -7
- package/gsd-core/references/phase-id-convention.md +28 -0
- package/gsd-core/references/planner-gap-closure.md +2 -0
- package/gsd-core/references/planner-load-graph-context.md +24 -13
- package/gsd-core/references/planner-verify-command-grounding.md +14 -0
- package/gsd-core/references/planning-config.md +12 -3
- package/gsd-core/references/spidr-splitting.md +1 -1
- package/gsd-core/references/tdd.md +37 -8
- package/gsd-core/references/ui-consideration-probe.md +10 -5
- package/gsd-core/references/verifier-phase-gates.md +5 -2
- package/gsd-core/references/verify-command-path-resolvability.md +10 -2
- package/gsd-core/references/verify-mvp-mode.md +2 -2
- package/gsd-core/references/workstream-flag.md +33 -3
- package/gsd-core/references/worktree-path-safety.md +321 -0
- package/gsd-core/templates/README.md +1 -1
- package/gsd-core/templates/UAT.md +17 -1
- package/gsd-core/templates/config.json +2 -11
- package/gsd-core/templates/verification-report.md +1 -1
- package/gsd-core/workflows/_runtime-launcher.snippet.sh +1 -1
- package/gsd-core/workflows/add-backlog.md +1 -1
- package/gsd-core/workflows/add-phase.md +8 -7
- package/gsd-core/workflows/add-tests.md +4 -3
- package/gsd-core/workflows/add-todo.md +6 -5
- package/gsd-core/workflows/ai-integration-phase.md +13 -4
- package/gsd-core/workflows/audit-fix.md +1 -1
- package/gsd-core/workflows/audit-milestone.md +4 -3
- package/gsd-core/workflows/audit-uat.md +1 -1
- package/gsd-core/workflows/autonomous/steps/converge-fail-fast.md +9 -18
- package/gsd-core/workflows/autonomous.md +43 -23
- package/gsd-core/workflows/check-todos.md +7 -6
- package/gsd-core/workflows/cleanup.md +2 -2
- package/gsd-core/workflows/code-review/steps/dispatch-fix.md +4 -3
- package/gsd-core/workflows/code-review/steps/structural-pre-pass.md +20 -13
- package/gsd-core/workflows/code-review-fix.md +112 -25
- package/gsd-core/workflows/code-review.md +146 -135
- package/gsd-core/workflows/complete-milestone/detail/elaboration.md +4 -3
- package/gsd-core/workflows/complete-milestone.md +13 -8
- package/gsd-core/workflows/debug.md +32 -7
- package/gsd-core/workflows/diagnose-issues.md +3 -2
- package/gsd-core/workflows/discuss-phase/modes/advisor.md +1 -1
- package/gsd-core/workflows/discuss-phase/modes/chain.md +1 -1
- package/gsd-core/workflows/discuss-phase-assumptions.md +4 -3
- package/gsd-core/workflows/discuss-phase.md +4 -3
- package/gsd-core/workflows/do.md +2 -2
- package/gsd-core/workflows/docs-update.md +6 -5
- package/gsd-core/workflows/edit-phase.md +4 -3
- package/gsd-core/workflows/eval-review.md +14 -5
- package/gsd-core/workflows/execute-phase/detail/elaboration.md +2 -2
- package/gsd-core/workflows/execute-phase/steps/code-review-disposition.md +1019 -0
- package/gsd-core/workflows/execute-phase/steps/codebase-drift-gate.md +15 -4
- package/gsd-core/workflows/execute-phase/steps/completion-reconciliation.md +10 -7
- package/gsd-core/workflows/execute-phase/steps/executor-isolation-dispatch.md +37 -3
- package/gsd-core/workflows/execute-phase/steps/gap-closure-artifacts.md +3 -1
- package/gsd-core/workflows/execute-phase/steps/partial-wave.md +2 -2
- package/gsd-core/workflows/execute-phase/steps/per-plan-executor-routing.md +1 -1
- package/gsd-core/workflows/execute-phase/steps/per-plan-worktree-gate.md +1 -1
- package/gsd-core/workflows/execute-phase/steps/post-merge-gate.md +46 -9
- package/gsd-core/workflows/execute-phase/steps/protected-branch.md +1 -1
- package/gsd-core/workflows/execute-phase/steps/ready-wave-gate.md +37 -0
- package/gsd-core/workflows/execute-phase/steps/regression-gate-run.md +1 -1
- package/gsd-core/workflows/execute-phase/steps/regression-gate.md +2 -0
- package/gsd-core/workflows/execute-phase/steps/tdd-applicability-resolution.md +1 -1
- package/gsd-core/workflows/execute-phase/steps/threat-id-gate.md +28 -0
- package/gsd-core/workflows/execute-phase/steps/verify-phase-goal.md +187 -0
- package/gsd-core/workflows/execute-phase/steps/wave-post-gate-hooks.md +2 -3
- package/gsd-core/workflows/execute-phase/steps/worktree-base-check.md +25 -0
- package/gsd-core/workflows/execute-phase.md +96 -178
- package/gsd-core/workflows/execute-plan.md +18 -23
- package/gsd-core/workflows/explore.md +4 -4
- package/gsd-core/workflows/extract-learnings.md +4 -2
- package/gsd-core/workflows/fast.md +1 -1
- package/gsd-core/workflows/forensics.md +1 -1
- package/gsd-core/workflows/graduation.md +1 -1
- package/gsd-core/workflows/health.md +3 -2
- package/gsd-core/workflows/help/modes/full.compact.md +3 -3
- package/gsd-core/workflows/help/modes/full.md +5 -5
- package/gsd-core/workflows/help/modes/topic.md +15 -5
- package/gsd-core/workflows/import.md +4 -3
- package/gsd-core/workflows/inbox.md +2 -2
- package/gsd-core/workflows/ingest-docs.md +3 -3
- package/gsd-core/workflows/insert-phase.md +4 -3
- package/gsd-core/workflows/list-seeds.md +1 -1
- package/gsd-core/workflows/list-workspaces.md +1 -1
- package/gsd-core/workflows/manager.md +6 -4
- package/gsd-core/workflows/map-codebase.md +5 -4
- package/gsd-core/workflows/milestone-summary.md +3 -2
- package/gsd-core/workflows/mvp-phase.md +14 -14
- package/gsd-core/workflows/new-milestone.md +11 -11
- package/gsd-core/workflows/new-project/steps/auto-mode-config.md +3 -3
- package/gsd-core/workflows/new-project/steps/codebase-map-offer.md +1 -1
- package/gsd-core/workflows/new-project.md +7 -7
- package/gsd-core/workflows/new-workspace.md +2 -2
- package/gsd-core/workflows/next.md +1 -1
- package/gsd-core/workflows/note.md +1 -1
- package/gsd-core/workflows/onboard.md +1 -1
- package/gsd-core/workflows/pause-work.md +2 -2
- package/gsd-core/workflows/plan-phase/detail/elaboration.md +1 -1
- package/gsd-core/workflows/plan-phase/steps/chunked-planning-mode.md +17 -5
- package/gsd-core/workflows/plan-phase/steps/closed-phase-gate.md +1 -1
- package/gsd-core/workflows/plan-phase/steps/prd-express-path.md +1 -1
- package/gsd-core/workflows/plan-phase/steps/stall-detection-helpers.md +23 -5
- package/gsd-core/workflows/plan-phase.md +50 -24
- package/gsd-core/workflows/plan-review-convergence.md +21 -5
- package/gsd-core/workflows/plant-seed.md +62 -20
- package/gsd-core/workflows/pr-branch.md +113 -13
- package/gsd-core/workflows/profile-user.md +2 -2
- package/gsd-core/workflows/progress.md +19 -49
- package/gsd-core/workflows/quick/steps/plan-checker-loop.md +27 -0
- package/gsd-core/workflows/quick/steps/quick-verification.md +4 -4
- package/gsd-core/workflows/quick/steps/worktree-pre-dispatch-commit.md +30 -11
- package/gsd-core/workflows/quick-batch/steps/batch-init.md +1 -1
- package/gsd-core/workflows/quick-batch/steps/completion.md +1 -1
- package/gsd-core/workflows/quick-batch/steps/merge-wave.md +1 -1
- package/gsd-core/workflows/quick-batch/steps/planner-wave.md +1 -1
- package/gsd-core/workflows/quick-batch/steps/research-phase.md +1 -1
- package/gsd-core/workflows/quick-batch/steps/resume-mode.md +1 -1
- package/gsd-core/workflows/quick-batch/steps/verification-wave.md +9 -3
- package/gsd-core/workflows/quick-batch/steps/worktree-dispatch.md +1 -1
- package/gsd-core/workflows/quick-batch.md +15 -10
- package/gsd-core/workflows/quick.md +62 -39
- package/gsd-core/workflows/reapply-patches.md +9 -3
- package/gsd-core/workflows/remove-phase.md +3 -2
- package/gsd-core/workflows/remove-workspace.md +2 -2
- package/gsd-core/workflows/resume-project.md +3 -2
- package/gsd-core/workflows/review.md +33 -17
- package/gsd-core/workflows/scan.md +3 -2
- package/gsd-core/workflows/secure-phase.md +13 -13
- package/gsd-core/workflows/settings-advanced.md +30 -10
- package/gsd-core/workflows/settings-integrations.md +2 -3
- package/gsd-core/workflows/settings.md +4 -4
- package/gsd-core/workflows/ship.md +14 -13
- package/gsd-core/workflows/sketch-wrap-up.md +1 -1
- package/gsd-core/workflows/sketch.md +1 -1
- package/gsd-core/workflows/smart-entry.md +2 -2
- package/gsd-core/workflows/spec-phase.md +15 -5
- package/gsd-core/workflows/spike-wrap-up.md +1 -1
- package/gsd-core/workflows/spike.md +1 -1
- package/gsd-core/workflows/stats.md +1 -1
- package/gsd-core/workflows/sync-skills.md +5 -5
- package/gsd-core/workflows/thread.md +2 -2
- package/gsd-core/workflows/transition.md +13 -23
- package/gsd-core/workflows/ui-phase.md +48 -11
- package/gsd-core/workflows/ui-review.md +21 -6
- package/gsd-core/workflows/ultraplan-phase.md +3 -2
- package/gsd-core/workflows/undo.md +339 -20
- package/gsd-core/workflows/update.md +7 -7
- package/gsd-core/workflows/validate-phase.md +12 -13
- package/gsd-core/workflows/verify-work/detail/elaboration.md +43 -3
- package/gsd-core/workflows/verify-work/steps/automated-ui-verification.md +1 -1
- package/gsd-core/workflows/verify-work/steps/mvp-uat-framing.md +5 -3
- package/gsd-core/workflows/verify-work.md +129 -55
- package/hooks/dist/gsd-agent-isolation-guard.js +32 -0
- package/hooks/dist/gsd-check-update-worker.js +8 -0
- package/hooks/dist/gsd-check-update.js +8 -0
- package/hooks/dist/gsd-context-monitor.js +31 -8
- package/hooks/dist/gsd-cursor-subagent-start.js +8 -0
- package/hooks/dist/gsd-secret-read-guard.js +161 -4
- package/hooks/dist/gsd-statusline.js +85 -20
- package/hooks/dist/gsd-update-banner.js +8 -0
- package/hooks/dist/gsd-validate-commit.sh +63 -4
- package/hooks/dist/gsd-windsurf-pre-write.js +11 -2
- package/hooks/dist/gsd-workflow-guard.js +5 -4
- package/hooks/dist/gsd-worktree-path-guard.js +6 -2
- package/hooks/dist/lib/cli-exit.js +12 -0
- package/hooks/dist/lib/git-probe.js +17 -1
- package/hooks/dist/lib/isolation-sentinel.js +2 -2
- package/hooks/gsd-agent-isolation-guard.js +32 -0
- package/hooks/gsd-check-update-worker.js +8 -0
- package/hooks/gsd-check-update.js +8 -0
- package/hooks/gsd-context-monitor.js +31 -8
- package/hooks/gsd-cursor-subagent-start.js +8 -0
- package/hooks/gsd-secret-read-guard.js +161 -4
- package/hooks/gsd-statusline.js +85 -20
- package/hooks/gsd-update-banner.js +8 -0
- package/hooks/gsd-validate-commit.sh +63 -4
- package/hooks/gsd-windsurf-pre-write.js +11 -2
- package/hooks/gsd-workflow-guard.js +5 -4
- package/hooks/gsd-worktree-path-guard.js +6 -2
- package/hooks/hooks.json +5 -5
- package/hooks/lib/cli-exit.js +12 -0
- package/hooks/lib/git-probe.js +17 -1
- package/hooks/lib/isolation-sentinel.js +2 -2
- package/package.json +22 -4
- package/scripts/build-hooks.js +15 -6
- package/scripts/changeset/parse.cjs +52 -4
- package/scripts/check-contract-drift.cjs +127 -11
- package/scripts/ci-timeout-report.cjs +770 -4
- package/scripts/command-contract-helpers.cjs +15 -8
- package/scripts/docs-guard-registry.cjs +34 -0
- package/scripts/gen-features.cjs +13 -8
- package/scripts/gen-hooks-cli-exit.cjs +12 -28
- package/scripts/gen-loop-host-contract.cjs +79 -1
- package/scripts/gen-platform-conformance-tier.cjs +187 -1
- package/scripts/gen-plugin-skills.cjs +87 -1
- package/scripts/gen-research-agents.cjs +24 -31
- package/scripts/gen-scripts-cli-exit.cjs +30 -3
- package/scripts/gen-test-timings.cjs +32 -7
- package/scripts/lib/cli-exit.cjs +12 -0
- package/scripts/lib/macos-conformance-tier.generated.cjs +34 -2
- package/scripts/lib/ndjson-reporter.cjs +31 -5
- package/scripts/lib/platform-conformance-tier.generated.cjs +45 -5
- package/scripts/lib/registration-ledger-preload.cjs +155 -0
- package/scripts/lib/vendor-bundle.cjs +59 -0
- package/scripts/lib/vendor-licenses/saxes-6.0.0.txt +64 -0
- package/scripts/lint-allow-test-rule-refs.effective-ceiling.json +1 -1
- package/scripts/lint-allow-test-rule-refs.unverified-ceiling.json +1 -1
- package/scripts/lint-completion-predicate-drift.cjs +18 -19
- package/scripts/lint-descriptions.cjs +7 -3
- package/scripts/lint-docs-guard-registration.exempt-baseline.cjs +38 -1
- package/scripts/lint-eslint-glob-coverage.allowlist.json +20 -0
- package/scripts/lint-frontmatter-fence-drift.cjs +313 -0
- package/scripts/lint-frontmatter-scalar-broad-grep.cjs +122 -16
- package/scripts/lint-phase-arg-assignment.cjs +257 -0
- package/scripts/lint-phase-enumeration-drift.cjs +12 -8
- package/scripts/lint-phase-id-drift.cjs +319 -5
- package/scripts/lint-planning-document-positive-control.cjs +329 -0
- package/scripts/lint-pr-branch-pattern-drift.cjs +148 -0
- package/scripts/lint-response-language-coverage.cjs +3 -0
- package/scripts/lint-retired-runtime-name.cjs +619 -0
- package/scripts/lint-skill-deps.cjs +7 -3
- package/scripts/lint-state-write-path-drift.cjs +93 -0
- package/scripts/lint-test-file-count.allowlist.json +38 -9
- package/scripts/lint-test-file-count.cjs +34 -1
- package/scripts/lint-vendored-deps.cjs +41 -5
- package/scripts/lint-workflow-shellcheck-baseline.json +25 -10
- package/scripts/mutation-matrix.cjs +50 -5
- package/scripts/prompt-injection-scan.sh +4 -0
- package/scripts/release-tarball-smoke.cjs +194 -1
- package/scripts/require-issue-link-policy.cjs +6 -2
- package/scripts/sync-runtime-launcher.cjs +184 -2
- package/scripts/verify-npm-publish.cjs +76 -20
- package/skills/gsd-add-tests/SKILL.md +6 -1
- package/skills/gsd-ai-integration-phase/SKILL.md +6 -1
- package/skills/gsd-audit-fix/SKILL.md +5 -0
- package/skills/gsd-audit-milestone/SKILL.md +6 -1
- package/skills/gsd-autonomous/SKILL.md +7 -2
- package/skills/gsd-capture/SKILL.md +9 -5
- package/skills/gsd-code-review/SKILL.md +7 -2
- package/skills/gsd-complete-milestone/SKILL.md +4 -0
- package/skills/gsd-config/SKILL.md +8 -4
- package/skills/gsd-debug/SKILL.md +11 -7
- package/skills/gsd-discuss-phase/SKILL.md +7 -3
- package/skills/gsd-docs-update/SKILL.md +12 -7
- package/skills/gsd-eval-review/SKILL.md +6 -1
- package/skills/gsd-execute-phase/SKILL.md +12 -7
- package/skills/gsd-extract-learnings/SKILL.md +5 -0
- package/skills/gsd-fast/SKILL.md +4 -0
- package/skills/gsd-forensics/SKILL.md +5 -1
- package/skills/gsd-graphify/SKILL.md +10 -6
- package/skills/gsd-health/SKILL.md +5 -0
- package/skills/gsd-help/SKILL.md +7 -2
- package/skills/gsd-import/SKILL.md +7 -3
- package/skills/gsd-inbox/SKILL.md +5 -0
- package/skills/gsd-ingest-docs/SKILL.md +5 -1
- package/skills/gsd-manager/SKILL.md +6 -1
- package/skills/gsd-map-codebase/SKILL.md +7 -3
- package/skills/gsd-mempalace-capture/SKILL.md +12 -4
- package/skills/gsd-mempalace-recall/SKILL.md +5 -1
- package/skills/gsd-milestone-summary/SKILL.md +5 -1
- package/skills/gsd-mvp-phase/SKILL.md +8 -3
- package/skills/gsd-new-milestone/SKILL.md +6 -1
- package/skills/gsd-new-project/SKILL.md +5 -0
- package/skills/gsd-next/SKILL.md +6 -1
- package/skills/gsd-ns-context/SKILL.md +4 -0
- package/skills/gsd-ns-ideate/SKILL.md +4 -0
- package/skills/gsd-ns-manage/SKILL.md +4 -0
- package/skills/gsd-ns-project/SKILL.md +4 -0
- package/skills/gsd-ns-review/SKILL.md +4 -0
- package/skills/gsd-ns-workflow/SKILL.md +4 -0
- package/skills/gsd-onboard/SKILL.md +6 -1
- package/skills/gsd-pause-work/SKILL.md +5 -1
- package/skills/gsd-phase/SKILL.md +8 -4
- package/skills/gsd-plan-phase/SKILL.md +6 -1
- package/skills/gsd-plan-review-convergence/SKILL.md +10 -6
- package/skills/gsd-pr-branch/SKILL.md +4 -0
- package/skills/gsd-profile-user/SKILL.md +5 -1
- package/skills/gsd-progress/SKILL.md +7 -2
- package/skills/gsd-quick/SKILL.md +16 -10
- package/skills/gsd-quick-batch/SKILL.md +21 -9
- package/skills/gsd-review/SKILL.md +7 -4
- package/skills/gsd-review-backlog/SKILL.md +3 -2
- package/skills/gsd-secure-phase/SKILL.md +6 -1
- package/skills/gsd-ship/SKILL.md +5 -0
- package/skills/gsd-sketch/SKILL.md +7 -2
- package/skills/gsd-spec-phase/SKILL.md +5 -1
- package/skills/gsd-spike/SKILL.md +8 -3
- package/skills/gsd-surface/SKILL.md +5 -1
- package/skills/gsd-thread/SKILL.md +4 -0
- package/skills/gsd-ui-phase/SKILL.md +6 -1
- package/skills/gsd-ui-review/SKILL.md +6 -1
- package/skills/gsd-ultraplan-phase/SKILL.md +5 -1
- package/skills/gsd-undo/SKILL.md +5 -1
- package/skills/gsd-update/SKILL.md +6 -2
- package/skills/gsd-validate-phase/SKILL.md +6 -1
- package/skills/gsd-verify-work/SKILL.md +6 -1
- package/skills/gsd-workspace/SKILL.md +7 -3
- package/skills/gsd-workstreams/SKILL.md +6 -6
- package/vscode/package.json +1 -1
|
@@ -0,0 +1,494 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
/**
|
|
3
|
+
* frontmatter-splice.cts — the frontmatter WRITER (#5105): `spliceFrontmatter` and the
|
|
4
|
+
* machinery only it uses — per-key layout slicing, comment and tail-line classification, the
|
|
5
|
+
* parse budget, key quoting for a regenerated key, the read-back post-condition and the
|
|
6
|
+
* write-refusal vocabulary (`FrontmatterWriteRefusedError` / `isFrontmatterWriteRefusal`).
|
|
7
|
+
*
|
|
8
|
+
* Split out of `frontmatter.cts` by module ownership (#5105): the reader — fence region,
|
|
9
|
+
* guarded parse, comment channel, `reconstructFrontmatter` — stays there; the decision whether
|
|
10
|
+
* and how a changed frontmatter block may be written lives here. `frontmatter.cts` re-exports
|
|
11
|
+
* this module's public names, so every caller's `require('./frontmatter.cjs')` is unchanged.
|
|
12
|
+
* Behavior is unchanged: every function below moved verbatim.
|
|
13
|
+
*
|
|
14
|
+
* Import direction: this module requires `frontmatter.cjs` at load time (the parser it builds
|
|
15
|
+
* on, through `frontmatter.spliceSeam`); `frontmatter.cjs` requires this module LAZILY — from
|
|
16
|
+
* its re-export getters and its set/merge commands, never while it is itself loading — so
|
|
17
|
+
* whichever of the two a caller loads first, the other is complete before it is read.
|
|
18
|
+
*
|
|
19
|
+
* Source in src/frontmatter-splice.cts, compiled to gsd-core/bin/lib/frontmatter-splice.cjs
|
|
20
|
+
* (gitignored), per the repo's ADR-457 build-at-publish convention.
|
|
21
|
+
*/
|
|
22
|
+
// eslint-disable-next-line @typescript-eslint/no-require-imports
|
|
23
|
+
const frontmatterMod = require("./frontmatter.cjs");
|
|
24
|
+
const text_lines_cjs_1 = require("./text-lines.cjs");
|
|
25
|
+
const frontmatter_fence_cjs_1 = require("./frontmatter-fence.cjs");
|
|
26
|
+
const js_yaml_cjs_1 = require("./vendor/js-yaml.cjs");
|
|
27
|
+
const { extractFrontmatter, reconstructFrontmatter, escapeDoubleQuotedScalar, frontmatterBlock, FRONTMATTER_UNPARSEABLE, spliceSeam } = frontmatterMod;
|
|
28
|
+
const { FULL_LINE_COMMENTS, YAML_LOAD_OPTS, commentPathKey, channelKeyLine, segmentKeyOf, escapeNullBytesForParse, unparseableResult, frontmatterDeepEqual, } = spliceSeam;
|
|
29
|
+
/**
|
|
30
|
+
* A line that can sit after a key's value without belonging to it (see `FrontmatterSegment`):
|
|
31
|
+
* a blank line or a column-0 full-line comment, and nothing else. Every other line after a
|
|
32
|
+
* key line — an indented line, a `- item`, and a column-0 continuation of a multi-line
|
|
33
|
+
* quoted scalar or of a flow collection split across lines (`tags: [a,` / `b]`) — is part of
|
|
34
|
+
* the key's own value and is replaced with it (found while implementing #5105).
|
|
35
|
+
*/
|
|
36
|
+
function isSegmentTailLine(line) {
|
|
37
|
+
return line.trim() === '' || line.startsWith('#');
|
|
38
|
+
}
|
|
39
|
+
/**
|
|
40
|
+
* The most YAML text, in characters (each parse also counts one for its own line break), one
|
|
41
|
+
* `spliceFrontmatter` call may parse while classifying lines — `segmentTailStart`,
|
|
42
|
+
* `isSegmentComment` and `inlineCommentStart` each decide a line by re-parsing the key's lines
|
|
43
|
+
* without it, so the work grows with (lines × `#`/tail lines × line length). A real planning
|
|
44
|
+
* document stays far below it (across every tracked `.md` file, splicing any key to a changed
|
|
45
|
+
* value parses at most ~60,000 characters — 0.3% of it, in the adversarial `huge-bounded.md`
|
|
46
|
+
* fixture); a pathological block — thousands of blank lines after a `|+` key,
|
|
47
|
+
* or hundreds of lines each holding many ` #` — is refused instead of stalling the writer
|
|
48
|
+
* (found while implementing #5105).
|
|
49
|
+
*/
|
|
50
|
+
const SPLICE_PARSE_BUDGET_CHARS = 20_000_000;
|
|
51
|
+
/**
|
|
52
|
+
* Parse one key's lines on their own, or null when they do not parse as YAML. Charges the
|
|
53
|
+
* text to `budget`, refusing with `FRONTMATTER_TOO_COMPLEX` once it is spent.
|
|
54
|
+
*/
|
|
55
|
+
function loadSegmentValue(lines, budget) {
|
|
56
|
+
const text = lines.join('\n');
|
|
57
|
+
budget.remaining -= text.length + 1;
|
|
58
|
+
if (budget.remaining < 0) {
|
|
59
|
+
throw new FrontmatterWriteRefusedError('FRONTMATTER_TOO_COMPLEX', 'frontmatter: refusing to write — telling this frontmatter block\'s comments and trailing blank ' +
|
|
60
|
+
`lines apart from its values would take more than ${budget.limit} characters of YAML ` +
|
|
61
|
+
'parsing (a very large block with many `#` or trailing blank lines), so the writer stops rather ' +
|
|
62
|
+
'than stall. Edit the file directly.');
|
|
63
|
+
}
|
|
64
|
+
try {
|
|
65
|
+
return { value: (0, js_yaml_cjs_1.load)(escapeNullBytesForParse(text), YAML_LOAD_OPTS) };
|
|
66
|
+
}
|
|
67
|
+
catch {
|
|
68
|
+
return null;
|
|
69
|
+
}
|
|
70
|
+
}
|
|
71
|
+
/**
|
|
72
|
+
* Where a key's trailing tail starts. The candidate tail is the trailing run of blank and
|
|
73
|
+
* column-0 `#` lines, but such a line can still be value text: a `#` line inside a
|
|
74
|
+
* multi-line quoted scalar, or a blank line a block scalar keeps. The parser decides — a
|
|
75
|
+
* tail line is only a tail line when the key's lines parse to the same value without it.
|
|
76
|
+
* When the key's lines do not parse on their own, the lexical candidate stands; the
|
|
77
|
+
* read-back check in `spliceFrontmatter` still refuses any block that would misread.
|
|
78
|
+
*/
|
|
79
|
+
function segmentTailStart(lines, budget) {
|
|
80
|
+
let cut = lines.length;
|
|
81
|
+
while (cut > 1 && isSegmentTailLine(lines[cut - 1]))
|
|
82
|
+
cut--;
|
|
83
|
+
if (cut === lines.length)
|
|
84
|
+
return cut;
|
|
85
|
+
const whole = loadSegmentValue(lines, budget);
|
|
86
|
+
if (!whole)
|
|
87
|
+
return cut;
|
|
88
|
+
while (cut < lines.length) {
|
|
89
|
+
const body = loadSegmentValue(lines.slice(0, cut), budget);
|
|
90
|
+
if (body && frontmatterDeepEqual(body.value, whole.value))
|
|
91
|
+
return cut;
|
|
92
|
+
cut++;
|
|
93
|
+
}
|
|
94
|
+
return cut;
|
|
95
|
+
}
|
|
96
|
+
/**
|
|
97
|
+
* Slice a frontmatter YAML body into its `preamble` (lines before the first top-level key,
|
|
98
|
+
* e.g. a leading comment) and per-top-level-key raw text segments. Each segment runs from a
|
|
99
|
+
* column-0 key line through the line before the next column-0 key (or the end), capturing
|
|
100
|
+
* all nested indented content. Used by `spliceFrontmatter` for per-key identity
|
|
101
|
+
* preservation (#1572): a structurally-unchanged key keeps its original raw text, so the
|
|
102
|
+
* lossy `reconstructFrontmatter` never touches object-lists the caller did not modify (e.g.
|
|
103
|
+
* must_haves.artifacts / .prohibitions). Every input line lands in exactly one of
|
|
104
|
+
* `preamble`, a segment `body` or a segment `tail` — nothing is discarded here.
|
|
105
|
+
*/
|
|
106
|
+
function sliceFrontmatterLayout(yaml, budget) {
|
|
107
|
+
const preamble = [];
|
|
108
|
+
const segments = [];
|
|
109
|
+
let current = null;
|
|
110
|
+
const close = (seg) => {
|
|
111
|
+
const cut = segmentTailStart(seg.lines, budget);
|
|
112
|
+
segments.push({
|
|
113
|
+
key: seg.key,
|
|
114
|
+
valueStart: seg.valueStart,
|
|
115
|
+
raw: seg.lines.join('\n'),
|
|
116
|
+
body: seg.lines.slice(0, cut),
|
|
117
|
+
tail: seg.lines.slice(cut),
|
|
118
|
+
});
|
|
119
|
+
};
|
|
120
|
+
for (const line of (0, text_lines_cjs_1.splitLines)(yaml)) {
|
|
121
|
+
const key = segmentKeyOf(line);
|
|
122
|
+
if (key) {
|
|
123
|
+
if (current)
|
|
124
|
+
close(current);
|
|
125
|
+
current = { key: key.key, valueStart: key.valueStart, lines: [line] };
|
|
126
|
+
}
|
|
127
|
+
else if (current) {
|
|
128
|
+
current.lines.push(line);
|
|
129
|
+
}
|
|
130
|
+
else {
|
|
131
|
+
preamble.push(line);
|
|
132
|
+
}
|
|
133
|
+
}
|
|
134
|
+
if (current)
|
|
135
|
+
close(current);
|
|
136
|
+
return { preamble, segments };
|
|
137
|
+
}
|
|
138
|
+
/**
|
|
139
|
+
* Regenerate one frontmatter key's serialization, fail-closed if the lossy
|
|
140
|
+
* `reconstructFrontmatter` cannot represent the value (#1572 codex review). Object-list
|
|
141
|
+
* items (e.g. must_haves.artifacts `{path, provides}` maps) serialize as the literal
|
|
142
|
+
* string "[object Object]"; rather than silently emit that and destroy the data, refuse
|
|
143
|
+
* so the caller (cmdFrontmatterSet/Merge) errors out WITHOUT writing — directing the
|
|
144
|
+
* user to edit the file directly. The reported #1572 case (mutating an UNRELATED field)
|
|
145
|
+
* is unaffected: unchanged keys preserve their original raw text and never reach here.
|
|
146
|
+
*/
|
|
147
|
+
function regenerateFrontmatterKey(key, value, comments) {
|
|
148
|
+
// Computed key: a key named `__proto__` is an own data property here, never the prototype.
|
|
149
|
+
const single = { [key]: value };
|
|
150
|
+
// `comments` are the comments written inside this key's own value lines, keyed by exact
|
|
151
|
+
// key path (`segmentComments`), so `reconstructFrontmatter` re-emits each beside the key it
|
|
152
|
+
// belongs to. The key's own leading comment and the block's trailing comments are not part
|
|
153
|
+
// of the value: they sit in the splice's preamble or a neighbour's tail, which is re-emitted
|
|
154
|
+
// verbatim.
|
|
155
|
+
if (comments)
|
|
156
|
+
single[FULL_LINE_COMMENTS] = comments;
|
|
157
|
+
const rendered = reconstructFrontmatter(single);
|
|
158
|
+
if (/\[object Object\]/.test(rendered)) {
|
|
159
|
+
throw new Error(`frontmatter: cannot faithfully serialize key "${key}" — it contains a nested object-list ` +
|
|
160
|
+
`(e.g. must_haves.artifacts) the frontmatter writer cannot represent, and serializing it would ` +
|
|
161
|
+
`emit "[object Object]". Edit the file directly instead of using frontmatter set/merge.`);
|
|
162
|
+
}
|
|
163
|
+
// `reconstructFrontmatter` emits keys bare. A key the parser would not read back as
|
|
164
|
+
// itself when bare (`a:b`, `a: b`, `#x`, `- k`, a trailing space) is emitted double-quoted
|
|
165
|
+
// instead, so a regenerated or appended key never re-parses as a different key.
|
|
166
|
+
if (rendered.startsWith(`${key}:`) && !/^[\p{L}\p{N}_][\p{L}\p{N}_.-]*$/u.test(key)) {
|
|
167
|
+
return `"${escapeDoubleQuotedScalar(key)}"` + rendered.slice(key.length);
|
|
168
|
+
}
|
|
169
|
+
return rendered;
|
|
170
|
+
}
|
|
171
|
+
class FrontmatterWriteRefusedError extends Error {
|
|
172
|
+
code;
|
|
173
|
+
constructor(code, message) {
|
|
174
|
+
super(message);
|
|
175
|
+
this.name = 'FrontmatterWriteRefusedError';
|
|
176
|
+
this.code = code;
|
|
177
|
+
}
|
|
178
|
+
}
|
|
179
|
+
function isFrontmatterWriteRefusal(err) {
|
|
180
|
+
return err instanceof FrontmatterWriteRefusedError;
|
|
181
|
+
}
|
|
182
|
+
/** Does a closed frontmatter block's parse mean "unparseable" for a writer? */
|
|
183
|
+
function frontmatterBlockUnparseable(parsed, innerYaml) {
|
|
184
|
+
if (parsed[FRONTMATTER_UNPARSEABLE] === true)
|
|
185
|
+
return true;
|
|
186
|
+
if (Object.keys(parsed).length > 0)
|
|
187
|
+
return false;
|
|
188
|
+
// `{}` from a block that still carries content: a bare scalar (`status:complete`) or a
|
|
189
|
+
// sequence at the top level. Only blank lines and comments make a genuinely empty block.
|
|
190
|
+
return (0, text_lines_cjs_1.splitLines)(innerYaml).some((line) => line.trim() !== '' && !line.trim().startsWith('#'));
|
|
191
|
+
}
|
|
192
|
+
/**
|
|
193
|
+
* What `extractFrontmatter` reads back for `value` once the writer has written it: every
|
|
194
|
+
* scalar is a string under `FAILSAFE_SCHEMA` (`2` → `'2'`, `true` → `'true'`), a
|
|
195
|
+
* null/undefined mapping entry is not written at all (`undefined` here), and a
|
|
196
|
+
* null/undefined list item reads back as `''`. The intended object a write must read back as.
|
|
197
|
+
*/
|
|
198
|
+
function readBackProjection(value, inArray = false) {
|
|
199
|
+
if (value === null || value === undefined)
|
|
200
|
+
return inArray ? '' : undefined;
|
|
201
|
+
if (Array.isArray(value))
|
|
202
|
+
return value.map((item) => readBackProjection(item, true));
|
|
203
|
+
if (typeof value === 'object') {
|
|
204
|
+
const out = {};
|
|
205
|
+
for (const [k, v] of Object.entries(value)) {
|
|
206
|
+
const projected = readBackProjection(v);
|
|
207
|
+
// `defineProperty`, never `out[k] =`: a key named `__proto__` stays an own data property.
|
|
208
|
+
if (projected !== undefined)
|
|
209
|
+
Object.defineProperty(out, k, { value: projected, writable: true, enumerable: true, configurable: true });
|
|
210
|
+
}
|
|
211
|
+
return out;
|
|
212
|
+
}
|
|
213
|
+
// eslint-disable-next-line @typescript-eslint/no-base-to-string
|
|
214
|
+
return String(value);
|
|
215
|
+
}
|
|
216
|
+
/**
|
|
217
|
+
* The write post-condition every splice path ends with: the document about to be returned
|
|
218
|
+
* must carry a frontmatter block that parses — through `extractFrontmatter` itself, the same
|
|
219
|
+
* reader every consumer uses — to exactly the intended object. Anything the serializer cannot
|
|
220
|
+
* represent faithfully (a nested key with `: ` in it, a value that re-types) is refused here,
|
|
221
|
+
* so no caller can write a block that does not read back as intended (found while
|
|
222
|
+
* implementing #5105).
|
|
223
|
+
*/
|
|
224
|
+
function verifyReadsBackAs(out, newObj) {
|
|
225
|
+
const reread = extractFrontmatter(out);
|
|
226
|
+
if (reread[FRONTMATTER_UNPARSEABLE] === true ||
|
|
227
|
+
!frontmatterDeepEqual(reread, readBackProjection(newObj))) {
|
|
228
|
+
throw new FrontmatterWriteRefusedError('FRONTMATTER_SPLICE_VERIFY_FAILED', 'frontmatter: refusing to write — the rewritten frontmatter block would not read back as the ' +
|
|
229
|
+
'requested values (a value or nested key the frontmatter writer cannot represent faithfully), so ' +
|
|
230
|
+
'writing it would store something other than what was asked. Edit the file directly.');
|
|
231
|
+
}
|
|
232
|
+
return out;
|
|
233
|
+
}
|
|
234
|
+
/**
|
|
235
|
+
* Is `lines[i]` a full-line comment the parser ignores — do the key's lines parse to the
|
|
236
|
+
* same value without it? A `#` line inside a block scalar or a multi-line quoted scalar is
|
|
237
|
+
* value text, not a comment. When the lines do not parse on their own (`whole` null) every
|
|
238
|
+
* `#` line counts, so the comment post-condition fails closed.
|
|
239
|
+
*/
|
|
240
|
+
function isSegmentComment(lines, i, whole, budget) {
|
|
241
|
+
if (!/^\s*#/.test(lines[i]))
|
|
242
|
+
return false;
|
|
243
|
+
if (!whole)
|
|
244
|
+
return true;
|
|
245
|
+
const without = loadSegmentValue([...lines.slice(0, i), ...lines.slice(i + 1)], budget);
|
|
246
|
+
return without !== null && frontmatterDeepEqual(without.value, whole.value);
|
|
247
|
+
}
|
|
248
|
+
/**
|
|
249
|
+
* Where the inline comment on `lines[i]` starts (the whitespace before its `#`), or -1. A
|
|
250
|
+
* ` #` is a comment only when the key's lines parse to the same value with the line cut
|
|
251
|
+
* there, so a `#` inside a quoted scalar or a block scalar never counts. When the lines do
|
|
252
|
+
* not parse on their own (`whole` null) the first ` #` counts.
|
|
253
|
+
*/
|
|
254
|
+
function inlineCommentStart(lines, i, whole, budget) {
|
|
255
|
+
const line = lines[i];
|
|
256
|
+
const hashes = /[ \t]#/g;
|
|
257
|
+
for (let m = hashes.exec(line); m !== null; m = hashes.exec(line)) {
|
|
258
|
+
let start = m.index;
|
|
259
|
+
while (start > 0 && (line[start - 1] === ' ' || line[start - 1] === '\t'))
|
|
260
|
+
start--;
|
|
261
|
+
if (!whole)
|
|
262
|
+
return start;
|
|
263
|
+
const cut = loadSegmentValue([...lines.slice(0, i), line.slice(0, start), ...lines.slice(i + 1)], budget);
|
|
264
|
+
if (cut !== null && frontmatterDeepEqual(cut.value, whole.value))
|
|
265
|
+
return start;
|
|
266
|
+
}
|
|
267
|
+
return -1;
|
|
268
|
+
}
|
|
269
|
+
function segmentComments(key, lines, budget) {
|
|
270
|
+
const whole = loadSegmentValue(lines, budget);
|
|
271
|
+
const leading = Object.create(null);
|
|
272
|
+
const inline = Object.create(null);
|
|
273
|
+
const unattached = [];
|
|
274
|
+
const stack = [];
|
|
275
|
+
let pending = [];
|
|
276
|
+
const loosen = (comments) => { for (const c of comments)
|
|
277
|
+
unattached.push(c.line.trim()); };
|
|
278
|
+
for (let i = 0; i < lines.length; i++) {
|
|
279
|
+
const line = lines[i];
|
|
280
|
+
if (i > 0 && line.trim() === '')
|
|
281
|
+
continue;
|
|
282
|
+
if (i > 0 && isSegmentComment(lines, i, whole, budget)) {
|
|
283
|
+
pending.push({ indent: /^\s*/.exec(line)?.[0].length ?? 0, line });
|
|
284
|
+
continue;
|
|
285
|
+
}
|
|
286
|
+
const at = inlineCommentStart(lines, i, whole, budget);
|
|
287
|
+
let owner = i === 0 ? [key] : null;
|
|
288
|
+
if (i > 0) {
|
|
289
|
+
const k = channelKeyLine(at === -1 ? line : line.slice(0, at));
|
|
290
|
+
if (k && k.indent > 0 && k.key.length > 0) {
|
|
291
|
+
while (stack.length > 0 && stack[stack.length - 1].indent >= k.indent)
|
|
292
|
+
stack.pop();
|
|
293
|
+
owner = [key, ...stack.map((e) => e.key), k.key];
|
|
294
|
+
const same = pending.filter((c) => c.indent === k.indent);
|
|
295
|
+
if (same.length > 0)
|
|
296
|
+
leading[commentPathKey(owner)] = same.map((c) => c.line);
|
|
297
|
+
loosen(pending.filter((c) => c.indent !== k.indent));
|
|
298
|
+
stack.push({ indent: k.indent, key: k.key });
|
|
299
|
+
}
|
|
300
|
+
else {
|
|
301
|
+
loosen(pending);
|
|
302
|
+
}
|
|
303
|
+
pending = [];
|
|
304
|
+
}
|
|
305
|
+
if (at !== -1) {
|
|
306
|
+
// A ` #` on lines that do not parse on their own may be value text: never re-emit it.
|
|
307
|
+
if (owner && whole)
|
|
308
|
+
inline[commentPathKey(owner)] = line.slice(at);
|
|
309
|
+
else
|
|
310
|
+
unattached.push(line.slice(at).trim());
|
|
311
|
+
}
|
|
312
|
+
}
|
|
313
|
+
loosen(pending);
|
|
314
|
+
return { channel: { leading, trailing: [], inline }, unattached };
|
|
315
|
+
}
|
|
316
|
+
/**
|
|
317
|
+
* The comment post-condition of regenerating a changed key: every comment its original value
|
|
318
|
+
* lines held — full-line or inline — appears in the regenerated text beside the SAME key path
|
|
319
|
+
* (compared trimmed — `reconstructFrontmatter` re-indents a nested comment to its key's
|
|
320
|
+
* depth). Compared per exact path, never as a global count, so a comment dropped in one place
|
|
321
|
+
* can never be balanced by an identical one elsewhere. A comment that could not be re-attached
|
|
322
|
+
* is refused, never silently dropped (found while implementing #5105).
|
|
323
|
+
*/
|
|
324
|
+
function assertCommentsKept(key, original, regenerated, budget) {
|
|
325
|
+
const placed = (c) => {
|
|
326
|
+
const counts = new Map();
|
|
327
|
+
const add = (entry) => { counts.set(entry, (counts.get(entry) ?? 0) + 1); };
|
|
328
|
+
for (const [path, lines] of Object.entries(c.channel.leading))
|
|
329
|
+
for (const l of lines)
|
|
330
|
+
add(JSON.stringify(['above', path, l.trim()]));
|
|
331
|
+
for (const [path, text] of Object.entries(c.channel.inline))
|
|
332
|
+
add(JSON.stringify(['beside', path, text.trim()]));
|
|
333
|
+
for (const text of c.unattached)
|
|
334
|
+
add(JSON.stringify(['loose', '', text]));
|
|
335
|
+
return counts;
|
|
336
|
+
};
|
|
337
|
+
const before = placed(original);
|
|
338
|
+
const after = placed(segmentComments(key, (0, text_lines_cjs_1.splitLines)(regenerated), budget));
|
|
339
|
+
const differs = (entry) => (before.get(entry) ?? 0) !== (after.get(entry) ?? 0);
|
|
340
|
+
const lost = [...before.keys()].filter(differs);
|
|
341
|
+
if (lost.length === 0 && ![...after.keys()].some(differs))
|
|
342
|
+
return;
|
|
343
|
+
const describe = (entry) => {
|
|
344
|
+
const [where, path, text] = JSON.parse(entry);
|
|
345
|
+
const keyPath = path === '' ? '' : JSON.parse(path).map((s) => JSON.stringify(s)).join(' › ');
|
|
346
|
+
return where === 'loose' ? `${JSON.stringify(text)} (not beside any key)` : `${JSON.stringify(text)} ${where} ${keyPath}`;
|
|
347
|
+
};
|
|
348
|
+
throw new FrontmatterWriteRefusedError('FRONTMATTER_COMMENT_WOULD_BE_LOST', `frontmatter: refusing to write — the new value of "${key}" cannot keep the comment(s) written ` +
|
|
349
|
+
`inside it (${(lost.length > 0 ? lost : [...after.keys()].filter(differs)).map(describe).join(', ')}): a ` +
|
|
350
|
+
'comment above or beside a key the new value removes or no longer holds as a key, on or between list ' +
|
|
351
|
+
'items, or after the last line of the value has nowhere to go. Edit the file directly.');
|
|
352
|
+
}
|
|
353
|
+
function spliceFrontmatter(content, newObj, { parseBudgetChars = SPLICE_PARSE_BUDGET_CHARS } = {}) {
|
|
354
|
+
if (!Number.isSafeInteger(parseBudgetChars) || parseBudgetChars < 0 || parseBudgetChars > SPLICE_PARSE_BUDGET_CHARS) {
|
|
355
|
+
throw new TypeError(`spliceFrontmatter: parseBudgetChars must be a non-negative safe integer no greater than ${SPLICE_PARSE_BUDGET_CHARS} (the option can only lower the limit), got ${String(parseBudgetChars)}`);
|
|
356
|
+
}
|
|
357
|
+
// The block is located through `frontmatterBlock` (the one fence owner's block), so the
|
|
358
|
+
// writer and every reader agree on where it is: BOM (#2977), CRLF, an empty block.
|
|
359
|
+
const located = frontmatterBlock(content);
|
|
360
|
+
if (located) {
|
|
361
|
+
const { bom, block: fmBlock, rest } = located;
|
|
362
|
+
const fmLines = (0, text_lines_cjs_1.splitLines)(fmBlock);
|
|
363
|
+
const innerLines = fmLines.slice(1, -1); // drop the opening `---` and closing `---`
|
|
364
|
+
const inner = innerLines.join('\n');
|
|
365
|
+
let originalParsed;
|
|
366
|
+
try {
|
|
367
|
+
originalParsed = extractFrontmatter(fmBlock);
|
|
368
|
+
}
|
|
369
|
+
catch {
|
|
370
|
+
originalParsed = unparseableResult();
|
|
371
|
+
}
|
|
372
|
+
// Fail closed on an unparseable block BEFORE anything else — never regenerate over it.
|
|
373
|
+
if (frontmatterBlockUnparseable(originalParsed, inner)) {
|
|
374
|
+
throw new FrontmatterWriteRefusedError('FRONTMATTER_UNPARSEABLE', 'frontmatter: refusing to write — the existing frontmatter block is not parseable YAML, so ' +
|
|
375
|
+
'rewriting it would discard or hide the fields the author wrote. Fix the YAML syntax error in ' +
|
|
376
|
+
'the frontmatter block first, then re-run.');
|
|
377
|
+
}
|
|
378
|
+
// Whole-document no-op guard: a true no-op returns content verbatim (byte-exact,
|
|
379
|
+
// including any formatting the lossy serializer would normalize). Compared on what the
|
|
380
|
+
// new values read back as, so `wave: 2` (a number) over `wave: 2` (text) is a no-op.
|
|
381
|
+
if (frontmatterDeepEqual(originalParsed, readBackProjection(newObj))) {
|
|
382
|
+
return content;
|
|
383
|
+
}
|
|
384
|
+
// Per-key identity preservation (#1572). `reconstructFrontmatter` is a deliberately
|
|
385
|
+
// lossy serializer — it cannot faithfully re-emit nested object-list items (e.g.
|
|
386
|
+
// must_haves.artifacts / .prohibitions, whose items are `{ path, provides }` /
|
|
387
|
+
// `{ statement, status }` maps; `extractFrontmatter` flattens those to scalar
|
|
388
|
+
// strings, so a round-trip drops `provides:` and collapses the list to a malformed
|
|
389
|
+
// inline array). For any top-level key whose value is STRUCTURALLY UNCHANGED between
|
|
390
|
+
// the original parse and `newObj`, preserve that key's ORIGINAL raw text verbatim;
|
|
391
|
+
// regenerate only keys that actually changed. This generalizes the whole-document
|
|
392
|
+
// no-op guard above to per-key fidelity, so mutating `wave` no longer destroys an
|
|
393
|
+
// unrelated `must_haves` block. Keys absent from the original (genuinely new) are
|
|
394
|
+
// regenerated and appended; keys absent from `newObj` are preserved (never silently
|
|
395
|
+
// deleted by a set/merge).
|
|
396
|
+
//
|
|
397
|
+
// A write never silently drops a line it did not parse: the preamble (e.g. a comment
|
|
398
|
+
// above the first key) and every segment's tail (blank lines, full-line comments) are
|
|
399
|
+
// re-emitted in place whatever happens to the key they sit next to.
|
|
400
|
+
// An adjacent empty block (`---\n---`) has NO lines between its fences, while a block
|
|
401
|
+
// holding one blank line has one empty line: both join to ''. Laying out '' yields that one
|
|
402
|
+
// blank line, so the empty block would gain a blank line above its first key (found while
|
|
403
|
+
// implementing #5105).
|
|
404
|
+
// One parse allowance for the whole call: every line classification below draws on it.
|
|
405
|
+
const budget = { remaining: parseBudgetChars, limit: parseBudgetChars };
|
|
406
|
+
const { preamble, segments } = innerLines.length === 0
|
|
407
|
+
? { preamble: [], segments: [] }
|
|
408
|
+
: sliceFrontmatterLayout(inner, budget);
|
|
409
|
+
const hasOwn = (o, k) => Object.prototype.hasOwnProperty.call(o, k);
|
|
410
|
+
// Every parsed key must own exactly one key line, and every key line must be a parsed
|
|
411
|
+
// key — otherwise a per-key splice could emit a key twice or lose one. Refuse instead.
|
|
412
|
+
const parsedKeys = Object.keys(originalParsed);
|
|
413
|
+
const segmentKeys = segments.map((s) => s.key);
|
|
414
|
+
if (new Set(segmentKeys).size !== segmentKeys.length ||
|
|
415
|
+
segmentKeys.length !== parsedKeys.length ||
|
|
416
|
+
!segmentKeys.every((k) => hasOwn(originalParsed, k))) {
|
|
417
|
+
throw new FrontmatterWriteRefusedError('FRONTMATTER_KEYS_UNRECONCILABLE', 'frontmatter: refusing to write — the frontmatter block\'s top-level key lines cannot be matched ' +
|
|
418
|
+
'one-to-one to its parsed keys (a duplicate key, an explicit "? key", a flow mapping, or a quoted ' +
|
|
419
|
+
'key using a YAML-only escape), so rewriting it could duplicate or drop a key. Edit the file directly.');
|
|
420
|
+
}
|
|
421
|
+
const emitted = [...preamble];
|
|
422
|
+
const seen = new Set();
|
|
423
|
+
for (const seg of segments) {
|
|
424
|
+
seen.add(seg.key);
|
|
425
|
+
if (hasOwn(newObj, seg.key)) {
|
|
426
|
+
// Key is in newObj: preserve original raw text if structurally unchanged,
|
|
427
|
+
// otherwise regenerate. The key SET is defined by newObj — keys that were in
|
|
428
|
+
// the original but are absent from newObj are intentionally dropped (the real
|
|
429
|
+
// cmdSet/cmdMerge flow always passes the full merged object, so this only
|
|
430
|
+
// matters for direct unit callers and matches spliceFrontmatter's contract:
|
|
431
|
+
// the result frontmatter IS newObj).
|
|
432
|
+
if (frontmatterDeepEqual(readBackProjection(newObj[seg.key]), originalParsed[seg.key])) {
|
|
433
|
+
emitted.push(seg.raw); // unchanged → preserve original raw text verbatim
|
|
434
|
+
}
|
|
435
|
+
else {
|
|
436
|
+
// changed → regenerate (fail-closed on object-lists), keeping the tail. A value
|
|
437
|
+
// that is not written at all (null) regenerates to '' — no line, only the tail:
|
|
438
|
+
// the key is deleted, and the comments inside its value go with it (#3257 AC5).
|
|
439
|
+
// Otherwise every comment inside the value — full-line or inline — is re-emitted
|
|
440
|
+
// beside the key it belongs to or the write is refused.
|
|
441
|
+
const comments = segmentComments(seg.key, seg.body, budget);
|
|
442
|
+
const regenerated = regenerateFrontmatterKey(seg.key, newObj[seg.key], comments.channel);
|
|
443
|
+
if (regenerated !== '')
|
|
444
|
+
assertCommentsKept(seg.key, comments, regenerated, budget);
|
|
445
|
+
emitted.push(...(regenerated === '' ? [] : [regenerated]), ...seg.tail);
|
|
446
|
+
}
|
|
447
|
+
}
|
|
448
|
+
else {
|
|
449
|
+
// Parsed key absent from newObj → drop its body; its tail is not its value.
|
|
450
|
+
emitted.push(...seg.tail);
|
|
451
|
+
}
|
|
452
|
+
}
|
|
453
|
+
// Append genuinely-new keys not present in the original frontmatter.
|
|
454
|
+
for (const k of Object.keys(newObj)) {
|
|
455
|
+
if (!seen.has(k)) {
|
|
456
|
+
const regenerated = regenerateFrontmatterKey(k, newObj[k]);
|
|
457
|
+
if (regenerated !== '')
|
|
458
|
+
emitted.push(regenerated);
|
|
459
|
+
}
|
|
460
|
+
}
|
|
461
|
+
// Re-emit the whole block with the document's own line ending (the opening fence's, as
|
|
462
|
+
// the one fence owner reads it), so a CRLF document never gains LF-only lines.
|
|
463
|
+
const eol = (0, frontmatter_fence_cjs_1.locateFrontmatterFence)(content)?.eol ?? '\n';
|
|
464
|
+
const block = ['---', ...emitted, '---'].join('\n').split('\n').join(eol);
|
|
465
|
+
return bom + verifyReadsBackAs(block + rest, newObj);
|
|
466
|
+
}
|
|
467
|
+
// No existing frontmatter — generate from scratch, fail-closed on unrepresentable values.
|
|
468
|
+
if (/\[object Object\]/.test(reconstructFrontmatter(newObj))) {
|
|
469
|
+
throw new Error('frontmatter: cannot faithfully serialize the requested frontmatter — it contains a nested ' +
|
|
470
|
+
'object-list (e.g. must_haves.artifacts) the writer cannot represent. Edit the file directly.');
|
|
471
|
+
}
|
|
472
|
+
// Key by key through `regenerateFrontmatterKey`, so a key the parser would misread bare
|
|
473
|
+
// (`a: b`, `#x`, one holding a line break) is quoted exactly as on the existing-block path.
|
|
474
|
+
const keyLines = Object.keys(newObj)
|
|
475
|
+
.map((k) => regenerateFrontmatterKey(k, newObj[k]))
|
|
476
|
+
.filter((line) => line !== '');
|
|
477
|
+
// #2977: a leading BOM stays the document's first character, ahead of the new fence.
|
|
478
|
+
const bom = content.charCodeAt(0) === 0xFEFF ? content.slice(0, 1) : '';
|
|
479
|
+
return bom + verifyReadsBackAs(`---\n${keyLines.join('\n')}\n---\n\n` + content.slice(bom.length), newObj);
|
|
480
|
+
}
|
|
481
|
+
module.exports = {
|
|
482
|
+
spliceFrontmatter,
|
|
483
|
+
// The one owner of "may a writer splice this frontmatter block?" — callers catch
|
|
484
|
+
// `isFrontmatterWriteRefusal(err)` and surface `err.message`/`err.code`.
|
|
485
|
+
FrontmatterWriteRefusedError,
|
|
486
|
+
isFrontmatterWriteRefusal,
|
|
487
|
+
// The parse allowance past which `spliceFrontmatter` refuses with FRONTMATTER_TOO_COMPLEX —
|
|
488
|
+
// exported so its boundary can be exercised exactly.
|
|
489
|
+
SPLICE_PARSE_BUDGET_CHARS,
|
|
490
|
+
// `frontmatter.cts`'s set/merge #1660 lossy-field check (`objectListFieldWouldLoseData`)
|
|
491
|
+
// asks the writer what a key regenerates to and what a value reads back as.
|
|
492
|
+
regenerateFrontmatterKey,
|
|
493
|
+
readBackProjection,
|
|
494
|
+
};
|