@opengsd/gsd-core 1.17.0 → 1.19.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/.claude-plugin/marketplace.json +1 -1
- package/.claude-plugin/plugin.json +2 -1
- package/.opencode/plugins/gsd-core.js +2 -3
- package/agents/gsd-advisor-researcher.compact.md +1 -1
- package/agents/gsd-advisor-researcher.md +1 -1
- package/agents/gsd-assumptions-analyzer.compact.md +1 -1
- package/agents/gsd-assumptions-analyzer.md +1 -1
- package/agents/gsd-code-fixer.compact.md +3 -3
- package/agents/gsd-code-fixer.md +5 -4
- package/agents/gsd-code-reviewer.compact.md +1 -1
- package/agents/gsd-code-reviewer.md +1 -1
- package/agents/gsd-codebase-mapper.compact.md +1 -1
- package/agents/gsd-codebase-mapper.md +1 -1
- package/agents/gsd-debugger.md +1 -1
- package/agents/gsd-doc-verifier.compact.md +4 -4
- package/agents/gsd-doc-verifier.md +4 -3
- package/agents/gsd-doc-writer.compact.md +23 -5
- package/agents/gsd-doc-writer.md +19 -6
- package/agents/gsd-eval-auditor.compact.md +1 -1
- package/agents/gsd-eval-auditor.md +1 -1
- package/agents/gsd-executor.md +7 -3
- package/agents/gsd-integration-checker.compact.md +1 -1
- package/agents/gsd-integration-checker.md +1 -1
- package/agents/gsd-nyquist-auditor.compact.md +1 -1
- package/agents/gsd-nyquist-auditor.md +1 -1
- package/agents/gsd-phase-researcher.md +1 -1
- package/agents/gsd-plan-checker.md +2 -1
- package/agents/gsd-planner.md +5 -4
- package/agents/gsd-project-researcher.compact.md +1 -1
- package/agents/gsd-project-researcher.md +1 -1
- package/agents/gsd-research-synthesizer.compact.md +5 -5
- package/agents/gsd-research-synthesizer.md +5 -5
- package/agents/gsd-roadmapper.compact.md +1 -1
- package/agents/gsd-roadmapper.md +1 -1
- package/agents/gsd-security-auditor.compact.md +1 -1
- package/agents/gsd-security-auditor.md +1 -1
- package/agents/gsd-ui-auditor.compact.md +1 -1
- package/agents/gsd-ui-auditor.md +1 -1
- package/agents/gsd-ui-checker.compact.md +1 -1
- package/agents/gsd-ui-checker.md +1 -1
- package/agents/gsd-ui-researcher.compact.md +13 -1
- package/agents/gsd-ui-researcher.md +28 -1
- package/agents/gsd-verifier.md +2 -2
- package/bin/install.js +460 -105
- package/commands/gsd/code-review.md +8 -0
- package/commands/gsd/pause-work.md +1 -1
- package/gsd-core/bin/gsd-tools.cjs +124 -18
- package/gsd-core/bin/lib/agent-install-check.cjs +29 -3
- package/gsd-core/bin/lib/args-terminator.cjs +114 -0
- package/gsd-core/bin/lib/broken-windows.cjs +59 -1
- package/gsd-core/bin/lib/capability-ledger.cjs +25 -0
- package/gsd-core/bin/lib/capability-lock.cjs +41 -8
- package/gsd-core/bin/lib/capability-registry.cjs +162 -74
- package/gsd-core/bin/lib/capability-source.cjs +3 -1
- package/gsd-core/bin/lib/capability-state.cjs +20 -11
- package/gsd-core/bin/lib/capability-validator.cjs +23 -4
- package/gsd-core/bin/lib/commands.cjs +5 -1
- package/gsd-core/bin/lib/config-loader.cjs +13 -3
- package/gsd-core/bin/lib/config.cjs +256 -58
- package/gsd-core/bin/lib/core-utils.cjs +34 -0
- package/gsd-core/bin/lib/decision-coverage-support.cjs +93 -0
- package/gsd-core/bin/lib/gate-context-drift.cjs +8 -3
- package/gsd-core/bin/lib/gate-decision-coverage-plan.cjs +21 -1
- package/gsd-core/bin/lib/gate-decision-coverage-verify.cjs +19 -1
- package/gsd-core/bin/lib/gate-evaluation-scope.cjs +134 -13
- package/gsd-core/bin/lib/gate-predicate-evaluator.cjs +23 -3
- package/gsd-core/bin/lib/gate-predicate.cjs +8 -12
- package/gsd-core/bin/lib/gate-tdd-review-checkpoint.cjs +114 -18
- package/gsd-core/bin/lib/gate-ui-safety.cjs +23 -5
- package/gsd-core/bin/lib/git-info-exclude.cjs +57 -0
- package/gsd-core/bin/lib/health-diagnostic-rules/phase-structure.cjs +18 -3
- package/gsd-core/bin/lib/health-diagnostic-types.cjs +4 -0
- package/gsd-core/bin/lib/health-diagnostic.cjs +52 -1
- package/gsd-core/bin/lib/host-integration.cjs +13 -6
- package/gsd-core/bin/lib/init.cjs +76 -5
- package/gsd-core/bin/lib/install-effort-resolver.cjs +2 -2
- package/gsd-core/bin/lib/install-engine.cjs +246 -32
- package/gsd-core/bin/lib/install-model-override-resolver.cjs +4 -3
- package/gsd-core/bin/lib/install-profiles.cjs +14 -7
- package/gsd-core/bin/lib/installer-migration-report.cjs +2 -0
- package/gsd-core/bin/lib/markdown-line-diff.cjs +147 -0
- package/gsd-core/bin/lib/markdown-sectionizer.cjs +113 -0
- package/gsd-core/bin/lib/milestone-lock.cjs +8 -1
- package/gsd-core/bin/lib/milestone-release-map.cjs +244 -0
- package/gsd-core/bin/lib/milestone-release-tags.cjs +440 -0
- package/gsd-core/bin/lib/milestone.cjs +43 -1
- package/gsd-core/bin/lib/model-catalog.cjs +8 -0
- package/gsd-core/bin/lib/normalize-test-command.cjs +67 -0
- package/gsd-core/bin/lib/phase-id.cjs +21 -4
- package/gsd-core/bin/lib/phase-lifecycle.cjs +37 -4
- package/gsd-core/bin/lib/phase.cjs +235 -37
- package/gsd-core/bin/lib/planning-snapshot.cjs +3 -1
- package/gsd-core/bin/lib/planning-workspace.cjs +18 -0
- package/gsd-core/bin/lib/pristine-baseline.cjs +11 -3
- package/gsd-core/bin/lib/read-only-git-env.cjs +58 -0
- package/gsd-core/bin/lib/report-parser.cjs +57 -1
- package/gsd-core/bin/lib/review-lane-descriptor.cjs +35 -2
- package/gsd-core/bin/lib/review-lane-invocation.cjs +40 -0
- package/gsd-core/bin/lib/review-lane-runner.cjs +33 -4
- package/gsd-core/bin/lib/review-reviewer-selection.cjs +20 -1
- package/gsd-core/bin/lib/roadmap-parser.cjs +12 -0
- package/gsd-core/bin/lib/roadmap-upgrade.cjs +39 -2
- package/gsd-core/bin/lib/roadmap.cjs +89 -30
- package/gsd-core/bin/lib/runtime-artifact-conversion.cjs +428 -16
- package/gsd-core/bin/lib/runtime-artifact-layout.cjs +8 -4
- package/gsd-core/bin/lib/runtime-homes.cjs +18 -0
- package/gsd-core/bin/lib/runtime-hooks-surface.cjs +60 -16
- package/gsd-core/bin/lib/shell-command-projection.cjs +123 -16
- package/gsd-core/bin/lib/smart-entry.cjs +37 -16
- package/gsd-core/bin/lib/state-contract.cjs +29 -9
- package/gsd-core/bin/lib/state-document.cjs +47 -5
- package/gsd-core/bin/lib/state-transition.cjs +150 -34
- package/gsd-core/bin/lib/state.cjs +140 -88
- package/gsd-core/bin/lib/surface.cjs +9 -0
- package/gsd-core/bin/lib/task-command-router.cjs +7 -28
- package/gsd-core/bin/lib/task-content-resolution.cjs +46 -1
- package/gsd-core/bin/lib/tdd-red-evidence.cjs +5 -0
- package/gsd-core/bin/lib/tdd-test-files.cjs +42 -0
- package/gsd-core/bin/lib/verification.cjs +96 -9
- package/gsd-core/bin/lib/verify.cjs +38 -1
- package/gsd-core/bin/lib/worktree-safety.cjs +22 -1
- package/gsd-core/bin/shared/config-defaults.manifest.json +1 -0
- package/gsd-core/bin/shared/config-schema.manifest.json +1 -0
- package/gsd-core/references/agent-contracts.md +1 -1
- package/gsd-core/references/agent-skills-bootstrap.md +26 -7
- package/gsd-core/references/git-integration.md +14 -3
- package/gsd-core/references/loop-hook-dispatch.md +13 -6
- package/gsd-core/references/planner-reversibility.md +18 -4
- package/gsd-core/references/planner-verify-command-grounding.md +18 -0
- package/gsd-core/references/planning-config.md +2 -0
- package/gsd-core/references/tdd.md +5 -4
- package/gsd-core/references/verifier-phase-gates.md +8 -1
- package/gsd-core/templates/DEBUG.md +16 -0
- package/gsd-core/templates/config.json +2 -1
- package/gsd-core/templates/verification-report.md +1 -1
- package/gsd-core/workflows/add-backlog.md +10 -2
- package/gsd-core/workflows/add-phase.md +5 -1
- package/gsd-core/workflows/add-tests.md +5 -1
- package/gsd-core/workflows/add-todo.md +5 -1
- package/gsd-core/workflows/ai-integration-phase.md +5 -1
- package/gsd-core/workflows/audit-fix.md +2 -1
- package/gsd-core/workflows/audit-milestone.md +21 -9
- package/gsd-core/workflows/autonomous/templates/minimal-context.md +41 -0
- package/gsd-core/workflows/autonomous.md +52 -64
- package/gsd-core/workflows/check-todos.md +5 -1
- package/gsd-core/workflows/code-review/steps/structural-pre-pass.md +10 -1
- package/gsd-core/workflows/code-review-fix.md +5 -1
- package/gsd-core/workflows/code-review.md +66 -8
- package/gsd-core/workflows/complete-milestone/detail/elaboration.md +5 -1
- package/gsd-core/workflows/complete-milestone/steps/project-md-evolution-review.md +145 -0
- package/gsd-core/workflows/complete-milestone/steps/release-map.md +80 -0
- package/gsd-core/workflows/complete-milestone.md +64 -157
- package/gsd-core/workflows/debug.md +5 -1
- package/gsd-core/workflows/diagnose-issues.md +34 -6
- package/gsd-core/workflows/discuss-phase/modes/chain.md +5 -1
- package/gsd-core/workflows/discuss-phase/modes/power.md +1 -1
- package/gsd-core/workflows/discuss-phase-assumptions.md +44 -5
- package/gsd-core/workflows/discuss-phase-power.md +19 -2
- package/gsd-core/workflows/discuss-phase.md +11 -11
- package/gsd-core/workflows/docs-update/detail/elaboration.md +7 -6
- package/gsd-core/workflows/docs-update.md +27 -5
- package/gsd-core/workflows/edit-phase.md +5 -1
- package/gsd-core/workflows/eval-review.md +11 -3
- package/gsd-core/workflows/execute-phase/steps/codebase-drift-gate.md +5 -1
- package/gsd-core/workflows/execute-phase/steps/per-plan-worktree-gate.md +13 -7
- package/gsd-core/workflows/execute-phase/steps/post-merge-gate.md +19 -8
- package/gsd-core/workflows/execute-phase/steps/regression-gate-run.md +21 -3
- package/gsd-core/workflows/execute-phase/steps/regression-gate.md +18 -1
- package/gsd-core/workflows/execute-phase/steps/verify-phase-goal.md +7 -3
- package/gsd-core/workflows/execute-phase/steps/wave-post-gate-hooks.md +11 -2
- package/gsd-core/workflows/execute-phase.md +46 -11
- package/gsd-core/workflows/execute-plan.md +8 -4
- package/gsd-core/workflows/extract-learnings.md +6 -2
- package/gsd-core/workflows/forensics.md +5 -1
- package/gsd-core/workflows/health.md +2 -1
- package/gsd-core/workflows/import.md +6 -2
- package/gsd-core/workflows/insert-phase.md +5 -1
- package/gsd-core/workflows/manager.md +10 -2
- package/gsd-core/workflows/map-codebase.md +5 -1
- package/gsd-core/workflows/milestone-summary.md +10 -2
- package/gsd-core/workflows/new-milestone.md +13 -5
- package/gsd-core/workflows/new-project.md +9 -5
- package/gsd-core/workflows/next.md +8 -4
- package/gsd-core/workflows/pause-work.md +3 -3
- package/gsd-core/workflows/plan-phase/steps/auto-advance-check.md +70 -0
- package/gsd-core/workflows/plan-phase/steps/chunked-planning-mode.md +7 -3
- package/gsd-core/workflows/plan-phase/steps/closed-phase-gate.md +6 -2
- package/gsd-core/workflows/plan-phase.md +54 -86
- package/gsd-core/workflows/plan-review-convergence.md +17 -5
- package/gsd-core/workflows/plant-seed.md +8 -4
- package/gsd-core/workflows/progress.md +6 -2
- package/gsd-core/workflows/quick/steps/plan-checker-loop.md +1 -1
- package/gsd-core/workflows/quick/steps/worktree-pre-dispatch-commit.md +8 -1
- package/gsd-core/workflows/quick-batch/steps/plan-checker-loop.md +2 -1
- package/gsd-core/workflows/quick-batch.md +5 -1
- package/gsd-core/workflows/quick.md +23 -8
- package/gsd-core/workflows/remove-phase.md +5 -1
- package/gsd-core/workflows/resume-project.md +5 -1
- package/gsd-core/workflows/review.md +22 -15
- package/gsd-core/workflows/scan.md +5 -1
- package/gsd-core/workflows/section-manifest.json +5 -0
- package/gsd-core/workflows/secure-phase.md +6 -2
- package/gsd-core/workflows/settings.md +4 -3
- package/gsd-core/workflows/ship.md +16 -14
- package/gsd-core/workflows/spec-phase.md +3 -6
- package/gsd-core/workflows/thread.md +10 -2
- package/gsd-core/workflows/transition.md +5 -1
- package/gsd-core/workflows/ui-phase.md +5 -1
- package/gsd-core/workflows/ui-review.md +6 -2
- package/gsd-core/workflows/ultraplan-phase.md +5 -1
- package/gsd-core/workflows/update.md +5 -1
- package/gsd-core/workflows/validate-phase.md +6 -2
- package/gsd-core/workflows/verify-work/detail/elaboration.md +1 -1
- package/gsd-core/workflows/verify-work/steps/mvp-uat-framing.md +5 -1
- package/gsd-core/workflows/verify-work.md +15 -6
- package/hooks/dist/gsd-agent-isolation-guard.js +14 -2
- package/hooks/dist/gsd-cursor-subagent-start.js +8 -3
- package/hooks/dist/gsd-path-export.js +95 -0
- package/hooks/dist/gsd-secret-read-guard.js +244 -53
- package/hooks/dist/gsd-statusline.js +35 -2
- package/hooks/dist/gsd-validate-commit.sh +27 -5
- package/hooks/dist/gsd-windsurf-pre-write.js +28 -1
- package/hooks/dist/gsd-workflow-guard.js +1 -1
- package/hooks/dist/gsd-worktree-path-guard.js +28 -1
- package/hooks/dist/lib/isolation-sentinel.js +44 -0
- package/hooks/dist/managed-hooks-registry.cjs +1 -0
- package/hooks/gsd-agent-isolation-guard.js +14 -2
- package/hooks/gsd-cursor-subagent-start.js +8 -3
- package/hooks/gsd-path-export.js +95 -0
- package/hooks/gsd-secret-read-guard.js +244 -53
- package/hooks/gsd-statusline.js +35 -2
- package/hooks/gsd-validate-commit.sh +27 -5
- package/hooks/gsd-windsurf-pre-write.js +28 -1
- package/hooks/gsd-workflow-guard.js +1 -1
- package/hooks/gsd-worktree-path-guard.js +28 -1
- package/hooks/hooks.json +1 -0
- package/hooks/lib/isolation-sentinel.js +44 -0
- package/hooks/managed-hooks-registry.cjs +1 -0
- package/package.json +13 -6
- package/pi/gsd.cjs +1047 -10
- package/pi/package-agents/gsd-advisor-researcher.md +113 -0
- package/pi/package-agents/gsd-ai-researcher.md +111 -0
- package/pi/package-agents/gsd-assumptions-analyzer.md +110 -0
- package/pi/package-agents/gsd-code-fixer.md +769 -0
- package/pi/package-agents/gsd-code-reviewer.md +402 -0
- package/pi/package-agents/gsd-codebase-mapper.md +850 -0
- package/pi/package-agents/gsd-debug-session-manager.md +416 -0
- package/pi/package-agents/gsd-debugger.md +1274 -0
- package/pi/package-agents/gsd-doc-classifier.md +269 -0
- package/pi/package-agents/gsd-doc-synthesizer.md +259 -0
- package/pi/package-agents/gsd-doc-verifier.md +212 -0
- package/pi/package-agents/gsd-doc-writer.md +626 -0
- package/pi/package-agents/gsd-dom-verifier.md +164 -0
- package/pi/package-agents/gsd-domain-researcher.md +144 -0
- package/pi/package-agents/gsd-eval-auditor.md +185 -0
- package/pi/package-agents/gsd-eval-planner.md +149 -0
- package/pi/package-agents/gsd-executor.md +894 -0
- package/pi/package-agents/gsd-framework-selector.md +159 -0
- package/pi/package-agents/gsd-integration-checker.md +476 -0
- package/pi/package-agents/gsd-intel-updater.md +338 -0
- package/pi/package-agents/gsd-mempalace-curator.md +49 -0
- package/pi/package-agents/gsd-nyquist-auditor.md +200 -0
- package/pi/package-agents/gsd-pattern-mapper.md +340 -0
- package/pi/package-agents/gsd-phase-researcher.md +897 -0
- package/pi/package-agents/gsd-plan-checker.md +1077 -0
- package/pi/package-agents/gsd-planner.md +1003 -0
- package/pi/package-agents/gsd-project-researcher.md +611 -0
- package/pi/package-agents/gsd-research-synthesizer.md +259 -0
- package/pi/package-agents/gsd-roadmapper.md +779 -0
- package/pi/package-agents/gsd-security-auditor.md +171 -0
- package/pi/package-agents/gsd-ui-auditor.md +581 -0
- package/pi/package-agents/gsd-ui-checker.md +417 -0
- package/pi/package-agents/gsd-ui-researcher.md +469 -0
- package/pi/package-agents/gsd-user-profiler.md +174 -0
- package/pi/package-agents/gsd-verifier.md +963 -0
- package/pi/package-agents/manifest.json +39 -0
- package/pi/package-skills/gsd-add-tests/SKILL.md +39 -0
- package/pi/package-skills/gsd-ai-integration-phase/SKILL.md +36 -0
- package/pi/package-skills/gsd-audit-fix/SKILL.md +34 -0
- package/pi/package-skills/gsd-audit-milestone/SKILL.md +40 -0
- package/pi/package-skills/gsd-audit-uat/SKILL.md +26 -0
- package/pi/package-skills/gsd-autonomous/SKILL.md +52 -0
- package/pi/package-skills/gsd-capture/SKILL.md +68 -0
- package/pi/package-skills/gsd-cleanup/SKILL.md +25 -0
- package/pi/package-skills/gsd-code-review/SKILL.md +71 -0
- package/pi/package-skills/gsd-complete-milestone/SKILL.md +147 -0
- package/pi/package-skills/gsd-config/SKILL.md +60 -0
- package/pi/package-skills/gsd-debug/SKILL.md +64 -0
- package/pi/package-skills/gsd-discuss-phase/SKILL.md +74 -0
- package/pi/package-skills/gsd-docs-update/SKILL.md +50 -0
- package/pi/package-skills/gsd-eval-review/SKILL.md +35 -0
- package/pi/package-skills/gsd-execute-phase/SKILL.md +62 -0
- package/pi/package-skills/gsd-explore/SKILL.md +26 -0
- package/pi/package-skills/gsd-extract-learnings/SKILL.md +25 -0
- package/pi/package-skills/gsd-fast/SKILL.md +33 -0
- package/pi/package-skills/gsd-forensics/SKILL.md +59 -0
- package/pi/package-skills/gsd-graphify/SKILL.md +210 -0
- package/pi/package-skills/gsd-health/SKILL.md +36 -0
- package/pi/package-skills/gsd-help/SKILL.md +37 -0
- package/pi/package-skills/gsd-import/SKILL.md +46 -0
- package/pi/package-skills/gsd-inbox/SKILL.md +42 -0
- package/pi/package-skills/gsd-ingest-docs/SKILL.md +43 -0
- package/pi/package-skills/gsd-manager/SKILL.md +46 -0
- package/pi/package-skills/gsd-map-codebase/SKILL.md +85 -0
- package/pi/package-skills/gsd-mempalace-capture/SKILL.md +111 -0
- package/pi/package-skills/gsd-mempalace-recall/SKILL.md +111 -0
- package/pi/package-skills/gsd-milestone-summary/SKILL.md +54 -0
- package/pi/package-skills/gsd-mvp-phase/SKILL.md +45 -0
- package/pi/package-skills/gsd-new-milestone/SKILL.md +49 -0
- package/pi/package-skills/gsd-new-project/SKILL.md +49 -0
- package/pi/package-skills/gsd-next/SKILL.md +34 -0
- package/pi/package-skills/gsd-ns-context/SKILL.md +31 -0
- package/pi/package-skills/gsd-ns-ideate/SKILL.md +30 -0
- package/pi/package-skills/gsd-ns-manage/SKILL.md +42 -0
- package/pi/package-skills/gsd-ns-project/SKILL.md +34 -0
- package/pi/package-skills/gsd-ns-review/SKILL.md +35 -0
- package/pi/package-skills/gsd-ns-workflow/SKILL.md +44 -0
- package/pi/package-skills/gsd-onboard/SKILL.md +46 -0
- package/pi/package-skills/gsd-pause-work/SKILL.md +48 -0
- package/pi/package-skills/gsd-phase/SKILL.md +61 -0
- package/pi/package-skills/gsd-plan-phase/SKILL.md +62 -0
- package/pi/package-skills/gsd-plan-review-convergence/SKILL.md +63 -0
- package/pi/package-skills/gsd-pr-branch/SKILL.md +31 -0
- package/pi/package-skills/gsd-profile-user/SKILL.md +48 -0
- package/pi/package-skills/gsd-progress/SKILL.md +51 -0
- package/pi/package-skills/gsd-quick/SKILL.md +179 -0
- package/pi/package-skills/gsd-quick-batch/SKILL.md +114 -0
- package/pi/package-skills/gsd-resume-work/SKILL.md +31 -0
- package/pi/package-skills/gsd-review/SKILL.md +50 -0
- package/pi/package-skills/gsd-review-backlog/SKILL.md +65 -0
- package/pi/package-skills/gsd-secure-phase/SKILL.md +37 -0
- package/pi/package-skills/gsd-settings/SKILL.md +30 -0
- package/pi/package-skills/gsd-ship/SKILL.md +27 -0
- package/pi/package-skills/gsd-sketch/SKILL.md +56 -0
- package/pi/package-skills/gsd-spec-phase/SKILL.md +63 -0
- package/pi/package-skills/gsd-spike/SKILL.md +53 -0
- package/pi/package-skills/gsd-stats/SKILL.md +22 -0
- package/pi/package-skills/gsd-surface/SKILL.md +177 -0
- package/pi/package-skills/gsd-thread/SKILL.md +29 -0
- package/pi/package-skills/gsd-ui-phase/SKILL.md +35 -0
- package/pi/package-skills/gsd-ui-review/SKILL.md +35 -0
- package/pi/package-skills/gsd-ultraplan-phase/SKILL.md +38 -0
- package/pi/package-skills/gsd-undo/SKILL.md +38 -0
- package/pi/package-skills/gsd-update/SKILL.md +51 -0
- package/pi/package-skills/gsd-validate-phase/SKILL.md +37 -0
- package/pi/package-skills/gsd-verify-work/SKILL.md +41 -0
- package/pi/package-skills/gsd-workspace/SKILL.md +57 -0
- package/pi/package-skills/gsd-workstreams/SKILL.md +73 -0
- package/scripts/audit-workflow-script-paths.cjs +1 -1
- package/scripts/build-hooks.js +4 -0
- package/scripts/command-contract-helpers.cjs +1 -1
- package/scripts/diagnose-windows-spawn-watcher.cjs +317 -0
- package/scripts/docs-guard-registry.cjs +22 -7
- package/scripts/gen-health-docs.cjs +13 -1
- package/scripts/gen-pi-package-skills.cjs +217 -0
- package/scripts/lib/macos-conformance-tier.generated.cjs +19 -0
- package/scripts/lib/platform-conformance-tier.generated.cjs +41 -0
- package/scripts/lint-docs-guard-registration.exempt-baseline.cjs +3 -3
- package/scripts/lint-phase-arg-assignment.cjs +2 -2
- package/scripts/lint-retired-runtime-name.cjs +5 -0
- package/scripts/lint-workflow-shellcheck-baseline.json +16 -16
- package/scripts/npm-audit-baseline.cjs +41 -7
- package/scripts/release-tarball-smoke.cjs +1 -0
- package/scripts/reviewed-or-maintainer-policy.cjs +181 -0
- package/scripts/select-docs-guards.cjs +1 -1
- package/scripts/self-test-report-policy.cjs +350 -0
- package/skills/gsd-code-review/SKILL.md +8 -0
- package/skills/gsd-pause-work/SKILL.md +1 -1
- package/vscode/package.json +1 -1
|
@@ -0,0 +1,317 @@
|
|
|
1
|
+
'use strict';
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* #5473 — a job-long watcher for the Windows conformance tier's bounded-child stalls.
|
|
5
|
+
*
|
|
6
|
+
* In one Windows run (job 39083981500) three different tests each hit their
|
|
7
|
+
* bound EXACTLY: the #4784 `sh -c 'printf | sed | head -1'` at 10 s, an
|
|
8
|
+
* `echo | grep | awk` bash pipeline at 5 s, and `npm ls` through cmd.exe at 30 s.
|
|
9
|
+
* Measured inside the tier, a bare `bash` starts in ~35 ms, Defender real-time
|
|
10
|
+
* protection is off, and 2,407 pipeline calls under 6-way concurrency never
|
|
11
|
+
* stalled past 2 s. So the stall is rare and does not reproduce on demand. This
|
|
12
|
+
* watcher runs beside the whole test step on the same VM and writes down, with a
|
|
13
|
+
* UTC wall-clock timestamp, every moment the VM itself stops making progress:
|
|
14
|
+
*
|
|
15
|
+
* - `loop-gap`: this process's own timer fired more than LOOP_GAP_MS late
|
|
16
|
+
* (the whole VM, or at least this process, was not scheduled);
|
|
17
|
+
* - `probe-slow`: a probe child (the #4784 sh pipeline, or `cmd /c exit`)
|
|
18
|
+
* took more than PROBE_SLOW_MS, or hit its bound;
|
|
19
|
+
* - `procs`: a snapshot of the live node/sh/bash/cmd/npm/git processes (name,
|
|
20
|
+
* parent, age, CPU time so far, and the *.test.cjs names in their
|
|
21
|
+
* arguments), taken after either anomaly and every two minutes, so a stall
|
|
22
|
+
* can be lined up with what else was running in the chunk. Earlier red
|
|
23
|
+
* `next`s (see defaultMaxUnmeasuredPerChunk in scripts/run-tests.cjs) lost
|
|
24
|
+
* whole chunks at 600000 ms with zero failing tests; the periodic snapshots
|
|
25
|
+
* show which test file a killed chunk was inside and whether it was idle.
|
|
26
|
+
*
|
|
27
|
+
* Timestamps are ISO UTC, the same clock as the lines in the job log, so a
|
|
28
|
+
* failing test's end time minus its duration can be lined up against these
|
|
29
|
+
* events: a stall that is the VM freezing shows up here at the same instant; a
|
|
30
|
+
* stall that is only that one child hanging does not.
|
|
31
|
+
*
|
|
32
|
+
* node scripts/diagnose-windows-spawn-watcher.cjs start [dir] spawn detached, return at once
|
|
33
|
+
* node scripts/diagnose-windows-spawn-watcher.cjs report [dir] stop it, print and summarize the events
|
|
34
|
+
*
|
|
35
|
+
* `dir` defaults to RUNNER_TEMP/spawn-watcher. No environment variable or value
|
|
36
|
+
* is read into the report. TEMPORARY: delete
|
|
37
|
+
* with scripts/diagnose-validate-commit-*.cjs once #5473 is resolved.
|
|
38
|
+
*/
|
|
39
|
+
|
|
40
|
+
const fs = require('node:fs');
|
|
41
|
+
const path = require('node:path');
|
|
42
|
+
const { spawn } = require('node:child_process');
|
|
43
|
+
const { ExitError, runMain } = require('./lib/cli-exit.cjs');
|
|
44
|
+
|
|
45
|
+
const TICK_MS = 250;
|
|
46
|
+
const LOOP_GAP_MS = 750;
|
|
47
|
+
// The watcher must not become the load it is looking for: a stall that shows up once in
|
|
48
|
+
// thousands of calls is easy to shift with extra spawn churn, so probes are sparse (one
|
|
49
|
+
// sh and one cmd every 5 s, about 480 pairs over a 40 minute job) and the CIM query,
|
|
50
|
+
// the heavy part, runs every 5 minutes plus at most once a minute after an anomaly.
|
|
51
|
+
const PROBE_EVERY_MS = 5000;
|
|
52
|
+
const PROBE_BOUND_MS = 15_000;
|
|
53
|
+
const PROBE_SLOW_MS = 1500;
|
|
54
|
+
const MAX_LIFETIME_MS = 40 * 60_000;
|
|
55
|
+
const PIPELINE = "printf '%s\\n' x | sed -n 's/x/y/p' | head -1";
|
|
56
|
+
|
|
57
|
+
const REPORT_WAIT_MS = 20_000;
|
|
58
|
+
const EVENTS_FILE = 'events.log';
|
|
59
|
+
const STOP_FILE = 'stop';
|
|
60
|
+
const SUMMARY_FILE = 'summary.json';
|
|
61
|
+
|
|
62
|
+
/** One event line: `<ISO UTC> <kind> <ms> <detail>`. */
|
|
63
|
+
function formatEvent(at, kind, ms, detail) {
|
|
64
|
+
return `${new Date(at).toISOString()} ${kind} ${ms} ${detail}`;
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
const SHELL_NAMES = new Set(['sh', 'bash', 'cmd']);
|
|
68
|
+
const LINGER_AGE_S = 30;
|
|
69
|
+
|
|
70
|
+
/**
|
|
71
|
+
* One compact line for a process snapshot. `rows` are `{pid, ppid, name, born, cpuMs, cmd}`
|
|
72
|
+
* (born = epoch ms, cmd = the command line). Only what identifies the work is kept: the
|
|
73
|
+
* process name, its age, its CPU time so far, and the `*.test.cjs` file names in its
|
|
74
|
+
* arguments (the node:test chunk children carry them). Nothing else from a command line
|
|
75
|
+
* is reported, because the job environment carries tokens and a report is public.
|
|
76
|
+
*/
|
|
77
|
+
function formatProcs(rows, now) {
|
|
78
|
+
return rows
|
|
79
|
+
.map((r) => {
|
|
80
|
+
const tests = [...new Set(String(r.cmd || '').match(/[\w.-]+\.test\.cjs/g) || [])].slice(0, 3);
|
|
81
|
+
const age = Math.max(0, Math.round((now - r.born) / 1000));
|
|
82
|
+
const cpu = Math.round(r.cpuMs / 1000);
|
|
83
|
+
// A shell that outlives every bound the tests set is the signature of a descendant
|
|
84
|
+
// still holding a spawnSync stdio pipe: node's timer then reports ETIMEDOUT at the
|
|
85
|
+
// exact bound although the direct child already exited (node src/spawn_sync.cc).
|
|
86
|
+
const lingering = SHELL_NAMES.has(r.name) && age > LINGER_AGE_S ? ' LINGERING' : '';
|
|
87
|
+
return `${r.name}#${r.pid}<${r.ppid} age ${age}s cpu ${cpu}s${lingering}${tests.length ? ` [${tests.join(' ')}]` : ''}`;
|
|
88
|
+
})
|
|
89
|
+
.join(' | ');
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
/** Inverse of formatEvent; null for a line that is not an event. */
|
|
93
|
+
function parseEvent(line) {
|
|
94
|
+
const m = /^(\d{4}-\d{2}-\d{2}T[\d:.]+Z) (loop-gap|probe-slow|probe-error|procs) (\d+) (.*)$/.exec(line);
|
|
95
|
+
if (!m) return null;
|
|
96
|
+
return { at: m[1], kind: m[2], ms: Number(m[3]), detail: m[4] };
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
/** Counts and extremes per kind, over parsed events. */
|
|
100
|
+
function summarizeEvents(events) {
|
|
101
|
+
const out = {};
|
|
102
|
+
for (const e of events) {
|
|
103
|
+
const s = out[e.kind] || (out[e.kind] = { count: 0, maxMs: 0 });
|
|
104
|
+
s.count += 1;
|
|
105
|
+
if (e.ms > s.maxMs) s.maxMs = e.ms;
|
|
106
|
+
}
|
|
107
|
+
return out;
|
|
108
|
+
}
|
|
109
|
+
|
|
110
|
+
/** Percentile (0..1) of numbers, nearest-rank on a sorted copy; null when empty. */
|
|
111
|
+
function percentile(values, p) {
|
|
112
|
+
if (values.length === 0) return null;
|
|
113
|
+
const sorted = [...values].sort((a, b) => a - b);
|
|
114
|
+
return sorted[Math.min(sorted.length - 1, Math.floor(p * sorted.length))];
|
|
115
|
+
}
|
|
116
|
+
|
|
117
|
+
/**
|
|
118
|
+
* Run one probe child. `onDone(label, ms, timedOut, started, error)`: `error` is the spawn
|
|
119
|
+
* error code (or the thrown message) when the probe never ran, else null. A probe that
|
|
120
|
+
* never ran must not look like a fast success, so callers keep it out of the latencies.
|
|
121
|
+
*/
|
|
122
|
+
function probe(label, bin, args, onDone) {
|
|
123
|
+
const started = Date.now();
|
|
124
|
+
let timedOut = false;
|
|
125
|
+
let settled = false;
|
|
126
|
+
let timer = null;
|
|
127
|
+
const finish = (error) => {
|
|
128
|
+
if (settled) return;
|
|
129
|
+
settled = true;
|
|
130
|
+
clearTimeout(timer);
|
|
131
|
+
onDone(label, Date.now() - started, timedOut, started, error || null);
|
|
132
|
+
};
|
|
133
|
+
let child;
|
|
134
|
+
try {
|
|
135
|
+
child = spawn(bin, args, { stdio: ['ignore', 'pipe', 'pipe'], windowsHide: true });
|
|
136
|
+
} catch (e) {
|
|
137
|
+
finish(`threw ${e && e.code ? e.code : 'error'}`);
|
|
138
|
+
return;
|
|
139
|
+
}
|
|
140
|
+
timer = setTimeout(() => { timedOut = true; try { child.kill('SIGKILL'); } catch { /* gone */ } finish(); }, PROBE_BOUND_MS);
|
|
141
|
+
child.stdout.resume();
|
|
142
|
+
child.stderr.resume();
|
|
143
|
+
child.on('error', (e) => finish(`spawn ${e && e.code ? e.code : 'error'}`));
|
|
144
|
+
child.on('close', () => finish());
|
|
145
|
+
}
|
|
146
|
+
|
|
147
|
+
const PS_PROCS = "Get-CimInstance Win32_Process | Where-Object { $_.Name -match '^(node|sh|bash|cmd|npm|git)\\.exe$' } | ForEach-Object { [pscustomobject]@{ pid = $_.ProcessId; ppid = $_.ParentProcessId; name = $_.Name -replace '\\.exe$', ''; born = [int64](([datetime]$_.CreationDate).ToUniversalTime() - [datetime]'1970-01-01').TotalMilliseconds; cpuMs = [int64](($_.KernelModeTime + $_.UserModeTime) / 10000); cmd = $_.CommandLine } } | ConvertTo-Json -Compress";
|
|
148
|
+
const SNAPSHOT_EVERY_MS = 300_000;
|
|
149
|
+
const SNAPSHOT_COOLDOWN_MS = 60_000;
|
|
150
|
+
const SNAPSHOT_BOUND_MS = 30_000;
|
|
151
|
+
|
|
152
|
+
/** Snapshot the live node/sh/bash/cmd/npm/git processes; calls back with the compact line. */
|
|
153
|
+
function snapshotProcs(onDone) {
|
|
154
|
+
const started = Date.now();
|
|
155
|
+
let out = '';
|
|
156
|
+
let err = '';
|
|
157
|
+
let child;
|
|
158
|
+
try {
|
|
159
|
+
child = spawn('powershell', ['-NoProfile', '-NonInteractive', '-Command', PS_PROCS], { stdio: ['ignore', 'pipe', 'pipe'], windowsHide: true });
|
|
160
|
+
} catch (e) {
|
|
161
|
+
onDone(Date.now() - started, `snapshot unavailable (threw ${e && e.code ? e.code : 'error'})`);
|
|
162
|
+
return;
|
|
163
|
+
}
|
|
164
|
+
const timer = setTimeout(() => { try { child.kill('SIGKILL'); } catch { /* gone */ } }, SNAPSHOT_BOUND_MS);
|
|
165
|
+
child.stdout.on('data', (d) => { out += d; });
|
|
166
|
+
// PowerShell's own error text carries no command lines; a short head tells a failure from a timeout.
|
|
167
|
+
child.stderr.on('data', (d) => { if (err.length < 200) err += d; });
|
|
168
|
+
child.on('error', (e) => { clearTimeout(timer); onDone(Date.now() - started, `snapshot unavailable (spawn ${e && e.code ? e.code : 'error'})`); });
|
|
169
|
+
child.on('close', (code, signal) => {
|
|
170
|
+
clearTimeout(timer);
|
|
171
|
+
let rows = [];
|
|
172
|
+
try { const parsed = JSON.parse(out); rows = Array.isArray(parsed) ? parsed : [parsed]; } catch { /* no rows */ }
|
|
173
|
+
// The watcher's own process and its probe/snapshot children are not what we are lining up against.
|
|
174
|
+
rows = rows.filter((r) => r.pid !== process.pid && r.ppid !== process.pid);
|
|
175
|
+
const why = signal ? `killed ${signal}` : `exit ${code}`;
|
|
176
|
+
onDone(
|
|
177
|
+
Date.now() - started,
|
|
178
|
+
rows.length ? `${rows.length} procs: ${formatProcs(rows, Date.now())}` : `snapshot unavailable (${why}${err ? `: ${err.replace(/\s+/g, ' ').trim().slice(0, 200)}` : ''})`,
|
|
179
|
+
);
|
|
180
|
+
});
|
|
181
|
+
}
|
|
182
|
+
|
|
183
|
+
function run(dir) {
|
|
184
|
+
const eventsPath = path.join(dir, EVENTS_FILE);
|
|
185
|
+
const record = (at, kind, ms, detail) => {
|
|
186
|
+
try { fs.appendFileSync(eventsPath, `${formatEvent(at, kind, ms, detail)}\n`); } catch { /* dir gone: nothing to do */ }
|
|
187
|
+
};
|
|
188
|
+
let lastSnapshotAt = 0;
|
|
189
|
+
let snapshotting = false;
|
|
190
|
+
const takeSnapshot = (why) => {
|
|
191
|
+
const now = Date.now();
|
|
192
|
+
if (snapshotting || now - lastSnapshotAt < SNAPSHOT_COOLDOWN_MS) return;
|
|
193
|
+
snapshotting = true;
|
|
194
|
+
lastSnapshotAt = now;
|
|
195
|
+
snapshotProcs((ms, detail) => { snapshotting = false; record(Date.now(), 'procs', ms, `${why}: ${detail}`); });
|
|
196
|
+
};
|
|
197
|
+
const snapshotTimer = setInterval(() => takeSnapshot('periodic'), SNAPSHOT_EVERY_MS);
|
|
198
|
+
const lifetimeEnd = Date.now() + MAX_LIFETIME_MS;
|
|
199
|
+
const probeMs = { sh: [], cmd: [] };
|
|
200
|
+
let probeCount = 0;
|
|
201
|
+
let last = Date.now();
|
|
202
|
+
|
|
203
|
+
const tick = setInterval(() => {
|
|
204
|
+
const now = Date.now();
|
|
205
|
+
const late = now - last - TICK_MS;
|
|
206
|
+
if (late > LOOP_GAP_MS) { record(now, 'loop-gap', late, `timer fired ${late}ms late`); takeSnapshot('after loop-gap'); }
|
|
207
|
+
last = now;
|
|
208
|
+
if (now > lifetimeEnd || fs.existsSync(path.join(dir, STOP_FILE))) finishRun();
|
|
209
|
+
}, TICK_MS);
|
|
210
|
+
|
|
211
|
+
const probeErrors = { sh: 0, cmd: 0 };
|
|
212
|
+
const onProbe = (label, ms, timedOut, started, error) => {
|
|
213
|
+
if (error) {
|
|
214
|
+
probeErrors[label] += 1;
|
|
215
|
+
record(Date.now(), 'probe-error', ms, `${label} never ran: ${error}`);
|
|
216
|
+
return;
|
|
217
|
+
}
|
|
218
|
+
probeCount += 1;
|
|
219
|
+
probeMs[label].push(ms);
|
|
220
|
+
if (ms > PROBE_SLOW_MS || timedOut) {
|
|
221
|
+
record(Date.now(), 'probe-slow', ms, `${label} started ${new Date(started).toISOString()}${timedOut ? ' hit the bound' : ''}`);
|
|
222
|
+
takeSnapshot('after probe-slow');
|
|
223
|
+
}
|
|
224
|
+
};
|
|
225
|
+
|
|
226
|
+
let probing = true;
|
|
227
|
+
let loopTimer = null;
|
|
228
|
+
// Run the two probes one after the other, then wait out the rest of the interval.
|
|
229
|
+
const loop = () => {
|
|
230
|
+
if (!probing) return;
|
|
231
|
+
const cycleStart = Date.now();
|
|
232
|
+
probe('sh', 'sh', ['-c', PIPELINE], (l, ms, to, st, er) => {
|
|
233
|
+
onProbe(l, ms, to, st, er);
|
|
234
|
+
probe('cmd', 'cmd', ['/c', 'exit', '0'], (l2, ms2, to2, st2, er2) => {
|
|
235
|
+
onProbe(l2, ms2, to2, st2, er2);
|
|
236
|
+
loopTimer = setTimeout(loop, Math.max(0, PROBE_EVERY_MS - (Date.now() - cycleStart)));
|
|
237
|
+
});
|
|
238
|
+
});
|
|
239
|
+
};
|
|
240
|
+
loop();
|
|
241
|
+
|
|
242
|
+
function finishRun() {
|
|
243
|
+
probing = false;
|
|
244
|
+
clearInterval(tick);
|
|
245
|
+
clearInterval(snapshotTimer);
|
|
246
|
+
clearTimeout(loopTimer);
|
|
247
|
+
const summary = {
|
|
248
|
+
probes: probeCount,
|
|
249
|
+
sh: { n: probeMs.sh.length, errors: probeErrors.sh, median: percentile(probeMs.sh, 0.5), p99: percentile(probeMs.sh, 0.99), max: percentile(probeMs.sh, 1) },
|
|
250
|
+
cmd: { n: probeMs.cmd.length, errors: probeErrors.cmd, median: percentile(probeMs.cmd, 0.5), p99: percentile(probeMs.cmd, 0.99), max: percentile(probeMs.cmd, 1) },
|
|
251
|
+
};
|
|
252
|
+
// Write then rename, so report never sees the file before its content has landed.
|
|
253
|
+
try {
|
|
254
|
+
const target = path.join(dir, SUMMARY_FILE);
|
|
255
|
+
fs.writeFileSync(`${target}.tmp`, JSON.stringify(summary));
|
|
256
|
+
fs.renameSync(`${target}.tmp`, target);
|
|
257
|
+
} catch { /* best effort */ }
|
|
258
|
+
// No process.exit (ADR-3889): every timer is cleared above, so the process ends when the
|
|
259
|
+
// probe or snapshot child still in flight (each bounded) has exited; report has already
|
|
260
|
+
// been released by the summary file, so nothing waits on this.
|
|
261
|
+
}
|
|
262
|
+
}
|
|
263
|
+
|
|
264
|
+
function start(dir) {
|
|
265
|
+
fs.mkdirSync(dir, { recursive: true });
|
|
266
|
+
const child = spawn(process.execPath, [__filename, 'run', dir], { detached: true, stdio: 'ignore', windowsHide: true });
|
|
267
|
+
child.unref();
|
|
268
|
+
}
|
|
269
|
+
|
|
270
|
+
function report(dir) {
|
|
271
|
+
try { fs.writeFileSync(path.join(dir, STOP_FILE), ''); } catch { /* start never ran */ }
|
|
272
|
+
// The stop file is only seen on the watcher's 250ms tick, so a VM stall longer than this
|
|
273
|
+
// wait (the very case under study) leaves no summary: say so below instead of "crashed".
|
|
274
|
+
const deadline = Date.now() + REPORT_WAIT_MS;
|
|
275
|
+
while (!fs.existsSync(path.join(dir, SUMMARY_FILE)) && Date.now() < deadline) {
|
|
276
|
+
Atomics.wait(new Int32Array(new SharedArrayBuffer(4)), 0, 0, 100);
|
|
277
|
+
}
|
|
278
|
+
let events = [];
|
|
279
|
+
try {
|
|
280
|
+
events = fs.readFileSync(path.join(dir, EVENTS_FILE), 'utf8').split('\n').map(parseEvent).filter(Boolean);
|
|
281
|
+
} catch { /* no events file: nothing recorded */ }
|
|
282
|
+
let summary = null;
|
|
283
|
+
try { summary = JSON.parse(fs.readFileSync(path.join(dir, SUMMARY_FILE), 'utf8')); } catch { /* watcher did not finish */ }
|
|
284
|
+
const lines = [
|
|
285
|
+
'### #5473 Windows spawn-stall watcher (whole test step, same VM)',
|
|
286
|
+
'',
|
|
287
|
+
summary
|
|
288
|
+
? `probes: ${summary.probes} ; sh pipeline median ${summary.sh.median} / p99 ${summary.sh.p99} / max ${summary.sh.max} ms (n=${summary.sh.n}, never ran ${summary.sh.errors}) ; cmd /c exit median ${summary.cmd.median} / p99 ${summary.cmd.p99} / max ${summary.cmd.max} ms (n=${summary.cmd.n}, never ran ${summary.cmd.errors})`
|
|
289
|
+
: `watcher summary missing after ${REPORT_WAIT_MS / 1000}s: it either crashed or was stalled past the wait (the events below show which: a stall leaves loop-gap/probe-slow lines up to the last tick)`,
|
|
290
|
+
`events: ${JSON.stringify(summarizeEvents(events))}`,
|
|
291
|
+
'',
|
|
292
|
+
...events.map((e) => `- ${e.at} ${e.kind} ${e.ms}ms ${e.detail}`),
|
|
293
|
+
];
|
|
294
|
+
const text = lines.join('\n');
|
|
295
|
+
process.stdout.write(`${text}\n`);
|
|
296
|
+
if (process.env.GITHUB_STEP_SUMMARY) {
|
|
297
|
+
try { fs.appendFileSync(process.env.GITHUB_STEP_SUMMARY, `${text}\n`); } catch { /* best effort */ }
|
|
298
|
+
}
|
|
299
|
+
}
|
|
300
|
+
|
|
301
|
+
// CONTRIBUTING forbids `${{ }}` inside a workflow `run:` block, so the workflow
|
|
302
|
+
// passes no directory: GitHub sets RUNNER_TEMP itself, and start and report agree on it here.
|
|
303
|
+
function defaultDir() {
|
|
304
|
+
return path.join(process.env.RUNNER_TEMP || require('node:os').tmpdir(), 'spawn-watcher');
|
|
305
|
+
}
|
|
306
|
+
|
|
307
|
+
function main() {
|
|
308
|
+
const [, , mode, dirArg] = process.argv;
|
|
309
|
+
if (!['start', 'run', 'report'].includes(mode)) {
|
|
310
|
+
throw new ExitError(2, 'usage: diagnose-windows-spawn-watcher.cjs start|report [dir]');
|
|
311
|
+
}
|
|
312
|
+
({ start, run, report })[mode](path.resolve(dirArg || defaultDir()));
|
|
313
|
+
}
|
|
314
|
+
|
|
315
|
+
if (require.main === module) runMain(main);
|
|
316
|
+
|
|
317
|
+
module.exports = { formatEvent, formatProcs, parseEvent, summarizeEvents, percentile, LOOP_GAP_MS, PROBE_SLOW_MS };
|
|
@@ -55,7 +55,7 @@
|
|
|
55
55
|
* narrow subset of guards that read it, instead of always running the
|
|
56
56
|
* entire registry. Each value is a non-empty array of PATTERNS:
|
|
57
57
|
*
|
|
58
|
-
* - a plain path (e.g. `'docs/
|
|
58
|
+
* - a plain path (e.g. `'docs/reference/agents.md'`) matches that exact file only;
|
|
59
59
|
* - a trailing-slash path (e.g. `'docs/adr/'`) matches any changed path
|
|
60
60
|
* under that directory prefix — use this for a test that walks or
|
|
61
61
|
* `readdirSync`s a whole docs/ subdirectory;
|
|
@@ -157,6 +157,8 @@ function assertNoSuiteCollision(tests) {
|
|
|
157
157
|
* unresolvable read is recorded as `'*'`, never a narrowed guess.
|
|
158
158
|
*/
|
|
159
159
|
const DOCS_GUARD_TESTS = {
|
|
160
|
+
'tests/pi-unsupported-skill-docs.test.cjs': ['docs/how-to/start-a-gsd-workflow-from-pi.md'],
|
|
161
|
+
'tests/pi-parity-matrix.test.cjs': ['docs/reference/pi-parity-matrix.md'],
|
|
160
162
|
'tests/adr-15-progress-converge.test.cjs': [
|
|
161
163
|
'docs/COMMANDS.md',
|
|
162
164
|
'docs/how-to/run-phases-autonomously.md',
|
|
@@ -168,11 +170,14 @@ const DOCS_GUARD_TESTS = {
|
|
|
168
170
|
// Walks docs/features/ as a directory (the fragment corpus) and asserts the
|
|
169
171
|
// committed docs/FEATURES.md equals the generated projection of it (#3840).
|
|
170
172
|
'tests/features-index-gate.test.cjs': ['docs/features/', 'docs/FEATURES.md'],
|
|
171
|
-
'tests/agent-classification-parity.test.cjs': ['docs/
|
|
173
|
+
'tests/agent-classification-parity.test.cjs': ['docs/reference/agents.md', 'docs/INVENTORY.md'],
|
|
172
174
|
// #3683: pins the learnings feature section's agreement with the canonical
|
|
173
175
|
// artifact registry (reads docs/FEATURES.md around the extract-learnings
|
|
174
176
|
// entry).
|
|
175
177
|
'tests/learnings.test.cjs': ['docs/FEATURES.md'],
|
|
178
|
+
// #5706: pins the release map statuses / tag states in docs/CONFIGURATION.md
|
|
179
|
+
// against the milestone-release-map module's exported enums.
|
|
180
|
+
'tests/milestone-release-map.test.cjs': ['docs/CONFIGURATION.md'],
|
|
176
181
|
'tests/analyze-dependencies.test.cjs': ['docs/COMMANDS.md'],
|
|
177
182
|
'tests/auto-select-attribute.test.cjs': ['docs/reference/plan-md.md'],
|
|
178
183
|
'tests/autonomous-converge.test.cjs': [
|
|
@@ -199,6 +204,7 @@ const DOCS_GUARD_TESTS = {
|
|
|
199
204
|
// files without re-deriving the scan's own file-discovery logic.
|
|
200
205
|
'tests/commit-files-pathspec.test.cjs': ['*'],
|
|
201
206
|
'tests/compact-content-4139.test.cjs': ['docs/CONFIGURATION.md'],
|
|
207
|
+
'tests/complete-milestone-release-map-routing.test.cjs': ['docs/README.md', 'docs/how-to/tag-a-multi-repo-milestone.md'],
|
|
202
208
|
'tests/config-field-docs.test.cjs': ['docs/CONFIGURATION.md'],
|
|
203
209
|
'tests/config.test.cjs': ['docs/CONFIGURATION.md'],
|
|
204
210
|
// #4974: doc/workflow content-parity checks read docs/CONFIGURATION.md's
|
|
@@ -245,7 +251,7 @@ const DOCS_GUARD_TESTS = {
|
|
|
245
251
|
'docs/ko-KR/reference/state-md.md',
|
|
246
252
|
'docs/pt-BR/reference/state-md.md',
|
|
247
253
|
],
|
|
248
|
-
'tests/drift-detection.test.cjs': ['docs/CONFIGURATION.md', 'docs/
|
|
254
|
+
'tests/drift-detection.test.cjs': ['docs/CONFIGURATION.md', 'docs/reference/agents.md'],
|
|
249
255
|
'tests/edge-probe-docs-fixtures.test.cjs': ['docs/adr/550-spec-phase-probe-contract.md'],
|
|
250
256
|
'tests/edit-phase.test.cjs': [
|
|
251
257
|
'docs/INVENTORY.md',
|
|
@@ -313,7 +319,7 @@ const DOCS_GUARD_TESTS = {
|
|
|
313
319
|
'tests/host-integration-descriptors.test.cjs': [
|
|
314
320
|
'docs/reference/host-integration-capability-matrix.md',
|
|
315
321
|
],
|
|
316
|
-
'tests/install.test.cjs': ['docs/
|
|
322
|
+
'tests/install.test.cjs': ['docs/reference/agents.md', 'docs/INVENTORY.md', 'docs/INVENTORY-MANIFEST.json'],
|
|
317
323
|
// SOURCE_DIRS includes 'docs' and walks it recursively for every .md file
|
|
318
324
|
// (intel.test.cjs:1223-1228) — a generic tree walk.
|
|
319
325
|
'tests/intel.test.cjs': ['*'],
|
|
@@ -321,6 +327,13 @@ const DOCS_GUARD_TESTS = {
|
|
|
321
327
|
'tests/inventory-manifest-sync.test.cjs': ['docs/INVENTORY.md', 'docs/INVENTORY-MANIFEST.json'],
|
|
322
328
|
'tests/kilo-upgrades.test.cjs': ['docs/how-to/connect-gsd-mcp-server.md'],
|
|
323
329
|
'tests/live-config-guard.test.cjs': ['docs/TESTING-SUITES.md'],
|
|
330
|
+
// #5601: localized INVENTORY coverage notes must state no agent counts.
|
|
331
|
+
'tests/localized-inventory-agent-counts.test.cjs': [
|
|
332
|
+
'docs/ja-JP/',
|
|
333
|
+
'docs/ko-KR/',
|
|
334
|
+
'docs/pt-BR/',
|
|
335
|
+
'docs/zh-CN/',
|
|
336
|
+
],
|
|
324
337
|
// #3726: pins the `milestone complete` synopsis (English + four localized
|
|
325
338
|
// mirrors), the `--confirm` flag row and the guard-override instructions —
|
|
326
339
|
// `type: Fixed` exempts that PR from the docs-required lint, so this is the
|
|
@@ -338,13 +351,15 @@ const DOCS_GUARD_TESTS = {
|
|
|
338
351
|
// (no-pending-3212-markers.test.cjs:37-46), which includes all of docs/ —
|
|
339
352
|
// cannot be resolved to a fixed docs/ path set.
|
|
340
353
|
'tests/no-pending-3212-markers.test.cjs': ['*'],
|
|
354
|
+
'tests/pause-work-improvements.test.cjs': ['docs/zh-CN/references/git-integration.md'],
|
|
341
355
|
'tests/phase6-capability-docs.test.cjs': ['docs/how-to/develop-a-capability.md', 'docs/README.md'],
|
|
342
356
|
'tests/phase6-review-capabilities.test.cjs': [
|
|
343
357
|
'docs/reference/review-verification-capabilities.md',
|
|
344
358
|
'docs/how-to/develop-a-capability.md',
|
|
345
359
|
'docs/README.md',
|
|
346
360
|
],
|
|
347
|
-
'tests/plan-
|
|
361
|
+
'tests/plan-authoring-verification-gaps.test.cjs': ['docs/reference/plan-md.md'],
|
|
362
|
+
'tests/plan-checker-coupling.test.cjs': ['docs/reference/agents.md'],
|
|
348
363
|
'tests/plan-phase-drift-guard.test.cjs': [
|
|
349
364
|
'docs/CONFIGURATION.md',
|
|
350
365
|
'docs/COMMANDS.md',
|
|
@@ -404,7 +419,7 @@ const DOCS_GUARD_TESTS = {
|
|
|
404
419
|
'docs/COMMANDS.md',
|
|
405
420
|
'docs/reference/plan-md.md',
|
|
406
421
|
'docs/how-to/plan-a-phase.md',
|
|
407
|
-
'docs/
|
|
422
|
+
'docs/reference/agents.md',
|
|
408
423
|
],
|
|
409
424
|
'tests/ui-interaction-capture.test.cjs': ['docs/CONFIGURATION.md'],
|
|
410
425
|
'tests/ui-spec-inventory-provenance.test.cjs': [
|
|
@@ -419,7 +434,7 @@ const DOCS_GUARD_TESTS = {
|
|
|
419
434
|
'docs/pt-BR/how-to/design-a-ui-phase.md',
|
|
420
435
|
],
|
|
421
436
|
'tests/verifier-behavior-unverified.test.cjs': ['docs/reference/planning-artifacts.md'],
|
|
422
|
-
'tests/verifier-coincidental-reliance.test.cjs': ['docs/
|
|
437
|
+
'tests/verifier-coincidental-reliance.test.cjs': ['docs/reference/agents.md'],
|
|
423
438
|
'tests/verify.test.cjs': ['docs/reference/plan-md.md'],
|
|
424
439
|
// #5118 V43: asserts the deleted execute-phase/steps/stale-reverification.md
|
|
425
440
|
// is absent from the generated inventory manifest.
|
|
@@ -122,6 +122,13 @@ const REMEDY_ACTION_METADATA = new Map([
|
|
|
122
122
|
risk: 'None — additive only; triggered by `--backfill` flag',
|
|
123
123
|
},
|
|
124
124
|
],
|
|
125
|
+
[
|
|
126
|
+
'repairVerificationStatus',
|
|
127
|
+
{
|
|
128
|
+
effect: 'Rewrite a known legacy verification `status` (`pass_with_caveats`) to `passed`, keeping the original as `legacy_status`',
|
|
129
|
+
risk: 'None — only the `status` line changes; unknown statuses are never touched',
|
|
130
|
+
},
|
|
131
|
+
],
|
|
125
132
|
]);
|
|
126
133
|
|
|
127
134
|
/** Order the Effect/Risk table renders in — matches `REMEDY_ACTION`'s own declaration order. */
|
|
@@ -132,6 +139,7 @@ const REMEDY_ACTION_ORDER = [
|
|
|
132
139
|
'addNyquistKey',
|
|
133
140
|
'addAiIntegrationPhaseKey',
|
|
134
141
|
'backfillMilestones',
|
|
142
|
+
'repairVerificationStatus',
|
|
135
143
|
];
|
|
136
144
|
|
|
137
145
|
/**
|
|
@@ -139,7 +147,11 @@ const REMEDY_ACTION_ORDER = [
|
|
|
139
147
|
* whose remedy is conditional on a flag the plain `Yes`/`No` can't express
|
|
140
148
|
* (mirrors the hand-written table's pre-existing `W018` row: `Yes (--backfill)`).
|
|
141
149
|
*/
|
|
142
|
-
const REPAIRABLE_DISPLAY_OVERRIDE = new Map([
|
|
150
|
+
const REPAIRABLE_DISPLAY_OVERRIDE = new Map([
|
|
151
|
+
['W018', 'Yes (`--backfill`)'],
|
|
152
|
+
// #5291: only a known legacy status (`pass_with_caveats`) is rewritten.
|
|
153
|
+
['W030', 'Yes (known legacy statuses only)'],
|
|
154
|
+
]);
|
|
143
155
|
|
|
144
156
|
const STATIC_NOT_REPAIRABLE_BULLETS = [
|
|
145
157
|
'PROJECT.md, ROADMAP.md content',
|
|
@@ -0,0 +1,217 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
'use strict';
|
|
3
|
+
|
|
4
|
+
/**
|
|
5
|
+
* gen-pi-package-skills.cjs — generates pi/package-skills/gsd-<stem>/SKILL.md,
|
|
6
|
+
* the Pi skill templates the Pi package (`pi install npm:@opengsd/gsd-core`)
|
|
7
|
+
* renders at session start.
|
|
8
|
+
*
|
|
9
|
+
* Usage:
|
|
10
|
+
* node scripts/gen-pi-package-skills.cjs # print summary
|
|
11
|
+
* node scripts/gen-pi-package-skills.cjs --write # write pi/package-skills/
|
|
12
|
+
* node scripts/gen-pi-package-skills.cjs --check # exit 1 if committed templates are stale
|
|
13
|
+
*
|
|
14
|
+
* #5558 (Phase 1b of epic #5556). Pi skill bodies embed the absolute path of the
|
|
15
|
+
* engine tree they `@`-include, and a package's install location is unknown at
|
|
16
|
+
* build time. So the templates are produced by running the real installer
|
|
17
|
+
* (`bin/install.js --pi --global --config-dir <tmp>`), then replacing the temp
|
|
18
|
+
* config dir with PI_ENGINE_ROOT_TOKEN. Rendering a template with a root R
|
|
19
|
+
* (`renderSkillTemplate`, exported for the parity test; pi/gsd.cjs carries the
|
|
20
|
+
* same two-line substitution) is therefore byte-identical to what the installer
|
|
21
|
+
* writes for config dir R.
|
|
22
|
+
*
|
|
23
|
+
* Depends on a built lib (`npm run build:lib`); must run after it in the build chain.
|
|
24
|
+
*/
|
|
25
|
+
|
|
26
|
+
const fs = require('node:fs');
|
|
27
|
+
const os = require('node:os');
|
|
28
|
+
const crypto = require('node:crypto');
|
|
29
|
+
const path = require('node:path');
|
|
30
|
+
const { spawnSync } = require('node:child_process');
|
|
31
|
+
const { ExitError, runMain } = require('./lib/cli-exit.cjs');
|
|
32
|
+
|
|
33
|
+
const ROOT = path.resolve(__dirname, '..');
|
|
34
|
+
const OUT_DIR = path.join(ROOT, 'pi', 'package-skills');
|
|
35
|
+
// #5569: the converted Pi role files the package's gsd_dispatch reads (ADR-5559 amendment).
|
|
36
|
+
const ROLES_DIR = path.join(ROOT, 'pi', 'package-agents');
|
|
37
|
+
const ROLES_MANIFEST = 'manifest.json';
|
|
38
|
+
const INSTALLER = path.join(ROOT, 'bin', 'install.js');
|
|
39
|
+
const PI_ENGINE_ROOT_TOKEN = '__GSD_PI_ENGINE_ROOT__';
|
|
40
|
+
const INSTALL_BOUND_MS = 30000;
|
|
41
|
+
|
|
42
|
+
/** Forward-slash form of a path; the installer emits either form depending on platform. */
|
|
43
|
+
function toSlash(p) {
|
|
44
|
+
return String(p).replace(/\\/g, '/');
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
/**
|
|
48
|
+
* Replace every occurrence of `root` (raw and forward-slash forms) with the token.
|
|
49
|
+
* @param {string} content
|
|
50
|
+
* @param {string} root
|
|
51
|
+
*/
|
|
52
|
+
function tokenizeSkill(content, root) {
|
|
53
|
+
// The installer may print either the path it was given or its symlink-resolved form (/private/var on macOS).
|
|
54
|
+
let real = root;
|
|
55
|
+
try { real = fs.realpathSync(root); } catch { /* removed already: only the given form */ }
|
|
56
|
+
const forms = [...new Set([root, toSlash(root), real, toSlash(real)])].sort((a, b) => b.length - a.length);
|
|
57
|
+
let out = content;
|
|
58
|
+
for (const form of forms) out = out.split(form).join(PI_ENGINE_ROOT_TOKEN);
|
|
59
|
+
return out;
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
/**
|
|
63
|
+
* Render a template for an engine root. The root is substituted in its
|
|
64
|
+
* forward-slash form, which is what the installer writes for the Pi skills.
|
|
65
|
+
* @param {string} template
|
|
66
|
+
* @param {string} engineRoot
|
|
67
|
+
*/
|
|
68
|
+
function renderSkillTemplate(template, engineRoot) {
|
|
69
|
+
return template.split(PI_ENGINE_ROOT_TOKEN).join(toSlash(engineRoot));
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
/**
|
|
73
|
+
* Run the installer for Pi into a fresh temp dir and return that dir. The run
|
|
74
|
+
* gets a separate throwaway HOME (removed straight after): the two must differ,
|
|
75
|
+
* because the installer writes a config dir that equals or sits under HOME as
|
|
76
|
+
* `~`/`$HOME`-relative paths.
|
|
77
|
+
*/
|
|
78
|
+
function installIntoTemp() {
|
|
79
|
+
const tmp = fs.mkdtempSync(path.join(os.tmpdir(), 'gsd-pi-skills-'));
|
|
80
|
+
const home = fs.mkdtempSync(path.join(os.tmpdir(), 'gsd-pi-home-'));
|
|
81
|
+
const res = spawnSync(process.execPath, [INSTALLER, '--pi', '--global', '--config-dir', tmp], {
|
|
82
|
+
cwd: ROOT,
|
|
83
|
+
encoding: 'utf8',
|
|
84
|
+
timeout: INSTALL_BOUND_MS,
|
|
85
|
+
windowsHide: true,
|
|
86
|
+
// HOME is redirected too: the installer writes GSD's global defaults store under
|
|
87
|
+
// $HOME/.gsd, and a generator run must not touch the developer's real one.
|
|
88
|
+
// GSD_ARGS_NONCE_OFF keeps the templates canonical: they ship in the package, so a per-install
|
|
89
|
+
// nonce stamped here would be a build-time constant and would change on every generation.
|
|
90
|
+
env: { ...process.env, PI_CODING_AGENT_DIR: tmp, HOME: home, USERPROFILE: home, GSD_HOME: home, GSD_ARGS_NONCE_OFF: '1' },
|
|
91
|
+
});
|
|
92
|
+
fs.rmSync(home, { recursive: true, force: true });
|
|
93
|
+
if (res.status !== 0) {
|
|
94
|
+
fs.rmSync(tmp, { recursive: true, force: true });
|
|
95
|
+
throw new ExitError(
|
|
96
|
+
1,
|
|
97
|
+
`gen-pi-package-skills: installer failed (${res.error ? res.error.message : `exit ${res.status}`}).\n` +
|
|
98
|
+
`${(res.stderr || res.stdout || '').slice(-1500)}\nRun \`npm run build:lib\` first.`,
|
|
99
|
+
);
|
|
100
|
+
}
|
|
101
|
+
return tmp;
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
/**
|
|
105
|
+
* The role templates and their hash manifest, from an installed `agents/` dir. Roles `@`-reference
|
|
106
|
+
* files under the engine root, so like the skills they ship with PI_ENGINE_ROOT_TOKEN in place of the
|
|
107
|
+
* install path; gsd_dispatch verifies the template bytes against this manifest, then renders the root.
|
|
108
|
+
* @returns {{ roles: { name: string, content: string }[], manifest: string }}
|
|
109
|
+
*/
|
|
110
|
+
function collectRoles(tmp) {
|
|
111
|
+
const dir = path.join(tmp, 'agents');
|
|
112
|
+
const roles = fs.readdirSync(dir, { withFileTypes: true })
|
|
113
|
+
.filter((e) => e.isFile() && e.name.endsWith('.md'))
|
|
114
|
+
.map((e) => e.name)
|
|
115
|
+
.sort()
|
|
116
|
+
.map((name) => ({ name, content: tokenizeSkill(fs.readFileSync(path.join(dir, name), 'utf8'), tmp) }));
|
|
117
|
+
const files = {};
|
|
118
|
+
for (const { name, content } of roles) files[name] = crypto.createHash('sha256').update(content).digest('hex');
|
|
119
|
+
return { roles, manifest: `${JSON.stringify({ files }, null, 2)}\n` };
|
|
120
|
+
}
|
|
121
|
+
|
|
122
|
+
/** @returns {{ templates: { skillName: string, content: string }[], roles: { name: string, content: string }[], manifest: string }} */
|
|
123
|
+
function generateAll() {
|
|
124
|
+
const tmp = installIntoTemp();
|
|
125
|
+
try {
|
|
126
|
+
const skillsDir = path.join(tmp, 'skills');
|
|
127
|
+
const names = fs.readdirSync(skillsDir, { withFileTypes: true })
|
|
128
|
+
.filter((e) => e.isDirectory() && e.name.startsWith('gsd-'))
|
|
129
|
+
.map((e) => e.name)
|
|
130
|
+
.sort();
|
|
131
|
+
const templates = names.map((skillName) => ({
|
|
132
|
+
skillName,
|
|
133
|
+
content: tokenizeSkill(fs.readFileSync(path.join(skillsDir, skillName, 'SKILL.md'), 'utf8'), tmp),
|
|
134
|
+
}));
|
|
135
|
+
return { templates, ...collectRoles(tmp) };
|
|
136
|
+
} finally {
|
|
137
|
+
fs.rmSync(tmp, { recursive: true, force: true });
|
|
138
|
+
}
|
|
139
|
+
}
|
|
140
|
+
|
|
141
|
+
/** @returns {{ skillName: string, content: string }[]} sorted by skill name */
|
|
142
|
+
function generateTemplates() {
|
|
143
|
+
return generateAll().templates;
|
|
144
|
+
}
|
|
145
|
+
|
|
146
|
+
function main() {
|
|
147
|
+
const args = new Set(process.argv.slice(2));
|
|
148
|
+
const { templates: results, roles, manifest } = generateAll();
|
|
149
|
+
|
|
150
|
+
if (args.has('--write')) {
|
|
151
|
+
fs.rmSync(ROLES_DIR, { recursive: true, force: true });
|
|
152
|
+
fs.mkdirSync(ROLES_DIR, { recursive: true });
|
|
153
|
+
for (const { name, content } of roles) fs.writeFileSync(path.join(ROLES_DIR, name), content);
|
|
154
|
+
fs.writeFileSync(path.join(ROLES_DIR, ROLES_MANIFEST), manifest);
|
|
155
|
+
fs.rmSync(OUT_DIR, { recursive: true, force: true });
|
|
156
|
+
for (const { skillName, content } of results) {
|
|
157
|
+
fs.mkdirSync(path.join(OUT_DIR, skillName), { recursive: true });
|
|
158
|
+
fs.writeFileSync(path.join(OUT_DIR, skillName, 'SKILL.md'), content);
|
|
159
|
+
}
|
|
160
|
+
process.stdout.write(`gen-pi-package-skills: wrote ${results.length} templates to ${path.relative(ROOT, OUT_DIR)}/ and ${roles.length} roles to ${path.relative(ROOT, ROLES_DIR)}/\n`);
|
|
161
|
+
return 0;
|
|
162
|
+
}
|
|
163
|
+
|
|
164
|
+
if (args.has('--check')) {
|
|
165
|
+
if (!fs.existsSync(OUT_DIR)) {
|
|
166
|
+
throw new ExitError(1, 'gen-pi-package-skills: pi/package-skills/ missing. Run: npm run gen:pi-package-skills');
|
|
167
|
+
}
|
|
168
|
+
let stale = 0;
|
|
169
|
+
const expected = new Set(results.map((r) => r.skillName));
|
|
170
|
+
for (const { skillName, content } of results) {
|
|
171
|
+
const file = path.join(OUT_DIR, skillName, 'SKILL.md');
|
|
172
|
+
if (!fs.existsSync(file)) {
|
|
173
|
+
process.stderr.write(`gen-pi-package-skills: missing ${path.relative(ROOT, file)}\n`);
|
|
174
|
+
stale++;
|
|
175
|
+
} else if (fs.readFileSync(file, 'utf8') !== content) {
|
|
176
|
+
process.stderr.write(`gen-pi-package-skills: stale ${path.relative(ROOT, file)}\n`);
|
|
177
|
+
stale++;
|
|
178
|
+
}
|
|
179
|
+
}
|
|
180
|
+
for (const e of fs.readdirSync(OUT_DIR, { withFileTypes: true })) {
|
|
181
|
+
if (e.isDirectory() && !expected.has(e.name)) {
|
|
182
|
+
process.stderr.write(`gen-pi-package-skills: stale (no source) ${path.relative(ROOT, path.join(OUT_DIR, e.name))}\n`);
|
|
183
|
+
stale++;
|
|
184
|
+
}
|
|
185
|
+
}
|
|
186
|
+
const wantRoles = new Map([...roles.map((r) => [r.name, r.content]), [ROLES_MANIFEST, manifest]]);
|
|
187
|
+
const haveRoles = fs.existsSync(ROLES_DIR) ? fs.readdirSync(ROLES_DIR) : [];
|
|
188
|
+
for (const [name, content] of wantRoles) {
|
|
189
|
+
const file = path.join(ROLES_DIR, name);
|
|
190
|
+
if (!fs.existsSync(file) || fs.readFileSync(file, 'utf8') !== content) {
|
|
191
|
+
process.stderr.write(`gen-pi-package-skills: stale ${path.relative(ROOT, file)}\n`);
|
|
192
|
+
stale++;
|
|
193
|
+
}
|
|
194
|
+
}
|
|
195
|
+
for (const name of haveRoles) {
|
|
196
|
+
if (!wantRoles.has(name)) {
|
|
197
|
+
process.stderr.write(`gen-pi-package-skills: stale (no source) ${path.relative(ROOT, path.join(ROLES_DIR, name))}\n`);
|
|
198
|
+
stale++;
|
|
199
|
+
}
|
|
200
|
+
}
|
|
201
|
+
if (stale > 0) {
|
|
202
|
+
throw new ExitError(1, `gen-pi-package-skills: ${stale} stale template(s). Run: npm run gen:pi-package-skills`);
|
|
203
|
+
}
|
|
204
|
+
process.stdout.write(`gen-pi-package-skills: ${results.length} templates up to date\n`);
|
|
205
|
+
return 0;
|
|
206
|
+
}
|
|
207
|
+
|
|
208
|
+
process.stdout.write(
|
|
209
|
+
`gen-pi-package-skills: would write ${results.length} templates to ${path.relative(ROOT, OUT_DIR)}/\n` +
|
|
210
|
+
' (use --write to generate, --check to verify staleness)\n',
|
|
211
|
+
);
|
|
212
|
+
return 0;
|
|
213
|
+
}
|
|
214
|
+
|
|
215
|
+
module.exports = { PI_ENGINE_ROOT_TOKEN, tokenizeSkill, renderSkillTemplate, toSlash, generateTemplates, generateAll };
|
|
216
|
+
|
|
217
|
+
if (require.main === module) runMain(main);
|