@opengsd/gsd-core 1.15.0 → 1.17.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 -2
- package/.opencode/plugins/gsd-core.js +336 -6
- package/.opencode/plugins/gsd-statusline/tui.tsx +55 -0
- package/GEMINI.md +1 -1
- package/agents/gsd-code-fixer.compact.md +1 -1
- package/agents/gsd-code-fixer.md +1 -1
- 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 +7 -1
- package/agents/gsd-debug-session-manager.md +7 -1
- package/agents/gsd-debugger.md +2 -2
- package/agents/gsd-eval-auditor.compact.md +1 -1
- package/agents/gsd-eval-auditor.md +1 -1
- package/agents/gsd-executor.md +4 -4
- 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 +1 -1
- package/agents/gsd-plan-checker.md +7 -2
- package/agents/gsd-planner.md +3 -3
- 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 +42 -51
- package/agents/gsd-ui-researcher.compact.md +1 -1
- package/agents/gsd-ui-researcher.md +1 -1
- package/agents/gsd-verifier.md +31 -9
- package/bin/gsd-mcp-server.js +3 -1
- package/bin/install.js +487 -290
- 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 +5 -0
- package/commands/gsd/capture.md +8 -4
- 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 +5 -1
- 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 +5 -1
- package/commands/gsd/pr-branch.md +4 -0
- package/commands/gsd/profile-user.md +5 -1
- package/commands/gsd/progress.md +6 -1
- package/commands/gsd/quick-batch.md +20 -8
- package/commands/gsd/quick.md +12 -7
- package/commands/gsd/review.md +5 -1
- 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 +159 -58
- package/gsd-core/bin/lib/active-workstream-store.cjs +15 -0
- package/gsd-core/bin/lib/adr-parser.cjs +37 -5
- package/gsd-core/bin/lib/agent-install-check.cjs +4 -1
- package/gsd-core/bin/lib/audit.cjs +78 -44
- 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 +249 -117
- package/gsd-core/bin/lib/capability-validator.cjs +18 -1
- package/gsd-core/bin/lib/capability-writer.cjs +2 -1
- package/gsd-core/bin/lib/check-auto-mode.cjs +35 -0
- package/gsd-core/bin/lib/check-command-router.cjs +164 -1712
- 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 +11 -8
- 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 +201 -178
- package/gsd-core/bin/lib/complexity-trigger.cjs +8 -7
- package/gsd-core/bin/lib/config.cjs +43 -12
- 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/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 +413 -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 +142 -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 +81 -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 +3 -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/init.cjs +194 -108
- package/gsd-core/bin/lib/install-engine.cjs +30 -35
- package/gsd-core/bin/lib/install-profiles.cjs +7 -4
- package/gsd-core/bin/lib/install-shadow-report.cjs +12 -0
- package/gsd-core/bin/lib/installer-migrations/011-opencode-mcp-no-prompts.cjs +99 -0
- package/gsd-core/bin/lib/installer-migrations.cjs +54 -1
- package/gsd-core/bin/lib/io.cjs +122 -3
- package/gsd-core/bin/lib/jsonc-edit.cjs +418 -0
- package/gsd-core/bin/lib/loop-host-contract.cjs +2 -1
- 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/mcp-server.cjs +5 -1
- package/gsd-core/bin/lib/milestone.cjs +19 -1
- package/gsd-core/bin/lib/model-resolver.cjs +18 -18
- 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/opencode-mcp-entry.cjs +39 -0
- package/gsd-core/bin/lib/opencode-version.cjs +486 -0
- package/gsd-core/bin/lib/pattern.cjs +10 -0
- package/gsd-core/bin/lib/phase-command-router.cjs +11 -4
- package/gsd-core/bin/lib/phase-estimation.cjs +5 -4
- package/gsd-core/bin/lib/phase-id.cjs +1 -1
- package/gsd-core/bin/lib/phase-lifecycle.cjs +9 -2
- package/gsd-core/bin/lib/phase-status.cjs +360 -0
- package/gsd-core/bin/lib/phase.cjs +301 -80
- package/gsd-core/bin/lib/plan-document.cjs +93 -19
- package/gsd-core/bin/lib/plan-drift-guard.cjs +5 -0
- package/gsd-core/bin/lib/planning-document.cjs +273 -40
- package/gsd-core/bin/lib/planning-inspect.cjs +42 -8
- package/gsd-core/bin/lib/planning-snapshot.cjs +18 -0
- package/gsd-core/bin/lib/planning-workspace.cjs +76 -53
- package/gsd-core/bin/lib/pristine-baseline.cjs +10 -0
- 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/quick-batch-command-router.cjs +35 -9
- package/gsd-core/bin/lib/quick-batch-dispatch.cjs +13 -9
- 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/roadmap-command-router.cjs +13 -15
- package/gsd-core/bin/lib/roadmap-parser.cjs +82 -15
- package/gsd-core/bin/lib/roadmap-upgrade.cjs +127 -65
- package/gsd-core/bin/lib/roadmap.cjs +169 -72
- package/gsd-core/bin/lib/runtime-artifact-conversion.cjs +186 -161
- package/gsd-core/bin/lib/runtime-artifact-install-plan.cjs +2 -1
- package/gsd-core/bin/lib/runtime-artifact-layout.cjs +57 -1
- package/gsd-core/bin/lib/runtime-homes.cjs +13 -10
- package/gsd-core/bin/lib/runtime-hooks-surface.cjs +365 -594
- package/gsd-core/bin/lib/runtime-name-policy.cjs +142 -27
- package/gsd-core/bin/lib/runtime-slash.cjs +60 -30
- package/gsd-core/bin/lib/shell-command-projection.cjs +37 -2
- package/gsd-core/bin/lib/smart-entry.cjs +111 -56
- 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 +67 -9
- package/gsd-core/bin/lib/state.cjs +668 -171
- package/gsd-core/bin/lib/surface.cjs +2 -1
- package/gsd-core/bin/lib/tdd-red-evidence.cjs +48 -152
- package/gsd-core/bin/lib/uat-predicate.cjs +313 -35
- package/gsd-core/bin/lib/uat.cjs +424 -7
- package/gsd-core/bin/lib/ui-frontend-evidence.cjs +94 -58
- package/gsd-core/bin/lib/update-context.cjs +18 -1
- 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 +1265 -324
- package/gsd-core/bin/lib/verify-command-router.cjs +18 -7
- package/gsd-core/bin/lib/verify.cjs +286 -567
- 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/shared/config-defaults.manifest.json +5 -0
- package/gsd-core/bin/shared/config-schema.manifest.json +4 -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/execute-mvp-tdd.md +5 -10
- package/gsd-core/references/execute-phase-between-wave-reset.md +3 -0
- package/gsd-core/references/execute-phase-response-language.md +1 -1
- package/gsd-core/references/gsd-run-resolver.md +1 -1
- package/gsd-core/references/loop-hook-dispatch.md +7 -1
- package/gsd-core/references/offer-next.md +1 -1
- package/gsd-core/references/planning-config.md +1 -1
- package/gsd-core/references/spidr-splitting.md +1 -1
- package/gsd-core/references/tdd.md +10 -4
- package/gsd-core/references/verifier-phase-gates.md +5 -2
- package/gsd-core/references/verify-mvp-mode.md +2 -2
- package/gsd-core/references/workstream-flag.md +33 -3
- 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 +3 -2
- package/gsd-core/workflows/add-todo.md +4 -3
- package/gsd-core/workflows/ai-integration-phase.md +3 -2
- 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.md +28 -18
- package/gsd-core/workflows/check-todos.md +6 -5
- package/gsd-core/workflows/cleanup.md +1 -1
- package/gsd-core/workflows/code-review/steps/structural-pre-pass.md +20 -13
- package/gsd-core/workflows/code-review-fix.md +5 -4
- package/gsd-core/workflows/code-review.md +100 -91
- package/gsd-core/workflows/complete-milestone/detail/elaboration.md +4 -3
- package/gsd-core/workflows/complete-milestone.md +12 -7
- package/gsd-core/workflows/debug.md +30 -5
- 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 +1 -1
- package/gsd-core/workflows/docs-update.md +4 -3
- package/gsd-core/workflows/edit-phase.md +4 -3
- package/gsd-core/workflows/eval-review.md +5 -3
- package/gsd-core/workflows/execute-phase/detail/elaboration.md +1 -1
- package/gsd-core/workflows/execute-phase/steps/code-review-disposition.md +4 -2
- 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 +2 -2
- 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 +1 -1
- package/gsd-core/workflows/execute-phase/steps/protected-branch.md +1 -1
- 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/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.md +81 -171
- package/gsd-core/workflows/execute-plan.md +13 -19
- package/gsd-core/workflows/explore.md +2 -2
- package/gsd-core/workflows/extract-learnings.md +3 -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 +2 -1
- package/gsd-core/workflows/import.md +3 -2
- package/gsd-core/workflows/inbox.md +1 -1
- package/gsd-core/workflows/ingest-docs.md +1 -1
- 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 +5 -3
- package/gsd-core/workflows/map-codebase.md +4 -3
- 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 +10 -10
- package/gsd-core/workflows/new-project/steps/auto-mode-config.md +1 -1
- package/gsd-core/workflows/new-project.md +1 -1
- package/gsd-core/workflows/new-workspace.md +1 -1
- package/gsd-core/workflows/next.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 +1 -1
- 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 +1 -1
- package/gsd-core/workflows/plan-phase.md +27 -18
- package/gsd-core/workflows/plan-review-convergence.md +1 -1
- package/gsd-core/workflows/plant-seed.md +1 -1
- package/gsd-core/workflows/pr-branch.md +1 -1
- package/gsd-core/workflows/profile-user.md +1 -1
- package/gsd-core/workflows/progress.md +19 -49
- package/gsd-core/workflows/quick/steps/plan-checker-loop.md +4 -2
- 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 +42 -31
- package/gsd-core/workflows/remove-phase.md +3 -2
- package/gsd-core/workflows/remove-workspace.md +1 -1
- package/gsd-core/workflows/resume-project.md +17 -12
- package/gsd-core/workflows/review.md +3 -2
- package/gsd-core/workflows/scan.md +3 -2
- package/gsd-core/workflows/secure-phase.md +11 -12
- package/gsd-core/workflows/settings-advanced.md +1 -1
- package/gsd-core/workflows/settings-integrations.md +1 -1
- package/gsd-core/workflows/settings.md +1 -1
- package/gsd-core/workflows/ship.md +12 -12
- 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 +1 -1
- package/gsd-core/workflows/spec-phase.md +1 -1
- 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 +1 -1
- package/gsd-core/workflows/thread.md +2 -2
- package/gsd-core/workflows/transition.md +13 -23
- package/gsd-core/workflows/ui-phase.md +5 -4
- package/gsd-core/workflows/ui-review.md +4 -3
- package/gsd-core/workflows/ultraplan-phase.md +3 -2
- package/gsd-core/workflows/undo.md +1 -1
- package/gsd-core/workflows/validate-phase.md +10 -12
- 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 +68 -59
- package/hooks/dist/gsd-agent-isolation-guard.js +8 -0
- package/hooks/dist/gsd-check-update-worker.js +8 -0
- package/hooks/dist/gsd-check-update.js +34 -20
- package/hooks/dist/gsd-context-monitor.js +45 -13
- package/hooks/dist/gsd-cursor-subagent-start.js +8 -0
- package/hooks/dist/gsd-secret-read-guard.js +263 -16
- package/hooks/dist/gsd-statusline.js +336 -29
- package/hooks/dist/gsd-update-banner.js +8 -0
- 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 +8 -0
- package/hooks/gsd-check-update-worker.js +8 -0
- package/hooks/gsd-check-update.js +34 -20
- package/hooks/gsd-context-monitor.js +45 -13
- package/hooks/gsd-cursor-subagent-start.js +8 -0
- package/hooks/gsd-secret-read-guard.js +263 -16
- package/hooks/gsd-statusline.js +336 -29
- package/hooks/gsd-update-banner.js +8 -0
- 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 +21 -4
- package/scripts/changeset/parse.cjs +52 -4
- package/scripts/ci-timeout-report.cjs +770 -4
- package/scripts/command-contract-helpers.cjs +12 -8
- package/scripts/docs-guard-registry.cjs +6 -0
- package/scripts/gen-capability-registry.cjs +7 -1
- package/scripts/gen-features.cjs +13 -8
- package/scripts/gen-hooks-cli-exit.cjs +12 -28
- package/scripts/gen-loop-host-contract.cjs +14 -2
- 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 +24 -2
- package/scripts/lib/ndjson-reporter.cjs +28 -3
- package/scripts/lib/platform-conformance-tier.generated.cjs +44 -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 +10 -0
- 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-enumeration-drift.cjs +12 -8
- package/scripts/lint-phase-id-drift.cjs +29 -0
- package/scripts/lint-planning-document-positive-control.cjs +329 -0
- package/scripts/lint-response-language-coverage.cjs +3 -0
- package/scripts/lint-skill-deps.cjs +7 -3
- package/scripts/lint-test-file-count.allowlist.json +11 -1
- package/scripts/lint-test-file-count.cjs +34 -1
- package/scripts/lint-vendored-deps.cjs +41 -5
- package/scripts/lint-workflow-shellcheck-baseline.json +10 -10
- package/scripts/mutation-matrix.cjs +53 -6
- package/scripts/release-tarball-smoke.cjs +14 -7
- 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 +5 -0
- package/skills/gsd-capture/SKILL.md +8 -4
- 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 +5 -1
- 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 +5 -1
- package/skills/gsd-pr-branch/SKILL.md +4 -0
- package/skills/gsd-profile-user/SKILL.md +5 -1
- package/skills/gsd-progress/SKILL.md +6 -1
- package/skills/gsd-quick/SKILL.md +16 -10
- package/skills/gsd-quick-batch/SKILL.md +20 -8
- package/skills/gsd-review/SKILL.md +5 -1
- 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
- package/gsd-core/workflows/execute-phase/steps/stale-reverification.md +0 -24
|
@@ -0,0 +1,418 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
/**
|
|
3
|
+
* Comment-preserving JSONC editing (issue #5334; epic #5331, Phase 2 #5338).
|
|
4
|
+
*
|
|
5
|
+
* OpenCode (1.x and 2.x) and Kilo read `opencode.jsonc`/`kilo.jsonc`, so users annotate them. The
|
|
6
|
+
* installer used to parse such a file and write it back with `JSON.stringify`, which dropped every
|
|
7
|
+
* comment, trailing comma and the user's layout. This module edits the TEXT instead: it locates the
|
|
8
|
+
* property GSD owns and splices only that, leaving every other byte as the user wrote it.
|
|
9
|
+
*
|
|
10
|
+
* Scope is deliberately small — set a value at a key path, remove a key path (pruning objects the
|
|
11
|
+
* removal emptied). It is not a general JSONC formatter. A document it cannot parse or whose root is
|
|
12
|
+
* not an object is reported as `null` so the caller can keep its existing behavior.
|
|
13
|
+
*/
|
|
14
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
15
|
+
exports.hasJsoncFeatures = hasJsoncFeatures;
|
|
16
|
+
exports.setJsoncValue = setJsoncValue;
|
|
17
|
+
exports.removeJsoncValue = removeJsoncValue;
|
|
18
|
+
exports.isEditableJsonc = isEditableJsonc;
|
|
19
|
+
exports.parseJsonc = parseJsonc;
|
|
20
|
+
class JsoncSyntaxError extends Error {
|
|
21
|
+
}
|
|
22
|
+
function skipTrivia(text, from) {
|
|
23
|
+
let i = from;
|
|
24
|
+
while (i < text.length) {
|
|
25
|
+
const c = text[i];
|
|
26
|
+
if (c === ' ' || c === '\t' || c === '\n' || c === '\r') {
|
|
27
|
+
i++;
|
|
28
|
+
}
|
|
29
|
+
else if (c === '/' && text[i + 1] === '/') {
|
|
30
|
+
while (i < text.length && text[i] !== '\n')
|
|
31
|
+
i++;
|
|
32
|
+
}
|
|
33
|
+
else if (c === '/' && text[i + 1] === '*') {
|
|
34
|
+
const close = text.indexOf('*/', i + 2);
|
|
35
|
+
if (close === -1)
|
|
36
|
+
throw new JsoncSyntaxError('unterminated block comment');
|
|
37
|
+
i = close + 2;
|
|
38
|
+
}
|
|
39
|
+
else {
|
|
40
|
+
break;
|
|
41
|
+
}
|
|
42
|
+
}
|
|
43
|
+
return i;
|
|
44
|
+
}
|
|
45
|
+
function scanString(text, from) {
|
|
46
|
+
let i = from + 1;
|
|
47
|
+
while (i < text.length) {
|
|
48
|
+
const c = text[i];
|
|
49
|
+
if (c === '\\') {
|
|
50
|
+
i += 2;
|
|
51
|
+
}
|
|
52
|
+
else if (c === '"') {
|
|
53
|
+
const raw = text.slice(from, i + 1);
|
|
54
|
+
return { value: JSON.parse(raw), end: i + 1 };
|
|
55
|
+
}
|
|
56
|
+
else if (c === '\n') {
|
|
57
|
+
throw new JsoncSyntaxError('unterminated string');
|
|
58
|
+
}
|
|
59
|
+
else {
|
|
60
|
+
i++;
|
|
61
|
+
}
|
|
62
|
+
}
|
|
63
|
+
throw new JsoncSyntaxError('unterminated string');
|
|
64
|
+
}
|
|
65
|
+
function parseValue(text, from) {
|
|
66
|
+
const i = skipTrivia(text, from);
|
|
67
|
+
const c = text[i];
|
|
68
|
+
if (c === '{')
|
|
69
|
+
return parseObject(text, i);
|
|
70
|
+
if (c === '[')
|
|
71
|
+
return parseArray(text, i);
|
|
72
|
+
if (c === '"') {
|
|
73
|
+
const s = scanString(text, i);
|
|
74
|
+
return { kind: 'string', start: i, end: s.end, props: [] };
|
|
75
|
+
}
|
|
76
|
+
const m = /^(?:-?\d+(?:\.\d+)?(?:[eE][+-]?\d+)?|true|false|null)/.exec(text.slice(i, i + 64));
|
|
77
|
+
if (!m)
|
|
78
|
+
throw new JsoncSyntaxError(`unexpected token at ${i}`);
|
|
79
|
+
return { kind: m[0] === 'true' || m[0] === 'false' || m[0] === 'null' ? 'literal' : 'number', start: i, end: i + m[0].length, props: [] };
|
|
80
|
+
}
|
|
81
|
+
function parseArray(text, from) {
|
|
82
|
+
let i = from + 1;
|
|
83
|
+
for (;;) {
|
|
84
|
+
i = skipTrivia(text, i);
|
|
85
|
+
if (text[i] === ']')
|
|
86
|
+
return { kind: 'array', start: from, end: i + 1, props: [] };
|
|
87
|
+
const v = parseValue(text, i);
|
|
88
|
+
i = skipTrivia(text, v.end);
|
|
89
|
+
if (text[i] === ',')
|
|
90
|
+
i++;
|
|
91
|
+
else if (text[i] !== ']')
|
|
92
|
+
throw new JsoncSyntaxError(`expected , or ] at ${i}`);
|
|
93
|
+
}
|
|
94
|
+
}
|
|
95
|
+
function parseObject(text, from) {
|
|
96
|
+
const props = [];
|
|
97
|
+
let i = from + 1;
|
|
98
|
+
for (;;) {
|
|
99
|
+
i = skipTrivia(text, i);
|
|
100
|
+
if (text[i] === '}')
|
|
101
|
+
return { kind: 'object', start: from, end: i + 1, props };
|
|
102
|
+
if (text[i] !== '"')
|
|
103
|
+
throw new JsoncSyntaxError(`expected key at ${i}`);
|
|
104
|
+
const k = scanString(text, i);
|
|
105
|
+
let j = skipTrivia(text, k.end);
|
|
106
|
+
if (text[j] !== ':')
|
|
107
|
+
throw new JsoncSyntaxError(`expected : at ${j}`);
|
|
108
|
+
const value = parseValue(text, j + 1);
|
|
109
|
+
const prop = { key: k.value, start: i, end: value.end, value, commaAfter: -1 };
|
|
110
|
+
props.push(prop);
|
|
111
|
+
j = skipTrivia(text, value.end);
|
|
112
|
+
if (text[j] === ',') {
|
|
113
|
+
prop.commaAfter = j;
|
|
114
|
+
i = j + 1;
|
|
115
|
+
}
|
|
116
|
+
else if (text[j] === '}') {
|
|
117
|
+
i = j;
|
|
118
|
+
}
|
|
119
|
+
else {
|
|
120
|
+
throw new JsoncSyntaxError(`expected , or } at ${j}`);
|
|
121
|
+
}
|
|
122
|
+
}
|
|
123
|
+
}
|
|
124
|
+
function parseRoot(text) {
|
|
125
|
+
try {
|
|
126
|
+
const bom = text.charCodeAt(0) === 0xfeff ? 1 : 0;
|
|
127
|
+
const i = skipTrivia(text, bom);
|
|
128
|
+
if (text[i] !== '{')
|
|
129
|
+
return null;
|
|
130
|
+
const root = parseObject(text, i);
|
|
131
|
+
if (skipTrivia(text, root.end) !== text.length)
|
|
132
|
+
return null;
|
|
133
|
+
return root;
|
|
134
|
+
}
|
|
135
|
+
catch (e) {
|
|
136
|
+
if (e instanceof JsoncSyntaxError || e instanceof SyntaxError)
|
|
137
|
+
return null;
|
|
138
|
+
throw e;
|
|
139
|
+
}
|
|
140
|
+
}
|
|
141
|
+
/** True when `text` carries JSONC-only syntax (comments or a trailing comma) that a rewrite would lose. */
|
|
142
|
+
function hasJsoncFeatures(text) {
|
|
143
|
+
let i = text.charCodeAt(0) === 0xfeff ? 1 : 0;
|
|
144
|
+
while (i < text.length) {
|
|
145
|
+
const c = text[i];
|
|
146
|
+
if (c === '"') {
|
|
147
|
+
try {
|
|
148
|
+
i = scanString(text, i).end;
|
|
149
|
+
}
|
|
150
|
+
catch {
|
|
151
|
+
return false;
|
|
152
|
+
}
|
|
153
|
+
}
|
|
154
|
+
else if (c === '/' && (text[i + 1] === '/' || text[i + 1] === '*')) {
|
|
155
|
+
return true;
|
|
156
|
+
}
|
|
157
|
+
else if (c === ',') {
|
|
158
|
+
const j = skipTrivia(text, i + 1);
|
|
159
|
+
if (text[j] === '}' || text[j] === ']')
|
|
160
|
+
return true;
|
|
161
|
+
i++;
|
|
162
|
+
}
|
|
163
|
+
else {
|
|
164
|
+
i++;
|
|
165
|
+
}
|
|
166
|
+
}
|
|
167
|
+
return false;
|
|
168
|
+
}
|
|
169
|
+
// ─── Layout helpers ──────────────────────────────────────────────────────────
|
|
170
|
+
function lineStart(text, pos) {
|
|
171
|
+
const nl = text.lastIndexOf('\n', pos - 1);
|
|
172
|
+
return nl === -1 ? 0 : nl + 1;
|
|
173
|
+
}
|
|
174
|
+
function indentOf(text, pos) {
|
|
175
|
+
const ls = lineStart(text, pos);
|
|
176
|
+
const lead = text.slice(ls, pos);
|
|
177
|
+
return /^[ \t]*$/.test(lead) ? lead : null;
|
|
178
|
+
}
|
|
179
|
+
function detectUnit(text, root) {
|
|
180
|
+
const first = root.props[0];
|
|
181
|
+
const lead = first ? indentOf(text, first.start) : null;
|
|
182
|
+
if (lead && lead.length > 0)
|
|
183
|
+
return lead;
|
|
184
|
+
return ' ';
|
|
185
|
+
}
|
|
186
|
+
/** Indentation for children of `node`, given the indentation of the line that opens it. */
|
|
187
|
+
function childIndent(text, node, openerIndent, unit) {
|
|
188
|
+
const first = node.props[0];
|
|
189
|
+
const lead = first ? indentOf(text, first.start) : null;
|
|
190
|
+
return lead !== null && lead.length > 0 ? lead : openerIndent + unit;
|
|
191
|
+
}
|
|
192
|
+
function serialize(value, baseIndent, unit) {
|
|
193
|
+
// A short array of primitives stays on one line, the way people write command arrays by hand.
|
|
194
|
+
if (Array.isArray(value) && value.every((v) => v === null || ['string', 'number', 'boolean'].includes(typeof v))) {
|
|
195
|
+
const inline = `[${value.map((v) => JSON.stringify(v)).join(', ')}]`;
|
|
196
|
+
if (inline.length <= 100)
|
|
197
|
+
return inline;
|
|
198
|
+
}
|
|
199
|
+
return JSON.stringify(value, null, unit).split('\n').join('\n' + baseIndent);
|
|
200
|
+
}
|
|
201
|
+
function nest(path, value) {
|
|
202
|
+
let v = value;
|
|
203
|
+
for (let i = path.length - 1; i >= 0; i--)
|
|
204
|
+
v = { [path[i]]: v };
|
|
205
|
+
return v;
|
|
206
|
+
}
|
|
207
|
+
function endOfSameLineTrivia(text, from) {
|
|
208
|
+
let i = from;
|
|
209
|
+
for (;;) {
|
|
210
|
+
while (text[i] === ' ' || text[i] === '\t')
|
|
211
|
+
i++;
|
|
212
|
+
if (text[i] === '/' && text[i + 1] === '/') {
|
|
213
|
+
while (i < text.length && text[i] !== '\n')
|
|
214
|
+
i++;
|
|
215
|
+
return i;
|
|
216
|
+
}
|
|
217
|
+
if (text[i] === '/' && text[i + 1] === '*') {
|
|
218
|
+
const close = text.indexOf('*/', i + 2);
|
|
219
|
+
if (close === -1)
|
|
220
|
+
return i;
|
|
221
|
+
if (text.slice(i, close).includes('\n'))
|
|
222
|
+
return i;
|
|
223
|
+
i = close + 2;
|
|
224
|
+
continue;
|
|
225
|
+
}
|
|
226
|
+
return i;
|
|
227
|
+
}
|
|
228
|
+
}
|
|
229
|
+
function splice(text, start, end, insert) {
|
|
230
|
+
return text.slice(0, start) + insert + text.slice(end);
|
|
231
|
+
}
|
|
232
|
+
// ─── Operations ──────────────────────────────────────────────────────────────
|
|
233
|
+
function insertProperty(text, node, openerIndent, unit, key, value) {
|
|
234
|
+
const indent = childIndent(text, node, openerIndent, unit);
|
|
235
|
+
const rendered = `${JSON.stringify(key)}: ${serialize(value, indent, unit)}`;
|
|
236
|
+
if (node.props.length === 0) {
|
|
237
|
+
const interior = text.slice(node.start + 1, node.end - 1);
|
|
238
|
+
if (interior.trim() === '') {
|
|
239
|
+
return splice(text, node.start + 1, node.end - 1, `\n${indent}${rendered}\n${openerIndent}`);
|
|
240
|
+
}
|
|
241
|
+
// Only comments inside: keep them, add the property after the last one.
|
|
242
|
+
let at = node.end - 1;
|
|
243
|
+
while (at > node.start + 1 && /\s/.test(text[at - 1]))
|
|
244
|
+
at--;
|
|
245
|
+
return splice(text, at, at, `\n${indent}${rendered}`);
|
|
246
|
+
}
|
|
247
|
+
const last = node.props[node.props.length - 1];
|
|
248
|
+
if (last.commaAfter >= 0) {
|
|
249
|
+
// Trailing-comma style: keep it for the new property.
|
|
250
|
+
const at = endOfSameLineTrivia(text, last.commaAfter + 1);
|
|
251
|
+
return splice(text, at, at, `\n${indent}${rendered},`);
|
|
252
|
+
}
|
|
253
|
+
const at = endOfSameLineTrivia(text, last.end);
|
|
254
|
+
const withProp = splice(text, at, at, `\n${indent}${rendered}`);
|
|
255
|
+
return splice(withProp, last.end, last.end, ',');
|
|
256
|
+
}
|
|
257
|
+
function removeProperty(text, node, index) {
|
|
258
|
+
const prop = node.props[index];
|
|
259
|
+
let end = prop.commaAfter >= 0 ? prop.commaAfter + 1 : prop.end;
|
|
260
|
+
end = endOfSameLineTrivia(text, end);
|
|
261
|
+
if (text[end] === '\r')
|
|
262
|
+
end++;
|
|
263
|
+
if (text[end] === '\n')
|
|
264
|
+
end++;
|
|
265
|
+
const lead = indentOf(text, prop.start);
|
|
266
|
+
const start = lead !== null ? lineStart(text, prop.start) : prop.start;
|
|
267
|
+
if (lead === null) {
|
|
268
|
+
// Sharing a line with other content: do not swallow the line break.
|
|
269
|
+
end = prop.commaAfter >= 0 ? prop.commaAfter + 1 : prop.end;
|
|
270
|
+
while (text[end] === ' ')
|
|
271
|
+
end++;
|
|
272
|
+
}
|
|
273
|
+
let out = splice(text, start, end, '');
|
|
274
|
+
// The removed property was last with no trailing comma: drop the comma that preceded it.
|
|
275
|
+
if (prop.commaAfter < 0 && index > 0) {
|
|
276
|
+
const prev = node.props[index - 1];
|
|
277
|
+
if (prev.commaAfter >= 0 && prev.commaAfter < start) {
|
|
278
|
+
out = splice(out, prev.commaAfter, prev.commaAfter + 1, '');
|
|
279
|
+
}
|
|
280
|
+
}
|
|
281
|
+
return out;
|
|
282
|
+
}
|
|
283
|
+
function descend(root, path) {
|
|
284
|
+
const chain = [root];
|
|
285
|
+
let node = root;
|
|
286
|
+
for (let i = 0; i < path.length; i++) {
|
|
287
|
+
const prop = node.props.find((p) => p.key === path[i]);
|
|
288
|
+
if (!prop)
|
|
289
|
+
return { chain, hit: null, depth: i };
|
|
290
|
+
if (i === path.length - 1)
|
|
291
|
+
return { chain, hit: prop, depth: i };
|
|
292
|
+
if (prop.value.kind !== 'object')
|
|
293
|
+
return { chain, hit: prop, depth: i };
|
|
294
|
+
node = prop.value;
|
|
295
|
+
chain.push(node);
|
|
296
|
+
}
|
|
297
|
+
return { chain, hit: null, depth: path.length };
|
|
298
|
+
}
|
|
299
|
+
function openerIndentFor(text, chain, depth, unit) {
|
|
300
|
+
// Indentation of the line holding the opener of chain[depth]; the root opens at column 0.
|
|
301
|
+
if (depth === 0)
|
|
302
|
+
return '';
|
|
303
|
+
const node = chain[depth];
|
|
304
|
+
const lead = indentOf(text, node.start);
|
|
305
|
+
if (lead !== null)
|
|
306
|
+
return lead;
|
|
307
|
+
// `"key": {` on a line of its own: use the key's line indentation.
|
|
308
|
+
const parentProps = (chain[depth - 1]).props;
|
|
309
|
+
const owner = parentProps.find((p) => p.value === node);
|
|
310
|
+
const keyLead = owner ? indentOf(text, owner.start) : null;
|
|
311
|
+
return keyLead ?? unit.repeat(depth);
|
|
312
|
+
}
|
|
313
|
+
/**
|
|
314
|
+
* Set `path` to `value`, creating missing objects. Returns the edited text, or `null` when the
|
|
315
|
+
* document is not an object-rooted JSONC document.
|
|
316
|
+
*/
|
|
317
|
+
function setJsoncValue(text, path, value) {
|
|
318
|
+
const root = parseRoot(text);
|
|
319
|
+
if (!root || path.length === 0)
|
|
320
|
+
return null;
|
|
321
|
+
const unit = detectUnit(text, root);
|
|
322
|
+
const { chain, hit, depth } = descend(root, path);
|
|
323
|
+
const atLeaf = hit !== null && depth === path.length - 1;
|
|
324
|
+
if (atLeaf && (hit).value.kind !== undefined) {
|
|
325
|
+
const prop = hit;
|
|
326
|
+
const base = indentOf(text, prop.start) ?? '';
|
|
327
|
+
return splice(text, prop.value.start, prop.value.end, serialize(value, base, unit));
|
|
328
|
+
}
|
|
329
|
+
if (hit !== null) {
|
|
330
|
+
// A non-object sits where an object is needed: replace it with the nested structure.
|
|
331
|
+
const base = indentOf(text, hit.start) ?? '';
|
|
332
|
+
return splice(text, hit.value.start, hit.value.end, serialize(nest(path.slice(depth + 1), value), base, unit));
|
|
333
|
+
}
|
|
334
|
+
const node = chain[chain.length - 1];
|
|
335
|
+
const parentDepth = chain.length - 1;
|
|
336
|
+
const opener = openerIndentFor(text, chain, parentDepth, unit);
|
|
337
|
+
const key = path[parentDepth];
|
|
338
|
+
return insertProperty(text, node, opener, unit, key, nest(path.slice(parentDepth + 1), value));
|
|
339
|
+
}
|
|
340
|
+
/**
|
|
341
|
+
* Remove the key at `path`, then remove any ancestor object (never the root) the removal emptied.
|
|
342
|
+
* Returns the edited text, or `null` when the document is not an object-rooted JSONC document.
|
|
343
|
+
* A path that does not exist leaves the text unchanged.
|
|
344
|
+
*/
|
|
345
|
+
function removeJsoncValue(text, path) {
|
|
346
|
+
let root = parseRoot(text);
|
|
347
|
+
if (!root || path.length === 0)
|
|
348
|
+
return null;
|
|
349
|
+
let current = text;
|
|
350
|
+
let target = path;
|
|
351
|
+
for (;;) {
|
|
352
|
+
const { chain, hit, depth } = descend(root, target);
|
|
353
|
+
if (hit === null || depth !== target.length - 1)
|
|
354
|
+
return current;
|
|
355
|
+
const parent = chain[chain.length - 1];
|
|
356
|
+
current = removeProperty(current, parent, parent.props.indexOf(hit));
|
|
357
|
+
root = parseRoot(current);
|
|
358
|
+
if (!root || target.length === 1)
|
|
359
|
+
return current;
|
|
360
|
+
target = target.slice(0, -1);
|
|
361
|
+
const again = descend(root, target);
|
|
362
|
+
const holder = again.hit;
|
|
363
|
+
if (holder === null || again.depth !== target.length - 1 || holder.value.kind !== 'object' || holder.value.props.length > 0) {
|
|
364
|
+
return current;
|
|
365
|
+
}
|
|
366
|
+
}
|
|
367
|
+
}
|
|
368
|
+
/** Parse tolerantly for callers that only need to know whether the text is edit-able. */
|
|
369
|
+
function isEditableJsonc(text) {
|
|
370
|
+
return parseRoot(text) !== null;
|
|
371
|
+
}
|
|
372
|
+
/**
|
|
373
|
+
* Parse JSONC (JSON with comments and trailing commas) into a value. Throws a `SyntaxError` (from
|
|
374
|
+
* `JSON.parse`) on malformed input, like `JSON.parse` itself — callers keep their existing
|
|
375
|
+
* "could not parse, do not touch the file" handling.
|
|
376
|
+
*/
|
|
377
|
+
function parseJsonc(content) {
|
|
378
|
+
let text = content.charCodeAt(0) === 0xfeff ? content.slice(1) : content;
|
|
379
|
+
let stripped = '';
|
|
380
|
+
let inString = false;
|
|
381
|
+
let i = 0;
|
|
382
|
+
while (i < text.length) {
|
|
383
|
+
const c = text[i];
|
|
384
|
+
const next = text[i + 1];
|
|
385
|
+
if (inString) {
|
|
386
|
+
stripped += c;
|
|
387
|
+
if (c === '\\' && i + 1 < text.length) {
|
|
388
|
+
stripped += next;
|
|
389
|
+
i += 2;
|
|
390
|
+
continue;
|
|
391
|
+
}
|
|
392
|
+
if (c === '"')
|
|
393
|
+
inString = false;
|
|
394
|
+
i++;
|
|
395
|
+
}
|
|
396
|
+
else if (c === '"') {
|
|
397
|
+
inString = true;
|
|
398
|
+
stripped += c;
|
|
399
|
+
i++;
|
|
400
|
+
}
|
|
401
|
+
else if (c === '/' && next === '/') {
|
|
402
|
+
while (i < text.length && text[i] !== '\n')
|
|
403
|
+
i++;
|
|
404
|
+
}
|
|
405
|
+
else if (c === '/' && next === '*') {
|
|
406
|
+
i += 2;
|
|
407
|
+
while (i < text.length - 1 && !(text[i] === '*' && text[i + 1] === '/'))
|
|
408
|
+
i++;
|
|
409
|
+
i += 2;
|
|
410
|
+
}
|
|
411
|
+
else {
|
|
412
|
+
stripped += c;
|
|
413
|
+
i++;
|
|
414
|
+
}
|
|
415
|
+
}
|
|
416
|
+
text = stripped.replace(/,(\s*[}\]])/g, '$1');
|
|
417
|
+
return JSON.parse(text);
|
|
418
|
+
}
|
|
@@ -28,10 +28,19 @@
|
|
|
28
28
|
* - capability-registry.cjs (byLoopPoint, consumed at call time)
|
|
29
29
|
* - capability-state.cjs (resolveCapabilityRuntimeState — for capabilities list)
|
|
30
30
|
*/
|
|
31
|
+
var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
32
|
+
return (mod && mod.__esModule) ? mod : { "default": mod };
|
|
33
|
+
};
|
|
34
|
+
const node_fs_1 = __importDefault(require("node:fs"));
|
|
35
|
+
const node_path_1 = __importDefault(require("node:path"));
|
|
31
36
|
// eslint-disable-next-line @typescript-eslint/no-require-imports
|
|
32
37
|
const ioMod = require("./io.cjs");
|
|
33
38
|
const { output: coreOutput, error: coreError } = ioMod;
|
|
34
39
|
// eslint-disable-next-line @typescript-eslint/no-require-imports
|
|
40
|
+
const verificationMod = require("./verification.cjs");
|
|
41
|
+
const { resolvePhaseArtifactFile } = verificationMod;
|
|
42
|
+
const security_cjs_1 = require("./security.cjs");
|
|
43
|
+
// eslint-disable-next-line @typescript-eslint/no-require-imports
|
|
35
44
|
const configLoaderModule = require("./config-loader.cjs");
|
|
36
45
|
const { loadConfig } = configLoaderModule;
|
|
37
46
|
// eslint-disable-next-line @typescript-eslint/no-require-imports
|
|
@@ -528,6 +537,56 @@ function resolveActiveHooksForPoint(cwd, point, options = {}) {
|
|
|
528
537
|
const combinedWarnings = [...(state.warnings || []), ...loadFailWarnings];
|
|
529
538
|
return { point: resolved.point, activeHooks: resolved.activeHooks, warnings: combinedWarnings };
|
|
530
539
|
}
|
|
540
|
+
/**
|
|
541
|
+
* #5105 R2: a `produces` entry `p` "exists" in `phaseDir` when it resolves via
|
|
542
|
+
* the SAME phase-artifact selection core `resolveVerificationFile`/
|
|
543
|
+
* `resolveUatFile` delegate to (`resolvePhaseArtifactFile`, `verification.cts`)
|
|
544
|
+
* — no second derivation of "which file counts as this phase's artifact"
|
|
545
|
+
* (#3473 F2's generative-divergence class). That core is pure and takes an
|
|
546
|
+
* already-read directory listing of REGULAR-file names only, so a directory
|
|
547
|
+
* of the same name, or a suffixed near-miss (`p.bak`, `p.tmp`), never counts,
|
|
548
|
+
* and `phaseDirName` scoping rejects a stray cross-phase file (`02-SECURITY.md`
|
|
549
|
+
* inside a `01-foo` phase dir) exactly as the aggregate scans do.
|
|
550
|
+
*/
|
|
551
|
+
function producesEntryPresent(fileNames, phaseDirName, p) {
|
|
552
|
+
return resolvePhaseArtifactFile([...fileNames], p, { phaseDirName, allowBare: true }) !== null;
|
|
553
|
+
}
|
|
554
|
+
/**
|
|
555
|
+
* #5105 R2: partition `activeHooks` by whether every one of a `kind:"step"`
|
|
556
|
+
* hook's declared `produces` artifacts already exists (as a regular file)
|
|
557
|
+
* directly in `phaseDir`. A hook with `produces: []` (e.g. mempalace-capture),
|
|
558
|
+
* and every gate/contribution, passes through unchanged in `activeHooks`.
|
|
559
|
+
*
|
|
560
|
+
* Throws when `phaseDir` cannot be read (fail closed — the caller converts
|
|
561
|
+
* this to a `coreError` exit, never a silent pass-everything-through).
|
|
562
|
+
*/
|
|
563
|
+
function partitionHooksByFingerprint(activeHooks, phaseDir) {
|
|
564
|
+
const entries = node_fs_1.default.readdirSync(phaseDir, { withFileTypes: true });
|
|
565
|
+
const fileNames = entries.filter((e) => e.isFile()).map((e) => e.name);
|
|
566
|
+
const phaseDirName = node_path_1.default.basename(phaseDir);
|
|
567
|
+
const kept = [];
|
|
568
|
+
const skipped = [];
|
|
569
|
+
for (const hook of activeHooks) {
|
|
570
|
+
if (hook.kind !== 'step' || !hook.produces || hook.produces.length === 0) {
|
|
571
|
+
kept.push(hook);
|
|
572
|
+
continue;
|
|
573
|
+
}
|
|
574
|
+
const everyProduced = hook.produces.every((p) => producesEntryPresent(fileNames, phaseDirName, p));
|
|
575
|
+
if (everyProduced) {
|
|
576
|
+
skipped.push({
|
|
577
|
+
capId: hook.capId,
|
|
578
|
+
kind: 'step',
|
|
579
|
+
ref: hook.ref,
|
|
580
|
+
reason: 'produces-present',
|
|
581
|
+
artifacts: [...hook.produces],
|
|
582
|
+
});
|
|
583
|
+
}
|
|
584
|
+
else {
|
|
585
|
+
kept.push(hook);
|
|
586
|
+
}
|
|
587
|
+
}
|
|
588
|
+
return { activeHooks: kept, skippedHooks: skipped };
|
|
589
|
+
}
|
|
531
590
|
function cmdLoopRenderHooks(cwd, point, raw, options = {}) {
|
|
532
591
|
if (!point) {
|
|
533
592
|
coreError('loop render-hooks requires a <point> argument. Valid points: ' + CANONICAL_POINTS.join(', '));
|
|
@@ -548,6 +607,38 @@ function cmdLoopRenderHooks(cwd, point, raw, options = {}) {
|
|
|
548
607
|
coreError(msg);
|
|
549
608
|
return;
|
|
550
609
|
}
|
|
610
|
+
// #5105 R2: --after-fingerprint <phaseDir> — gate out verify:post steps
|
|
611
|
+
// whose declared artifact(s) already exist in phaseDir (execute-phase
|
|
612
|
+
// already dispatched them before its own fingerprint; a re-dispatch here
|
|
613
|
+
// would write a post-fingerprint covered path for no reason, #4981/#4887).
|
|
614
|
+
const afterFingerprintDir = typeof options['afterFingerprint'] === 'string' ? options['afterFingerprint'] : undefined;
|
|
615
|
+
let skippedHooks;
|
|
616
|
+
if (afterFingerprintDir !== undefined) {
|
|
617
|
+
// #5105 S10: resolve relative to the handler's own cwd (never the
|
|
618
|
+
// process cwd) and fail closed if it escapes the project root — the same
|
|
619
|
+
// `requireSafePath` seam `uat.cts`'s `cmdUatCompleteSession` uses for its
|
|
620
|
+
// own path argument.
|
|
621
|
+
let safePhaseDir;
|
|
622
|
+
try {
|
|
623
|
+
safePhaseDir = (0, security_cjs_1.requireSafePath)(afterFingerprintDir, cwd, '--after-fingerprint directory', security_cjs_1.PathAcceptance.AbsoluteInsideRoot);
|
|
624
|
+
}
|
|
625
|
+
catch (err) {
|
|
626
|
+
const msg = (err instanceof Error) ? err.message : String(err);
|
|
627
|
+
coreError(`--after-fingerprint directory is unsafe: ${msg}`);
|
|
628
|
+
return;
|
|
629
|
+
}
|
|
630
|
+
let partition;
|
|
631
|
+
try {
|
|
632
|
+
partition = partitionHooksByFingerprint(result.activeHooks, safePhaseDir);
|
|
633
|
+
}
|
|
634
|
+
catch (err) {
|
|
635
|
+
const msg = (err instanceof Error) ? err.message : String(err);
|
|
636
|
+
coreError(`--after-fingerprint phase directory not found or unreadable: ${afterFingerprintDir} (${msg})`);
|
|
637
|
+
return;
|
|
638
|
+
}
|
|
639
|
+
result = { point: result.point, activeHooks: partition.activeHooks, warnings: result.warnings };
|
|
640
|
+
skippedHooks = partition.skippedHooks;
|
|
641
|
+
}
|
|
551
642
|
if (activeCapId !== undefined) {
|
|
552
643
|
const isActive = result.activeHooks.some((h) => h.capId === activeCapId);
|
|
553
644
|
process.stdout.write(isActive ? 'true\n' : 'false\n');
|
|
@@ -562,6 +653,9 @@ function cmdLoopRenderHooks(cwd, point, raw, options = {}) {
|
|
|
562
653
|
if (result.warnings.length > 0) {
|
|
563
654
|
envelope.warnings = result.warnings;
|
|
564
655
|
}
|
|
656
|
+
if (skippedHooks !== undefined) {
|
|
657
|
+
envelope.skippedHooks = skippedHooks;
|
|
658
|
+
}
|
|
565
659
|
coreOutput(envelope, raw);
|
|
566
660
|
}
|
|
567
661
|
module.exports = {
|
|
@@ -569,6 +663,7 @@ module.exports = {
|
|
|
569
663
|
renderLoopHooks,
|
|
570
664
|
cmdLoopRenderHooks,
|
|
571
665
|
resolveActiveHooksForPoint,
|
|
666
|
+
partitionHooksByFingerprint,
|
|
572
667
|
// Exported for tests
|
|
573
668
|
_getNestedConfigValue,
|
|
574
669
|
_resolveActivationValue,
|
|
@@ -11,6 +11,7 @@
|
|
|
11
11
|
* ADR-457 build-at-publish: compiled by tsc to gsd-core/bin/lib/markdown-sectionizer.cjs.
|
|
12
12
|
*/
|
|
13
13
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
14
|
+
exports.XML_DECISION_TAG_NAMES = void 0;
|
|
14
15
|
exports.stripFencedCode = stripFencedCode;
|
|
15
16
|
exports.stripInlineCode = stripInlineCode;
|
|
16
17
|
exports.scanInlineCodeSpans = scanInlineCodeSpans;
|
|
@@ -21,11 +22,13 @@ exports.collectSections = collectSections;
|
|
|
21
22
|
exports.collectSection = collectSection;
|
|
22
23
|
exports.iterateBullets = iterateBullets;
|
|
23
24
|
exports.updateBullet = updateBullet;
|
|
25
|
+
exports.updateHeading = updateHeading;
|
|
24
26
|
exports.extractTaggedBlocks = extractTaggedBlocks;
|
|
25
27
|
exports.stripTaggedBlocks = stripTaggedBlocks;
|
|
26
28
|
exports.replaceSection = replaceSection;
|
|
27
29
|
exports.withSection = withSection;
|
|
28
30
|
exports.deleteSection = deleteSection;
|
|
31
|
+
exports.extractXmlTagBodies = extractXmlTagBodies;
|
|
29
32
|
const pattern_cjs_1 = require("./pattern.cjs");
|
|
30
33
|
// ─── stripFencedCode ──────────────────────────────────────────────────────────
|
|
31
34
|
/**
|
|
@@ -722,7 +725,7 @@ function updateBullet(content, match, transform) {
|
|
|
722
725
|
bulletText = nm[2];
|
|
723
726
|
}
|
|
724
727
|
}
|
|
725
|
-
if (bulletText !== null && match(bulletText, rawLine)) {
|
|
728
|
+
if (bulletText !== null && match(bulletText, rawLine, i)) {
|
|
726
729
|
const newLine = transform(rawLine);
|
|
727
730
|
if (typeof newLine !== 'string')
|
|
728
731
|
return content;
|
|
@@ -732,6 +735,40 @@ function updateBullet(content, match, transform) {
|
|
|
732
735
|
}
|
|
733
736
|
return content;
|
|
734
737
|
}
|
|
738
|
+
// ─── updateHeading ────────────────────────────────────────────────────────────
|
|
739
|
+
/**
|
|
740
|
+
* Rewrite the physical line of the FIRST ATX heading (outside fenced code)
|
|
741
|
+
* accepted by `match` — the heading analogue of `updateBullet` (ADR-5057 §6,
|
|
742
|
+
* Phase 13, #5217). Heading discovery is `tokenizeHeadings`' (the one fence
|
|
743
|
+
* state machine for headings); this function adds only the splice.
|
|
744
|
+
*
|
|
745
|
+
* `rawLine` (second argument to `match`, the only argument to `transform`) is
|
|
746
|
+
* the UNMODIFIED physical line between `\n` separators, so on a CRLF document
|
|
747
|
+
* its trailing `\r` is included and every untouched byte — including every
|
|
748
|
+
* other line's own terminator — is copied from `content` verbatim. `lineIndex`
|
|
749
|
+
* is the 0-based physical line index, the same indexing as
|
|
750
|
+
* `content.split('\n')`, so a caller holding a pre-computed line-indexed plan
|
|
751
|
+
* can disambiguate two headings with identical text.
|
|
752
|
+
*
|
|
753
|
+
* Bounded no-op: no accepted heading, or `transform` returning a non-string,
|
|
754
|
+
* returns `content` unchanged.
|
|
755
|
+
*/
|
|
756
|
+
function updateHeading(content, match, transform) {
|
|
757
|
+
if (typeof content !== 'string' || content.length === 0)
|
|
758
|
+
return content;
|
|
759
|
+
for (const heading of tokenizeHeadings(content)) {
|
|
760
|
+
const newlineAt = content.indexOf('\n', heading.offset);
|
|
761
|
+
const lineEnd = newlineAt === -1 ? content.length : newlineAt;
|
|
762
|
+
const rawLine = content.slice(heading.offset, lineEnd);
|
|
763
|
+
if (!match(heading, rawLine, heading.line - 1))
|
|
764
|
+
continue;
|
|
765
|
+
const newLine = transform(rawLine);
|
|
766
|
+
if (typeof newLine !== 'string')
|
|
767
|
+
return content;
|
|
768
|
+
return content.slice(0, heading.offset) + newLine + content.slice(lineEnd);
|
|
769
|
+
}
|
|
770
|
+
return content;
|
|
771
|
+
}
|
|
735
772
|
// ─── extractTaggedBlocks ──────────────────────────────────────────────────────
|
|
736
773
|
/**
|
|
737
774
|
* Return the inner text of every `<tagName>…</tagName>` block in `content`,
|
|
@@ -962,5 +999,42 @@ function deleteSection(content, headingPredicate, opts = {}) {
|
|
|
962
999
|
const collapsedBefore = before.replace(/(?:\r\n|\n){3,}$/, (m) => (m.includes('\r\n') ? '\r\n\r\n' : '\n\n'));
|
|
963
1000
|
return collapsedBefore + after;
|
|
964
1001
|
}
|
|
1002
|
+
// ─── extractXmlTagBodies ──────────────────────────────────────────────────────
|
|
1003
|
+
// #2372: scanned-tag set must match the planner-canonical surfaces where a D-NN citation
|
|
1004
|
+
// is meaningful. `<objective>`/`<tasks>`/`<task>`/`<action>` are the historical core. The
|
|
1005
|
+
// planner is also explicitly told (plan-phase.md) to cite decisions in `<read_first>`,
|
|
1006
|
+
// `<behavior>`, `<verify>`, `<acceptance_criteria>`, and `<done>` — those are scanned too,
|
|
1007
|
+
// so the decision-coverage gate does not report a false gap when a decision is cited in any of them.
|
|
1008
|
+
//
|
|
1009
|
+
// Implementation: per-tag matching, NOT a single wide alternation. A single alternation
|
|
1010
|
+
// like `<(?:a|b|c)>...<\/(?:a|b|c)>` halts the outer tag's body capture at any inner tag
|
|
1011
|
+
// in the set, dropping any citation in the outer tag's prefix prose — e.g.
|
|
1012
|
+
// `<action>per D-05 <verify>npm test</verify></action>` would lose D-05 because `<verify>`
|
|
1013
|
+
// halts the `<action>` body before the citation. Per-tag matching avoids this: each tag's
|
|
1014
|
+
// body terminates only at its OWN closing tag, so `<verify>` inside `<action>` is absorbed
|
|
1015
|
+
// into `<action>`'s body (D-05 caught) AND `<verify>` is matched separately on its own pass.
|
|
1016
|
+
// Each per-tag regex keeps the ReDoS-safe negative-lookahead tempering (#2128).
|
|
1017
|
+
exports.XML_DECISION_TAG_NAMES = ['objective', 'tasks', 'task', 'action', 'read_first', 'behavior', 'verify', 'acceptance_criteria', 'done'];
|
|
1018
|
+
function buildXmlDecisionTagRegex(tagName) {
|
|
1019
|
+
// Per-tag: body tempering stops only at the SAME tag's reopening or closing — other
|
|
1020
|
+
// scanned tags pass through as text into this body. Non-greedy `*?` to first close.
|
|
1021
|
+
return new RegExp(`<${tagName}(?:\\s[^>]{0,1000})?>((?:(?!<${tagName}[\\s>])[\\s\\S])*?)<\\/${tagName}>`, 'gi');
|
|
1022
|
+
}
|
|
1023
|
+
/**
|
|
1024
|
+
* The bodies of every planner-canonical XML tag (`XML_DECISION_TAG_NAMES`) in `text`,
|
|
1025
|
+
* joined by `\n`. Each tag's body ends only at its OWN closing tag, so an inner scanned tag
|
|
1026
|
+
* is absorbed into the outer body AND matched on its own pass.
|
|
1027
|
+
*/
|
|
1028
|
+
function extractXmlTagBodies(text) {
|
|
1029
|
+
const parts = [];
|
|
1030
|
+
for (const tagName of exports.XML_DECISION_TAG_NAMES) {
|
|
1031
|
+
const re = buildXmlDecisionTagRegex(tagName);
|
|
1032
|
+
for (const match of text.matchAll(re)) {
|
|
1033
|
+
if (match[1])
|
|
1034
|
+
parts.push(match[1]);
|
|
1035
|
+
}
|
|
1036
|
+
}
|
|
1037
|
+
return parts.join('\n');
|
|
1038
|
+
}
|
|
965
1039
|
// Consumers: require('../gsd-core/bin/lib/markdown-sectionizer.cjs')
|
|
966
1040
|
// Named CJS exports are the canonical surface (ADR-457 .cts → .cjs build-at-publish).
|