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,130 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* template.mjs — Canonical .vault.yaml template renderer for vault-backfill.
|
|
3
|
+
*
|
|
4
|
+
* Reads the template once from projects-baseline; subsequent calls use cache.
|
|
5
|
+
* Part of scripts/vault-backfill.mjs (Issue #241).
|
|
6
|
+
*/
|
|
7
|
+
|
|
8
|
+
import { readFileSync, existsSync } from 'node:fs';
|
|
9
|
+
import { homedir } from 'node:os';
|
|
10
|
+
import { resolve } from 'node:path';
|
|
11
|
+
|
|
12
|
+
export const TEMPLATE_PATH = process.env.PROJECTS_BASELINE_DIR
|
|
13
|
+
? resolve(process.env.PROJECTS_BASELINE_DIR, 'templates/shared/.vault.yaml.template')
|
|
14
|
+
: resolve(homedir(), 'Projects/projects-baseline/templates/shared/.vault.yaml.template');
|
|
15
|
+
|
|
16
|
+
const TODAY = new Date().toISOString().slice(0, 10);
|
|
17
|
+
|
|
18
|
+
let _templateContent = null;
|
|
19
|
+
|
|
20
|
+
/**
|
|
21
|
+
* Load and cache the canonical template. Calls dieFn(2, ...) if missing.
|
|
22
|
+
*/
|
|
23
|
+
export function loadTemplate(dieFn) {
|
|
24
|
+
if (_templateContent !== null) return _templateContent;
|
|
25
|
+
|
|
26
|
+
if (!existsSync(TEMPLATE_PATH)) {
|
|
27
|
+
dieFn(
|
|
28
|
+
2,
|
|
29
|
+
`canonical template not found at ${TEMPLATE_PATH} — ` +
|
|
30
|
+
`set PROJECTS_BASELINE_DIR env var or check projects-baseline checkout at $HOME/Projects/projects-baseline`,
|
|
31
|
+
);
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
try {
|
|
35
|
+
_templateContent = readFileSync(TEMPLATE_PATH, 'utf8');
|
|
36
|
+
} catch (err) {
|
|
37
|
+
dieFn(2, `cannot read template at ${TEMPLATE_PATH}: ${err.message}`);
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
return _templateContent;
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
/**
|
|
44
|
+
* Convert a kebab-case slug to a human-readable Title Case name.
|
|
45
|
+
* e.g. "auth-service" → "Auth Service"
|
|
46
|
+
*/
|
|
47
|
+
export function slugToHumanName(slug) {
|
|
48
|
+
return slug
|
|
49
|
+
.split('-')
|
|
50
|
+
.map((word) => word.charAt(0).toUpperCase() + word.slice(1))
|
|
51
|
+
.join(' ');
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
/**
|
|
55
|
+
* Derive a kebab-case slug from a repo path (last segment).
|
|
56
|
+
*/
|
|
57
|
+
export function pathToSlug(repoPath) {
|
|
58
|
+
const last = repoPath.split('/').filter(Boolean).pop() ?? repoPath;
|
|
59
|
+
return last
|
|
60
|
+
.toLowerCase()
|
|
61
|
+
.replace(/[._]/g, '-')
|
|
62
|
+
.replace(/[^a-z0-9-]/g, '')
|
|
63
|
+
.replace(/-+/g, '-')
|
|
64
|
+
.replace(/^-+|-+$/g, '');
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
/**
|
|
68
|
+
* Produce a safe YAML 1.2 scalar for a user-supplied string value.
|
|
69
|
+
*
|
|
70
|
+
* Uses JSON.stringify (YAML 1.2 is a superset of JSON) which:
|
|
71
|
+
* - wraps the value in double quotes
|
|
72
|
+
* - escapes newlines (\n), carriage returns (\r), tabs (\t), backslashes, and double quotes
|
|
73
|
+
*
|
|
74
|
+
* This prevents YAML injection via embedded newlines or special characters
|
|
75
|
+
* in externally-sourced fields such as `owner` (GitLab API) and `gitlabPath`
|
|
76
|
+
* (manifest file). CWE-1336 / Issue #247.
|
|
77
|
+
*
|
|
78
|
+
* @param {string} value
|
|
79
|
+
* @returns {string} — a JSON-encoded double-quoted YAML scalar, e.g. "alice"
|
|
80
|
+
*/
|
|
81
|
+
export function yamlScalar(value) {
|
|
82
|
+
return JSON.stringify(String(value));
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
/**
|
|
86
|
+
* Render .vault.yaml content from the canonical template.
|
|
87
|
+
*
|
|
88
|
+
* Substitutions:
|
|
89
|
+
* {{PROJECT_NAME}} → humanName (metadata.name, summary placeholder)
|
|
90
|
+
* metadata.slug → slug (overwrite the {{PROJECT_NAME}} echo on slug field)
|
|
91
|
+
* tier → manifest tier
|
|
92
|
+
* owner → fetched owner [sanitised via yamlScalar]
|
|
93
|
+
* {{GITLAB_GROUP}}/{{PROJECT_NAME}} → full gitlab path_with_namespace [sanitised via yamlScalar]
|
|
94
|
+
*
|
|
95
|
+
* @param {{ humanName: string, slug: string, tier: string, gitlabPath: string, owner: string }} opts
|
|
96
|
+
* @param {string} templateContent - loaded template string
|
|
97
|
+
* @returns {string}
|
|
98
|
+
*/
|
|
99
|
+
export function renderTemplate({ humanName, slug, tier, gitlabPath, owner }, templateContent) {
|
|
100
|
+
let out =
|
|
101
|
+
`# Generated by scripts/vault-backfill.mjs on ${TODAY} — review and update before committing.\n` +
|
|
102
|
+
templateContent;
|
|
103
|
+
|
|
104
|
+
// {{PROJECT_NAME}} → humanName (appears in metadata.name, summary)
|
|
105
|
+
// humanName is derived from a manifest-validated slug ([a-z0-9-]) so it only
|
|
106
|
+
// contains word characters and spaces — no YAML-special characters.
|
|
107
|
+
out = out.replace(/\{\{PROJECT_NAME\}\}/g, humanName);
|
|
108
|
+
|
|
109
|
+
// metadata.slug line: after PROJECT_NAME substitution it reads slug: "humanName" — fix to actual slug
|
|
110
|
+
// slug is validated to /^[a-z0-9]+(?:-[a-z0-9]+)*$/ by validateManifest — safe to embed bare.
|
|
111
|
+
out = out.replace(/^(\s*slug:\s*)"[^"]*"(.*)$/m, `$1"${slug}"$2`);
|
|
112
|
+
|
|
113
|
+
// tier: active → tier: <manifest tier>
|
|
114
|
+
// tier is a closed enum (top|active|archived) — safe to embed bare.
|
|
115
|
+
out = out.replace(/^(\s*tier:\s*)active(.*)$/m, `$1${tier}$2`);
|
|
116
|
+
|
|
117
|
+
// owner field — value comes from GitLab API (externally controlled).
|
|
118
|
+
// Use yamlScalar() to produce a quoted scalar, preventing newline injection (CWE-1336, #247).
|
|
119
|
+
// The template has a bare unquoted owner value (e.g. `owner: bernhard`);
|
|
120
|
+
// the regex replaces only the bare value token, preserving any trailing comment.
|
|
121
|
+
out = out.replace(/^(\s*owner:\s*)\S+(.*)$/m, `$1${yamlScalar(owner)}$2`);
|
|
122
|
+
|
|
123
|
+
// spec.links.gitlab — template uses {{GITLAB_GROUP}}/{{PROJECT_NAME}};
|
|
124
|
+
// after PROJECT_NAME substitution the token reads {{GITLAB_GROUP}}/humanName, still inside "...".
|
|
125
|
+
// gitlabPath comes from the manifest `path` field which is not validated for YAML-special chars.
|
|
126
|
+
// Replace the whole "..." quoted token with yamlScalar(gitlabPath) to prevent injection (#247).
|
|
127
|
+
out = out.replace(/"?\{\{GITLAB_GROUP\}\}\/[^\n"]*"?/g, yamlScalar(gitlabPath));
|
|
128
|
+
|
|
129
|
+
return out;
|
|
130
|
+
}
|
|
@@ -0,0 +1,331 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* vault-consolidate-fs.mjs — filesystem + classification helpers for
|
|
3
|
+
* scripts/vault-consolidate.mjs (Issue #514 / #607).
|
|
4
|
+
*
|
|
5
|
+
* Extracted from the previously-monolithic vault-consolidate.mjs so the
|
|
6
|
+
* walk / hash / classify / backup logic is unit-importable in isolation. The
|
|
7
|
+
* script itself (vault-consolidate.mjs) wires these helpers behind an
|
|
8
|
+
* `import.meta.url === pathToFileURL(process.argv[1])` entry-guard so importing
|
|
9
|
+
* the script no longer executes the migration as a top-level side effect.
|
|
10
|
+
*
|
|
11
|
+
* Every function here is behaviour-identical to its former in-script form;
|
|
12
|
+
* the only change is that previously-closed-over module constants/state
|
|
13
|
+
* (SCRIPT_NAME, BACKUP_PREFIX, DECISIONS_SIDECAR_REL, canonicalRoot) are now
|
|
14
|
+
* exported constants / explicit parameters so the helpers are pure with
|
|
15
|
+
* respect to their inputs.
|
|
16
|
+
*/
|
|
17
|
+
|
|
18
|
+
import { promises as fs } from 'node:fs';
|
|
19
|
+
import { isUtf8 } from 'node:buffer';
|
|
20
|
+
import path from 'node:path';
|
|
21
|
+
import crypto from 'node:crypto';
|
|
22
|
+
import { spawnSync } from 'node:child_process';
|
|
23
|
+
|
|
24
|
+
export const SCRIPT_NAME = 'vault-consolidate';
|
|
25
|
+
export const BACKUP_PREFIX = '.vault-backup-';
|
|
26
|
+
export const DECISIONS_SIDECAR_REL = '.vault-consolidate-decisions.json';
|
|
27
|
+
|
|
28
|
+
/**
|
|
29
|
+
* Recursively walk `root`, returning absolute paths of regular files.
|
|
30
|
+
* Skips: hidden directories (start with `.`), and any descendant of an
|
|
31
|
+
* existing `.vault-backup-*` directory.
|
|
32
|
+
*/
|
|
33
|
+
export async function walkFiles(root) {
|
|
34
|
+
/** @type {string[]} */
|
|
35
|
+
const out = [];
|
|
36
|
+
/** @type {string[]} */
|
|
37
|
+
const stack = [root];
|
|
38
|
+
|
|
39
|
+
while (stack.length > 0) {
|
|
40
|
+
const dir = stack.pop();
|
|
41
|
+
let entries;
|
|
42
|
+
try {
|
|
43
|
+
entries = await fs.readdir(dir, { withFileTypes: true });
|
|
44
|
+
} catch (err) {
|
|
45
|
+
process.stderr.write(`${SCRIPT_NAME}: WARN readdir failed for ${dir}: ${err.message}\n`);
|
|
46
|
+
continue;
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
for (const entry of entries) {
|
|
50
|
+
const fullPath = path.join(dir, entry.name);
|
|
51
|
+
|
|
52
|
+
// F5 (issue #514 fold-in) — symlink guard, evaluated FIRST.
|
|
53
|
+
//
|
|
54
|
+
// `fs.readdir(dir, { withFileTypes: true })` populates each Dirent from
|
|
55
|
+
// an `lstat` of the entry, so a symlink-to-directory already reports
|
|
56
|
+
// `isDirectory() === false` (and `isSymbolicLink() === true`) and would
|
|
57
|
+
// fall through both branches below untouched. This explicit guard makes
|
|
58
|
+
// the skip OBSERVABLE (a WARN line) and hardens against any future
|
|
59
|
+
// refactor that switches to `fs.stat`-based classification — which DOES
|
|
60
|
+
// follow symlinks and could recurse into an out-of-tree directory or
|
|
61
|
+
// copy a dereferenced target into the backup. We never follow a symlink:
|
|
62
|
+
// neither symlinked directories (recursion risk) nor symlinked files
|
|
63
|
+
// (the dereference-into-backup risk #514 also guards at stageBackup).
|
|
64
|
+
if (entry.isSymbolicLink()) {
|
|
65
|
+
process.stderr.write(
|
|
66
|
+
`${SCRIPT_NAME}: WARN skipping symlink (not dereferenced): ${fullPath}\n`
|
|
67
|
+
);
|
|
68
|
+
continue;
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
if (entry.isDirectory()) {
|
|
72
|
+
// Skip hidden dirs and prior backup staging dirs
|
|
73
|
+
if (entry.name === '.git') continue;
|
|
74
|
+
if (entry.name.startsWith(BACKUP_PREFIX)) continue;
|
|
75
|
+
if (entry.name === '.obsidian' || entry.name === '.trash') continue;
|
|
76
|
+
if (entry.name === 'node_modules') continue;
|
|
77
|
+
// Allow other dotfiles (vault-mirror writes none, but be conservative)
|
|
78
|
+
stack.push(fullPath);
|
|
79
|
+
continue;
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
if (entry.isFile()) {
|
|
83
|
+
// Skip macOS metadata, the decisions sidecar, and .DS_Store
|
|
84
|
+
if (entry.name === '.DS_Store') continue;
|
|
85
|
+
if (entry.name === DECISIONS_SIDECAR_REL) continue;
|
|
86
|
+
if (entry.name.startsWith('._')) continue;
|
|
87
|
+
// Skip backup archives left by prior --apply runs (idempotency).
|
|
88
|
+
// Matches both `.vault-backup-<ts>.tar.gz` and `.vault-backup-<ts>` dirs
|
|
89
|
+
// (the dir form is also filtered by the directory branch above).
|
|
90
|
+
if (entry.name.startsWith(BACKUP_PREFIX)) continue;
|
|
91
|
+
out.push(fullPath);
|
|
92
|
+
}
|
|
93
|
+
// (Symlinks already handled by the isSymbolicLink() guard above.)
|
|
94
|
+
}
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
out.sort();
|
|
98
|
+
return out;
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
export async function sha256OfFile(absPath) {
|
|
102
|
+
const buf = await fs.readFile(absPath);
|
|
103
|
+
return crypto.createHash('sha256').update(buf).digest('hex');
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
/**
|
|
107
|
+
* Check whether `B` starts with `A` modulo trailing whitespace.
|
|
108
|
+
* Both are utf-8 strings.
|
|
109
|
+
*/
|
|
110
|
+
export function isPrefix(a, b) {
|
|
111
|
+
const aTrim = a.trimEnd();
|
|
112
|
+
if (aTrim.length === 0) return false;
|
|
113
|
+
if (aTrim.length > b.length) return false;
|
|
114
|
+
return b.startsWith(aTrim);
|
|
115
|
+
}
|
|
116
|
+
|
|
117
|
+
/**
|
|
118
|
+
* Classify one source file.
|
|
119
|
+
*
|
|
120
|
+
* @param {string} srcAbs absolute path inside source vault
|
|
121
|
+
* @param {string} rel relative path under source root
|
|
122
|
+
* @param {string} canonicalRoot absolute path of the canonical vault root
|
|
123
|
+
* @returns {Promise<object>} action record
|
|
124
|
+
*/
|
|
125
|
+
export async function classifyFile(srcAbs, rel, canonicalRoot) {
|
|
126
|
+
const dstAbs = path.join(canonicalRoot, rel);
|
|
127
|
+
|
|
128
|
+
let srcStat;
|
|
129
|
+
try {
|
|
130
|
+
srcStat = await fs.stat(srcAbs);
|
|
131
|
+
} catch (err) {
|
|
132
|
+
return {
|
|
133
|
+
kind: 'action',
|
|
134
|
+
action: 'error',
|
|
135
|
+
rel,
|
|
136
|
+
source: srcAbs,
|
|
137
|
+
canonical: dstAbs,
|
|
138
|
+
error: `stat source failed: ${err.message}`,
|
|
139
|
+
};
|
|
140
|
+
}
|
|
141
|
+
|
|
142
|
+
let dstStat;
|
|
143
|
+
try {
|
|
144
|
+
dstStat = await fs.stat(dstAbs);
|
|
145
|
+
} catch {
|
|
146
|
+
dstStat = null;
|
|
147
|
+
}
|
|
148
|
+
|
|
149
|
+
if (dstStat === null) {
|
|
150
|
+
return {
|
|
151
|
+
kind: 'action',
|
|
152
|
+
action: 'copy',
|
|
153
|
+
rel,
|
|
154
|
+
source: srcAbs,
|
|
155
|
+
canonical: dstAbs,
|
|
156
|
+
src_size: srcStat.size,
|
|
157
|
+
src_mtime: srcStat.mtime.toISOString(),
|
|
158
|
+
};
|
|
159
|
+
}
|
|
160
|
+
|
|
161
|
+
// Both sides exist — hash both
|
|
162
|
+
let srcSha;
|
|
163
|
+
let dstSha;
|
|
164
|
+
try {
|
|
165
|
+
[srcSha, dstSha] = await Promise.all([sha256OfFile(srcAbs), sha256OfFile(dstAbs)]);
|
|
166
|
+
} catch (err) {
|
|
167
|
+
return {
|
|
168
|
+
kind: 'action',
|
|
169
|
+
action: 'error',
|
|
170
|
+
rel,
|
|
171
|
+
source: srcAbs,
|
|
172
|
+
canonical: dstAbs,
|
|
173
|
+
error: `hash failed: ${err.message}`,
|
|
174
|
+
};
|
|
175
|
+
}
|
|
176
|
+
|
|
177
|
+
if (srcSha === dstSha) {
|
|
178
|
+
return {
|
|
179
|
+
kind: 'action',
|
|
180
|
+
action: 'skip-already-present',
|
|
181
|
+
rel,
|
|
182
|
+
source: srcAbs,
|
|
183
|
+
canonical: dstAbs,
|
|
184
|
+
src_size: srcStat.size,
|
|
185
|
+
dst_size: dstStat.size,
|
|
186
|
+
src_sha: srcSha,
|
|
187
|
+
dst_sha: dstSha,
|
|
188
|
+
};
|
|
189
|
+
}
|
|
190
|
+
|
|
191
|
+
// Content differs — check subset relationship to pre-suggest a winner.
|
|
192
|
+
// We use explicit `isUtf8()` binary detection (not throw-based) to classify
|
|
193
|
+
// as `merge` vs `conflict-needs-review`. Node 20+ silently substitutes the
|
|
194
|
+
// U+FFFD replacement char for invalid UTF-8 sequences instead of throwing
|
|
195
|
+
// from `readFile('utf8')`, which previously left the conflict-needs-review
|
|
196
|
+
// branch unreachable for binary files (issue #508).
|
|
197
|
+
let subsetHint = null;
|
|
198
|
+
let textReadable = false;
|
|
199
|
+
try {
|
|
200
|
+
const [srcBuf, dstBuf] = await Promise.all([fs.readFile(srcAbs), fs.readFile(dstAbs)]);
|
|
201
|
+
// Both sides must be valid UTF-8 to be classified as `merge`. If either
|
|
202
|
+
// is binary (or non-UTF-8 encoded), fall through to conflict-needs-review
|
|
203
|
+
// so the operator inspects the pair manually with the right tool.
|
|
204
|
+
if (isUtf8(srcBuf) && isUtf8(dstBuf)) {
|
|
205
|
+
textReadable = true;
|
|
206
|
+
const srcContent = srcBuf.toString('utf8');
|
|
207
|
+
const dstContent = dstBuf.toString('utf8');
|
|
208
|
+
if (isPrefix(srcContent, dstContent)) {
|
|
209
|
+
// canonical is a superset of source → prefer canonical (dst)
|
|
210
|
+
subsetHint = 'dst-is-superset';
|
|
211
|
+
} else if (isPrefix(dstContent, srcContent)) {
|
|
212
|
+
// source is a superset of canonical → prefer source (src)
|
|
213
|
+
subsetHint = 'src-is-superset';
|
|
214
|
+
}
|
|
215
|
+
}
|
|
216
|
+
} catch {
|
|
217
|
+
// Read error (permissions, disappeared mid-walk, etc.) — leave
|
|
218
|
+
// textReadable=false; treat as conflict-needs-review so the operator
|
|
219
|
+
// inspects manually.
|
|
220
|
+
}
|
|
221
|
+
|
|
222
|
+
// Classification:
|
|
223
|
+
// - both sides valid UTF-8 → merge (AUQ-resolvable;
|
|
224
|
+
// subset_hint may suggest winner)
|
|
225
|
+
// - unreadable / binary / non-UTF-8 → conflict-needs-review
|
|
226
|
+
//
|
|
227
|
+
// Both classes still require an operator decision via the two-phase
|
|
228
|
+
// coordinator flow, but the label tells the caller whether the diff can be
|
|
229
|
+
// shown inline (merge) or requires opening the file in a binary tool
|
|
230
|
+
// (conflict-needs-review).
|
|
231
|
+
const action = textReadable ? 'merge' : 'conflict-needs-review';
|
|
232
|
+
|
|
233
|
+
return {
|
|
234
|
+
kind: 'action',
|
|
235
|
+
action,
|
|
236
|
+
rel,
|
|
237
|
+
source: srcAbs,
|
|
238
|
+
canonical: dstAbs,
|
|
239
|
+
src_size: srcStat.size,
|
|
240
|
+
dst_size: dstStat.size,
|
|
241
|
+
src_mtime: srcStat.mtime.toISOString(),
|
|
242
|
+
dst_mtime: dstStat.mtime.toISOString(),
|
|
243
|
+
src_sha: srcSha,
|
|
244
|
+
dst_sha: dstSha,
|
|
245
|
+
subset_hint: subsetHint,
|
|
246
|
+
};
|
|
247
|
+
}
|
|
248
|
+
|
|
249
|
+
/**
|
|
250
|
+
* Stage one file into the backup directory, preserving its relative path.
|
|
251
|
+
*
|
|
252
|
+
* #514 — symlink defense (defense-in-depth alongside the walk-level guard).
|
|
253
|
+
* `fs.copyFile()` and `fs.stat()` both FOLLOW symlinks, so a symlinked source
|
|
254
|
+
* entry (e.g. `evil.md → /etc/passwd`) would silently copy the TARGET's
|
|
255
|
+
* contents into the backup. We `fs.lstat()` first (which does NOT follow the
|
|
256
|
+
* link) and skip the copy entirely if the source is a symlink, so a symlink is
|
|
257
|
+
* never dereferenced into the backup.
|
|
258
|
+
*
|
|
259
|
+
* @returns {Promise<{staged: boolean}>} staged=false when skipped (symlink)
|
|
260
|
+
*/
|
|
261
|
+
export async function stageBackup(backupRoot, srcAbs, rel) {
|
|
262
|
+
const linkStat = await fs.lstat(srcAbs);
|
|
263
|
+
if (linkStat.isSymbolicLink()) {
|
|
264
|
+
process.stderr.write(
|
|
265
|
+
`${SCRIPT_NAME}: WARN refusing to back up symlink (not dereferenced): ${srcAbs}\n`
|
|
266
|
+
);
|
|
267
|
+
return { staged: false };
|
|
268
|
+
}
|
|
269
|
+
|
|
270
|
+
const stagedAbs = path.join(backupRoot, rel);
|
|
271
|
+
await fs.mkdir(path.dirname(stagedAbs), { recursive: true });
|
|
272
|
+
await fs.copyFile(srcAbs, stagedAbs);
|
|
273
|
+
try {
|
|
274
|
+
const st = await fs.stat(srcAbs);
|
|
275
|
+
await fs.utimes(stagedAbs, st.atime, st.mtime);
|
|
276
|
+
} catch {
|
|
277
|
+
// non-fatal
|
|
278
|
+
}
|
|
279
|
+
return { staged: true };
|
|
280
|
+
}
|
|
281
|
+
|
|
282
|
+
/**
|
|
283
|
+
* Run `tar -czf <archive> -C <parent> <basename>` then remove the staging dir.
|
|
284
|
+
* Falls back to leaving the staging directory in place if tar is unavailable.
|
|
285
|
+
*
|
|
286
|
+
* @param {string} backupRoot absolute path of the staging directory
|
|
287
|
+
* @returns {Promise<{archive: string|null, removed: boolean}>}
|
|
288
|
+
*/
|
|
289
|
+
export async function compressAndCleanupBackup(backupRoot) {
|
|
290
|
+
const parent = path.dirname(backupRoot);
|
|
291
|
+
const basename = path.basename(backupRoot);
|
|
292
|
+
const archive = `${backupRoot}.tar.gz`;
|
|
293
|
+
|
|
294
|
+
const res = spawnSync('tar', ['-czf', archive, '-C', parent, basename], {
|
|
295
|
+
stdio: ['ignore', 'pipe', 'pipe'],
|
|
296
|
+
encoding: 'utf8',
|
|
297
|
+
});
|
|
298
|
+
|
|
299
|
+
// F1 (issue #514 fold-in) — distinguish "tar not on PATH" (ENOENT) from a
|
|
300
|
+
// non-zero tar exit. On ENOENT `spawnSync` sets `res.error` and leaves
|
|
301
|
+
// `res.status === null`; the generic branch below would otherwise print a
|
|
302
|
+
// confusing "tar failed (status null)". Surface the real cause and keep the
|
|
303
|
+
// uncompressed staging dir as the documented fallback.
|
|
304
|
+
if (res.error) {
|
|
305
|
+
const reason = res.error.code === 'ENOENT' ? 'tar not found on PATH' : res.error.message;
|
|
306
|
+
process.stderr.write(
|
|
307
|
+
`${SCRIPT_NAME}: WARN ${reason} — cannot compress backup. ` +
|
|
308
|
+
`Leaving staging directory at ${backupRoot} for manual archival.\n`
|
|
309
|
+
);
|
|
310
|
+
return { archive: null, removed: false };
|
|
311
|
+
}
|
|
312
|
+
|
|
313
|
+
if (res.status === 0) {
|
|
314
|
+
// Remove the now-redundant staging directory
|
|
315
|
+
try {
|
|
316
|
+
await fs.rm(backupRoot, { recursive: true, force: true });
|
|
317
|
+
return { archive, removed: true };
|
|
318
|
+
} catch (err) {
|
|
319
|
+
process.stderr.write(
|
|
320
|
+
`${SCRIPT_NAME}: WARN backup compressed but staging dir cleanup failed: ${err.message}\n`
|
|
321
|
+
);
|
|
322
|
+
return { archive, removed: false };
|
|
323
|
+
}
|
|
324
|
+
}
|
|
325
|
+
|
|
326
|
+
process.stderr.write(
|
|
327
|
+
`${SCRIPT_NAME}: WARN tar failed (status ${res.status}): ${res.stderr || res.stdout || 'no detail'}. ` +
|
|
328
|
+
`Leaving staging directory at ${backupRoot} for manual archival.\n`
|
|
329
|
+
);
|
|
330
|
+
return { archive: null, removed: false };
|
|
331
|
+
}
|
|
@@ -0,0 +1,155 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
/**
|
|
3
|
+
* vault-migration-rules.mjs — Per-user config loader for the vault migration scripts.
|
|
4
|
+
*
|
|
5
|
+
* Reads ~/.config/session-orchestrator/vault-migration-rules.yaml and returns
|
|
6
|
+
* structured defaults for the operator-private slug / username-rewrite data
|
|
7
|
+
* that the migration scripts (migrate-vault-paths.mjs, migrate-cold-start-seed.mjs)
|
|
8
|
+
* used to carry as hardcoded literals.
|
|
9
|
+
*
|
|
10
|
+
* Out of scope for this loader: argument-parsing, YAML schema versioning beyond
|
|
11
|
+
* v1, runtime mutation. The migration scripts retain `--repos` / `--from` / `--to`
|
|
12
|
+
* CLI args as the override path; this loader only supplies sensible defaults.
|
|
13
|
+
*
|
|
14
|
+
* Schema (schema-version: 1):
|
|
15
|
+
*
|
|
16
|
+
* schema-version: 1
|
|
17
|
+
* username-rewrites:
|
|
18
|
+
* - from: '/Users/oldname/'
|
|
19
|
+
* to: '/Users/newname/'
|
|
20
|
+
* audited-repos: # default --repos for migrate-vault-paths.mjs
|
|
21
|
+
* - repo-name-or-absolute-path
|
|
22
|
+
* dormant-repos: # default --repos for migrate-cold-start-seed.mjs
|
|
23
|
+
* - '~/Projects/path/to/dormant-repo'
|
|
24
|
+
*
|
|
25
|
+
* All three sections are optional. Missing file → defaults are empty arrays;
|
|
26
|
+
* the migration scripts then fall back to their CLI-args path or print a
|
|
27
|
+
* helpful error if no override is given.
|
|
28
|
+
*
|
|
29
|
+
* Path: ~/.config/session-orchestrator/vault-migration-rules.yaml
|
|
30
|
+
* (same per-user location convention as owner.yaml — outside every repo,
|
|
31
|
+
* appended to ~/.gitignore by the migration-script bootstrap on first
|
|
32
|
+
* write).
|
|
33
|
+
*/
|
|
34
|
+
|
|
35
|
+
import { readFileSync, existsSync } from 'node:fs';
|
|
36
|
+
import { join } from 'node:path';
|
|
37
|
+
import { homedir } from 'node:os';
|
|
38
|
+
import yaml from 'js-yaml';
|
|
39
|
+
|
|
40
|
+
export const VAULT_MIGRATION_RULES_PATH = join(
|
|
41
|
+
homedir(),
|
|
42
|
+
'.config',
|
|
43
|
+
'session-orchestrator',
|
|
44
|
+
'vault-migration-rules.yaml',
|
|
45
|
+
);
|
|
46
|
+
|
|
47
|
+
/**
|
|
48
|
+
* Return the empty-defaults shape — used when the file does not exist or
|
|
49
|
+
* fails to parse. Callers decide whether to error or fall back to CLI args.
|
|
50
|
+
*
|
|
51
|
+
* @returns {{ usernameRewrites: Array<{from:string,to:string}>, auditedRepos: string[], dormantRepos: string[] }}
|
|
52
|
+
*/
|
|
53
|
+
export function getDefaults() {
|
|
54
|
+
return {
|
|
55
|
+
usernameRewrites: [],
|
|
56
|
+
auditedRepos: [],
|
|
57
|
+
dormantRepos: [],
|
|
58
|
+
};
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
/**
|
|
62
|
+
* Load and validate the vault-migration-rules config. Pure, never throws.
|
|
63
|
+
*
|
|
64
|
+
* @param {string} [filePath] - override for the default path (testing only)
|
|
65
|
+
* @returns {{ config: ReturnType<typeof getDefaults>, source: 'file'|'defaults', errors: string[] }}
|
|
66
|
+
*/
|
|
67
|
+
export function loadVaultMigrationRules(filePath = VAULT_MIGRATION_RULES_PATH) {
|
|
68
|
+
if (!existsSync(filePath)) {
|
|
69
|
+
return { config: getDefaults(), source: 'defaults', errors: [] };
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
let raw;
|
|
73
|
+
try {
|
|
74
|
+
raw = readFileSync(filePath, 'utf8');
|
|
75
|
+
} catch (err) {
|
|
76
|
+
return {
|
|
77
|
+
config: getDefaults(),
|
|
78
|
+
source: 'defaults',
|
|
79
|
+
errors: [`failed to read vault-migration-rules.yaml: ${err.message}`],
|
|
80
|
+
};
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
let parsed;
|
|
84
|
+
try {
|
|
85
|
+
parsed = yaml.load(raw);
|
|
86
|
+
} catch (err) {
|
|
87
|
+
return {
|
|
88
|
+
config: getDefaults(),
|
|
89
|
+
source: 'defaults',
|
|
90
|
+
errors: [`failed to parse vault-migration-rules.yaml: ${err.message}`],
|
|
91
|
+
};
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
const { config, errors } = normalize(parsed);
|
|
95
|
+
return { config, source: errors.length === 0 ? 'file' : 'defaults', errors };
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
/**
|
|
99
|
+
* Normalize a parsed YAML object into the canonical shape, returning errors
|
|
100
|
+
* for malformed entries. Unknown sections are ignored (forward-compatible).
|
|
101
|
+
*
|
|
102
|
+
* @param {unknown} obj
|
|
103
|
+
* @returns {{ config: ReturnType<typeof getDefaults>, errors: string[] }}
|
|
104
|
+
*/
|
|
105
|
+
function normalize(obj) {
|
|
106
|
+
const errors = [];
|
|
107
|
+
const config = getDefaults();
|
|
108
|
+
|
|
109
|
+
if (!isPlainObject(obj)) {
|
|
110
|
+
return { config, errors: ['config must be a YAML mapping'] };
|
|
111
|
+
}
|
|
112
|
+
|
|
113
|
+
// username-rewrites: [{ from: string, to: string }]
|
|
114
|
+
const rewrites = obj['username-rewrites'];
|
|
115
|
+
if (rewrites !== undefined) {
|
|
116
|
+
if (!Array.isArray(rewrites)) {
|
|
117
|
+
errors.push('username-rewrites must be an array');
|
|
118
|
+
} else {
|
|
119
|
+
for (let i = 0; i < rewrites.length; i++) {
|
|
120
|
+
const r = rewrites[i];
|
|
121
|
+
if (!isPlainObject(r) || typeof r.from !== 'string' || typeof r.to !== 'string') {
|
|
122
|
+
errors.push(`username-rewrites[${i}]: must have string 'from' and 'to' fields`);
|
|
123
|
+
continue;
|
|
124
|
+
}
|
|
125
|
+
config.usernameRewrites.push({ from: r.from, to: r.to });
|
|
126
|
+
}
|
|
127
|
+
}
|
|
128
|
+
}
|
|
129
|
+
|
|
130
|
+
// audited-repos: string[]
|
|
131
|
+
const audited = obj['audited-repos'];
|
|
132
|
+
if (audited !== undefined) {
|
|
133
|
+
if (!Array.isArray(audited) || !audited.every((s) => typeof s === 'string')) {
|
|
134
|
+
errors.push('audited-repos must be an array of strings');
|
|
135
|
+
} else {
|
|
136
|
+
config.auditedRepos = audited.slice();
|
|
137
|
+
}
|
|
138
|
+
}
|
|
139
|
+
|
|
140
|
+
// dormant-repos: string[]
|
|
141
|
+
const dormant = obj['dormant-repos'];
|
|
142
|
+
if (dormant !== undefined) {
|
|
143
|
+
if (!Array.isArray(dormant) || !dormant.every((s) => typeof s === 'string')) {
|
|
144
|
+
errors.push('dormant-repos must be an array of strings');
|
|
145
|
+
} else {
|
|
146
|
+
config.dormantRepos = dormant.slice();
|
|
147
|
+
}
|
|
148
|
+
}
|
|
149
|
+
|
|
150
|
+
return { config, errors };
|
|
151
|
+
}
|
|
152
|
+
|
|
153
|
+
function isPlainObject(v) {
|
|
154
|
+
return typeof v === 'object' && v !== null && !Array.isArray(v);
|
|
155
|
+
}
|