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,289 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
/**
|
|
3
|
+
* validate-wave-scope.mjs — Validate .claude/wave-scope.json before enforcement hooks consume it.
|
|
4
|
+
*
|
|
5
|
+
* Part of v3.0 Bash→Node migration (Epic #124). Replaces validate-wave-scope.sh
|
|
6
|
+
* which depended on scripts/lib/common.sh → scripts/lib/platform.sh (removed).
|
|
7
|
+
*
|
|
8
|
+
* Usage:
|
|
9
|
+
* node scripts/validate-wave-scope.mjs <path-to-wave-scope.json>
|
|
10
|
+
* cat wave-scope.json | node scripts/validate-wave-scope.mjs
|
|
11
|
+
* node scripts/validate-wave-scope.mjs --assert-subset <agent-filescope.json> < wave-scope.json
|
|
12
|
+
*
|
|
13
|
+
* Flags:
|
|
14
|
+
* --assert-subset <path> After schema validation passes, read the agent
|
|
15
|
+
* fileScope file (a JSON array of strings) and assert
|
|
16
|
+
* it is a subset of wave-scope.allowedPaths (#796).
|
|
17
|
+
* Fails (exit 1) with "missing: [...]" on violation.
|
|
18
|
+
*
|
|
19
|
+
* Exit codes:
|
|
20
|
+
* 0 — valid (validated JSON echoed to stdout)
|
|
21
|
+
* 1 — invalid input / validation failure (error messages written to stderr)
|
|
22
|
+
* 2 — I/O error (file not found, unreadable stdin, unreadable --assert-subset file)
|
|
23
|
+
*/
|
|
24
|
+
|
|
25
|
+
import { readFileSync, existsSync, statSync } from 'node:fs';
|
|
26
|
+
import { warn } from './lib/common.mjs';
|
|
27
|
+
import { assertFileScopeSubset } from './lib/scope-gate.mjs';
|
|
28
|
+
|
|
29
|
+
/**
|
|
30
|
+
* Write an error to stderr and exit with the given code.
|
|
31
|
+
* @param {string} msg
|
|
32
|
+
* @param {number} [code=1]
|
|
33
|
+
* @returns {never}
|
|
34
|
+
*/
|
|
35
|
+
function die(msg, code = 1) {
|
|
36
|
+
process.stderr.write(`ERROR: ${msg}\n`);
|
|
37
|
+
process.exit(code);
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
/**
|
|
41
|
+
* Parse CLI flags out of argv, leaving positional args behind.
|
|
42
|
+
*
|
|
43
|
+
* Recognised: `--assert-subset <path>` (#796). Everything else is treated as a
|
|
44
|
+
* positional argument (the wave-scope.json file path), preserving legacy
|
|
45
|
+
* behaviour where argv[2] is the input file.
|
|
46
|
+
*
|
|
47
|
+
* @param {string[]} argv - full process.argv
|
|
48
|
+
* @returns {{ assertSubset: string|null, positionals: string[] }}
|
|
49
|
+
*/
|
|
50
|
+
function parseArgs(argv) {
|
|
51
|
+
const positionals = [];
|
|
52
|
+
let assertSubset = null;
|
|
53
|
+
for (let i = 2; i < argv.length; i++) {
|
|
54
|
+
const a = argv[i];
|
|
55
|
+
if (a === '--assert-subset') {
|
|
56
|
+
assertSubset = argv[i + 1];
|
|
57
|
+
if (assertSubset === undefined) {
|
|
58
|
+
die('--assert-subset requires a file-path argument', 1);
|
|
59
|
+
}
|
|
60
|
+
i++; // consume the value
|
|
61
|
+
} else {
|
|
62
|
+
positionals.push(a);
|
|
63
|
+
}
|
|
64
|
+
}
|
|
65
|
+
return { assertSubset, positionals };
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
/**
|
|
69
|
+
* Read raw input: from a file path arg or from stdin (fd 0).
|
|
70
|
+
*
|
|
71
|
+
* Exit codes used here:
|
|
72
|
+
* 1 — bad argument (file path argument given but file not found)
|
|
73
|
+
* 2 — unexpected I/O error (file exists but cannot be read, stdin failure)
|
|
74
|
+
*
|
|
75
|
+
* @param {string|undefined} arg - the positional wave-scope.json file path (or undefined for stdin)
|
|
76
|
+
* @returns {string}
|
|
77
|
+
*/
|
|
78
|
+
function readInput(arg) {
|
|
79
|
+
if (arg) {
|
|
80
|
+
// Exit 1: file not found is a user/argument error
|
|
81
|
+
if (!existsSync(arg) || !statSync(arg).isFile()) {
|
|
82
|
+
die(`File not found: ${arg}`, 1);
|
|
83
|
+
}
|
|
84
|
+
// Exit 2: file exists but cannot be read is an I/O error
|
|
85
|
+
try {
|
|
86
|
+
return readFileSync(arg, 'utf8');
|
|
87
|
+
} catch (err) {
|
|
88
|
+
die(`Cannot read file ${arg}: ${err.message}`, 2);
|
|
89
|
+
}
|
|
90
|
+
}
|
|
91
|
+
// Exit 2: stdin read failure is an I/O error
|
|
92
|
+
try {
|
|
93
|
+
return readFileSync(0, 'utf8');
|
|
94
|
+
} catch (err) {
|
|
95
|
+
die(`Cannot read stdin: ${err.message}`, 2);
|
|
96
|
+
}
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
/**
|
|
100
|
+
* Parse raw JSON input; exits 1 on parse failure.
|
|
101
|
+
* @param {string} input
|
|
102
|
+
* @returns {unknown}
|
|
103
|
+
*/
|
|
104
|
+
function parseJson(input) {
|
|
105
|
+
try {
|
|
106
|
+
return JSON.parse(input);
|
|
107
|
+
} catch {
|
|
108
|
+
die('Input is not valid JSON');
|
|
109
|
+
}
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
/**
|
|
113
|
+
* Validate required scalar fields: wave, role, enforcement.
|
|
114
|
+
* @param {Record<string, unknown>} obj
|
|
115
|
+
* @param {string[]} errors
|
|
116
|
+
*/
|
|
117
|
+
function validateRequired(obj, errors) {
|
|
118
|
+
// wave — positive integer
|
|
119
|
+
if (!('wave' in obj) || obj.wave === null || obj.wave === undefined) {
|
|
120
|
+
errors.push('Missing required field: wave');
|
|
121
|
+
} else if (typeof obj.wave !== 'number' || !Number.isInteger(obj.wave) || obj.wave <= 0) {
|
|
122
|
+
errors.push(`wave must be a positive integer, got: ${JSON.stringify(obj.wave)}`);
|
|
123
|
+
}
|
|
124
|
+
|
|
125
|
+
// role — non-empty string
|
|
126
|
+
const roleType = obj.role === null ? 'null' : typeof obj.role;
|
|
127
|
+
if (!('role' in obj) || roleType !== 'string') {
|
|
128
|
+
errors.push(`role must be a string, got type: ${roleType}`);
|
|
129
|
+
} else if (obj.role.length === 0) {
|
|
130
|
+
errors.push('role must be a non-empty string');
|
|
131
|
+
}
|
|
132
|
+
|
|
133
|
+
// enforcement — one of strict|warn|off
|
|
134
|
+
const enfType = obj.enforcement === null ? 'null' : typeof obj.enforcement;
|
|
135
|
+
if (!('enforcement' in obj) || enfType !== 'string') {
|
|
136
|
+
errors.push(`enforcement must be a string, got type: ${enfType}`);
|
|
137
|
+
} else if (!['strict', 'warn', 'off'].includes(obj.enforcement)) {
|
|
138
|
+
errors.push(`enforcement must be one of: strict, warn, off — got: ${obj.enforcement}`);
|
|
139
|
+
}
|
|
140
|
+
}
|
|
141
|
+
|
|
142
|
+
/**
|
|
143
|
+
* Validate allowedPaths array: must exist, be an array of non-empty strings,
|
|
144
|
+
* with no absolute paths and no path-traversal segments.
|
|
145
|
+
* @param {Record<string, unknown>} obj
|
|
146
|
+
* @param {string[]} errors
|
|
147
|
+
* @param {string[]} warnings
|
|
148
|
+
*/
|
|
149
|
+
function validateAllowedPaths(obj, errors, warnings) {
|
|
150
|
+
if (!('allowedPaths' in obj)) {
|
|
151
|
+
errors.push('Missing required field: allowedPaths');
|
|
152
|
+
return;
|
|
153
|
+
}
|
|
154
|
+
const ap = obj.allowedPaths;
|
|
155
|
+
if (!Array.isArray(ap)) {
|
|
156
|
+
errors.push(`allowedPaths must be an array, got type: ${ap === null ? 'null' : typeof ap}`);
|
|
157
|
+
return;
|
|
158
|
+
}
|
|
159
|
+
for (const entry of ap) {
|
|
160
|
+
if (typeof entry !== 'string' || entry.length === 0) {
|
|
161
|
+
errors.push('allowedPaths contains empty string');
|
|
162
|
+
continue;
|
|
163
|
+
}
|
|
164
|
+
// Reject absolute paths (must be repo-relative)
|
|
165
|
+
if (entry.startsWith('/')) {
|
|
166
|
+
errors.push(`allowedPaths contains absolute path: ${entry}`);
|
|
167
|
+
}
|
|
168
|
+
// Reject path traversal: any `../` segment
|
|
169
|
+
if (entry.includes('../')) {
|
|
170
|
+
errors.push(`allowedPaths contains path traversal: ${entry}`);
|
|
171
|
+
}
|
|
172
|
+
// Warn on overly permissive glob patterns
|
|
173
|
+
if (entry === '**/*' || entry === '*') {
|
|
174
|
+
warnings.push(`allowedPaths contains overly permissive pattern: ${entry}`);
|
|
175
|
+
}
|
|
176
|
+
}
|
|
177
|
+
}
|
|
178
|
+
|
|
179
|
+
/**
|
|
180
|
+
* Validate blockedCommands: must exist and be an array.
|
|
181
|
+
* @param {Record<string, unknown>} obj
|
|
182
|
+
* @param {string[]} errors
|
|
183
|
+
*/
|
|
184
|
+
function validateBlockedCommands(obj, errors) {
|
|
185
|
+
if (!('blockedCommands' in obj)) {
|
|
186
|
+
errors.push('Missing required field: blockedCommands');
|
|
187
|
+
return;
|
|
188
|
+
}
|
|
189
|
+
if (!Array.isArray(obj.blockedCommands)) {
|
|
190
|
+
const t = obj.blockedCommands === null ? 'null' : typeof obj.blockedCommands;
|
|
191
|
+
errors.push(`blockedCommands must be an array, got type: ${t}`);
|
|
192
|
+
}
|
|
193
|
+
}
|
|
194
|
+
|
|
195
|
+
/**
|
|
196
|
+
* Validate optional gates field: if present must be an object of string→boolean entries.
|
|
197
|
+
* @param {Record<string, unknown>} obj
|
|
198
|
+
* @param {string[]} errors
|
|
199
|
+
*/
|
|
200
|
+
function validateGates(obj, errors) {
|
|
201
|
+
if (!('gates' in obj)) return;
|
|
202
|
+
const gates = obj.gates;
|
|
203
|
+
if (gates === null || typeof gates !== 'object' || Array.isArray(gates)) {
|
|
204
|
+
const t = gates === null ? 'null' : Array.isArray(gates) ? 'array' : typeof gates;
|
|
205
|
+
errors.push(`gates must be an object, got type: ${t}`);
|
|
206
|
+
return;
|
|
207
|
+
}
|
|
208
|
+
const bad = Object.entries(gates)
|
|
209
|
+
.filter(([, v]) => typeof v !== 'boolean')
|
|
210
|
+
.map(([k]) => k);
|
|
211
|
+
if (bad.length > 0) {
|
|
212
|
+
errors.push(`gates values must be booleans, invalid entries: ${bad.join(', ')}`);
|
|
213
|
+
}
|
|
214
|
+
}
|
|
215
|
+
|
|
216
|
+
/**
|
|
217
|
+
* Read the agent fileScope sidecar file and assert it is a subset of the
|
|
218
|
+
* wave's allowedPaths union (#796). Exits on failure; returns on success.
|
|
219
|
+
*
|
|
220
|
+
* Exit codes:
|
|
221
|
+
* 1 — fileScope file is not valid JSON, is not an array of strings, or the
|
|
222
|
+
* subset assertion fails (validation error)
|
|
223
|
+
* 2 — fileScope file is missing / not a regular file / unreadable (I/O error)
|
|
224
|
+
*
|
|
225
|
+
* @param {Record<string, unknown>} obj - the already schema-validated wave-scope object
|
|
226
|
+
* @param {string} fileScopePath - path to the agent fileScope JSON file
|
|
227
|
+
*/
|
|
228
|
+
function assertSubsetOrDie(obj, fileScopePath) {
|
|
229
|
+
if (!existsSync(fileScopePath) || !statSync(fileScopePath).isFile()) {
|
|
230
|
+
die(`Cannot read --assert-subset file: ${fileScopePath}`, 2);
|
|
231
|
+
}
|
|
232
|
+
let raw;
|
|
233
|
+
try {
|
|
234
|
+
raw = readFileSync(fileScopePath, 'utf8');
|
|
235
|
+
} catch (err) {
|
|
236
|
+
die(`Cannot read --assert-subset file ${fileScopePath}: ${err.message}`, 2);
|
|
237
|
+
}
|
|
238
|
+
let fileScope;
|
|
239
|
+
try {
|
|
240
|
+
fileScope = JSON.parse(raw);
|
|
241
|
+
} catch {
|
|
242
|
+
die(`--assert-subset file is not valid JSON: ${fileScopePath}`, 1);
|
|
243
|
+
}
|
|
244
|
+
if (!Array.isArray(fileScope) || !fileScope.every((e) => typeof e === 'string')) {
|
|
245
|
+
die('--assert-subset file must be a JSON array of strings', 1);
|
|
246
|
+
}
|
|
247
|
+
const { ok, missing } = assertFileScopeSubset(fileScope, obj.allowedPaths);
|
|
248
|
+
if (!ok) {
|
|
249
|
+
die(`agent fileScope not ⊆ allowedPaths — missing: [${missing.join(', ')}]`, 1);
|
|
250
|
+
}
|
|
251
|
+
}
|
|
252
|
+
|
|
253
|
+
/**
|
|
254
|
+
* Main validation entry point. Reads input, validates, exits with appropriate code.
|
|
255
|
+
* @param {string} input - raw JSON string
|
|
256
|
+
* @param {string|null} [assertSubsetPath] - optional agent fileScope file for the #796 subset assertion
|
|
257
|
+
*/
|
|
258
|
+
function validate(input, assertSubsetPath = null) {
|
|
259
|
+
const obj = parseJson(input);
|
|
260
|
+
const errors = [];
|
|
261
|
+
const warnings = [];
|
|
262
|
+
|
|
263
|
+
validateRequired(obj, errors);
|
|
264
|
+
validateAllowedPaths(obj, errors, warnings);
|
|
265
|
+
validateBlockedCommands(obj, errors);
|
|
266
|
+
validateGates(obj, errors);
|
|
267
|
+
|
|
268
|
+
for (const w of warnings) {
|
|
269
|
+
warn(w);
|
|
270
|
+
}
|
|
271
|
+
|
|
272
|
+
if (errors.length > 0) {
|
|
273
|
+
for (const e of errors) {
|
|
274
|
+
process.stderr.write(`ERROR: ${e}\n`);
|
|
275
|
+
}
|
|
276
|
+
process.exit(1);
|
|
277
|
+
}
|
|
278
|
+
|
|
279
|
+
// #796 — optional dispatch-time subset assertion (runs only after schema validation passes)
|
|
280
|
+
if (assertSubsetPath) {
|
|
281
|
+
assertSubsetOrDie(obj, assertSubsetPath);
|
|
282
|
+
}
|
|
283
|
+
|
|
284
|
+
// Echo validated JSON to stdout (trailing newline normalised)
|
|
285
|
+
process.stdout.write(input.endsWith('\n') ? input : input + '\n');
|
|
286
|
+
}
|
|
287
|
+
|
|
288
|
+
const { assertSubset, positionals } = parseArgs(process.argv);
|
|
289
|
+
validate(readInput(positionals[0]), assertSubset);
|
|
@@ -0,0 +1,404 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
/**
|
|
3
|
+
* vault-backfill.mjs — GitLab group scanner + .vault.yaml backfill CLI (Issue #241).
|
|
4
|
+
*
|
|
5
|
+
* Scans GitLab groups for repos missing .vault.yaml and either previews (dry-run,
|
|
6
|
+
* default) or renders the canonical template content (--apply).
|
|
7
|
+
*
|
|
8
|
+
* CLI usage:
|
|
9
|
+
* node scripts/vault-backfill.mjs [--groups <CSV>] [--dry-run] [--apply]
|
|
10
|
+
* [--out-dir <path>] [--yes <manifest.json>] [--vault-dir <path>] [--verbose]
|
|
11
|
+
*
|
|
12
|
+
* Exit codes:
|
|
13
|
+
* 0 — success (clean run, dry or apply, even if some repos skipped)
|
|
14
|
+
* 1 — validation error (bad CLI flags, missing --groups AND no Session Config
|
|
15
|
+
* gitlab-groups, malformed manifest, missing glab CLI)
|
|
16
|
+
* 2 — filesystem error (template missing, cannot write to out-dir)
|
|
17
|
+
* 3 — GitLab API error (auth failure, rate limit, group not found — when ALL
|
|
18
|
+
* groups fail to resolve OR all repo-probes fail and nothing was processed)
|
|
19
|
+
* 4 — dry-run found nothing to backfill (informational; not an error)
|
|
20
|
+
*
|
|
21
|
+
* All logging → stderr. Structured JSON actions → stdout (one object per line).
|
|
22
|
+
* No external dependencies — Node stdlib only.
|
|
23
|
+
*
|
|
24
|
+
* Part of Vault-Docs Sub-Epic C (Issue #241).
|
|
25
|
+
*/
|
|
26
|
+
|
|
27
|
+
import { readFileSync, writeFileSync, mkdirSync, existsSync } from 'node:fs';
|
|
28
|
+
import { join, resolve, dirname } from 'node:path';
|
|
29
|
+
import { createInterface } from 'node:readline';
|
|
30
|
+
|
|
31
|
+
import {
|
|
32
|
+
assertGlabExists, setVerbose as setGlabVerbose,
|
|
33
|
+
listGroupRepos, checkVaultYaml, fetchRepoOwner,
|
|
34
|
+
} from './lib/vault-backfill/glab.mjs';
|
|
35
|
+
|
|
36
|
+
import {
|
|
37
|
+
loadTemplate, slugToHumanName, pathToSlug, renderTemplate,
|
|
38
|
+
} from './lib/vault-backfill/template.mjs';
|
|
39
|
+
|
|
40
|
+
import { validateManifest, SLUG_RE } from './lib/vault-backfill/manifest.mjs';
|
|
41
|
+
|
|
42
|
+
// ── CLI argument parsing ──────────────────────────────────────────────────────
|
|
43
|
+
|
|
44
|
+
const args = process.argv.slice(2);
|
|
45
|
+
|
|
46
|
+
function getArg(name) {
|
|
47
|
+
const idx = args.indexOf(name);
|
|
48
|
+
return idx !== -1 ? args[idx + 1] : undefined;
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
const flagGroups = getArg('--groups');
|
|
52
|
+
const flagDryRun = args.includes('--dry-run');
|
|
53
|
+
const flagApply = args.includes('--apply');
|
|
54
|
+
const flagYes = getArg('--yes');
|
|
55
|
+
const flagVaultDir = getArg('--vault-dir');
|
|
56
|
+
const flagOutDir = getArg('--out-dir');
|
|
57
|
+
const flagVerbose = args.includes('--verbose');
|
|
58
|
+
|
|
59
|
+
// ── Logging + output ──────────────────────────────────────────────────────────
|
|
60
|
+
|
|
61
|
+
function log(msg) {
|
|
62
|
+
process.stderr.write(`[vault-backfill] ${msg}\n`);
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
function emitAction(action, repoPath, extra = {}) {
|
|
66
|
+
process.stdout.write(JSON.stringify({ action, path: repoPath, ...extra }) + '\n');
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
function die(code, msg) {
|
|
70
|
+
process.stderr.write(`[vault-backfill] ERROR: ${msg}\n`);
|
|
71
|
+
process.exit(code);
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
// ── Validation ────────────────────────────────────────────────────────────────
|
|
75
|
+
|
|
76
|
+
if (flagDryRun && flagApply) die(1, '--dry-run and --apply are mutually exclusive');
|
|
77
|
+
|
|
78
|
+
const applyWrites = flagApply === true;
|
|
79
|
+
|
|
80
|
+
/** Resolved staging output directory (used when --apply is set). */
|
|
81
|
+
const outDir = resolve(flagOutDir ?? './.vault-backfill-staging');
|
|
82
|
+
|
|
83
|
+
/** Whether any file writes failed (contributes to exit 2). */
|
|
84
|
+
let hadWriteError = false;
|
|
85
|
+
|
|
86
|
+
if (flagVerbose) setGlabVerbose(true);
|
|
87
|
+
|
|
88
|
+
// ── Session Config reader (inline) ───────────────────────────────────────────
|
|
89
|
+
|
|
90
|
+
/** Read vault-integration.{gitlab-groups,vault-dir} from CLAUDE.md / AGENTS.md in CWD. */
|
|
91
|
+
function readVaultIntegrationConfig() {
|
|
92
|
+
const candidates = [join(process.cwd(), 'CLAUDE.md'), join(process.cwd(), 'AGENTS.md')];
|
|
93
|
+
|
|
94
|
+
let content = null;
|
|
95
|
+
for (const p of candidates) {
|
|
96
|
+
if (existsSync(p)) {
|
|
97
|
+
try { content = readFileSync(p, 'utf8'); break; } catch { /* try next */ }
|
|
98
|
+
}
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
if (!content) return { 'gitlab-groups': null, 'vault-dir': null };
|
|
102
|
+
|
|
103
|
+
let inBlock = false;
|
|
104
|
+
let gitlabGroups = null;
|
|
105
|
+
let vaultDir = null;
|
|
106
|
+
|
|
107
|
+
for (const rawLine of content.split(/\r?\n/)) {
|
|
108
|
+
const line = rawLine.replace(/\r$/, '');
|
|
109
|
+
|
|
110
|
+
if (!inBlock && /^vault-integration:\s*$/.test(line)) { inBlock = true; continue; }
|
|
111
|
+
if (inBlock) {
|
|
112
|
+
if (line.length > 0 && !/^\s/.test(line)) break;
|
|
113
|
+
|
|
114
|
+
const m = line.match(/^\s+([a-zA-Z_-]+):\s*(.*)/);
|
|
115
|
+
if (!m) continue;
|
|
116
|
+
|
|
117
|
+
const k = m[1].trim();
|
|
118
|
+
let v = m[2].trim().replace(/\s*#.*$/, '').trim();
|
|
119
|
+
if ((v.startsWith('"') && v.endsWith('"')) || (v.startsWith("'") && v.endsWith("'"))) {
|
|
120
|
+
v = v.slice(1, -1);
|
|
121
|
+
}
|
|
122
|
+
|
|
123
|
+
if (k === 'gitlab-groups' && v) {
|
|
124
|
+
gitlabGroups = v.replace(/^\[/, '').replace(/\]$/, '').trim() || null;
|
|
125
|
+
}
|
|
126
|
+
if (k === 'vault-dir' && v && v !== 'none' && v !== 'null') {
|
|
127
|
+
vaultDir = v;
|
|
128
|
+
}
|
|
129
|
+
}
|
|
130
|
+
}
|
|
131
|
+
|
|
132
|
+
return { 'gitlab-groups': gitlabGroups, 'vault-dir': vaultDir };
|
|
133
|
+
}
|
|
134
|
+
|
|
135
|
+
// ── Vault-dir folder stub ─────────────────────────────────────────────────────
|
|
136
|
+
|
|
137
|
+
function createVaultFolderStub(vaultDir, slug) {
|
|
138
|
+
if (!vaultDir) return;
|
|
139
|
+
const stubDir = join(resolve(vaultDir), '01-projects', slug);
|
|
140
|
+
try {
|
|
141
|
+
if (!existsSync(stubDir)) {
|
|
142
|
+
if (applyWrites) {
|
|
143
|
+
mkdirSync(stubDir, { recursive: true });
|
|
144
|
+
log(`created vault folder stub: ${stubDir}`);
|
|
145
|
+
} else {
|
|
146
|
+
log(`[dry-run] would create vault folder stub: ${stubDir}`);
|
|
147
|
+
}
|
|
148
|
+
}
|
|
149
|
+
} catch (err) {
|
|
150
|
+
process.stderr.write(
|
|
151
|
+
`[vault-backfill] WARN: could not create vault stub for ${slug}: ${err.message}\n`,
|
|
152
|
+
);
|
|
153
|
+
}
|
|
154
|
+
}
|
|
155
|
+
|
|
156
|
+
// ── Process one repo ──────────────────────────────────────────────────────────
|
|
157
|
+
|
|
158
|
+
function processRepo(entry, vaultDir, templateContent) {
|
|
159
|
+
const { path: repoPath, slug, tier, visibility, group, id } = entry;
|
|
160
|
+
const humanName = slugToHumanName(slug);
|
|
161
|
+
|
|
162
|
+
const owner = applyWrites ? fetchRepoOwner(id) : 'unknown';
|
|
163
|
+
|
|
164
|
+
let renderedContent;
|
|
165
|
+
try {
|
|
166
|
+
renderedContent = renderTemplate(
|
|
167
|
+
{ humanName, slug, tier, gitlabPath: repoPath, owner },
|
|
168
|
+
templateContent,
|
|
169
|
+
);
|
|
170
|
+
} catch (err) {
|
|
171
|
+
process.stderr.write(
|
|
172
|
+
`[vault-backfill] WARN: template render failed for ${repoPath}: ${err.message}\n`,
|
|
173
|
+
);
|
|
174
|
+
emitAction('skipped-render-error', repoPath, { slug, tier, visibility, error: err.message });
|
|
175
|
+
return;
|
|
176
|
+
}
|
|
177
|
+
|
|
178
|
+
if (!applyWrites) {
|
|
179
|
+
// Dry-run: emit preview action only (no disk write)
|
|
180
|
+
emitAction('vault-yaml-rendered', repoPath, { slug, tier, visibility, group, content: renderedContent });
|
|
181
|
+
log(`[dry-run] ${repoPath}: would write .vault.yaml (slug=${slug}, tier=${tier})`);
|
|
182
|
+
createVaultFolderStub(vaultDir, slug);
|
|
183
|
+
return;
|
|
184
|
+
}
|
|
185
|
+
|
|
186
|
+
// Apply: write to staging directory at <out-dir>/<group>/<repo>/.vault.yaml
|
|
187
|
+
const writePath = join(outDir, repoPath, '.vault.yaml');
|
|
188
|
+
try {
|
|
189
|
+
mkdirSync(dirname(writePath), { recursive: true });
|
|
190
|
+
writeFileSync(writePath, renderedContent, 'utf8');
|
|
191
|
+
emitAction('wrote', writePath, { slug, id, tier, visibility, group });
|
|
192
|
+
log(`${repoPath}: wrote .vault.yaml → ${writePath}`);
|
|
193
|
+
} catch (err) {
|
|
194
|
+
emitAction('write-failed', writePath, { error: err.message, slug, group });
|
|
195
|
+
process.stderr.write(
|
|
196
|
+
`[vault-backfill] ERROR: cannot write ${writePath}: ${err.message}\n`,
|
|
197
|
+
);
|
|
198
|
+
hadWriteError = true;
|
|
199
|
+
}
|
|
200
|
+
|
|
201
|
+
createVaultFolderStub(vaultDir, slug);
|
|
202
|
+
}
|
|
203
|
+
|
|
204
|
+
// ── Scan groups ───────────────────────────────────────────────────────────────
|
|
205
|
+
|
|
206
|
+
function scanGroups(groups) {
|
|
207
|
+
let failedGroups = 0;
|
|
208
|
+
const missing = [];
|
|
209
|
+
|
|
210
|
+
for (const group of groups) {
|
|
211
|
+
log(`scanning group: ${group}`);
|
|
212
|
+
const repos = listGroupRepos(group);
|
|
213
|
+
if (repos === null) { failedGroups++; continue; }
|
|
214
|
+
|
|
215
|
+
log(` found ${repos.length} repo(s) in ${group}`);
|
|
216
|
+
for (const repo of repos) {
|
|
217
|
+
if (!repo.path) continue;
|
|
218
|
+
const status = checkVaultYaml(repo.path);
|
|
219
|
+
if (status === 'present') {
|
|
220
|
+
log(` ${repo.path}: already has .vault.yaml — skipping`);
|
|
221
|
+
emitAction('skipped-has-vault-yaml', repo.path, { visibility: repo.visibility, createdAt: repo.createdAt });
|
|
222
|
+
continue;
|
|
223
|
+
}
|
|
224
|
+
missing.push({ ...repo, group });
|
|
225
|
+
}
|
|
226
|
+
}
|
|
227
|
+
|
|
228
|
+
const allGroupsFailed = failedGroups > 0 && failedGroups === groups.length;
|
|
229
|
+
return { repos: missing, apiError: failedGroups > 0, allGroupsFailed };
|
|
230
|
+
}
|
|
231
|
+
|
|
232
|
+
// ── Table printer ─────────────────────────────────────────────────────────────
|
|
233
|
+
|
|
234
|
+
function printTable(repos) {
|
|
235
|
+
if (repos.length === 0) { process.stderr.write('\n (no repos to backfill)\n'); return; }
|
|
236
|
+
|
|
237
|
+
const COL_REPO = Math.max(4, ...repos.map((r) => r.path.length));
|
|
238
|
+
const pad = (s, n) => String(s).padEnd(n);
|
|
239
|
+
const sep = ` ${'-'.repeat(COL_REPO + 26)}\n`;
|
|
240
|
+
|
|
241
|
+
process.stderr.write('\n');
|
|
242
|
+
process.stderr.write(` ${pad('Repo', COL_REPO)} ${pad('Date', 10)} Visibility\n`);
|
|
243
|
+
process.stderr.write(sep);
|
|
244
|
+
for (const r of repos) {
|
|
245
|
+
process.stderr.write(` ${pad(r.path, COL_REPO)} ${pad(r.createdAt, 10)} ${r.visibility}\n`);
|
|
246
|
+
}
|
|
247
|
+
process.stderr.write('\n');
|
|
248
|
+
}
|
|
249
|
+
|
|
250
|
+
// ── Interactive prompt ────────────────────────────────────────────────────────
|
|
251
|
+
|
|
252
|
+
async function promptForRepo(rl, repoPath, suggestedSlug) {
|
|
253
|
+
const ask = (q) => new Promise((res) => rl.question(q, (a) => res(a.trim())));
|
|
254
|
+
|
|
255
|
+
process.stderr.write(`\n Repo: ${repoPath}\n`);
|
|
256
|
+
|
|
257
|
+
let slug;
|
|
258
|
+
while (true) {
|
|
259
|
+
const input = await ask(` Slug [${suggestedSlug}]: `);
|
|
260
|
+
slug = input || suggestedSlug;
|
|
261
|
+
if (SLUG_RE.test(slug)) break;
|
|
262
|
+
process.stderr.write(` Invalid slug — must match [a-z0-9]+(-[a-z0-9]+)*\n`);
|
|
263
|
+
}
|
|
264
|
+
|
|
265
|
+
let tier;
|
|
266
|
+
while (true) {
|
|
267
|
+
const input = await ask(` Tier (top/active/archived) [active]: `);
|
|
268
|
+
tier = input || 'active';
|
|
269
|
+
if (['top', 'active', 'archived'].includes(tier)) break;
|
|
270
|
+
process.stderr.write(` Invalid tier — must be top|active|archived\n`);
|
|
271
|
+
}
|
|
272
|
+
|
|
273
|
+
const skip = await ask(` Skip this repo? [y/N]: `);
|
|
274
|
+
if (skip.toLowerCase() === 'y') return null;
|
|
275
|
+
return { slug, tier };
|
|
276
|
+
}
|
|
277
|
+
|
|
278
|
+
// ── Headless (--yes) path ─────────────────────────────────────────────────────
|
|
279
|
+
|
|
280
|
+
async function runHeadless(manifestPath, vaultDir) {
|
|
281
|
+
let raw;
|
|
282
|
+
try {
|
|
283
|
+
raw = JSON.parse(readFileSync(manifestPath, 'utf8'));
|
|
284
|
+
} catch (err) {
|
|
285
|
+
die(1, `cannot parse manifest '${manifestPath}': ${err.message}`);
|
|
286
|
+
}
|
|
287
|
+
|
|
288
|
+
const repos = validateManifest(raw, die);
|
|
289
|
+
const templateContent = loadTemplate(die);
|
|
290
|
+
const pending = repos.filter((r) => !r.skip);
|
|
291
|
+
|
|
292
|
+
log(`manifest: ${repos.length} entries, ${pending.length} to process`);
|
|
293
|
+
if (pending.length === 0) { log('nothing to backfill (all skip:true)'); process.exit(4); }
|
|
294
|
+
|
|
295
|
+
// When --yes manifest is provided, it is the authoritative scope — skip the
|
|
296
|
+
// GitLab API .vault.yaml presence check so the CLI works without live repo access.
|
|
297
|
+
for (const entry of pending) {
|
|
298
|
+
processRepo(
|
|
299
|
+
{ ...entry, group: entry.path.split('/')[0] ?? '' },
|
|
300
|
+
vaultDir,
|
|
301
|
+
templateContent,
|
|
302
|
+
);
|
|
303
|
+
}
|
|
304
|
+
}
|
|
305
|
+
|
|
306
|
+
// ── Interactive path ──────────────────────────────────────────────────────────
|
|
307
|
+
|
|
308
|
+
async function runInteractive(groups, vaultDir) {
|
|
309
|
+
const templateContent = loadTemplate(die);
|
|
310
|
+
|
|
311
|
+
log('scanning groups for repos missing .vault.yaml …');
|
|
312
|
+
const { repos: missing, apiError, allGroupsFailed } = scanGroups(groups);
|
|
313
|
+
|
|
314
|
+
if (apiError) log('WARN: one or more groups had API errors; results may be incomplete');
|
|
315
|
+
|
|
316
|
+
// Exit 3 only when ALL groups failed and nothing was processed at all
|
|
317
|
+
if (allGroupsFailed) {
|
|
318
|
+
die(3, 'all GitLab group scans failed — check auth (GITLAB_TOKEN) and group paths');
|
|
319
|
+
}
|
|
320
|
+
|
|
321
|
+
if (missing.length === 0) { log('all scanned repos already have .vault.yaml — nothing to backfill'); process.exit(4); }
|
|
322
|
+
|
|
323
|
+
printTable(missing);
|
|
324
|
+
log(`found ${missing.length} repo(s) without .vault.yaml`);
|
|
325
|
+
|
|
326
|
+
if (!applyWrites) {
|
|
327
|
+
log('(dry-run mode — use --apply to write .vault.yaml files)');
|
|
328
|
+
for (const repo of missing) {
|
|
329
|
+
processRepo({ ...repo, slug: pathToSlug(repo.path), tier: 'active' }, vaultDir, templateContent);
|
|
330
|
+
}
|
|
331
|
+
return;
|
|
332
|
+
}
|
|
333
|
+
|
|
334
|
+
// Interactive prompts
|
|
335
|
+
const rl = createInterface({ input: process.stdin, output: process.stderr, terminal: true });
|
|
336
|
+
const decisions = [];
|
|
337
|
+
|
|
338
|
+
for (const repo of missing) {
|
|
339
|
+
const decision = await promptForRepo(rl, repo.path, pathToSlug(repo.path));
|
|
340
|
+
if (decision === null) {
|
|
341
|
+
log(`${repo.path}: skipped by user`);
|
|
342
|
+
emitAction('skipped-by-user', repo.path, { visibility: repo.visibility });
|
|
343
|
+
continue;
|
|
344
|
+
}
|
|
345
|
+
decisions.push({ repo, ...decision });
|
|
346
|
+
}
|
|
347
|
+
|
|
348
|
+
if (decisions.length === 0) {
|
|
349
|
+
rl.close();
|
|
350
|
+
log('no repos selected — nothing to apply');
|
|
351
|
+
process.exit(0);
|
|
352
|
+
}
|
|
353
|
+
|
|
354
|
+
process.stderr.write(`\n ${decisions.length} repo(s) will receive .vault.yaml. Apply? [y/N]: `);
|
|
355
|
+
const confirmed = await new Promise((resolve) => {
|
|
356
|
+
rl.question('', (a) => { rl.close(); resolve(a.trim().toLowerCase()); });
|
|
357
|
+
});
|
|
358
|
+
|
|
359
|
+
if (confirmed !== 'y') { log('aborted by user — no writes performed'); process.exit(0); }
|
|
360
|
+
|
|
361
|
+
for (const { repo, slug, tier } of decisions) {
|
|
362
|
+
processRepo({ ...repo, slug, tier }, vaultDir, templateContent);
|
|
363
|
+
}
|
|
364
|
+
}
|
|
365
|
+
|
|
366
|
+
// ── Main ──────────────────────────────────────────────────────────────────────
|
|
367
|
+
|
|
368
|
+
async function main() {
|
|
369
|
+
const cfgConfig = readVaultIntegrationConfig();
|
|
370
|
+
|
|
371
|
+
let groups;
|
|
372
|
+
if (flagGroups) {
|
|
373
|
+
groups = flagGroups.split(',').map((g) => g.trim()).filter(Boolean);
|
|
374
|
+
} else if (cfgConfig['gitlab-groups']) {
|
|
375
|
+
groups = cfgConfig['gitlab-groups'].split(',').map((g) => g.trim()).filter(Boolean);
|
|
376
|
+
}
|
|
377
|
+
|
|
378
|
+
const vaultDir = flagVaultDir ?? cfgConfig['vault-dir'] ?? null;
|
|
379
|
+
|
|
380
|
+
if (!flagYes && (!groups || groups.length === 0)) {
|
|
381
|
+
die(1, 'no GitLab groups specified — pass --groups <CSV> or set vault-integration.gitlab-groups in CLAUDE.md');
|
|
382
|
+
}
|
|
383
|
+
|
|
384
|
+
if (flagYes) {
|
|
385
|
+
// Headless path: manifest is authoritative. No glab required (CI / air-gapped).
|
|
386
|
+
await runHeadless(flagYes, vaultDir);
|
|
387
|
+
} else {
|
|
388
|
+
// Interactive path: glab is required for group scanning + per-repo presence checks.
|
|
389
|
+
assertGlabExists(die);
|
|
390
|
+
await runInteractive(groups, vaultDir);
|
|
391
|
+
}
|
|
392
|
+
|
|
393
|
+
// Propagate write failures (EACCES, ENOSPC, etc.) as exit code 2
|
|
394
|
+
if (hadWriteError) {
|
|
395
|
+
process.stderr.write('[vault-backfill] ERROR: one or more .vault.yaml writes failed (see above)\n');
|
|
396
|
+
process.exit(2);
|
|
397
|
+
}
|
|
398
|
+
}
|
|
399
|
+
|
|
400
|
+
main().catch((err) => {
|
|
401
|
+
process.stderr.write(`[vault-backfill] unexpected error: ${err.message}\n`);
|
|
402
|
+
if (flagVerbose && err.stack) process.stderr.write(err.stack + '\n');
|
|
403
|
+
process.exit(2);
|
|
404
|
+
});
|