@opengsd/gsd-core 1.15.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/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 +1 -1
- package/agents/gsd-debug-session-manager.md +1 -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/install.js +126 -196
- 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 +142 -56
- package/gsd-core/bin/lib/active-workstream-store.cjs +15 -0
- 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 +183 -103
- package/gsd-core/bin/lib/capability-validator.cjs +16 -0
- 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 +173 -170
- 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 +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/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 +162 -96
- package/gsd-core/bin/lib/install-engine.cjs +19 -35
- package/gsd-core/bin/lib/install-profiles.cjs +7 -4
- 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 +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/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 +280 -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 +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/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 +91 -157
- 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 +307 -591
- package/gsd-core/bin/lib/runtime-name-policy.cjs +142 -27
- package/gsd-core/bin/lib/runtime-slash.cjs +47 -30
- package/gsd-core/bin/lib/shell-command-projection.cjs +37 -2
- 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.cjs +214 -105
- 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/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 +1165 -314
- 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 +84 -90
- 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 +1 -1
- 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 +78 -170
- 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 +3 -2
- 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 +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 +147 -16
- package/hooks/dist/gsd-statusline.js +15 -7
- 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 +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 +147 -16
- package/hooks/gsd-statusline.js +15 -7
- 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-features.cjs +13 -8
- package/scripts/gen-hooks-cli-exit.cjs +12 -28
- package/scripts/gen-loop-host-contract.cjs +10 -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 +20 -2
- package/scripts/lib/ndjson-reporter.cjs +28 -3
- package/scripts/lib/platform-conformance-tier.generated.cjs +34 -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 +50 -5
- 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
|
@@ -12,6 +12,11 @@
|
|
|
12
12
|
* enforced by both consumers.
|
|
13
13
|
*/
|
|
14
14
|
|
|
15
|
+
// The one frontmatter fence owner (src/frontmatter-fence.cts, built to bin/lib by
|
|
16
|
+
// build:lib). Every consumer of this module runs after the build: lint:ci (CI builds
|
|
17
|
+
// bin/lib first) and the test suite (pretest).
|
|
18
|
+
const { locateFrontmatterFence } = require('../gsd-core/bin/lib/frontmatter-fence.cjs');
|
|
19
|
+
|
|
15
20
|
const CANONICAL_TOOLS = new Set([
|
|
16
21
|
'Read', 'Write', 'Edit', 'Bash', 'Glob', 'Grep',
|
|
17
22
|
'Task', 'Agent', 'Skill', 'SlashCommand',
|
|
@@ -22,16 +27,15 @@ const CANONICAL_TOOLS = new Set([
|
|
|
22
27
|
]);
|
|
23
28
|
|
|
24
29
|
function parseFrontmatter(content) {
|
|
25
|
-
//
|
|
26
|
-
//
|
|
27
|
-
//
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
if (end === -1) return {};
|
|
30
|
+
// The block is the one the one fence owner finds (found while implementing
|
|
31
|
+
// #5105) — BOM, CRLF (a Windows autocrlf checkout), an adjacent empty block
|
|
32
|
+
// and a `----` look-alike all read exactly as every runtime reader sees them.
|
|
33
|
+
// Its lines are split CRLF-tolerantly so a trailing \r never leaks into a value.
|
|
34
|
+
const fence = locateFrontmatterFence(content);
|
|
35
|
+
if (!fence || !fence.closed) return {};
|
|
32
36
|
const fm = {};
|
|
33
37
|
let key = null;
|
|
34
|
-
for (const line of
|
|
38
|
+
for (const line of content.slice(fence.openEnd, fence.bodyEnd).split(/\r?\n/)) {
|
|
35
39
|
const kv = line.match(/^([a-zA-Z0-9_-]+):\s*(.*)/);
|
|
36
40
|
if (kv) { key = kv[1]; fm[key] = kv[2].trim(); }
|
|
37
41
|
else if (key && line.match(/^\s+-\s+/)) {
|
|
@@ -201,6 +201,9 @@ const DOCS_GUARD_TESTS = {
|
|
|
201
201
|
'tests/compact-content-4139.test.cjs': ['docs/CONFIGURATION.md'],
|
|
202
202
|
'tests/config-field-docs.test.cjs': ['docs/CONFIGURATION.md'],
|
|
203
203
|
'tests/config.test.cjs': ['docs/CONFIGURATION.md'],
|
|
204
|
+
// #4974: doc/workflow content-parity checks read docs/CONFIGURATION.md's
|
|
205
|
+
// Gate Settings section directly (see tests/gates-confirmation-toggle-config.test.cjs).
|
|
206
|
+
'tests/gates-confirmation-toggle-config.test.cjs': ['docs/CONFIGURATION.md'],
|
|
204
207
|
'tests/context-index-sync.test.cjs': ['docs/CONTEXT-INDEX.json'],
|
|
205
208
|
'tests/context-predicates-query.test.cjs': ['docs/contributor-standards.md'],
|
|
206
209
|
// SCAN_DIRS includes 'docs' and recursively walks every .md file under it
|
|
@@ -418,6 +421,9 @@ const DOCS_GUARD_TESTS = {
|
|
|
418
421
|
'tests/verifier-behavior-unverified.test.cjs': ['docs/reference/planning-artifacts.md'],
|
|
419
422
|
'tests/verifier-coincidental-reliance.test.cjs': ['docs/AGENTS.md'],
|
|
420
423
|
'tests/verify.test.cjs': ['docs/reference/plan-md.md'],
|
|
424
|
+
// #5118 V43: asserts the deleted execute-phase/steps/stale-reverification.md
|
|
425
|
+
// is absent from the generated inventory manifest.
|
|
426
|
+
'tests/verify-lifecycle-writes-e2e.test.cjs': ['docs/INVENTORY-MANIFEST.json'],
|
|
421
427
|
'tests/workflow-fragments.test.cjs': ['docs/reference/workflow-fragments.md'],
|
|
422
428
|
};
|
|
423
429
|
|
package/scripts/gen-features.cjs
CHANGED
|
@@ -66,6 +66,10 @@ const fs = require('node:fs');
|
|
|
66
66
|
const path = require('node:path');
|
|
67
67
|
|
|
68
68
|
const { ExitError, runMain } = require('./lib/cli-exit.cjs');
|
|
69
|
+
// The one frontmatter fence owner (src/frontmatter-fence.cts, built by build:lib).
|
|
70
|
+
// Every entry point runs after the build: lint:generated-sync inside lint:ci (CI
|
|
71
|
+
// builds bin/lib first), regen:derived (`npm run build` first), and the tests.
|
|
72
|
+
const { locateFrontmatterFence } = require('../gsd-core/bin/lib/frontmatter-fence.cjs');
|
|
69
73
|
|
|
70
74
|
const ROOT = path.resolve(__dirname, '..');
|
|
71
75
|
const FEATURES_DIR = path.join(ROOT, 'docs', 'features');
|
|
@@ -255,13 +259,12 @@ function renderFrontmatter(data, body) {
|
|
|
255
259
|
*/
|
|
256
260
|
function parseFrontmatter(text) {
|
|
257
261
|
const normalized = String(text).replace(/\r\n/g, '\n');
|
|
258
|
-
|
|
259
|
-
|
|
260
|
-
|
|
261
|
-
if (end === -1) return { data: null, body: normalized };
|
|
262
|
+
// The block is the one the one fence owner finds (found while implementing #5105).
|
|
263
|
+
const fence = locateFrontmatterFence(normalized);
|
|
264
|
+
if (!fence || !fence.closed) return { data: null, body: normalized };
|
|
262
265
|
|
|
263
266
|
const data = {};
|
|
264
|
-
for (const line of normalized.slice(
|
|
267
|
+
for (const line of normalized.slice(fence.openEnd, fence.bodyEnd).split('\n')) {
|
|
265
268
|
if (line.trim() === '') continue;
|
|
266
269
|
const sep = line.indexOf(':');
|
|
267
270
|
if (sep === -1) continue;
|
|
@@ -270,9 +273,11 @@ function parseFrontmatter(text) {
|
|
|
270
273
|
data[key] = parseScalar(line.slice(sep + 1).trim());
|
|
271
274
|
}
|
|
272
275
|
|
|
273
|
-
//
|
|
274
|
-
// closing fence is consumed here so body
|
|
275
|
-
|
|
276
|
+
// Past the closing fence line and its line ending; the blank line
|
|
277
|
+
// renderFrontmatter emits after the closing fence is consumed here so body
|
|
278
|
+
// text starts at its first real line.
|
|
279
|
+
let body = normalized.slice(fence.closingFenceEnd);
|
|
280
|
+
if (body.startsWith('\n')) body = body.slice(1);
|
|
276
281
|
if (body.startsWith('\n')) body = body.slice(1);
|
|
277
282
|
return { data, body };
|
|
278
283
|
}
|
|
@@ -18,6 +18,17 @@
|
|
|
18
18
|
* target (`.js`, not `.cjs`, to match the hooks/lib/*.js convention) would
|
|
19
19
|
* tangle a script that is otherwise simple and single-purpose.
|
|
20
20
|
*
|
|
21
|
+
* compileToTemp() itself IS shared (required from ./gen-scripts-cli-exit.cjs)
|
|
22
|
+
* rather than duplicated: it compiles only src/cli-exit.cts (via a throwaway
|
|
23
|
+
* tsconfig that extends tsconfig.build.json with files/include scoped to
|
|
24
|
+
* that one module) rather than the whole project, since the emit this
|
|
25
|
+
* script needs depends only on cli-exit.cts's own import closure. A
|
|
26
|
+
* whole-project `-p tsconfig.build.json` type-check of every .cts file under
|
|
27
|
+
* a fixed COMPILE_TIMEOUT_MS ceiling can get killed by that timeout on a
|
|
28
|
+
* CPU-contended shared bench even though the one file this script needs
|
|
29
|
+
* compiles in a fraction of the time — and the compile strategy itself has
|
|
30
|
+
* nothing sibling-specific to fork.
|
|
31
|
+
*
|
|
21
32
|
* Usage:
|
|
22
33
|
* node scripts/gen-hooks-cli-exit.cjs # same as --write
|
|
23
34
|
* node scripts/gen-hooks-cli-exit.cjs --write # write hooks/lib/cli-exit.js
|
|
@@ -35,14 +46,12 @@
|
|
|
35
46
|
|
|
36
47
|
'use strict';
|
|
37
48
|
|
|
38
|
-
const { execFileSync } = require('node:child_process');
|
|
39
49
|
const fs = require('node:fs');
|
|
40
|
-
const os = require('node:os');
|
|
41
50
|
const path = require('node:path');
|
|
51
|
+
const { compileToTemp } = require('./gen-scripts-cli-exit.cjs');
|
|
42
52
|
|
|
43
53
|
const REPO_ROOT = path.resolve(__dirname, '..');
|
|
44
54
|
const OUTPUT_PATH = path.join(REPO_ROOT, 'hooks', 'lib', 'cli-exit.js');
|
|
45
|
-
const COMPILE_TIMEOUT_MS = 60_000;
|
|
46
55
|
|
|
47
56
|
/** Frozen reason codes so tests assert on structure, not prose. */
|
|
48
57
|
const REASON = Object.freeze({
|
|
@@ -78,31 +87,6 @@ const BANNER = [
|
|
|
78
87
|
'',
|
|
79
88
|
].join('\n');
|
|
80
89
|
|
|
81
|
-
/** Compile the whole project to a throwaway outDir so the work tree is untouched. */
|
|
82
|
-
function compileToTemp() {
|
|
83
|
-
const tmp = fs.mkdtempSync(path.join(os.tmpdir(), 'gsd-hooks-cli-exit-'));
|
|
84
|
-
try {
|
|
85
|
-
execFileSync(
|
|
86
|
-
process.execPath,
|
|
87
|
-
[
|
|
88
|
-
path.join(REPO_ROOT, 'node_modules', 'typescript', 'bin', 'tsc'),
|
|
89
|
-
'-p', path.join(REPO_ROOT, 'tsconfig.build.json'),
|
|
90
|
-
'--outDir', tmp,
|
|
91
|
-
// A throwaway outDir must not reuse the in-tree incremental state, or
|
|
92
|
-
// tsc skips emit for files it believes are already current.
|
|
93
|
-
'--incremental', 'false',
|
|
94
|
-
'--tsBuildInfoFile', 'null',
|
|
95
|
-
],
|
|
96
|
-
{ cwd: REPO_ROOT, encoding: 'utf8', stdio: 'pipe', timeout: COMPILE_TIMEOUT_MS },
|
|
97
|
-
);
|
|
98
|
-
return { ok: true, dir: tmp };
|
|
99
|
-
} catch (err) {
|
|
100
|
-
fs.rmSync(tmp, { recursive: true, force: true });
|
|
101
|
-
const detail = [err.stdout, err.stderr].filter(Boolean).join('\n').trim();
|
|
102
|
-
return { ok: false, detail };
|
|
103
|
-
}
|
|
104
|
-
}
|
|
105
|
-
|
|
106
90
|
/**
|
|
107
91
|
* Compile src/cli-exit.cts to a throwaway outDir and return the expected
|
|
108
92
|
* generated content (banner + compiled bytes, with the sibling registry
|
|
@@ -37,7 +37,16 @@ const STEP_WORKFLOWS = [
|
|
|
37
37
|
{ file: 'quick.md', point: 'plan:pre', kinds: ['contribution'], into: 'planner' },
|
|
38
38
|
],
|
|
39
39
|
},
|
|
40
|
-
{
|
|
40
|
+
{
|
|
41
|
+
file: 'execute-phase.md',
|
|
42
|
+
step: 'execute',
|
|
43
|
+
// #5118: execute:post's step AND gate dispatch (code_review_gate with
|
|
44
|
+
// its TDD escalation) lives in the ONE shared verification step that
|
|
45
|
+
// execute-phase includes (and verify-work includes too) — no per-caller copy.
|
|
46
|
+
auxiliaryHosts: [
|
|
47
|
+
{ file: 'execute-phase/steps/verify-phase-goal.md', point: 'execute:post', kinds: ['step', 'gate'] },
|
|
48
|
+
],
|
|
49
|
+
},
|
|
41
50
|
{ file: 'verify-work.md', step: 'verify' },
|
|
42
51
|
{ file: 'ship.md', step: 'ship' },
|
|
43
52
|
];
|
|
@@ -97,6 +97,26 @@
|
|
|
97
97
|
* point the CLI at a small temp fixture tree instead of this repo's real,
|
|
98
98
|
* 900+-file tests/ tree. Production usage (package.json's lint:generated-sync
|
|
99
99
|
* / regen:derived chains) passes no flags and gets the real repo paths.
|
|
100
|
+
*
|
|
101
|
+
* #5074 — the `.platform` sibling rule. A handful of the heaviest tier files
|
|
102
|
+
* carry their real Windows signal in only a few tests out of hundreds; the
|
|
103
|
+
* fix is to move those tests into a same-directory `X.platform.test.cjs`
|
|
104
|
+
* sibling of `X.test.cjs`, so only the sibling (not the whole base) needs
|
|
105
|
+
* real-OS coverage. That split is enforced here as a FAILING invariant
|
|
106
|
+
* (`findPlatformSplitViolations`, wired into `classifyTree`) rather than as a
|
|
107
|
+
* silent filename-based exclusion rule, for two reasons: epic #4589 Phase 2
|
|
108
|
+
* requires real-OS overrides to be centrally enumerated, not implied by a
|
|
109
|
+
* naming convention (a bare "base excluded whenever a sibling exists" rule
|
|
110
|
+
* would BE such a convention); and a platform-sensitive test later added
|
|
111
|
+
* back into a split base must not silently re-admit the whole base to
|
|
112
|
+
* Linux-only on the Windows tier — it must fail generation loudly instead, at
|
|
113
|
+
* the same `lint:generated-sync` gate this module already backs. This
|
|
114
|
+
* invariant is scoped to the Windows CATEGORIES and wired only into
|
|
115
|
+
* `classifyTree`; `classifyMacosTree` stays pure content selection by design,
|
|
116
|
+
* so a macOS-only signal (e.g. case-sensitivity wording) left in a split base
|
|
117
|
+
* re-admits that base to the macOS tier instead of failing — over-inclusion,
|
|
118
|
+
* the safe direction. See docs/how-to/split-platform-sensitive-tests.md for
|
|
119
|
+
* the authoring rules this invariant enforces.
|
|
100
120
|
*/
|
|
101
121
|
|
|
102
122
|
const fs = require('node:fs');
|
|
@@ -316,6 +336,140 @@ function classifyMacosContent(content) {
|
|
|
316
336
|
return { needsRealOs: signals.length > 0, signals };
|
|
317
337
|
}
|
|
318
338
|
|
|
339
|
+
/**
|
|
340
|
+
* The `.platform` sibling suffix (#5074). A sibling of `X.test.cjs` is a file
|
|
341
|
+
* whose basename ENDS with this suffix and has a non-empty stem before it —
|
|
342
|
+
* `foo.platform.test.cjs` is a sibling of `foo.test.cjs`, but a bare
|
|
343
|
+
* `platform.test.cjs` (empty stem) is not, and neither is a hyphenated name
|
|
344
|
+
* like `x-platform.test.cjs` or `platform-conformance-tier.test.cjs` (this
|
|
345
|
+
* module's own test file) — the rule keys on the literal dotted suffix, not
|
|
346
|
+
* on the substring "platform" appearing anywhere in the name.
|
|
347
|
+
*/
|
|
348
|
+
const PLATFORM_SIBLING_SUFFIX = '.platform.test.cjs';
|
|
349
|
+
|
|
350
|
+
/**
|
|
351
|
+
* Parse `rel` (a POSIX `tests/...`-relative path) as a `.platform` sibling.
|
|
352
|
+
* Returns `{ dir, stem }` when it is one (dir is the POSIX dirname, stem is
|
|
353
|
+
* the basename with `PLATFORM_SIBLING_SUFFIX` stripped), or `null` when it is
|
|
354
|
+
* not — either because the basename does not end with the suffix, or because
|
|
355
|
+
* the stem would be empty (the `platform.test.cjs` negative-space case).
|
|
356
|
+
*
|
|
357
|
+
* @param {string} rel
|
|
358
|
+
* @returns {{ dir: string, stem: string } | null}
|
|
359
|
+
*/
|
|
360
|
+
function parsePlatformSibling(rel) {
|
|
361
|
+
const dir = path.posix.dirname(rel);
|
|
362
|
+
const base = path.posix.basename(rel);
|
|
363
|
+
if (!base.endsWith(PLATFORM_SIBLING_SUFFIX)) return null;
|
|
364
|
+
const stem = base.slice(0, -PLATFORM_SIBLING_SUFFIX.length);
|
|
365
|
+
if (stem.length === 0) return null;
|
|
366
|
+
return { dir, stem };
|
|
367
|
+
}
|
|
368
|
+
|
|
369
|
+
/** Resolve `X.test.cjs`'s rel path for a `.platform` sibling's `{ dir, stem }`. */
|
|
370
|
+
function baseRelFor({ dir, stem }) {
|
|
371
|
+
return (dir === '.' ? '' : dir + '/') + stem + '.test.cjs';
|
|
372
|
+
}
|
|
373
|
+
|
|
374
|
+
/** Resolve `X.platform.test.cjs`'s rel path for a base rel path (inverse of baseRelFor). */
|
|
375
|
+
function siblingRelForBase(baseRel) {
|
|
376
|
+
const dir = path.posix.dirname(baseRel);
|
|
377
|
+
const stem = path.posix.basename(baseRel).slice(0, -'.test.cjs'.length);
|
|
378
|
+
return (dir === '.' ? '' : dir + '/') + stem + PLATFORM_SIBLING_SUFFIX;
|
|
379
|
+
}
|
|
380
|
+
|
|
381
|
+
/**
|
|
382
|
+
* The `.platform` sibling invariant (#5074), pure over an explicit `entries`
|
|
383
|
+
* array so it can be driven directly against fixtures (and against the real
|
|
384
|
+
* `ALWAYS_REAL_OS` map, which no temp fixture tree can key into) without
|
|
385
|
+
* touching the filesystem. `entries` is the ELIGIBLE (unit-suite) test-file
|
|
386
|
+
* set — a suite-tagged base is never a member, which is what makes it
|
|
387
|
+
* "never eligible" per the design doc's negative space rather than something
|
|
388
|
+
* this function has to special-case.
|
|
389
|
+
*
|
|
390
|
+
* For every `.platform` sibling present in `entries` (see
|
|
391
|
+
* `parsePlatformSibling`), checks three independent things against its base
|
|
392
|
+
* (`X.test.cjs`, same directory):
|
|
393
|
+
* - `base-has-signal` — the base IS in `entries` and still carries a
|
|
394
|
+
* genuine Windows signal (`classifyContent` on its own content) — the
|
|
395
|
+
* split did not actually remove the signal from the base.
|
|
396
|
+
* - `sibling-without-signal` — the sibling's OWN content carries no
|
|
397
|
+
* Windows signal, regardless of whether its base exists — a `.platform`
|
|
398
|
+
* file whose header claims a platform reason the classifier cannot see
|
|
399
|
+
* in its own text.
|
|
400
|
+
* - `base-always-real-os` — the base is a key of `alwaysRealOs`: the
|
|
401
|
+
* escape hatch says the WHOLE base needs real-OS coverage, which
|
|
402
|
+
* contradicts splitting only some of its tests into a sibling.
|
|
403
|
+
*
|
|
404
|
+
* A base that is absent from `entries` (suite-tagged, deleted, or renamed)
|
|
405
|
+
* never triggers `base-has-signal` — there is nothing to read.
|
|
406
|
+
*
|
|
407
|
+
* @param {{rel: string, content: string}[]} entries
|
|
408
|
+
* @param {Map<string, string>} [alwaysRealOs]
|
|
409
|
+
* @returns {{file: string, kind: 'base-has-signal'|'sibling-without-signal'|'base-always-real-os', signals: string[]}[]}
|
|
410
|
+
* sorted by file, then kind.
|
|
411
|
+
*/
|
|
412
|
+
function findPlatformSplitViolations(entries, alwaysRealOs = ALWAYS_REAL_OS) {
|
|
413
|
+
const byRel = new Map(entries.map((entry) => [entry.rel, entry]));
|
|
414
|
+
const violations = [];
|
|
415
|
+
|
|
416
|
+
for (const entry of entries) {
|
|
417
|
+
const parsed = parsePlatformSibling(entry.rel);
|
|
418
|
+
if (!parsed) continue;
|
|
419
|
+
const baseRel = baseRelFor(parsed);
|
|
420
|
+
|
|
421
|
+
const base = byRel.get(baseRel);
|
|
422
|
+
if (base !== undefined) {
|
|
423
|
+
const { signals } = classifyContent(base.content);
|
|
424
|
+
if (signals.length > 0) {
|
|
425
|
+
violations.push({ file: baseRel, kind: 'base-has-signal', signals });
|
|
426
|
+
}
|
|
427
|
+
}
|
|
428
|
+
|
|
429
|
+
const { signals: siblingSignals } = classifyContent(entry.content);
|
|
430
|
+
if (siblingSignals.length === 0) {
|
|
431
|
+
violations.push({ file: entry.rel, kind: 'sibling-without-signal', signals: [] });
|
|
432
|
+
}
|
|
433
|
+
|
|
434
|
+
if (alwaysRealOs.has(baseRel)) {
|
|
435
|
+
violations.push({ file: baseRel, kind: 'base-always-real-os', signals: [] });
|
|
436
|
+
}
|
|
437
|
+
}
|
|
438
|
+
|
|
439
|
+
violations.sort((a, b) => {
|
|
440
|
+
if (a.file !== b.file) return a.file < b.file ? -1 : 1;
|
|
441
|
+
if (a.kind !== b.kind) return a.kind < b.kind ? -1 : 1;
|
|
442
|
+
return 0;
|
|
443
|
+
});
|
|
444
|
+
return violations;
|
|
445
|
+
}
|
|
446
|
+
|
|
447
|
+
/**
|
|
448
|
+
* Render `findPlatformSplitViolations`' result as the Error message
|
|
449
|
+
* `classifyTree` throws: starts with the literal phrase callers/tests grep
|
|
450
|
+
* for, lists one line per violation (file, kind, residual signal names, and
|
|
451
|
+
* — for `base-has-signal` only — the sibling path those tests belong in),
|
|
452
|
+
* and points at the how-to doc for the authoring rules.
|
|
453
|
+
*
|
|
454
|
+
* @param {ReturnType<typeof findPlatformSplitViolations>} violations
|
|
455
|
+
* @returns {string}
|
|
456
|
+
*/
|
|
457
|
+
function formatPlatformSplitViolations(violations) {
|
|
458
|
+
const lines = violations.map((v) => {
|
|
459
|
+
let line = ` - ${v.file} [${v.kind}]`;
|
|
460
|
+
if (v.signals.length > 0) line += ` residual signal(s): ${v.signals.join(', ')}`;
|
|
461
|
+
if (v.kind === 'base-has-signal') {
|
|
462
|
+
line += ` — move these tests into ${siblingRelForBase(v.file)}`;
|
|
463
|
+
}
|
|
464
|
+
return line;
|
|
465
|
+
});
|
|
466
|
+
return (
|
|
467
|
+
`platform split invariant violated (#5074) — ${violations.length} file(s):\n` +
|
|
468
|
+
lines.join('\n') +
|
|
469
|
+
'\nSee docs/how-to/split-platform-sensitive-tests.md for the split-invariant rules.\n'
|
|
470
|
+
);
|
|
471
|
+
}
|
|
472
|
+
|
|
319
473
|
/**
|
|
320
474
|
* Recursively collect every `*.test.cjs` file beneath `dir`.
|
|
321
475
|
* @param {string} dir
|
|
@@ -355,10 +509,15 @@ function classifyTree(testsDir) {
|
|
|
355
509
|
// tier — see the header doc-comment for why suite-tagged files are excluded
|
|
356
510
|
// entirely rather than merely deprioritized.
|
|
357
511
|
const unitFiles = absoluteFiles.filter((absPath) => suiteOf(absPath) === null);
|
|
512
|
+
const entries = [];
|
|
358
513
|
const flagged = [];
|
|
359
514
|
for (const absPath of unitFiles) {
|
|
360
515
|
const rel = 'tests/' + path.relative(testsDir, absPath).replace(/\\/g, '/');
|
|
361
516
|
const content = fs.readFileSync(absPath, 'utf8');
|
|
517
|
+
// Read once, reused below both for this file's own flagging AND (via
|
|
518
|
+
// `entries`) for the #5074 split-invariant check — a second read of the
|
|
519
|
+
// same file for that check would double the I/O for no reason.
|
|
520
|
+
entries.push({ rel, content });
|
|
362
521
|
const { needsRealOs } = classifyContent(content);
|
|
363
522
|
// The ALWAYS_REAL_OS escape hatch (Windows tier only — see its doc
|
|
364
523
|
// comment) is unioned in HERE, keyed off a file that this walk actually
|
|
@@ -369,6 +528,19 @@ function classifyTree(testsDir) {
|
|
|
369
528
|
flagged.push(rel);
|
|
370
529
|
}
|
|
371
530
|
}
|
|
531
|
+
|
|
532
|
+
// #5074: a `.platform` sibling pair that violates the split invariant
|
|
533
|
+
// (residual signal left in the base, a sibling with no signal of its own,
|
|
534
|
+
// or a base that ALWAYS_REAL_OS says needs the whole file on real OS) fails
|
|
535
|
+
// generation loudly rather than being silently included or excluded. This
|
|
536
|
+
// check is Windows-CATEGORIES-only and wired here, not into
|
|
537
|
+
// classifyMacosTree below — a macOS-only signal in a split base is handled
|
|
538
|
+
// there by ordinary content selection (over-inclusion), not this invariant.
|
|
539
|
+
const violations = findPlatformSplitViolations(entries, ALWAYS_REAL_OS);
|
|
540
|
+
if (violations.length > 0) {
|
|
541
|
+
throw new Error(formatPlatformSplitViolations(violations));
|
|
542
|
+
}
|
|
543
|
+
|
|
372
544
|
const result = [...new Set(flagged)].sort();
|
|
373
545
|
return { total: absoluteFiles.length, files: result };
|
|
374
546
|
}
|
|
@@ -484,7 +656,19 @@ function main() {
|
|
|
484
656
|
const isMacos = target === 'macos';
|
|
485
657
|
const label = isMacos ? 'gen-platform-conformance-tier --target macos' : 'gen-platform-conformance-tier';
|
|
486
658
|
const exportKey = isMacos ? 'MACOS_CONFORMANCE_TIER_FILES' : 'CONFORMANCE_TIER_FILES';
|
|
487
|
-
|
|
659
|
+
// classifyTree throws a plain Error on a #5074 split-invariant violation
|
|
660
|
+
// (it has no ExitError/exit-code opinion of its own — it is also called
|
|
661
|
+
// in-process by tests). runMain only special-cases ExitError (stderr gets
|
|
662
|
+
// err.message; anything else gets the full err.stack); wrapping it here
|
|
663
|
+
// keeps the CLI's stderr output to the clean, purpose-built violation
|
|
664
|
+
// report instead of a raw stack trace.
|
|
665
|
+
let total, files;
|
|
666
|
+
try {
|
|
667
|
+
({ total, files } = isMacos ? classifyMacosTree(testsDir) : classifyTree(testsDir));
|
|
668
|
+
} catch (err) {
|
|
669
|
+
if (err instanceof ExitError) throw err;
|
|
670
|
+
throw new ExitError(1, err && err.message ? err.message : String(err));
|
|
671
|
+
}
|
|
488
672
|
|
|
489
673
|
if (mode === 'write') {
|
|
490
674
|
fs.mkdirSync(path.dirname(outPath), { recursive: true });
|
|
@@ -554,4 +738,6 @@ module.exports = {
|
|
|
554
738
|
MACOS_CATEGORIES,
|
|
555
739
|
classifyMacosTree,
|
|
556
740
|
renderMacosGeneratedFile,
|
|
741
|
+
PLATFORM_SIBLING_SUFFIX,
|
|
742
|
+
findPlatformSplitViolations,
|
|
557
743
|
};
|
|
@@ -17,6 +17,21 @@
|
|
|
17
17
|
* installer uses, producing a build-generated skills/ dir that ships in the
|
|
18
18
|
* npm package and serves plugin-only installs.
|
|
19
19
|
*
|
|
20
|
+
* #4995: commands/gsd/*.md still instruct bare command-position `gsd-tools
|
|
21
|
+
* <verb>` calls in 4 files (workstreams.md, quick.md, review-backlog.md,
|
|
22
|
+
* config.md), which fail with "command not found" on a shim-only install.
|
|
23
|
+
* That source can't be normalized to `gsd_run` directly the way #2751 did for
|
|
24
|
+
* agents/*.md and gsd-core/workflows/*.md: tests/gsd-tools-path-refs.test.cjs
|
|
25
|
+
* (#1766) pins commands/gsd/workstreams.md to the literal string
|
|
26
|
+
* `gsd-tools query workstream.list`, and bringing commands/ under the #2751
|
|
27
|
+
* guard was tried and reverted for unrelated load-bearing reasons (11ad7881ed
|
|
28
|
+
* / d286e54163). So this generator rewrites bare gsd-tools -> gsd_run in its
|
|
29
|
+
* OWN output — the same shape of fix #725 already established for the Codex
|
|
30
|
+
* conversion pipeline (rewrite the generated artifact, not the shared
|
|
31
|
+
* source) — reusing the exact `gsd_run` resolver preamble #2751 established
|
|
32
|
+
* (scripts/sync-runtime-launcher.cjs's transformFile) rather than inventing a
|
|
33
|
+
* new resolution mechanism.
|
|
34
|
+
*
|
|
20
35
|
* Depends on: gsd-core/bin/lib/runtime-artifact-conversion.cjs (compiled from
|
|
21
36
|
* src/runtime-artifact-conversion.cts by `npm run build:lib`). Must run AFTER
|
|
22
37
|
* build:lib in the build chain.
|
|
@@ -25,6 +40,7 @@
|
|
|
25
40
|
const fs = require('node:fs');
|
|
26
41
|
const path = require('node:path');
|
|
27
42
|
const { ExitError, runMain } = require('./lib/cli-exit.cjs');
|
|
43
|
+
const { transformFile, loadPreamble } = require('./sync-runtime-launcher.cjs');
|
|
28
44
|
|
|
29
45
|
const ROOT = path.resolve(__dirname, '..');
|
|
30
46
|
const COMMANDS_DIR = path.join(ROOT, 'commands', 'gsd');
|
|
@@ -33,6 +49,73 @@ const CONVERSION_MODULE = path.join(ROOT, 'gsd-core', 'bin', 'lib', 'runtime-art
|
|
|
33
49
|
const PREFIX = 'gsd-';
|
|
34
50
|
const RUNTIME = 'claude';
|
|
35
51
|
|
|
52
|
+
// #4995: the 4 commands/gsd/*.md stems (census taken by the issue) that carry
|
|
53
|
+
// command-position bare `gsd-tools <verb>` calls. Deliberately an explicit
|
|
54
|
+
// allowlist, NOT a blanket regex applied to every generated skill: several
|
|
55
|
+
// other skills (e.g. gsd-graphify) merely NAME `gsd-tools`/`gsd_run`
|
|
56
|
+
// descriptively (prose, "DO NOT use ..." warnings) in ways a command-position
|
|
57
|
+
// regex cannot safely tell apart from a real instruction without per-site
|
|
58
|
+
// human review -- exactly the ambiguity tests/no-bare-gsd-tools-command-
|
|
59
|
+
// position.test.cjs's PROSE_ALLOWLIST exists to resolve by hand for #2751/
|
|
60
|
+
// #3809. gsd-graphify also carries 5 deliberately-separate per-block
|
|
61
|
+
// preambles (tests/graphify-visualization.test.cjs extracts and runs each
|
|
62
|
+
// fenced Step-3 block standalone); running every skill through
|
|
63
|
+
// transformFile's "collapse to one preamble per file" behavior would
|
|
64
|
+
// reproduce the exact regression d286e54163 already found and reverted for
|
|
65
|
+
// commands/gsd/graphify.md. Restricting the rewrite to the 4 files the
|
|
66
|
+
// census in #4995 actually names avoids all of that.
|
|
67
|
+
const BARE_GSD_TOOLS_STEMS = new Set(['workstreams', 'quick', 'review-backlog', 'config']);
|
|
68
|
+
|
|
69
|
+
// #4995: command-position bare `gsd-tools` -> `gsd_run` token swap. Mirrors 3
|
|
70
|
+
// of the 4 command-position shapes rewriteBareGsdToolsCommandsForCodex
|
|
71
|
+
// (src/runtime-artifact-conversion.cts) already covers for the Codex
|
|
72
|
+
// pipeline: start-of-line, inside `$( ... )`, and immediately after a
|
|
73
|
+
// backtick (inline "Run: `gsd-tools verb args`" prose). Deliberately DROPS
|
|
74
|
+
// that function's 4th shape -- a bare, unescaped `|` counted as a shell
|
|
75
|
+
// pipe separator -- because a Markdown table row (`| col | gsd-tools verb
|
|
76
|
+
// arg |`) also starts a cell with `| gsd-tools`, and none of the 11 sites in
|
|
77
|
+
// #4995's census need it: config.md's own routing TABLE names `gsd-tools
|
|
78
|
+
// query config-set-model-profile` descriptively in a `|`-delimited cell one
|
|
79
|
+
// row above the real operative site, and rewriting that cell too would be a
|
|
80
|
+
// change nobody asked for. (`&&`/`;` are kept: two-character or dedicated
|
|
81
|
+
// separators, not also a Markdown table delimiter.) Only applied to
|
|
82
|
+
// BARE_GSD_TOOLS_STEMS files (see above). Within those 4 files this narrower
|
|
83
|
+
// pattern still matches one site beyond the issue's 11-site census --
|
|
84
|
+
// gsd-quick/SKILL.md's <security_notes> line ("Status fields read via
|
|
85
|
+
// `gsd-tools query frontmatter.get`") is a backtick-wrapped DESCRIPTIVE
|
|
86
|
+
// mention, not a `Run:` instruction, but it has the identical command-
|
|
87
|
+
// position shape and gets swapped too. That's harmless (the sentence reads
|
|
88
|
+
// the same with `gsd_run` in it) and left as-is rather than special-cased,
|
|
89
|
+
// but the true count this generator fixes is 12 operative-shaped sites, not
|
|
90
|
+
// 11 -- the 12th just wasn't literal-command "operative" in the issue's own
|
|
91
|
+
// sense.
|
|
92
|
+
function rewriteBareGsdToolsCommandsToGsdRun(content) {
|
|
93
|
+
return content
|
|
94
|
+
.replace(/(^[ \t]*)gsd-tools(?=\s)/gm, '$1gsd_run')
|
|
95
|
+
.replace(/(\$\(\s*)gsd-tools(?=\s)/g, '$1gsd_run')
|
|
96
|
+
.replace(/(`\s*)gsd-tools(?=\s)/g, '$1gsd_run')
|
|
97
|
+
.replace(/((?:&&|;)\s*)gsd-tools(?=\s)/g, '$1gsd_run');
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
// #4995: after the token swap, any fenced bash/sh/shell block that now calls
|
|
101
|
+
// `gsd_run` needs the resolver preamble that defines it -- reuse
|
|
102
|
+
// sync-runtime-launcher.cjs's transformFile verbatim (the same mechanism
|
|
103
|
+
// applied to agents/*.md and gsd-core/workflows/*.md) rather than
|
|
104
|
+
// reimplementing preamble placement here. Inline (non-fenced) "Run: `gsd_run
|
|
105
|
+
// verb args`" prose has no fenced block to anchor a preamble to; per the
|
|
106
|
+
// precedent already set for this exact shape (commands/gsd/workstreams.md
|
|
107
|
+
// and config.md in 11ad7881ed), it is left as a bare `gsd_run` token --
|
|
108
|
+
// `gsd_run` is itself a shipped npm bin name, so it resolves identically to
|
|
109
|
+
// how the pre-fix `gsd-tools` form resolved once PATH carries it (e.g. via
|
|
110
|
+
// the CLAUDE_ENV_FILE export in an earlier-run preamble in the same
|
|
111
|
+
// session), without regressing anything that worked before.
|
|
112
|
+
function rewriteSkillContent(content) {
|
|
113
|
+
const swapped = rewriteBareGsdToolsCommandsToGsdRun(content);
|
|
114
|
+
const preamble = loadPreamble();
|
|
115
|
+
const withPreamble = transformFile(swapped, preamble);
|
|
116
|
+
return withPreamble === null ? swapped : withPreamble;
|
|
117
|
+
}
|
|
118
|
+
|
|
36
119
|
function generateSkills(conversion) {
|
|
37
120
|
const cmdNames = conversion.readGsdCommandNames();
|
|
38
121
|
const files = fs.readdirSync(COMMANDS_DIR).filter(f => f.endsWith('.md'));
|
|
@@ -41,7 +124,10 @@ function generateSkills(conversion) {
|
|
|
41
124
|
const stem = file.slice(0, -3);
|
|
42
125
|
const skillName = PREFIX + stem;
|
|
43
126
|
const src = fs.readFileSync(path.join(COMMANDS_DIR, file), 'utf8');
|
|
44
|
-
|
|
127
|
+
let converted = conversion.convertClaudeCommandToClaudeSkill(src, skillName, RUNTIME, cmdNames, true);
|
|
128
|
+
if (BARE_GSD_TOOLS_STEMS.has(stem)) {
|
|
129
|
+
converted = rewriteSkillContent(converted);
|
|
130
|
+
}
|
|
45
131
|
results.push({ skillName, content: converted });
|
|
46
132
|
}
|
|
47
133
|
return results;
|
|
@@ -25,6 +25,9 @@ const path = require('node:path');
|
|
|
25
25
|
|
|
26
26
|
const { PROFILES } = require('./research-profiles.cjs');
|
|
27
27
|
const { ExitError, runMain } = require('./lib/cli-exit.cjs');
|
|
28
|
+
// The one frontmatter fence owner (src/frontmatter-fence.cts, built by build:lib).
|
|
29
|
+
// This dev-time script runs after the build (manually, and from the tests).
|
|
30
|
+
const { locateFrontmatterFence } = require('../gsd-core/bin/lib/frontmatter-fence.cjs');
|
|
28
31
|
|
|
29
32
|
const ROOT = path.resolve(__dirname, '..');
|
|
30
33
|
const AGENTS_DIR = path.join(ROOT, 'agents');
|
|
@@ -41,12 +44,13 @@ const AGENTS_DIR = path.join(ROOT, 'agents');
|
|
|
41
44
|
* We read the CURRENT commented-hooks block from the agent file and preserve it
|
|
42
45
|
* byte-for-byte; only name/description/tools/color are regenerated.
|
|
43
46
|
*/
|
|
44
|
-
function buildFrontmatter(profile,
|
|
45
|
-
// Extract the commented hooks section from the existing frontmatter
|
|
46
|
-
//
|
|
47
|
-
//
|
|
48
|
-
//
|
|
49
|
-
|
|
47
|
+
function buildFrontmatter(profile, existingRegion) {
|
|
48
|
+
// Extract the commented hooks section from the existing frontmatter region
|
|
49
|
+
// (the YAML text between the fences), if any. The hooks block starts at
|
|
50
|
+
// `# hooks:` and runs to the end of the region — the closing `---` follows it.
|
|
51
|
+
// In the committed files there is NO blank line between `color:` and
|
|
52
|
+
// `# hooks:`, so we append it directly after the color line's `\n`.
|
|
53
|
+
const hooksMatch = existingRegion.match(/(# hooks:[\s\S]*)$/);
|
|
50
54
|
// hooksSuffix: if present, the block followed by a newline so `---` is on its own line;
|
|
51
55
|
// if absent, empty string (the closing `---` follows directly after color's `\n`).
|
|
52
56
|
const hooksSuffix = hooksMatch ? hooksMatch[1] + '\n' : '';
|
|
@@ -67,37 +71,26 @@ function buildFrontmatter(profile, existingFrontmatter) {
|
|
|
67
71
|
/**
|
|
68
72
|
* Parse a .md file and return { frontmatterRaw, body, frontmatterFields }.
|
|
69
73
|
*
|
|
70
|
-
* frontmatterRaw: the
|
|
71
|
-
*
|
|
74
|
+
* frontmatterRaw: the YAML text between the opening and closing fences (exclusive)
|
|
75
|
+
* fullFrontmatter: the block from the opening fence through the closing fence line
|
|
76
|
+
* body: everything after the closing fence line's line ending
|
|
72
77
|
* frontmatterFields: { name, description, color, tools }
|
|
78
|
+
*
|
|
79
|
+
* The block is the one the one fence owner finds (found while implementing #5105):
|
|
80
|
+
* it opens at byte 0 (after a BOM), exactly as the runtime that loads the agent reads it.
|
|
73
81
|
*/
|
|
74
82
|
function parseAgentFile(filePath) {
|
|
75
83
|
const raw = fs.readFileSync(filePath, 'utf8');
|
|
76
84
|
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
let start = -1;
|
|
80
|
-
let end = -1;
|
|
81
|
-
for (let i = 0; i < lines.length; i++) {
|
|
82
|
-
if (lines[i].trim() === '---') {
|
|
83
|
-
if (start === -1) {
|
|
84
|
-
start = i;
|
|
85
|
-
} else {
|
|
86
|
-
end = i;
|
|
87
|
-
break;
|
|
88
|
-
}
|
|
89
|
-
}
|
|
90
|
-
}
|
|
91
|
-
|
|
92
|
-
if (start === -1 || end === -1) {
|
|
85
|
+
const fence = locateFrontmatterFence(raw);
|
|
86
|
+
if (!fence || !fence.closed) {
|
|
93
87
|
throw new Error('No valid frontmatter delimiters found in ' + filePath);
|
|
94
88
|
}
|
|
95
89
|
|
|
96
|
-
const
|
|
97
|
-
const
|
|
98
|
-
|
|
99
|
-
const
|
|
100
|
-
const body = lines.slice(end + 1).join('\n');
|
|
90
|
+
const frontmatterRaw = raw.slice(fence.openEnd, fence.bodyEnd);
|
|
91
|
+
const frontmatterLines = frontmatterRaw.split(/\r?\n/);
|
|
92
|
+
const fullFrontmatter = raw.slice(fence.bom.length, fence.closingFenceEnd);
|
|
93
|
+
const body = raw.slice(fence.closingFenceEnd).replace(/^\r?\n/, '');
|
|
101
94
|
|
|
102
95
|
const fields = {};
|
|
103
96
|
// Parse simple key: value pairs (not nested YAML, no multi-line values here)
|
|
@@ -214,9 +207,9 @@ function runCheck() {
|
|
|
214
207
|
*/
|
|
215
208
|
function writeAgent(profile) {
|
|
216
209
|
const agentPath = path.join(AGENTS_DIR, profile.name + '.md');
|
|
217
|
-
const {
|
|
210
|
+
const { frontmatterRaw, body } = parseAgentFile(agentPath);
|
|
218
211
|
|
|
219
|
-
const newFrontmatter = buildFrontmatter(profile,
|
|
212
|
+
const newFrontmatter = buildFrontmatter(profile, frontmatterRaw);
|
|
220
213
|
const newContent = newFrontmatter + '\n' + body;
|
|
221
214
|
|
|
222
215
|
fs.writeFileSync(agentPath, newContent, 'utf8');
|