session-orchestrator 3.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 +29 -0
- package/.claude-plugin/plugin.json +18 -0
- package/.codex-plugin/agents/explorer.toml +14 -0
- package/.codex-plugin/agents/session-reviewer.toml +23 -0
- package/.codex-plugin/agents/wave-worker.toml +15 -0
- package/.codex-plugin/config.toml +20 -0
- package/.codex-plugin/plugin.json +37 -0
- package/.cursor/rules/000-session-orchestrator.mdc +73 -0
- package/.cursor/rules/010-session-workflow.mdc +170 -0
- package/.cursor/rules/020-quality-gates.mdc +128 -0
- package/.cursor/rules/030-wave-execution.mdc +216 -0
- package/.cursor/rules/040-discovery.mdc +242 -0
- package/.cursor/rules/050-plan.mdc +235 -0
- package/.cursor/rules/060-evolve.mdc +232 -0
- package/.cursor/rules/070-gitlab-ops.mdc +246 -0
- package/.cursor/rules/080-ecosystem-health.mdc +145 -0
- package/.mcp.json +8 -0
- package/CHANGELOG.md +1544 -0
- package/LICENSE +21 -0
- package/NOTICE +64 -0
- package/README.md +242 -0
- package/SECURITY.md +90 -0
- package/agents/AGENTS.md +136 -0
- package/agents/analyst.md +99 -0
- package/agents/architect-reviewer.md +93 -0
- package/agents/code-implementer.md +106 -0
- package/agents/db-specialist.md +104 -0
- package/agents/dialectic-deriver.md +139 -0
- package/agents/docs-writer.md +113 -0
- package/agents/eval-judge.md +146 -0
- package/agents/memory-proposal-collector.md +297 -0
- package/agents/qa-strategist.md +102 -0
- package/agents/schemas/analyst.schema.json +46 -0
- package/agents/schemas/architect-reviewer.schema.json +50 -0
- package/agents/schemas/code-implementer.schema.json +61 -0
- package/agents/schemas/db-specialist.schema.json +80 -0
- package/agents/schemas/docs-writer.schema.json +56 -0
- package/agents/schemas/persona-panel-sidecar.schema.json +245 -0
- package/agents/schemas/qa-strategist.schema.json +46 -0
- package/agents/schemas/security-reviewer.schema.json +86 -0
- package/agents/schemas/session-reviewer.schema.json +69 -0
- package/agents/schemas/test-writer.schema.json +69 -0
- package/agents/schemas/ui-developer.schema.json +90 -0
- package/agents/schemas/ux-evaluator.schema.json +51 -0
- package/agents/security-reviewer.md +236 -0
- package/agents/session-reviewer.md +201 -0
- package/agents/skill-applied-judge.md +122 -0
- package/agents/test-writer.md +123 -0
- package/agents/ui-developer.md +109 -0
- package/agents/ux-evaluator.md +161 -0
- package/assets/icon.svg +11 -0
- package/assets/og-card.png +0 -0
- package/assets/og-card.svg +47 -0
- package/commands/autopilot-multi.md +74 -0
- package/commands/autopilot.md +80 -0
- package/commands/bootstrap.md +56 -0
- package/commands/brainstorm.md +48 -0
- package/commands/close.md +24 -0
- package/commands/debug.md +36 -0
- package/commands/discovery.md +32 -0
- package/commands/dispatcher.md +59 -0
- package/commands/eval.md +28 -0
- package/commands/evolve.md +10 -0
- package/commands/go.md +41 -0
- package/commands/grill.md +45 -0
- package/commands/harness-audit.md +26 -0
- package/commands/memory-cleanup.md +25 -0
- package/commands/persona-panel.md +121 -0
- package/commands/plan.md +15 -0
- package/commands/portfolio.md +97 -0
- package/commands/reconcile.md +23 -0
- package/commands/repo-audit.md +24 -0
- package/commands/session.md +30 -0
- package/commands/spinout.md +15 -0
- package/commands/sunset-review.md +27 -0
- package/commands/templates-ack.md +96 -0
- package/commands/test.md +97 -0
- package/docs/README.md +105 -0
- package/docs/USER-GUIDE.md +1403 -0
- package/docs/ci-setup.md +81 -0
- package/docs/codex-setup.md +142 -0
- package/docs/components.md +74 -0
- package/docs/cursor-setup.md +104 -0
- package/docs/events-schema.md +81 -0
- package/docs/migration-v3.md +148 -0
- package/docs/owner-config-schema.md +154 -0
- package/docs/persona-panel.md +433 -0
- package/docs/pi-setup.md +115 -0
- package/docs/plugin-architecture-v3.md +296 -0
- package/docs/pm-skills-marketplace.md +114 -0
- package/docs/policy-cache-validation-2026-04-28.md +118 -0
- package/docs/rule-authoring.md +316 -0
- package/docs/session-config-reference.md +1439 -0
- package/docs/session-config-template.md +961 -0
- package/docs/vault-docs-architecture.md +297 -0
- package/hooks/_lib/lock-bootstrap.mjs +272 -0
- package/hooks/_lib/lock-reconcile.mjs +93 -0
- package/hooks/_lib/profile-gate.mjs +95 -0
- package/hooks/_lib/transcript-history.mjs +211 -0
- package/hooks/agent-teams-h3-test.sh +362 -0
- package/hooks/config-protection.mjs +0 -0
- package/hooks/cwd-change-restore.mjs +131 -0
- package/hooks/enforce-commands.mjs +179 -0
- package/hooks/enforce-scope.mjs +273 -0
- package/hooks/hooks-codex.json +60 -0
- package/hooks/hooks-cursor.json +15 -0
- package/hooks/hooks-pi.json +115 -0
- package/hooks/hooks.json +215 -0
- package/hooks/loop-guard.mjs +260 -0
- package/hooks/on-session-end.mjs +217 -0
- package/hooks/on-session-start.mjs +660 -0
- package/hooks/on-stop.mjs +294 -0
- package/hooks/operator-steer.mjs +64 -0
- package/hooks/post-edit-validate.mjs +225 -0
- package/hooks/post-subagent-discovery-validator.mjs +398 -0
- package/hooks/post-tool-batch-wave-signal.mjs +328 -0
- package/hooks/post-tool-failure-corrective-context.mjs +248 -0
- package/hooks/post-tooluse-frontend-slop.mjs +184 -0
- package/hooks/pre-bash-destructive-guard.mjs +515 -0
- package/hooks/pre-bash-memory-propose-audit.mjs +206 -0
- package/hooks/pre-bash-staging-fence.mjs +223 -0
- package/hooks/pre-bash-templates-first.mjs +404 -0
- package/hooks/run-node.sh +72 -0
- package/hooks/skill-invocation-telemetry.mjs +99 -0
- package/hooks/subagent-telemetry.mjs +249 -0
- package/hooks/wave-scope-commit-guard.mjs +191 -0
- package/monitors/monitors.json +14 -0
- package/output-styles/finding-report.md +48 -0
- package/output-styles/session-report.md +53 -0
- package/output-styles/wave-summary.md +38 -0
- package/package.json +94 -0
- package/pi/extensions/session-orchestrator.ts +25 -0
- package/pi/prompts/autopilot-multi.md +12 -0
- package/pi/prompts/autopilot.md +12 -0
- package/pi/prompts/bootstrap.md +12 -0
- package/pi/prompts/brainstorm.md +12 -0
- package/pi/prompts/close.md +11 -0
- package/pi/prompts/debug.md +12 -0
- package/pi/prompts/discovery.md +12 -0
- package/pi/prompts/dispatcher.md +12 -0
- package/pi/prompts/eval.md +12 -0
- package/pi/prompts/evolve.md +12 -0
- package/pi/prompts/go.md +12 -0
- package/pi/prompts/grill.md +12 -0
- package/pi/prompts/harness-audit.md +12 -0
- package/pi/prompts/memory-cleanup.md +12 -0
- package/pi/prompts/persona-panel.md +12 -0
- package/pi/prompts/plan.md +12 -0
- package/pi/prompts/portfolio.md +12 -0
- package/pi/prompts/reconcile.md +12 -0
- package/pi/prompts/repo-audit.md +12 -0
- package/pi/prompts/session.md +12 -0
- package/pi/prompts/spinout.md +12 -0
- package/pi/prompts/sunset-review.md +12 -0
- package/pi/prompts/templates-ack.md +12 -0
- package/pi/prompts/test.md +12 -0
- package/rules/_index.md +51 -0
- package/rules/always-on/commit-discipline.md +26 -0
- package/rules/always-on/npm-quality-gates.md +26 -0
- package/rules/always-on/parallel-sessions.md +43 -0
- package/rules/opt-in-domain/prompt-caching.md +270 -0
- package/rules/opt-in-stack/backend-data.md +188 -0
- package/rules/opt-in-stack/backend.md +390 -0
- package/rules/opt-in-stack/frontend.md +98 -0
- package/rules/opt-in-stack/security-web.md +194 -0
- package/rules/opt-in-stack/swift.md +65 -0
- package/scripts/archive-closed-prds.mjs +416 -0
- package/scripts/autopilot-multi.mjs +802 -0
- package/scripts/autopilot.mjs +383 -0
- package/scripts/backfill-abandoned-sessions.mjs +265 -0
- package/scripts/backfill-learnings-expires.mjs +196 -0
- package/scripts/backfill-learnings.mjs +203 -0
- package/scripts/backfill-sessions.mjs +282 -0
- package/scripts/check-doc-consistency.sh +279 -0
- package/scripts/check-package-manager.mjs +445 -0
- package/scripts/ci/assert-vitest-green.mjs +267 -0
- package/scripts/codex-install.mjs +435 -0
- package/scripts/compute-grounding-injection.sh +186 -0
- package/scripts/cursor-install.mjs +113 -0
- package/scripts/dialectic-deriver.mjs +573 -0
- package/scripts/emit-event.mjs +160 -0
- package/scripts/emit-session.mjs +212 -0
- package/scripts/eval-session.mjs +262 -0
- package/scripts/export-hw-learnings.mjs +437 -0
- package/scripts/gc-stale-worktrees.mjs +666 -0
- package/scripts/generate-pi-prompts.mjs +127 -0
- package/scripts/harness-audit.mjs +287 -0
- package/scripts/lib/agent-frontmatter.mjs +266 -0
- package/scripts/lib/agent-output-schema.mjs +166 -0
- package/scripts/lib/agent-status.mjs +303 -0
- package/scripts/lib/ajv-loader.mjs +34 -0
- package/scripts/lib/auto-dialectic.mjs +382 -0
- package/scripts/lib/auto-dream.mjs +471 -0
- package/scripts/lib/autonomy/suitability.mjs +212 -0
- package/scripts/lib/autopilot/dep-graph.mjs +417 -0
- package/scripts/lib/autopilot/durable-telemetry.mjs +121 -0
- package/scripts/lib/autopilot/flags.mjs +104 -0
- package/scripts/lib/autopilot/kill-switches.mjs +174 -0
- package/scripts/lib/autopilot/loop.mjs +320 -0
- package/scripts/lib/autopilot/mr-draft.mjs +520 -0
- package/scripts/lib/autopilot/multi-killswitch.mjs +184 -0
- package/scripts/lib/autopilot/recent-runs.mjs +106 -0
- package/scripts/lib/autopilot/stall-sampler.mjs +97 -0
- package/scripts/lib/autopilot/telemetry.mjs +224 -0
- package/scripts/lib/autopilot/worktree-pipeline.mjs +605 -0
- package/scripts/lib/autopilot-telemetry.mjs +11 -0
- package/scripts/lib/autopilot.mjs +39 -0
- package/scripts/lib/backlog-scan.mjs +179 -0
- package/scripts/lib/bootstrap-lock-freshness.mjs +260 -0
- package/scripts/lib/bootstrap-lock-refresh.mjs +186 -0
- package/scripts/lib/build-live-signals.mjs +150 -0
- package/scripts/lib/ci-status-banner.mjs +425 -0
- package/scripts/lib/claude-md-budget-lint.mjs +246 -0
- package/scripts/lib/cli-flags.mjs +158 -0
- package/scripts/lib/codex/plugin-contract.mjs +610 -0
- package/scripts/lib/cold-start-detector.mjs +240 -0
- package/scripts/lib/command-blocker.mjs +458 -0
- package/scripts/lib/common.mjs +333 -0
- package/scripts/lib/config/auto-dream.mjs +77 -0
- package/scripts/lib/config/block-header.mjs +94 -0
- package/scripts/lib/config/broken-window.mjs +114 -0
- package/scripts/lib/config/coercers.mjs +248 -0
- package/scripts/lib/config/cold-start.mjs +92 -0
- package/scripts/lib/config/config-protection.mjs +120 -0
- package/scripts/lib/config/cross-repo.mjs +104 -0
- package/scripts/lib/config/custom-phases.mjs +213 -0
- package/scripts/lib/config/dialectic.mjs +92 -0
- package/scripts/lib/config/discovery-validator.mjs +75 -0
- package/scripts/lib/config/dispatcher-autonomy-capture.mjs +240 -0
- package/scripts/lib/config/dispatcher-autonomy.mjs +152 -0
- package/scripts/lib/config/docs-orchestrator.mjs +90 -0
- package/scripts/lib/config/docs-staleness.mjs +96 -0
- package/scripts/lib/config/drift-check.mjs +155 -0
- package/scripts/lib/config/eval.mjs +130 -0
- package/scripts/lib/config/events-rotation.mjs +74 -0
- package/scripts/lib/config/evolve.mjs +308 -0
- package/scripts/lib/config/frontend-slop-hook.mjs +104 -0
- package/scripts/lib/config/gitlab-portfolio.mjs +150 -0
- package/scripts/lib/config/handover-gate.mjs +106 -0
- package/scripts/lib/config/host-paths.mjs +76 -0
- package/scripts/lib/config/io.mjs +54 -0
- package/scripts/lib/config/loop-guard.mjs +117 -0
- package/scripts/lib/config/memory.mjs +150 -0
- package/scripts/lib/config/persona-gate-wave.mjs +258 -0
- package/scripts/lib/config/reconcile.mjs +205 -0
- package/scripts/lib/config/section-extractor.mjs +100 -0
- package/scripts/lib/config/skill-evolution.mjs +112 -0
- package/scripts/lib/config/slopcheck.mjs +99 -0
- package/scripts/lib/config/state-md-lock.mjs +83 -0
- package/scripts/lib/config/templates-first.mjs +94 -0
- package/scripts/lib/config/test.mjs +113 -0
- package/scripts/lib/config/vault-integration.mjs +201 -0
- package/scripts/lib/config/vault-mirror-quality.mjs +99 -0
- package/scripts/lib/config/vault-staleness.mjs +84 -0
- package/scripts/lib/config/vault-sync.mjs +96 -0
- package/scripts/lib/config/verification-auto-fix.mjs +84 -0
- package/scripts/lib/config/wave-reviewers.mjs +133 -0
- package/scripts/lib/config-schema.mjs +345 -0
- package/scripts/lib/config.mjs +474 -0
- package/scripts/lib/convergence-monitor.mjs +389 -0
- package/scripts/lib/coordinator-snapshot.mjs +371 -0
- package/scripts/lib/crypto-digest-utils.mjs +91 -0
- package/scripts/lib/discovery/helpers.mjs +127 -0
- package/scripts/lib/discovery/triage-state.mjs +279 -0
- package/scripts/lib/dispatcher/cli.mjs +257 -0
- package/scripts/lib/dispatcher/enumerate.mjs +243 -0
- package/scripts/lib/dispatcher/rank.mjs +363 -0
- package/scripts/lib/ecosystem-health.mjs +224 -0
- package/scripts/lib/ecosystem-wizard/ci-detector.mjs +18 -0
- package/scripts/lib/ecosystem-wizard/config-parser.mjs +54 -0
- package/scripts/lib/ecosystem-wizard/config-writer.mjs +287 -0
- package/scripts/lib/ecosystem-wizard/package-manager-detector.mjs +42 -0
- package/scripts/lib/ecosystem-wizard/wizard-prompt.mjs +246 -0
- package/scripts/lib/ecosystem-wizard.mjs +48 -0
- package/scripts/lib/env-check.mjs +89 -0
- package/scripts/lib/eval/engine.mjs +605 -0
- package/scripts/lib/eval/judge.mjs +433 -0
- package/scripts/lib/eval/report.mjs +367 -0
- package/scripts/lib/eval/schema.mjs +618 -0
- package/scripts/lib/eval/session-resolve.mjs +137 -0
- package/scripts/lib/eval/sink.mjs +77 -0
- package/scripts/lib/events-rotation.mjs +86 -0
- package/scripts/lib/events-schema.mjs +81 -0
- package/scripts/lib/events.mjs +80 -0
- package/scripts/lib/evolve/autonomy-verdict.mjs +461 -0
- package/scripts/lib/evolve/autopilot-effectiveness.mjs +293 -0
- package/scripts/lib/exclusivity-matrix.mjs +68 -0
- package/scripts/lib/fetch-baseline.mjs +311 -0
- package/scripts/lib/file-lock.mjs +512 -0
- package/scripts/lib/frontend-detect/detect.mjs +138 -0
- package/scripts/lib/frontend-detect/rules.mjs +295 -0
- package/scripts/lib/frontmatter-guard.mjs +241 -0
- package/scripts/lib/gates/echo-stub-detect.mjs +39 -0
- package/scripts/lib/gates/gate-baseline.mjs +42 -0
- package/scripts/lib/gates/gate-full.mjs +85 -0
- package/scripts/lib/gates/gate-helpers.mjs +231 -0
- package/scripts/lib/gates/gate-incremental.mjs +76 -0
- package/scripts/lib/gates/gate-per-file.mjs +55 -0
- package/scripts/lib/gitlab-ops/stale-mr-sweep.mjs +447 -0
- package/scripts/lib/gitlab-portfolio/aggregator.mjs +383 -0
- package/scripts/lib/gitlab-portfolio/cli.mjs +428 -0
- package/scripts/lib/gitlab-portfolio/markdown-writer.mjs +289 -0
- package/scripts/lib/gitlab-portfolio/vcs-detect.mjs +182 -0
- package/scripts/lib/handover-gate.mjs +222 -0
- package/scripts/lib/hardening.mjs +43 -0
- package/scripts/lib/hardware-pattern-detector.mjs +238 -0
- package/scripts/lib/harness-audit/categories/category1.mjs +123 -0
- package/scripts/lib/harness-audit/categories/category2.mjs +145 -0
- package/scripts/lib/harness-audit/categories/category3.mjs +143 -0
- package/scripts/lib/harness-audit/categories/category4.mjs +202 -0
- package/scripts/lib/harness-audit/categories/category5.mjs +152 -0
- package/scripts/lib/harness-audit/categories/category6.mjs +211 -0
- package/scripts/lib/harness-audit/categories/category7.mjs +125 -0
- package/scripts/lib/harness-audit/categories/category8.mjs +328 -0
- package/scripts/lib/harness-audit/categories/category9.mjs +294 -0
- package/scripts/lib/harness-audit/categories/helpers.mjs +165 -0
- package/scripts/lib/harness-audit/categories.mjs +19 -0
- package/scripts/lib/historical-guard.mjs +15 -0
- package/scripts/lib/host-identity.mjs +262 -0
- package/scripts/lib/instruction-budget-guard.mjs +332 -0
- package/scripts/lib/io.mjs +304 -0
- package/scripts/lib/issue-close-strip-labels.mjs +161 -0
- package/scripts/lib/language-mappers/README.md +57 -0
- package/scripts/lib/language-mappers/index.mjs +165 -0
- package/scripts/lib/language-mappers/markdown.mjs +149 -0
- package/scripts/lib/language-mappers/python.mjs +249 -0
- package/scripts/lib/language-mappers/swift.mjs +201 -0
- package/scripts/lib/language-mappers/typescript.mjs +433 -0
- package/scripts/lib/learnings/expiry-sweep.mjs +164 -0
- package/scripts/lib/learnings/filters.mjs +43 -0
- package/scripts/lib/learnings/io.mjs +255 -0
- package/scripts/lib/learnings/schema.mjs +518 -0
- package/scripts/lib/learnings/surface.mjs +207 -0
- package/scripts/lib/learnings.mjs +42 -0
- package/scripts/lib/lock-reaper.mjs +648 -0
- package/scripts/lib/locks/index.mjs +31 -0
- package/scripts/lib/locks/lock-body.mjs +62 -0
- package/scripts/lib/locks/staging-fence-lock.mjs +267 -0
- package/scripts/lib/locks/state-md-lock.mjs +351 -0
- package/scripts/lib/loop-readiness-banner.mjs +144 -0
- package/scripts/lib/memory-banner.mjs +478 -0
- package/scripts/lib/memory-cleanup/worktree-sweep.mjs +108 -0
- package/scripts/lib/memory-cleanup-stamp.mjs +56 -0
- package/scripts/lib/memory-paths.mjs +31 -0
- package/scripts/lib/memory-proposals/collector.mjs +334 -0
- package/scripts/lib/memory-proposals/schema.mjs +289 -0
- package/scripts/lib/memory-proposals/sink.mjs +507 -0
- package/scripts/lib/memory-proposals/store.mjs +441 -0
- package/scripts/lib/mission-status-schema.mjs +114 -0
- package/scripts/lib/mode-selector/alternatives.mjs +64 -0
- package/scripts/lib/mode-selector/constants.mjs +29 -0
- package/scripts/lib/mode-selector/context-pressure.mjs +157 -0
- package/scripts/lib/mode-selector/rationale.mjs +55 -0
- package/scripts/lib/mode-selector/scoring.mjs +221 -0
- package/scripts/lib/mode-selector-accuracy.mjs +121 -0
- package/scripts/lib/mode-selector.mjs +160 -0
- package/scripts/lib/multi-provider-build/providers.mjs +64 -0
- package/scripts/lib/multi-provider-build/templating.mjs +130 -0
- package/scripts/lib/named-baseline-resolver.mjs +233 -0
- package/scripts/lib/named-vault-resolver.mjs +433 -0
- package/scripts/lib/owner-config/coerce.mjs +29 -0
- package/scripts/lib/owner-config/constants.mjs +21 -0
- package/scripts/lib/owner-config/defaults.mjs +50 -0
- package/scripts/lib/owner-config/error.mjs +19 -0
- package/scripts/lib/owner-config/index.mjs +13 -0
- package/scripts/lib/owner-config/merge.mjs +52 -0
- package/scripts/lib/owner-config/validate.mjs +259 -0
- package/scripts/lib/owner-config-banner.mjs +126 -0
- package/scripts/lib/owner-config-loader.mjs +159 -0
- package/scripts/lib/owner-config.example.yaml +72 -0
- package/scripts/lib/owner-config.mjs +28 -0
- package/scripts/lib/owner-interview.mjs +243 -0
- package/scripts/lib/owner-yaml.mjs +571 -0
- package/scripts/lib/package-manager.mjs +160 -0
- package/scripts/lib/path-utils.mjs +217 -0
- package/scripts/lib/peer-cards/merger.mjs +310 -0
- package/scripts/lib/peer-cards/reader.mjs +125 -0
- package/scripts/lib/peer-cards/schema.mjs +230 -0
- package/scripts/lib/peer-cards/staleness-banner.mjs +86 -0
- package/scripts/lib/peer-cards/writer.mjs +138 -0
- package/scripts/lib/peer-discovery.mjs +200 -0
- package/scripts/lib/persona-panel/catalog-loader.mjs +577 -0
- package/scripts/lib/persona-panel/consolidator.mjs +370 -0
- package/scripts/lib/persona-panel/persona-runner.mjs +375 -0
- package/scripts/lib/persona-panel/threshold.mjs +130 -0
- package/scripts/lib/pi-hook-bridge.mjs +328 -0
- package/scripts/lib/platform.mjs +266 -0
- package/scripts/lib/playwright-driver/runner.mjs +297 -0
- package/scripts/lib/plugin-root.mjs +210 -0
- package/scripts/lib/pre-dispatch-check.mjs +126 -0
- package/scripts/lib/product-repo-detect.mjs +121 -0
- package/scripts/lib/profiles/registry.mjs +176 -0
- package/scripts/lib/profiles/schema.mjs +209 -0
- package/scripts/lib/qg-command-drift-banner.mjs +88 -0
- package/scripts/lib/quality-gate/diagnostics.mjs +92 -0
- package/scripts/lib/quality-gate.mjs +536 -0
- package/scripts/lib/quality-gates-cache.mjs +228 -0
- package/scripts/lib/quality-gates-policy.mjs +95 -0
- package/scripts/lib/recommendations-v0.mjs +156 -0
- package/scripts/lib/reconcile/eligibility.mjs +203 -0
- package/scripts/lib/reconcile/emitter.mjs +244 -0
- package/scripts/lib/reconcile/engine.mjs +412 -0
- package/scripts/lib/reconcile/idempotency.mjs +239 -0
- package/scripts/lib/reconcile/renderer.mjs +211 -0
- package/scripts/lib/reconcile/writer.mjs +293 -0
- package/scripts/lib/reconcile-nudge-banner.mjs +284 -0
- package/scripts/lib/resource-probe/evaluate.mjs +190 -0
- package/scripts/lib/resource-probe/parsers.mjs +181 -0
- package/scripts/lib/resource-probe/probe-platform.mjs +300 -0
- package/scripts/lib/resource-probe.mjs +95 -0
- package/scripts/lib/rule-loader.mjs +552 -0
- package/scripts/lib/rules-sync.mjs +439 -0
- package/scripts/lib/scope-gate.mjs +496 -0
- package/scripts/lib/session-close-backfill.mjs +539 -0
- package/scripts/lib/session-discovery.mjs +256 -0
- package/scripts/lib/session-end/phase-skip.mjs +357 -0
- package/scripts/lib/session-end/worktree-cleanup.mjs +112 -0
- package/scripts/lib/session-id.mjs +362 -0
- package/scripts/lib/session-lock.mjs +703 -0
- package/scripts/lib/session-registry.mjs +355 -0
- package/scripts/lib/session-schema/aliases.mjs +71 -0
- package/scripts/lib/session-schema/constants.mjs +115 -0
- package/scripts/lib/session-schema/normalizer.mjs +66 -0
- package/scripts/lib/session-schema/timestamps.mjs +64 -0
- package/scripts/lib/session-schema/validator.mjs +453 -0
- package/scripts/lib/session-schema.mjs +71 -0
- package/scripts/lib/session-token-rollup.mjs +137 -0
- package/scripts/lib/sessions-staleness-banner.mjs +247 -0
- package/scripts/lib/skill-evolution/blast-radius-classifier.mjs +114 -0
- package/scripts/lib/skill-evolution/candidate-intake.mjs +270 -0
- package/scripts/lib/skill-evolution/config-validation-gate.mjs +279 -0
- package/scripts/lib/skill-evolution/engine.mjs +719 -0
- package/scripts/lib/skill-evolution/idempotency.mjs +279 -0
- package/scripts/lib/skill-evolution/mr-opener.mjs +507 -0
- package/scripts/lib/skill-health/join.mjs +181 -0
- package/scripts/lib/skill-health/score.mjs +123 -0
- package/scripts/lib/skill-invocations-schema.mjs +214 -0
- package/scripts/lib/skill-judge.mjs +348 -0
- package/scripts/lib/skill-judgments-schema.mjs +264 -0
- package/scripts/lib/slopcheck.mjs +501 -0
- package/scripts/lib/soul-resolve.mjs +118 -0
- package/scripts/lib/spiral-carryover.mjs +495 -0
- package/scripts/lib/state-md/body-sections.mjs +851 -0
- package/scripts/lib/state-md/frontmatter-mutators.mjs +453 -0
- package/scripts/lib/state-md/mission-status.mjs +247 -0
- package/scripts/lib/state-md/recommendations.mjs +57 -0
- package/scripts/lib/state-md/yaml-parser.mjs +234 -0
- package/scripts/lib/state-md-peer-guard.mjs +232 -0
- package/scripts/lib/state-md.mjs +53 -0
- package/scripts/lib/subagents-schema.mjs +309 -0
- package/scripts/lib/sunset/walker.mjs +1192 -0
- package/scripts/lib/test-runner/artifact-paths.mjs +94 -0
- package/scripts/lib/test-runner/fingerprint.mjs +33 -0
- package/scripts/lib/test-runner/issue-reconcile.mjs +770 -0
- package/scripts/lib/tmux-layout/layouts.mjs +224 -0
- package/scripts/lib/tmux-layout/telemetry-stats.mjs +100 -0
- package/scripts/lib/tmux-layout/telemetry.mjs +88 -0
- package/scripts/lib/tmux-layout/tmux-shell.mjs +82 -0
- package/scripts/lib/tmux-layout/vcs-detector.mjs +88 -0
- package/scripts/lib/validate/check-agents.mjs +457 -0
- package/scripts/lib/validate/check-codex-plugin.mjs +37 -0
- package/scripts/lib/validate/check-commands.mjs +148 -0
- package/scripts/lib/validate/check-component-paths.mjs +112 -0
- package/scripts/lib/validate/check-dead-bridge.mjs +180 -0
- package/scripts/lib/validate/check-hooks-symmetry.mjs +258 -0
- package/scripts/lib/validate/check-json-files.mjs +116 -0
- package/scripts/lib/validate/check-owner-leakage.mjs +1011 -0
- package/scripts/lib/validate/check-path-utils-canary.mjs +175 -0
- package/scripts/lib/validate/check-peekaboo-driver-canary.mjs +201 -0
- package/scripts/lib/validate/check-pi-package.mjs +110 -0
- package/scripts/lib/validate/check-pi-prompts.mjs +43 -0
- package/scripts/lib/validate/check-playwright-mcp-canary.mjs +154 -0
- package/scripts/lib/validate/check-plugin-json.mjs +96 -0
- package/scripts/lib/validate/check-plugin-monitors.mjs +206 -0
- package/scripts/lib/validate/check-plugin-schema.mjs +137 -0
- package/scripts/lib/validate/check-rules.mjs +143 -0
- package/scripts/lib/validate/check-session-plan-routing.mjs +154 -0
- package/scripts/lib/validate/check-test-fixture-shapes.mjs +280 -0
- package/scripts/lib/validate/check-unicode-safety.mjs +533 -0
- package/scripts/lib/validate/confidential-names.mjs +169 -0
- package/scripts/lib/validate/dead-bridge-corpus.mjs +141 -0
- package/scripts/lib/validate/dead-bridge-detectors.mjs +568 -0
- package/scripts/lib/validate/tier-inference.mjs +100 -0
- package/scripts/lib/validate-vendored-rules.mjs +519 -0
- package/scripts/lib/vault-archive.mjs +404 -0
- package/scripts/lib/vault-backfill/glab.mjs +164 -0
- package/scripts/lib/vault-backfill/manifest.mjs +75 -0
- package/scripts/lib/vault-backfill/template.mjs +130 -0
- package/scripts/lib/vault-consolidate-fs.mjs +331 -0
- package/scripts/lib/vault-migration-rules.mjs +155 -0
- package/scripts/lib/vault-mirror/auto-commit.mjs +203 -0
- package/scripts/lib/vault-mirror/namespace.mjs +152 -0
- package/scripts/lib/vault-mirror/process.mjs +567 -0
- package/scripts/lib/vault-mirror/pseudonym-map.mjs +164 -0
- package/scripts/lib/vault-mirror/render-learnings.mjs +201 -0
- package/scripts/lib/vault-mirror/render-sessions.mjs +367 -0
- package/scripts/lib/vault-mirror/render.mjs +8 -0
- package/scripts/lib/vault-mirror/utils.mjs +217 -0
- package/scripts/lib/vault-relocation-rules.mjs +555 -0
- package/scripts/lib/vault-repo-backfill.mjs +235 -0
- package/scripts/lib/vault-staleness-banner.mjs +142 -0
- package/scripts/lib/vault-status/board-writer.mjs +769 -0
- package/scripts/lib/vault-status/narrative-mirror.mjs +544 -0
- package/scripts/lib/vault-sync-baseline.mjs +152 -0
- package/scripts/lib/wave-context.mjs +29 -0
- package/scripts/lib/wave-executor/pool.mjs +248 -0
- package/scripts/lib/wave-resource-gate.mjs +204 -0
- package/scripts/lib/wave-sizing.mjs +75 -0
- package/scripts/lib/webhook-url.mjs +105 -0
- package/scripts/lib/workspace.mjs +198 -0
- package/scripts/lib/worktree/constants.mjs +35 -0
- package/scripts/lib/worktree/index.mjs +17 -0
- package/scripts/lib/worktree/lifecycle.mjs +287 -0
- package/scripts/lib/worktree/listing.mjs +118 -0
- package/scripts/lib/worktree/meta.mjs +64 -0
- package/scripts/lib/worktree-freshness.mjs +313 -0
- package/scripts/lib/worktree.mjs +15 -0
- package/scripts/lifecycle-sim-v6.mjs +347 -0
- package/scripts/lock-reaper.mjs +185 -0
- package/scripts/mcp-server.sh +241 -0
- package/scripts/measure-policy-cache-effectiveness.mjs +427 -0
- package/scripts/memory-propose.mjs +464 -0
- package/scripts/migrate-cold-start-seed.mjs +404 -0
- package/scripts/migrate-learnings-jsonl.mjs +189 -0
- package/scripts/migrate-legacy-learnings.sh +61 -0
- package/scripts/migrate-sessions-jsonl.mjs +448 -0
- package/scripts/migrate-subagents-jsonl.mjs +196 -0
- package/scripts/migrate-vault-paths.mjs +796 -0
- package/scripts/parse-config.mjs +149 -0
- package/scripts/pi-install.mjs +117 -0
- package/scripts/print-applicable-rules.mjs +247 -0
- package/scripts/promote-vault-strict.mjs +496 -0
- package/scripts/relocate-vault-corpus.mjs +1178 -0
- package/scripts/run-migrate-v2-cross-repo.mjs +385 -0
- package/scripts/run-quality-gate.mjs +216 -0
- package/scripts/spikes/h3-agent-teams/preflight.sh +53 -0
- package/scripts/spikes/h3-agent-teams/run-h3.sh +112 -0
- package/scripts/spikes/h3-agent-teams/setup.sh +137 -0
- package/scripts/spikes/h3-agent-teams/toggle.sh +38 -0
- package/scripts/sweep-expired-learnings.mjs +135 -0
- package/scripts/sync-vault-schema.mjs +376 -0
- package/scripts/tests/fixtures/fetch-baseline/sample-rule.md +8 -0
- package/scripts/tmux-layout.mjs +245 -0
- package/scripts/token-audit.sh +191 -0
- package/scripts/typecheck.mjs +42 -0
- package/scripts/upload-social-preview.mjs +316 -0
- package/scripts/validate-config.mjs +46 -0
- package/scripts/validate-plugin-manifests.mjs +163 -0
- package/scripts/validate-plugin.mjs +264 -0
- package/scripts/validate-wave-scope.mjs +289 -0
- package/scripts/vault-backfill.mjs +404 -0
- package/scripts/vault-consolidate.mjs +596 -0
- package/scripts/vault-integration-watcher.mjs +394 -0
- package/scripts/vault-mirror.mjs +430 -0
- package/skills/_shared/bootstrap-gate.md +111 -0
- package/skills/_shared/config-reading.md +226 -0
- package/skills/_shared/instruction-file-resolution.md +79 -0
- package/skills/_shared/model-selection.md +64 -0
- package/skills/_shared/monitor-patterns.md +300 -0
- package/skills/_shared/parallel-aware-auq.md +121 -0
- package/skills/_shared/parallel-aware-preamble.md +185 -0
- package/skills/_shared/platform-tools.md +96 -0
- package/skills/_shared/state-ownership.md +221 -0
- package/skills/architecture/DEEPENING.md +37 -0
- package/skills/architecture/INTERFACE-DESIGN.md +44 -0
- package/skills/architecture/LANGUAGE.md +53 -0
- package/skills/architecture/SKILL.md +92 -0
- package/skills/autopilot/SKILL.md +419 -0
- package/skills/bootstrap/SKILL.md +592 -0
- package/skills/bootstrap/STATE.md.template +24 -0
- package/skills/bootstrap/_shared-template.md +243 -0
- package/skills/bootstrap/deep-template.md +659 -0
- package/skills/bootstrap/fast-template.md +251 -0
- package/skills/bootstrap/intensity-heuristic.md +80 -0
- package/skills/bootstrap/public-fallback.md +342 -0
- package/skills/bootstrap/standard-template.md +736 -0
- package/skills/bootstrap/templates/agents/project-code-review.md +18 -0
- package/skills/bootstrap/templates/agents/project-discovery.md +18 -0
- package/skills/bootstrap/templates/agents/project-quality-gate.md +18 -0
- package/skills/brainstorm/SKILL.md +268 -0
- package/skills/brainstorm/soul.md +49 -0
- package/skills/claude-md-drift-check/SKILL.md +186 -0
- package/skills/claude-md-drift-check/checker.mjs +1380 -0
- package/skills/claude-md-drift-check/checker.sh +37 -0
- package/skills/claude-md-drift-check/package.json +16 -0
- package/skills/convergence-monitoring/README.md +39 -0
- package/skills/convergence-monitoring/SIGNALS.md +246 -0
- package/skills/convergence-monitoring/SKILL.md +285 -0
- package/skills/daily/SKILL.md +222 -0
- package/skills/daily/generate.sh +92 -0
- package/skills/daily/templates/daily.md.tpl +36 -0
- package/skills/debug/SKILL.md +188 -0
- package/skills/debug/soul.md +35 -0
- package/skills/discovery/SKILL.md +567 -0
- package/skills/discovery/issue-templates.md +237 -0
- package/skills/discovery/probes/docs-staleness.mjs +195 -0
- package/skills/discovery/probes/frontend-slop.mjs +186 -0
- package/skills/discovery/probes/ssot-code-diff.mjs +310 -0
- package/skills/discovery/probes/supply-chain-slopcheck.mjs +440 -0
- package/skills/discovery/probes/vault-narrative-staleness.mjs +355 -0
- package/skills/discovery/probes/vault-staleness.mjs +272 -0
- package/skills/discovery/probes-arch.md +252 -0
- package/skills/discovery/probes-audit.md +95 -0
- package/skills/discovery/probes-code.md +329 -0
- package/skills/discovery/probes-docs.md +76 -0
- package/skills/discovery/probes-feature.md +150 -0
- package/skills/discovery/probes-infra.md +138 -0
- package/skills/discovery/probes-intro.md +25 -0
- package/skills/discovery/probes-session.md +495 -0
- package/skills/discovery/probes-supply-chain.md +94 -0
- package/skills/discovery/probes-ui.md +147 -0
- package/skills/discovery/probes-vault.md +64 -0
- package/skills/discovery/slop-patterns.md +115 -0
- package/skills/dispatcher/SKILL.md +173 -0
- package/skills/docs-orchestrator/SKILL.md +362 -0
- package/skills/docs-orchestrator/audience-mapping.md +140 -0
- package/skills/domain-model/ADR-FORMAT.md +47 -0
- package/skills/domain-model/CONTEXT-FORMAT.md +77 -0
- package/skills/domain-model/SKILL.md +85 -0
- package/skills/ecosystem-health/SKILL.md +119 -0
- package/skills/ecosystem-health/wizard.md +193 -0
- package/skills/eval/SKILL.md +293 -0
- package/skills/eval/rubric-v1.md +218 -0
- package/skills/evolve/SKILL.md +546 -0
- package/skills/frontmatter-guard/SKILL.md +126 -0
- package/skills/gitlab-ops/SKILL.md +368 -0
- package/skills/gitlab-portfolio/SKILL.md +196 -0
- package/skills/grill/SKILL.md +185 -0
- package/skills/grill/soul.md +55 -0
- package/skills/hook-development/SKILL.md +413 -0
- package/skills/mcp-builder/SKILL.md +260 -0
- package/skills/memory-cleanup/SKILL.md +310 -0
- package/skills/mode-selector/SKILL.md +226 -0
- package/skills/peekaboo-driver/SKILL.md +237 -0
- package/skills/peekaboo-driver/soul.md +32 -0
- package/skills/persona-panel/SKILL.md +365 -0
- package/skills/persona-panel/persona-format.md +205 -0
- package/skills/persona-panel/presets/designer-lens.md +87 -0
- package/skills/persona-panel/presets/engineer-lens.md +88 -0
- package/skills/persona-panel/presets/pm-lens.md +86 -0
- package/skills/plan/SKILL.md +496 -0
- package/skills/plan/mode-feature.md +141 -0
- package/skills/plan/mode-new.md +297 -0
- package/skills/plan/mode-retro.md +271 -0
- package/skills/plan/prd-feature-template.md +132 -0
- package/skills/plan/prd-full-template.md +151 -0
- package/skills/plan/prd-reviewer-prompt.md +103 -0
- package/skills/plan/retro-template.md +75 -0
- package/skills/plan/soul.md +62 -0
- package/skills/playwright-driver/SKILL.md +226 -0
- package/skills/playwright-driver/soul.md +30 -0
- package/skills/quality-gates/SKILL.md +212 -0
- package/skills/reconcile/SKILL.md +324 -0
- package/skills/repo-audit/SKILL.md +272 -0
- package/skills/session-end/SKILL.md +1044 -0
- package/skills/session-end/discovery-scan.md +37 -0
- package/skills/session-end/drift-operations.md +97 -0
- package/skills/session-end/learning-patterns.md +78 -0
- package/skills/session-end/metrics-collection.md +175 -0
- package/skills/session-end/phase-3-2-docs-verification.md +148 -0
- package/skills/session-end/phase-3-6-tail.md +344 -0
- package/skills/session-end/phase-3-7a-recommendations.md +86 -0
- package/skills/session-end/plan-verification.md +288 -0
- package/skills/session-end/session-metrics-write.md +223 -0
- package/skills/session-end/vault-operations.md +50 -0
- package/skills/session-end/verification-checklist.md +20 -0
- package/skills/session-plan/SKILL.md +554 -0
- package/skills/session-plan/wave-template.md +37 -0
- package/skills/session-start/SKILL.md +1043 -0
- package/skills/session-start/phase-2-5-docs-planning.md +119 -0
- package/skills/session-start/phase-4-5-resource-health.md +49 -0
- package/skills/session-start/phase-7-1-premise-check.md +47 -0
- package/skills/session-start/phase-7-5-mode-selector.md +237 -0
- package/skills/session-start/phase-8-5-express-path.md +61 -0
- package/skills/session-start/presentation-format.md +81 -0
- package/skills/session-start/soul.md +57 -0
- package/skills/skill-creator/SKILL.md +168 -0
- package/skills/spinout/SKILL.md +76 -0
- package/skills/sunset-review/SKILL.md +96 -0
- package/skills/test-runner/SKILL.md +362 -0
- package/skills/test-runner/rubric-v1.md +388 -0
- package/skills/test-runner/soul.md +46 -0
- package/skills/tmux-layout/SKILL.md +104 -0
- package/skills/ubiquitous-language/SKILL.md +97 -0
- package/skills/using-orchestrator/SKILL.md +144 -0
- package/skills/vault-mirror/SKILL.md +234 -0
- package/skills/vault-sync/SKILL.md +319 -0
- package/skills/vault-sync/package-lock.json +40 -0
- package/skills/vault-sync/package.json +11 -0
- package/skills/vault-sync/tests/fixtures/archive-test-vault/90-archive/bad-archived.md +8 -0
- package/skills/vault-sync/tests/fixtures/archive-test-vault/_meta/.gitkeep +0 -0
- package/skills/vault-sync/tests/fixtures/archive-test-vault/live-note.md +8 -0
- package/skills/vault-sync/tests/fixtures/broken-frontmatter-vault/_meta/.gitkeep +0 -0
- package/skills/vault-sync/tests/fixtures/broken-frontmatter-vault/bad-type.md +8 -0
- package/skills/vault-sync/tests/fixtures/broken-frontmatter-vault/good-note.md +8 -0
- package/skills/vault-sync/tests/fixtures/clean-vault/.obsidian/config.md +8 -0
- package/skills/vault-sync/tests/fixtures/clean-vault/01-projects/foo/projects-baseline.md +10 -0
- package/skills/vault-sync/tests/fixtures/clean-vault/03-daily/daily-2026-04-13.md +8 -0
- package/skills/vault-sync/tests/fixtures/clean-vault/README.md +3 -0
- package/skills/vault-sync/tests/fixtures/clean-vault/hello-world.md +11 -0
- package/skills/vault-sync/tests/fixtures/dangling-link-vault/_meta/.gitkeep +0 -0
- package/skills/vault-sync/tests/fixtures/dangling-link-vault/has-dangling.md +9 -0
- package/skills/vault-sync/tests/fixtures/dangling-link-vault/real-target.md +8 -0
- package/skills/vault-sync/tests/fixtures/empty-vault/_meta/.gitkeep +0 -0
- package/skills/vault-sync/tests/fixtures/missing-field-vault/_meta/.gitkeep +0 -0
- package/skills/vault-sync/tests/fixtures/missing-field-vault/missing-id.md +7 -0
- package/skills/vault-sync/tests/fixtures/nested-tag-vault/03-daily/daily-2026-04-13.md +9 -0
- package/skills/vault-sync/tests/fixtures/nested-tag-vault/_meta/.gitkeep +0 -0
- package/skills/vault-sync/tests/fixtures/nested-tag-vault/nested-tags-note.md +11 -0
- package/skills/vault-sync/tests/fixtures/no-frontmatter-vault/README.md +3 -0
- package/skills/vault-sync/tests/fixtures/no-frontmatter-vault/_MOC.md +3 -0
- package/skills/vault-sync/tests/fixtures/no-frontmatter-vault/_meta/.gitkeep +0 -0
- package/skills/vault-sync/tests/fixtures/with-moc-vault/_MOC.md +11 -0
- package/skills/vault-sync/tests/fixtures/with-moc-vault/_meta/.gitkeep +0 -0
- package/skills/vault-sync/tests/fixtures/with-moc-vault/hello-world.md +11 -0
- package/skills/vault-sync/tests/schema-drift.test.mjs +133 -0
- package/skills/vault-sync/validator.mjs +658 -0
- package/skills/vault-sync/validator.sh +55 -0
- package/skills/wave-executor/SKILL.md +496 -0
- package/skills/wave-executor/circuit-breaker.md +169 -0
- package/skills/wave-executor/wave-loop.md +1043 -0
- package/skills/write-executable-plan/SKILL.md +237 -0
- package/skills/write-executable-plan/plan-template.md +154 -0
- package/templates/_minimal/CLAUDE.md.tmpl +41 -0
- package/templates/_minimal/README.md.tmpl +15 -0
- package/templates/_minimal/gitignore.tmpl +47 -0
- package/templates/_shared/harte-regeln.md +16 -0
- package/templates/_shared/loop.md +90 -0
- package/templates/_shared/rules/parallel-sessions.md +77 -0
- package/templates/nextjs-minimal/README.md +30 -0
- package/templates/nextjs-minimal/app/layout.tsx +18 -0
- package/templates/nextjs-minimal/app/page.tsx +7 -0
- package/templates/nextjs-minimal/eslint.config.mjs +16 -0
- package/templates/nextjs-minimal/next.config.mjs +4 -0
- package/templates/nextjs-minimal/package.json +27 -0
- package/templates/nextjs-minimal/tsconfig.json +23 -0
- package/templates/node-minimal/README.md +33 -0
- package/templates/node-minimal/eslint.config.mjs +10 -0
- package/templates/node-minimal/package.json +21 -0
- package/templates/node-minimal/src/index.ts +1 -0
- package/templates/node-minimal/tests/sanity.test.ts +5 -0
- package/templates/node-minimal/tsconfig.json +17 -0
- package/templates/personas/README.md +150 -0
- package/templates/personas/accounting-compliance.v1.md +120 -0
- package/templates/personas/accounting-tax-advisor.v1.md +116 -0
- package/templates/personas/buyer-p1-cto.v1.md +125 -0
- package/templates/personas/buyer-p2-kanzlei.v1.md +134 -0
- package/templates/personas/buyer-p3-build.v1.md +130 -0
- package/templates/personas/buyer-p4-tech-veto.v1.md +130 -0
- package/templates/personas/buyer-p5-solo.v1.md +132 -0
- package/templates/personas/buyer-p6-ld.v1.md +130 -0
- package/templates/personas/klima-ai-expert.v1.md +114 -0
- package/templates/personas/klima-physicist.v1.md +117 -0
- package/templates/python-uv/README.md +28 -0
- package/templates/python-uv/pyproject.toml +32 -0
- package/templates/python-uv/src/__PROJECT_NAME__/__init__.py +0 -0
- package/templates/python-uv/src/__PROJECT_NAME__/main.py +6 -0
- package/templates/python-uv/tests/test_sanity.py +2 -0
- package/templates/static-html/README.md +19 -0
- package/templates/static-html/index.html +15 -0
- package/templates/static-html/script.js +1 -0
- package/templates/static-html/styles.css +26 -0
|
@@ -0,0 +1,179 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* backlog-scan.mjs — VCS backlog signal source for Mode-Selector (Phase B-3, issue #293).
|
|
3
|
+
*
|
|
4
|
+
* Provides `scanBacklog({limit, vcs})` returning structured counts derived from the
|
|
5
|
+
* project's open-issue list via `glab` (GitLab) or `gh` (GitHub). The result is fed
|
|
6
|
+
* into `signals.backlog` consumed by `selectMode` in `mode-selector.mjs`.
|
|
7
|
+
*
|
|
8
|
+
* Design contract:
|
|
9
|
+
* - Pure structural counts, never raw issue objects (keeps the selector pure).
|
|
10
|
+
* - Module-level cache keyed on (vcs, limit) — one VCS round-trip per session.
|
|
11
|
+
* - Graceful degradation: missing CLI / non-zero exit / parse failure → null.
|
|
12
|
+
* Never throws to the caller.
|
|
13
|
+
*
|
|
14
|
+
* Stale threshold: 30 days since `updated_at`.
|
|
15
|
+
*
|
|
16
|
+
* Dependencies:
|
|
17
|
+
* - VCS detection follows `skills/gitlab-ops/SKILL.md` (origin URL contains "github.com" → gh, else glab).
|
|
18
|
+
*/
|
|
19
|
+
|
|
20
|
+
import { spawnSync } from 'node:child_process';
|
|
21
|
+
|
|
22
|
+
export const STALE_THRESHOLD_DAYS = 30;
|
|
23
|
+
|
|
24
|
+
/** Module-level cache. Keyed by JSON.stringify({vcs, limit}). */
|
|
25
|
+
const _cache = new Map();
|
|
26
|
+
|
|
27
|
+
/**
|
|
28
|
+
* Detect the VCS for the current working directory by inspecting the origin URL.
|
|
29
|
+
* Returns 'github' | 'gitlab' | null. Never throws.
|
|
30
|
+
*
|
|
31
|
+
* @returns {'github'|'gitlab'|null}
|
|
32
|
+
*/
|
|
33
|
+
export function detectVcs() {
|
|
34
|
+
try {
|
|
35
|
+
const r = spawnSync('git', ['remote', 'get-url', 'origin'], { encoding: 'utf8' });
|
|
36
|
+
if (r.status !== 0) return null;
|
|
37
|
+
const url = String(r.stdout || '').trim();
|
|
38
|
+
if (!url) return null;
|
|
39
|
+
return url.includes('github.com') ? 'github' : 'gitlab';
|
|
40
|
+
} catch {
|
|
41
|
+
return null;
|
|
42
|
+
}
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
/**
|
|
46
|
+
* Run a CLI command and parse stdout as JSON. Returns null on any failure
|
|
47
|
+
* (binary missing, non-zero exit, empty stdout, unparsable JSON, non-array).
|
|
48
|
+
* spawnSync sets `r.status = null` and `r.error.code = 'ENOENT'` when the
|
|
49
|
+
* binary is absent — the `r.status !== 0` check covers that path.
|
|
50
|
+
*
|
|
51
|
+
* @param {string} bin
|
|
52
|
+
* @param {string[]} args
|
|
53
|
+
* @returns {Array|null}
|
|
54
|
+
*/
|
|
55
|
+
function runJson(bin, args) {
|
|
56
|
+
try {
|
|
57
|
+
const r = spawnSync(bin, args, { encoding: 'utf8' });
|
|
58
|
+
if (r.status !== 0) return null;
|
|
59
|
+
const out = String(r.stdout || '').trim();
|
|
60
|
+
if (!out) return null;
|
|
61
|
+
const parsed = JSON.parse(out);
|
|
62
|
+
return Array.isArray(parsed) ? parsed : null;
|
|
63
|
+
} catch {
|
|
64
|
+
return null;
|
|
65
|
+
}
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
/**
|
|
69
|
+
* Compute days elapsed between an ISO-8601 timestamp and `now`.
|
|
70
|
+
* Returns Infinity if the input is unparsable so unparsable dates never count
|
|
71
|
+
* as "fresh".
|
|
72
|
+
*
|
|
73
|
+
* @param {string|undefined|null} iso
|
|
74
|
+
* @param {number} nowMs
|
|
75
|
+
* @returns {number}
|
|
76
|
+
*/
|
|
77
|
+
function ageDays(iso, nowMs) {
|
|
78
|
+
if (!iso || typeof iso !== 'string') return Infinity;
|
|
79
|
+
const t = Date.parse(iso);
|
|
80
|
+
if (Number.isNaN(t)) return Infinity;
|
|
81
|
+
return (nowMs - t) / 86_400_000;
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
/**
|
|
85
|
+
* Aggregate a list of issue records into the structural backlog summary.
|
|
86
|
+
*
|
|
87
|
+
* Issue shape requirements (tolerant — missing fields are skipped):
|
|
88
|
+
* - `labels`: array of strings OR array of {name: string} objects
|
|
89
|
+
* - `updated_at` (glab) or `updatedAt` (gh): ISO-8601 timestamp
|
|
90
|
+
*
|
|
91
|
+
* @param {Array<object>} issues
|
|
92
|
+
* @param {number} nowMs — injected for tests
|
|
93
|
+
* @returns {{criticalCount: number, highCount: number, staleCount: number, byLabel: Record<string, number>, total: number}}
|
|
94
|
+
*/
|
|
95
|
+
export function summarizeIssues(issues, nowMs = Date.now()) {
|
|
96
|
+
let criticalCount = 0;
|
|
97
|
+
let highCount = 0;
|
|
98
|
+
let staleCount = 0;
|
|
99
|
+
const byLabel = {};
|
|
100
|
+
|
|
101
|
+
for (const issue of issues) {
|
|
102
|
+
if (!issue || typeof issue !== 'object') continue;
|
|
103
|
+
|
|
104
|
+
const rawLabels = Array.isArray(issue.labels) ? issue.labels : [];
|
|
105
|
+
const labelNames = rawLabels
|
|
106
|
+
.map((l) => (typeof l === 'string' ? l : l && typeof l === 'object' ? l.name : null))
|
|
107
|
+
.filter((n) => typeof n === 'string' && n.length > 0);
|
|
108
|
+
|
|
109
|
+
for (const name of labelNames) {
|
|
110
|
+
byLabel[name] = (byLabel[name] || 0) + 1;
|
|
111
|
+
if (name === 'priority:critical') criticalCount += 1;
|
|
112
|
+
else if (name === 'priority:high') highCount += 1;
|
|
113
|
+
}
|
|
114
|
+
|
|
115
|
+
const updated = issue.updated_at || issue.updatedAt || null;
|
|
116
|
+
if (ageDays(updated, nowMs) > STALE_THRESHOLD_DAYS) staleCount += 1;
|
|
117
|
+
}
|
|
118
|
+
|
|
119
|
+
return {
|
|
120
|
+
criticalCount,
|
|
121
|
+
highCount,
|
|
122
|
+
staleCount,
|
|
123
|
+
byLabel,
|
|
124
|
+
total: issues.length,
|
|
125
|
+
};
|
|
126
|
+
}
|
|
127
|
+
|
|
128
|
+
/**
|
|
129
|
+
* Scan the project's open backlog and return a structural summary suitable for
|
|
130
|
+
* `signals.backlog`. Caches the result per (vcs, limit) within the running process.
|
|
131
|
+
*
|
|
132
|
+
* Returns null on any of:
|
|
133
|
+
* - VCS cannot be detected (no git origin)
|
|
134
|
+
* - CLI binary missing (`glab` for gitlab, `gh` for github)
|
|
135
|
+
* - CLI exits non-zero or produces unparsable output
|
|
136
|
+
*
|
|
137
|
+
* Never throws.
|
|
138
|
+
*
|
|
139
|
+
* @param {{limit?: number, vcs?: 'github'|'gitlab'|null, nowMs?: number}} [opts]
|
|
140
|
+
* @returns {Promise<null | {criticalCount: number, highCount: number, staleCount: number, byLabel: Record<string, number>, total: number, vcs: string, limit: number}>}
|
|
141
|
+
*/
|
|
142
|
+
export async function scanBacklog(opts = {}) {
|
|
143
|
+
const limit = Number.isInteger(opts.limit) && opts.limit > 0 ? opts.limit : 50;
|
|
144
|
+
// Distinguish "user did not pass vcs" (auto-detect) from "user explicitly passed
|
|
145
|
+
// null" (degrade). 'vcs' in opts catches the explicit-null path so callers can
|
|
146
|
+
// force the no-vcs branch in tests without monkey-patching detectVcs.
|
|
147
|
+
const vcs = 'vcs' in opts ? opts.vcs : detectVcs();
|
|
148
|
+
const nowMs = typeof opts.nowMs === 'number' ? opts.nowMs : Date.now();
|
|
149
|
+
|
|
150
|
+
if (vcs !== 'github' && vcs !== 'gitlab') return null;
|
|
151
|
+
|
|
152
|
+
const cacheKey = JSON.stringify({ vcs, limit });
|
|
153
|
+
if (_cache.has(cacheKey)) return _cache.get(cacheKey);
|
|
154
|
+
|
|
155
|
+
const bin = vcs === 'github' ? 'gh' : 'glab';
|
|
156
|
+
const args =
|
|
157
|
+
vcs === 'github'
|
|
158
|
+
? ['issue', 'list', '--limit', String(limit), '--json', 'number,labels,updatedAt,state']
|
|
159
|
+
: ['issue', 'list', '--per-page', String(limit), '--output', 'json'];
|
|
160
|
+
|
|
161
|
+
const issues = runJson(bin, args);
|
|
162
|
+
if (issues === null) {
|
|
163
|
+
_cache.set(cacheKey, null);
|
|
164
|
+
return null;
|
|
165
|
+
}
|
|
166
|
+
|
|
167
|
+
const summary = summarizeIssues(issues, nowMs);
|
|
168
|
+
const result = { ...summary, vcs, limit };
|
|
169
|
+
_cache.set(cacheKey, result);
|
|
170
|
+
return result;
|
|
171
|
+
}
|
|
172
|
+
|
|
173
|
+
/**
|
|
174
|
+
* Test/coordinator helper: clear the in-process cache. Not part of the
|
|
175
|
+
* production surface but exported so tests don't need to reload the module.
|
|
176
|
+
*/
|
|
177
|
+
export function clearBacklogCache() {
|
|
178
|
+
_cache.clear();
|
|
179
|
+
}
|
|
@@ -0,0 +1,260 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* bootstrap-lock-freshness.mjs — #186 / #290 / #203
|
|
3
|
+
* Validates .orchestrator/bootstrap.lock freshness + plugin-version drift.
|
|
4
|
+
*
|
|
5
|
+
* Plain-JS validation — no Zod dependency.
|
|
6
|
+
* Never throws. Never mutates input.
|
|
7
|
+
*/
|
|
8
|
+
|
|
9
|
+
import { existsSync, readFileSync } from 'node:fs';
|
|
10
|
+
import { join } from 'node:path';
|
|
11
|
+
|
|
12
|
+
/** Milliseconds in one day — extracted for readability + testability (#203). */
|
|
13
|
+
export const MS_PER_DAY = 24 * 60 * 60 * 1000;
|
|
14
|
+
|
|
15
|
+
/**
|
|
16
|
+
* Minimal single-level YAML parser for .orchestrator/bootstrap.lock.
|
|
17
|
+
* Returns an object with all key:value pairs found. Unknown fields are
|
|
18
|
+
* preserved. Comment lines and blank lines are ignored. Values are trimmed.
|
|
19
|
+
*
|
|
20
|
+
* @returns {Record<string, string>}
|
|
21
|
+
*/
|
|
22
|
+
export function parseBootstrapLock(contents) {
|
|
23
|
+
if (typeof contents !== 'string') return {};
|
|
24
|
+
|
|
25
|
+
const result = {};
|
|
26
|
+
for (const raw of contents.split('\n')) {
|
|
27
|
+
const line = raw.trim();
|
|
28
|
+
if (!line || line.startsWith('#')) continue;
|
|
29
|
+
const colonIdx = line.indexOf(':');
|
|
30
|
+
if (colonIdx === -1) continue;
|
|
31
|
+
const key = line.slice(0, colonIdx).trim();
|
|
32
|
+
const value = line.slice(colonIdx + 1).trim();
|
|
33
|
+
if (key) result[key] = value;
|
|
34
|
+
}
|
|
35
|
+
return result;
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
/**
|
|
39
|
+
* Reads the plugin version from package.json at the given plugin root.
|
|
40
|
+
* Returns null if the file is absent or unparseable — never throws.
|
|
41
|
+
*
|
|
42
|
+
* @param {string} pluginRoot Absolute path to the session-orchestrator plugin root.
|
|
43
|
+
* @returns {string|null}
|
|
44
|
+
*/
|
|
45
|
+
export function readPluginVersionFromPackageJson(pluginRoot) {
|
|
46
|
+
try {
|
|
47
|
+
const pkgPath = join(pluginRoot, 'package.json');
|
|
48
|
+
const raw = readFileSync(pkgPath, 'utf8');
|
|
49
|
+
const pkg = JSON.parse(raw);
|
|
50
|
+
return typeof pkg.version === 'string' ? pkg.version : null;
|
|
51
|
+
} catch {
|
|
52
|
+
return null;
|
|
53
|
+
}
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
/**
|
|
57
|
+
* Classifies a version mismatch severity between two semver strings.
|
|
58
|
+
*
|
|
59
|
+
* Rules (#290):
|
|
60
|
+
* - major component differs → 'alert'
|
|
61
|
+
* - minor or patch only differs → 'info'
|
|
62
|
+
* - either string is not parseable semver → 'warn' (safe fallback)
|
|
63
|
+
*
|
|
64
|
+
* @param {string} lockVer Version string from bootstrap.lock.
|
|
65
|
+
* @param {string} currentVer Current plugin version.
|
|
66
|
+
* @returns {'info'|'warn'|'alert'}
|
|
67
|
+
*/
|
|
68
|
+
export function classifyVersionMismatch(lockVer, currentVer) {
|
|
69
|
+
// Simple semver: split on '.' and compare the major component.
|
|
70
|
+
const parse = (v) => v.match(/^(\d+)\.(\d+)\.(\d+)/);
|
|
71
|
+
const lm = parse(lockVer);
|
|
72
|
+
const cm = parse(currentVer);
|
|
73
|
+
if (!lm || !cm) return 'warn'; // not parseable — safe fallback
|
|
74
|
+
if (lm[1] !== cm[1]) return 'alert'; // major mismatch
|
|
75
|
+
return 'info'; // minor or patch only
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
/**
|
|
79
|
+
* Checks .orchestrator/bootstrap.lock for freshness and plugin-version drift.
|
|
80
|
+
*
|
|
81
|
+
* Tiered severity:
|
|
82
|
+
* info — ageDays < 30 AND (no version mismatch OR patch/minor-only mismatch)
|
|
83
|
+
* warn — ageDays 30..89 OR non-parseable version mismatch
|
|
84
|
+
* alert — ageDays >= 90 OR bootstrappedAt unparseable OR lock missing
|
|
85
|
+
* OR major plugin-version mismatch
|
|
86
|
+
*
|
|
87
|
+
* Legacy lock support: if `bootstrapped-at` is absent, falls back to
|
|
88
|
+
* `timestamp` (present in all pre-#186 locks). If both absent, severity=alert.
|
|
89
|
+
*
|
|
90
|
+
* Backward compat (#290): if the lock file predates the `plugin-version` field,
|
|
91
|
+
* a soft informational message is added to details but severity is NOT raised.
|
|
92
|
+
*
|
|
93
|
+
* Refresh awareness (#57): `refreshed-at` / `refreshed-plugin-version`
|
|
94
|
+
* (written by `scripts/lib/bootstrap-lock-refresh.mjs`) take precedence over
|
|
95
|
+
* `bootstrapped-at`/`timestamp` and `plugin-version` respectively when present
|
|
96
|
+
* — a refresh resets the freshness clock and acknowledges the current plugin
|
|
97
|
+
* version without disturbing the lock's original bootstrap provenance
|
|
98
|
+
* (`details.bootstrappedAt`/`details.pluginVersion` always report the
|
|
99
|
+
* ORIGINAL values, unchanged by a refresh).
|
|
100
|
+
*
|
|
101
|
+
* `details.reason` names the single highest-severity contributor driving the
|
|
102
|
+
* returned severity: `'missing-repoRoot' | 'missing' | 'read-error' |
|
|
103
|
+
* 'stale-age' | 'unparseable-timestamp' | 'version-mismatch-major' |
|
|
104
|
+
* 'version-mismatch-unparseable' | null` (null when severity is `'info'`).
|
|
105
|
+
* When both age and version-drift would independently raise severity, the
|
|
106
|
+
* higher-ranked contributor wins (alert > warn > info); a tie is broken in
|
|
107
|
+
* favor of the age reason.
|
|
108
|
+
*
|
|
109
|
+
* @param {{repoRoot: string, currentPluginVersion?: string, now?: number}} opts
|
|
110
|
+
* @returns {{ok: boolean, severity: 'info'|'warn'|'alert', message: string, details: object}}
|
|
111
|
+
*/
|
|
112
|
+
export function checkBootstrapLockFreshness({
|
|
113
|
+
repoRoot,
|
|
114
|
+
currentPluginVersion,
|
|
115
|
+
now = Date.now(),
|
|
116
|
+
} = {}) {
|
|
117
|
+
if (!repoRoot) {
|
|
118
|
+
return {
|
|
119
|
+
ok: false,
|
|
120
|
+
severity: 'alert',
|
|
121
|
+
message: 'bootstrap.lock: repoRoot not provided',
|
|
122
|
+
details: { reason: 'missing-repoRoot' },
|
|
123
|
+
};
|
|
124
|
+
}
|
|
125
|
+
|
|
126
|
+
const lockPath = join(repoRoot, '.orchestrator', 'bootstrap.lock');
|
|
127
|
+
|
|
128
|
+
if (!existsSync(lockPath)) {
|
|
129
|
+
return {
|
|
130
|
+
ok: false,
|
|
131
|
+
severity: 'alert',
|
|
132
|
+
message: 'bootstrap.lock missing',
|
|
133
|
+
details: { reason: 'missing' },
|
|
134
|
+
};
|
|
135
|
+
}
|
|
136
|
+
|
|
137
|
+
let parsed;
|
|
138
|
+
try {
|
|
139
|
+
parsed = parseBootstrapLock(readFileSync(lockPath, 'utf8'));
|
|
140
|
+
} catch {
|
|
141
|
+
return {
|
|
142
|
+
ok: false,
|
|
143
|
+
severity: 'alert',
|
|
144
|
+
message: 'bootstrap.lock: failed to read file',
|
|
145
|
+
details: { reason: 'read-error' },
|
|
146
|
+
};
|
|
147
|
+
}
|
|
148
|
+
|
|
149
|
+
// Fall back to legacy `timestamp` field for pre-#186 locks. `bootstrappedAt`
|
|
150
|
+
// and `pluginVersion` report the ORIGINAL bootstrap provenance and are never
|
|
151
|
+
// overwritten by a later refresh (#57 provenance-honesty guarantee).
|
|
152
|
+
const bootstrappedAt = parsed['bootstrapped-at'] || parsed['timestamp'] || null;
|
|
153
|
+
const pluginVersion = parsed['plugin-version'] || null;
|
|
154
|
+
|
|
155
|
+
// #57 — refresh-writer fields (scripts/lib/bootstrap-lock-refresh.mjs).
|
|
156
|
+
// Raw, possibly null; absent on every pre-#57 lock and on any lock that has
|
|
157
|
+
// never been refreshed.
|
|
158
|
+
const refreshedAt = parsed['refreshed-at'] || null;
|
|
159
|
+
const refreshedPluginVersion = parsed['refreshed-plugin-version'] || null;
|
|
160
|
+
|
|
161
|
+
// Age-source precedence (#57): refreshed-at → bootstrapped-at → timestamp.
|
|
162
|
+
// A refresh resets the freshness clock without touching bootstrappedAt.
|
|
163
|
+
const ageSourceAt = refreshedAt || bootstrappedAt;
|
|
164
|
+
|
|
165
|
+
let ageDays = null;
|
|
166
|
+
if (ageSourceAt) {
|
|
167
|
+
const ts = Date.parse(ageSourceAt);
|
|
168
|
+
if (!Number.isNaN(ts)) {
|
|
169
|
+
ageDays = Math.floor((now - ts) / MS_PER_DAY);
|
|
170
|
+
}
|
|
171
|
+
}
|
|
172
|
+
|
|
173
|
+
// Version-drift source precedence (#57): refreshed-plugin-version → plugin-version.
|
|
174
|
+
// A refresh acknowledges the current plugin version without touching pluginVersion.
|
|
175
|
+
const effectivePluginVersion = refreshedPluginVersion || pluginVersion;
|
|
176
|
+
|
|
177
|
+
// Backward compat (#290): lock predates plugin-version field — soft signal only.
|
|
178
|
+
// #57: a refresh that stamps refreshed-plugin-version resolves this (the
|
|
179
|
+
// effective version is now comparable), so this check uses the same
|
|
180
|
+
// refreshed-plugin-version → plugin-version precedence as the mismatch check.
|
|
181
|
+
const legacyLockNoPluinVersion =
|
|
182
|
+
typeof currentPluginVersion === 'string' && effectivePluginVersion === null;
|
|
183
|
+
|
|
184
|
+
let versionMismatch = false;
|
|
185
|
+
let versionMismatchSeverity = null; // 'info' | 'warn' | 'alert' — null when no mismatch
|
|
186
|
+
|
|
187
|
+
if (
|
|
188
|
+
typeof currentPluginVersion === 'string' &&
|
|
189
|
+
typeof effectivePluginVersion === 'string' &&
|
|
190
|
+
effectivePluginVersion !== currentPluginVersion
|
|
191
|
+
) {
|
|
192
|
+
versionMismatch = true;
|
|
193
|
+
versionMismatchSeverity = classifyVersionMismatch(effectivePluginVersion, currentPluginVersion);
|
|
194
|
+
}
|
|
195
|
+
|
|
196
|
+
let severity = 'info';
|
|
197
|
+
|
|
198
|
+
// #57 — reason-aware tiering. `reason` names the single HIGHEST-severity
|
|
199
|
+
// contributor (age vs. version-drift) so downstream prose can recommend the
|
|
200
|
+
// right remediation instead of always pointing at --retroactive. Rank:
|
|
201
|
+
// alert=2, warn=1, info=0, none=-1. `considerReason` only replaces the
|
|
202
|
+
// current pick on a STRICT rank increase, so calling the age contributor
|
|
203
|
+
// before the version contributor implements "tie → age reason".
|
|
204
|
+
let reason = null;
|
|
205
|
+
let reasonRank = -1;
|
|
206
|
+
const rank = (s) => (s === 'alert' ? 2 : s === 'warn' ? 1 : s === 'info' ? 0 : -1);
|
|
207
|
+
const considerReason = (candidateSeverity, candidateReason) => {
|
|
208
|
+
const r = rank(candidateSeverity);
|
|
209
|
+
if (r > reasonRank) {
|
|
210
|
+
reasonRank = r;
|
|
211
|
+
reason = candidateReason;
|
|
212
|
+
}
|
|
213
|
+
};
|
|
214
|
+
|
|
215
|
+
// Age-based escalation.
|
|
216
|
+
if (ageDays !== null && ageDays >= 30) {
|
|
217
|
+
severity = 'warn';
|
|
218
|
+
considerReason('warn', 'stale-age');
|
|
219
|
+
}
|
|
220
|
+
if (ageDays !== null && ageDays >= 90) {
|
|
221
|
+
severity = 'alert';
|
|
222
|
+
considerReason('alert', 'stale-age');
|
|
223
|
+
}
|
|
224
|
+
if (ageDays === null) {
|
|
225
|
+
severity = 'alert';
|
|
226
|
+
considerReason('alert', 'unparseable-timestamp');
|
|
227
|
+
}
|
|
228
|
+
|
|
229
|
+
// Version-mismatch escalation (only when mismatch detected).
|
|
230
|
+
if (versionMismatch && versionMismatchSeverity === 'warn') {
|
|
231
|
+
if (severity === 'info') severity = 'warn';
|
|
232
|
+
considerReason('warn', 'version-mismatch-unparseable');
|
|
233
|
+
}
|
|
234
|
+
if (versionMismatch && versionMismatchSeverity === 'alert') {
|
|
235
|
+
severity = 'alert';
|
|
236
|
+
considerReason('alert', 'version-mismatch-major');
|
|
237
|
+
}
|
|
238
|
+
// 'info'-classified mismatches (minor/patch) do NOT raise severity beyond
|
|
239
|
+
// whatever age already determined, and do not contribute a reason.
|
|
240
|
+
|
|
241
|
+
return {
|
|
242
|
+
ok: severity === 'info',
|
|
243
|
+
severity,
|
|
244
|
+
message: `bootstrap.lock: age=${ageDays ?? 'unknown'}d, plugin-version=${pluginVersion ?? 'unknown'} (current=${currentPluginVersion ?? 'unknown'})`,
|
|
245
|
+
details: {
|
|
246
|
+
ageDays,
|
|
247
|
+
pluginVersion,
|
|
248
|
+
currentPluginVersion: currentPluginVersion ?? null,
|
|
249
|
+
bootstrappedAt,
|
|
250
|
+
refreshedAt,
|
|
251
|
+
refreshedPluginVersion,
|
|
252
|
+
versionMismatch,
|
|
253
|
+
versionMismatchSeverity,
|
|
254
|
+
legacyLock: legacyLockNoPluinVersion
|
|
255
|
+
? 'lock predates plugin-version field; consider /bootstrap --refresh-lock to stamp a current plugin-version reference'
|
|
256
|
+
: null,
|
|
257
|
+
reason,
|
|
258
|
+
},
|
|
259
|
+
};
|
|
260
|
+
}
|
|
@@ -0,0 +1,186 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* bootstrap-lock-refresh.mjs — #57
|
|
3
|
+
* Provenance-honest `.orchestrator/bootstrap.lock` refresh writer.
|
|
4
|
+
*
|
|
5
|
+
* `checkBootstrapLockFreshness()` (bootstrap-lock-freshness.mjs) can only ever
|
|
6
|
+
* *diagnose* a stale/drifted lock — it is read-only by design. Before #57 the
|
|
7
|
+
* only remediation the surrounding prose offered was `/bootstrap --retroactive`,
|
|
8
|
+
* which is a no-op once the lock already has valid `version`/`tier` fields
|
|
9
|
+
* (see the Retroactive Flow's idempotency guard in `skills/bootstrap/SKILL.md`)
|
|
10
|
+
* — the operator would re-run the recommended command and see nothing change.
|
|
11
|
+
*
|
|
12
|
+
* `refreshBootstrapLock()` closes that gap: it acknowledges the current
|
|
13
|
+
* plugin version and resets the freshness clock by appending/updating exactly
|
|
14
|
+
* two fields — `refreshed-at` and `refreshed-plugin-version` — while leaving
|
|
15
|
+
* every other line of the lock file byte-identical. The ORIGINAL bootstrap
|
|
16
|
+
* provenance (`bootstrapped-at`/`timestamp`, `plugin-version`, `tier`,
|
|
17
|
+
* `archetype`, `source`, …) is never rewritten. This is the load-bearing
|
|
18
|
+
* guarantee: a refresh is an acknowledgement, not a re-bootstrap.
|
|
19
|
+
*
|
|
20
|
+
* Plain-JS — no Zod dependency. Never throws; every failure path returns a
|
|
21
|
+
* structured `{ ok: false, reason, message }` object. Never fabricates a lock
|
|
22
|
+
* — a missing or structurally invalid lock is refused, not synthesized.
|
|
23
|
+
*/
|
|
24
|
+
|
|
25
|
+
import { existsSync, readFileSync, writeFileSync, renameSync, mkdirSync } from 'node:fs';
|
|
26
|
+
import { join, dirname } from 'node:path';
|
|
27
|
+
import { randomBytes } from 'node:crypto';
|
|
28
|
+
import { parseBootstrapLock } from './bootstrap-lock-freshness.mjs';
|
|
29
|
+
|
|
30
|
+
const PRECONDITION_MESSAGE_SUFFIX =
|
|
31
|
+
'run /bootstrap or /bootstrap --retroactive first.';
|
|
32
|
+
|
|
33
|
+
/**
|
|
34
|
+
* Locate the (trimmed, comment-skipping) line index for a given top-level
|
|
35
|
+
* lock key, mirroring {@link parseBootstrapLock}'s own key-extraction rules
|
|
36
|
+
* so upsert targets the same line parseBootstrapLock would report for `key`.
|
|
37
|
+
*
|
|
38
|
+
* @param {string[]} lines
|
|
39
|
+
* @param {string} key
|
|
40
|
+
* @returns {number} 0-based index, or -1 when not found.
|
|
41
|
+
*/
|
|
42
|
+
function findKeyLineIndex(lines, key) {
|
|
43
|
+
for (let i = 0; i < lines.length; i++) {
|
|
44
|
+
const trimmed = lines[i].trim();
|
|
45
|
+
if (!trimmed || trimmed.startsWith('#')) continue;
|
|
46
|
+
const colonIdx = trimmed.indexOf(':');
|
|
47
|
+
if (colonIdx === -1) continue;
|
|
48
|
+
if (trimmed.slice(0, colonIdx).trim() === key) return i;
|
|
49
|
+
}
|
|
50
|
+
return -1;
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
/**
|
|
54
|
+
* Replace the line for `key` in place, or append a new `key: value` line
|
|
55
|
+
* when absent. Mutates `lines` in place; every other line is left untouched.
|
|
56
|
+
*
|
|
57
|
+
* @param {string[]} lines
|
|
58
|
+
* @param {string} key
|
|
59
|
+
* @param {string} value
|
|
60
|
+
*/
|
|
61
|
+
function upsertKeyLine(lines, key, value) {
|
|
62
|
+
const formatted = `${key}: ${value}`;
|
|
63
|
+
const idx = findKeyLineIndex(lines, key);
|
|
64
|
+
if (idx === -1) {
|
|
65
|
+
lines.push(formatted);
|
|
66
|
+
} else {
|
|
67
|
+
lines[idx] = formatted;
|
|
68
|
+
}
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
/**
|
|
72
|
+
* Strip CR/LF from a value before it is composed into an upserted `key:
|
|
73
|
+
* value` lock line — defense-in-depth: a malformed `currentPluginVersion`
|
|
74
|
+
* (or, symmetrically, the generated ISO timestamp) containing an embedded
|
|
75
|
+
* newline could otherwise inject an arbitrary forged `key: value` line into
|
|
76
|
+
* the lock file via {@link upsertKeyLine}. Never throws; non-string input is
|
|
77
|
+
* returned unchanged (callers already guard the string check).
|
|
78
|
+
*
|
|
79
|
+
* @param {string} value
|
|
80
|
+
* @returns {string}
|
|
81
|
+
*/
|
|
82
|
+
function sanitizeLockValue(value) {
|
|
83
|
+
return typeof value === 'string' ? value.replace(/[\r\n]/g, '') : value;
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
/**
|
|
87
|
+
* Atomically write `content` to `filePath` via tmp-file + renameSync — the
|
|
88
|
+
* same crash-safe pattern used by `scripts/lib/io.mjs#writeJsonAtomicSync`
|
|
89
|
+
* and `scripts/lib/session-lock.mjs`, adapted for raw (non-JSON) text.
|
|
90
|
+
*
|
|
91
|
+
* @param {string} filePath
|
|
92
|
+
* @param {string} content
|
|
93
|
+
*/
|
|
94
|
+
function writeTextAtomicSync(filePath, content) {
|
|
95
|
+
const dir = dirname(filePath);
|
|
96
|
+
mkdirSync(dir, { recursive: true });
|
|
97
|
+
const tmpSuffix = randomBytes(6).toString('hex');
|
|
98
|
+
const tmpFile = join(dir, `.bootstrap-lock-refresh.${tmpSuffix}.tmp`);
|
|
99
|
+
writeFileSync(tmpFile, content, { encoding: 'utf8' });
|
|
100
|
+
renameSync(tmpFile, filePath);
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
/**
|
|
104
|
+
* Refreshes `.orchestrator/bootstrap.lock` in place: acknowledges the current
|
|
105
|
+
* plugin version and resets the freshness clock without disturbing the
|
|
106
|
+
* lock's original bootstrap provenance.
|
|
107
|
+
*
|
|
108
|
+
* Precondition: the lock must exist and parse with non-empty `version` and
|
|
109
|
+
* `tier` fields. A missing or structurally invalid lock is refused — this
|
|
110
|
+
* function never fabricates a lock (that is `/bootstrap` or
|
|
111
|
+
* `/bootstrap --retroactive`'s job, not this one's).
|
|
112
|
+
*
|
|
113
|
+
* Write contract (provenance honesty, #57): every line of the existing lock
|
|
114
|
+
* is preserved byte-for-byte EXCEPT the `refreshed-at` line (always written)
|
|
115
|
+
* and the `refreshed-plugin-version` line (written only when
|
|
116
|
+
* `currentPluginVersion` is a non-empty string). Re-running twice replaces
|
|
117
|
+
* those two lines in place — it never duplicates them (idempotent).
|
|
118
|
+
*
|
|
119
|
+
* @param {{repoRoot: string, currentPluginVersion?: string, now?: number}} opts
|
|
120
|
+
* @returns {{ok: true, path: string, refreshedAt: string, refreshedPluginVersion: string|null}
|
|
121
|
+
* | {ok: false, reason: 'missing-or-invalid', message: string}}
|
|
122
|
+
*/
|
|
123
|
+
export function refreshBootstrapLock({ repoRoot, currentPluginVersion, now = Date.now() } = {}) {
|
|
124
|
+
if (!repoRoot) {
|
|
125
|
+
return {
|
|
126
|
+
ok: false,
|
|
127
|
+
reason: 'missing-or-invalid',
|
|
128
|
+
message: `bootstrap.lock: repoRoot not provided — ${PRECONDITION_MESSAGE_SUFFIX}`,
|
|
129
|
+
};
|
|
130
|
+
}
|
|
131
|
+
|
|
132
|
+
const lockPath = join(repoRoot, '.orchestrator', 'bootstrap.lock');
|
|
133
|
+
|
|
134
|
+
if (!existsSync(lockPath)) {
|
|
135
|
+
return {
|
|
136
|
+
ok: false,
|
|
137
|
+
reason: 'missing-or-invalid',
|
|
138
|
+
message: `bootstrap.lock missing — ${PRECONDITION_MESSAGE_SUFFIX}`,
|
|
139
|
+
};
|
|
140
|
+
}
|
|
141
|
+
|
|
142
|
+
let raw;
|
|
143
|
+
try {
|
|
144
|
+
raw = readFileSync(lockPath, 'utf8');
|
|
145
|
+
} catch {
|
|
146
|
+
return {
|
|
147
|
+
ok: false,
|
|
148
|
+
reason: 'missing-or-invalid',
|
|
149
|
+
message: `bootstrap.lock: failed to read file — ${PRECONDITION_MESSAGE_SUFFIX}`,
|
|
150
|
+
};
|
|
151
|
+
}
|
|
152
|
+
|
|
153
|
+
const parsed = parseBootstrapLock(raw);
|
|
154
|
+
if (!parsed['version'] || !parsed['tier']) {
|
|
155
|
+
return {
|
|
156
|
+
ok: false,
|
|
157
|
+
reason: 'missing-or-invalid',
|
|
158
|
+
message: `bootstrap.lock: missing required version/tier fields — ${PRECONDITION_MESSAGE_SUFFIX}`,
|
|
159
|
+
};
|
|
160
|
+
}
|
|
161
|
+
|
|
162
|
+
const hadTrailingNewline = raw.endsWith('\n');
|
|
163
|
+
const lines = raw.split('\n');
|
|
164
|
+
if (hadTrailingNewline) lines.pop(); // drop the implicit empty tail element from split()
|
|
165
|
+
|
|
166
|
+
const refreshedAt = sanitizeLockValue(new Date(now).toISOString());
|
|
167
|
+
const refreshedPluginVersion =
|
|
168
|
+
typeof currentPluginVersion === 'string' && currentPluginVersion.length > 0
|
|
169
|
+
? sanitizeLockValue(currentPluginVersion)
|
|
170
|
+
: null;
|
|
171
|
+
|
|
172
|
+
upsertKeyLine(lines, 'refreshed-at', refreshedAt);
|
|
173
|
+
if (refreshedPluginVersion !== null) {
|
|
174
|
+
upsertKeyLine(lines, 'refreshed-plugin-version', refreshedPluginVersion);
|
|
175
|
+
}
|
|
176
|
+
|
|
177
|
+
const newContent = lines.join('\n') + (hadTrailingNewline ? '\n' : '');
|
|
178
|
+
writeTextAtomicSync(lockPath, newContent);
|
|
179
|
+
|
|
180
|
+
return {
|
|
181
|
+
ok: true,
|
|
182
|
+
path: lockPath,
|
|
183
|
+
refreshedAt,
|
|
184
|
+
refreshedPluginVersion,
|
|
185
|
+
};
|
|
186
|
+
}
|