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,796 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
/**
|
|
3
|
+
* migrate-vault-paths.mjs — one-shot cross-repo migration for username-drift in vault/project paths.
|
|
4
|
+
*
|
|
5
|
+
* Rewrites a literal "from" username path-segment to a "to" segment across markdown
|
|
6
|
+
* files under ~/Projects/. Only the username path segment changes; the trailing
|
|
7
|
+
* path is preserved.
|
|
8
|
+
*
|
|
9
|
+
* Typical use: a host username change (`/Users/oldname/` → `/Users/newname/`) leaves
|
|
10
|
+
* hardcoded paths in CLAUDE.md / AGENTS.md / STATE.md across every checked-out repo.
|
|
11
|
+
* This script sweeps them in one pass.
|
|
12
|
+
*
|
|
13
|
+
* Usage:
|
|
14
|
+
* node scripts/migrate-vault-paths.mjs --from <old/> --to <new/> [--dry-run|--apply] [--repos <comma,list>] [--json] [--help]
|
|
15
|
+
*
|
|
16
|
+
* Rule resolution (in priority order):
|
|
17
|
+
* 1. --from / --to flags (explicit CLI override)
|
|
18
|
+
* 2. username-rewrites: [{from,to}] in
|
|
19
|
+
* ~/.config/session-orchestrator/vault-migration-rules.yaml
|
|
20
|
+
* (first entry is used; multiple rewrites in one run require multiple invocations)
|
|
21
|
+
* 3. <none> → fail with non-zero exit and a hint to the config file.
|
|
22
|
+
*
|
|
23
|
+
* Target repo resolution (in priority order):
|
|
24
|
+
* 1. --repos <comma,list> (explicit CLI override)
|
|
25
|
+
* 2. audited-repos: [...] in the same vault-migration-rules.yaml
|
|
26
|
+
* 3. <none, no --repos> → scan only the auto-discover sweep under ~/Projects/**.
|
|
27
|
+
*
|
|
28
|
+
* Flags:
|
|
29
|
+
* --from SEG Literal source segment (e.g. '/Users/oldname/'). Required if no config entry.
|
|
30
|
+
* --to SEG Literal target segment (e.g. '/Users/newname/'). Required if no config entry.
|
|
31
|
+
* --dry-run Preview changes without writing (DEFAULT)
|
|
32
|
+
* --apply Write fixes in-place (atomic: tmp + rename)
|
|
33
|
+
* --repos LIST Comma-separated repo names or absolute paths to scan
|
|
34
|
+
* --json Emit machine-readable JSONL on stdout (one record per hit)
|
|
35
|
+
* --help, -h Show this help
|
|
36
|
+
*
|
|
37
|
+
* Exit codes:
|
|
38
|
+
* 0 Success (including no-op)
|
|
39
|
+
* 1 Input/arg error
|
|
40
|
+
* 2 I/O error
|
|
41
|
+
*
|
|
42
|
+
* Idempotent: re-running --apply after a successful run is a no-op.
|
|
43
|
+
*
|
|
44
|
+
* Output (stdout — human or --json):
|
|
45
|
+
* migrate-vault-paths: <abbrev-path>:<line>: <classification> → would-fix|fixed|skipped
|
|
46
|
+
*
|
|
47
|
+
* Summary line (stderr):
|
|
48
|
+
* migrate-vault-paths: N files scanned, M lines fixed, K historical skipped [dry-run|applied]
|
|
49
|
+
*/
|
|
50
|
+
|
|
51
|
+
import { promises as fs, existsSync } from 'node:fs';
|
|
52
|
+
import { spawnSync } from 'node:child_process';
|
|
53
|
+
import { pathToFileURL } from 'node:url';
|
|
54
|
+
import path from 'node:path';
|
|
55
|
+
import os from 'node:os';
|
|
56
|
+
import {
|
|
57
|
+
loadVaultMigrationRules,
|
|
58
|
+
VAULT_MIGRATION_RULES_PATH,
|
|
59
|
+
} from './lib/vault-migration-rules.mjs';
|
|
60
|
+
import { parseColumnFlags, CliFlagError } from './lib/cli-flags.mjs';
|
|
61
|
+
|
|
62
|
+
// ---------------------------------------------------------------------------
|
|
63
|
+
// Constants
|
|
64
|
+
// ---------------------------------------------------------------------------
|
|
65
|
+
|
|
66
|
+
const HOME = os.homedir();
|
|
67
|
+
const PROJECTS_ROOT = path.join(HOME, 'Projects');
|
|
68
|
+
|
|
69
|
+
// Set at startup from CLI args or config. Helpers read these; main() resolves them.
|
|
70
|
+
let OLD_SEGMENT = null;
|
|
71
|
+
let NEW_SEGMENT = null;
|
|
72
|
+
|
|
73
|
+
/**
|
|
74
|
+
* Test-only seam (architect MED #607 D3): set the module-level OLD/NEW segments
|
|
75
|
+
* that the exported pure helpers (rewriteContent, findMissingSegmentHits,
|
|
76
|
+
* isOwnedByUsernamePath, classifyHit) read. The CLI path sets these inside
|
|
77
|
+
* main() from --from/--to or config; importing tests have no other way to drive
|
|
78
|
+
* the segment-dependent helpers. Underscore prefix = internal-but-test-visible,
|
|
79
|
+
* mirroring the _parseVaultIntegration / _parseMemoryProposals convention.
|
|
80
|
+
*
|
|
81
|
+
* Returns the prior values so a test can save/restore around an assertion.
|
|
82
|
+
*/
|
|
83
|
+
function _setSegmentsForTest(oldSeg, newSeg) {
|
|
84
|
+
const prev = { OLD_SEGMENT, NEW_SEGMENT };
|
|
85
|
+
OLD_SEGMENT = oldSeg;
|
|
86
|
+
NEW_SEGMENT = newSeg;
|
|
87
|
+
return prev;
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
// ── Missing-segment class (GitLab #600 D3) ────────────────────────────────────
|
|
91
|
+
// A second, username-independent drift class: a `vault-dir:` value that points at
|
|
92
|
+
// `~/Projects/vault` (or `/Users/<user>/Projects/vault`) — MISSING the canonical
|
|
93
|
+
// `/Bernhard/` owner segment — must become `~/Projects/Bernhard/vault`.
|
|
94
|
+
//
|
|
95
|
+
// Scoping discipline (do NOT blunt-rewrite every `~/Projects/` path):
|
|
96
|
+
// - Only lines in a `vault-dir:` context (vault-integration.vault-dir /
|
|
97
|
+
// vault-sync.vault-dir) are touched. `cache: ~/Projects/vault-backups` and
|
|
98
|
+
// other unrelated `~/Projects/...` paths are left untouched.
|
|
99
|
+
// - The match anchors the value to `~` or `/Users/<user>` immediately before
|
|
100
|
+
// `/Projects/vault`, and requires a path boundary AFTER `vault` (negative
|
|
101
|
+
// lookahead `(?![\w-])`) so `vault-backups` / `vaultfoo` never match.
|
|
102
|
+
// - Matching `Projects/vault` directly (not `Projects/Bernhard/vault`) yields
|
|
103
|
+
// idempotency for free: a canonical line has `Projects/Bernhard/vault`, so
|
|
104
|
+
// the pattern does not match it → no `.../Bernhard/Bernhard/vault`.
|
|
105
|
+
//
|
|
106
|
+
// The owner segment is the canonical `Bernhard`; the rewrite inserts it between
|
|
107
|
+
// `Projects/` and `vault`.
|
|
108
|
+
const MISSING_SEGMENT_OWNER = 'Bernhard';
|
|
109
|
+
|
|
110
|
+
// Classification label for the missing-/Bernhard/-segment drift class. Referenced
|
|
111
|
+
// in findMissingSegmentHits() (hit record) and the emit() action strings — hoisted
|
|
112
|
+
// here so the literal lives in exactly one place (architect MED #607 D3).
|
|
113
|
+
const MISSING_SEGMENT_CLASS = 'vault-dir-missing-segment';
|
|
114
|
+
|
|
115
|
+
const MISSING_SEGMENT_RE = new RegExp(
|
|
116
|
+
// (1) vault-dir: prefix (with optional surrounding whitespace)
|
|
117
|
+
// (2) value root: ~ or /Users/<user>
|
|
118
|
+
// (3) /Projects/vault, NOT followed by a word char or hyphen (path boundary)
|
|
119
|
+
String.raw`(vault-dir:\s*['"]?)(~|/Users/[^/\s'"]+)/Projects/vault(?![\w-])`,
|
|
120
|
+
'g',
|
|
121
|
+
);
|
|
122
|
+
|
|
123
|
+
const EXCLUDE_GLOBS = [
|
|
124
|
+
'/tests/',
|
|
125
|
+
'/fixtures/',
|
|
126
|
+
'/.git/',
|
|
127
|
+
'/.vault-backup-',
|
|
128
|
+
'/node_modules/',
|
|
129
|
+
// Mutation-testing & build-temp sandboxes — these are file copies, not source
|
|
130
|
+
'/.stryker-tmp/',
|
|
131
|
+
'/.next/',
|
|
132
|
+
'/dist/',
|
|
133
|
+
'/build/',
|
|
134
|
+
'/coverage/',
|
|
135
|
+
];
|
|
136
|
+
|
|
137
|
+
/**
|
|
138
|
+
* Historical heuristic: skip rewriting these hits (still report them in dry-run
|
|
139
|
+
* so reviewers see the scope). Matches:
|
|
140
|
+
* - decisions.md (session decision log)
|
|
141
|
+
* - /history/ in path (any history directory)
|
|
142
|
+
* - /archive/ or /XX-archive/ in path (vault-style archives like 90-archive/)
|
|
143
|
+
* - basename containing 'archive' (ARCHIVE-INSTRUCTIONS.md, …)
|
|
144
|
+
* - completed/snapshot session-evidence under .research/, .task-evidence/
|
|
145
|
+
* when filename includes a date stamp or "session-" prefix
|
|
146
|
+
*/
|
|
147
|
+
function isHistorical(filePath) {
|
|
148
|
+
const lower = filePath.toLowerCase();
|
|
149
|
+
const base = path.basename(lower);
|
|
150
|
+
if (base === 'decisions.md') return true;
|
|
151
|
+
if (lower.includes('/history/')) return true;
|
|
152
|
+
if (lower.includes('-history/')) return true; // e.g. pricing-history/
|
|
153
|
+
// Vault-style archive directories: /90-archive/, /archive/, /archived/
|
|
154
|
+
if (/\/(?:\d{2}-)?archived?\//.test(lower)) return true;
|
|
155
|
+
if (base.includes('archive')) return true;
|
|
156
|
+
return false;
|
|
157
|
+
}
|
|
158
|
+
|
|
159
|
+
// ---------------------------------------------------------------------------
|
|
160
|
+
// Arg parsing
|
|
161
|
+
// ---------------------------------------------------------------------------
|
|
162
|
+
|
|
163
|
+
function parseArgs(argv) {
|
|
164
|
+
// Migrated to scripts/lib/cli-flags.mjs (#510). Per-script semantics
|
|
165
|
+
// (mutex check, --repos comma-split, "next arg starts with --" guards)
|
|
166
|
+
// remain here; the helper owns argv tokenisation + unknown-flag rejection.
|
|
167
|
+
//
|
|
168
|
+
// Note: `node:util` parseArgs strict mode already rejects "--from --to"
|
|
169
|
+
// patterns by treating `--to` as the missing-value for `--from` (the prior
|
|
170
|
+
// hand-rolled check covered the same case, with a different error message).
|
|
171
|
+
let parsedFlags;
|
|
172
|
+
try {
|
|
173
|
+
parsedFlags = parseColumnFlags({
|
|
174
|
+
argv: argv.slice(2),
|
|
175
|
+
knownBool: {
|
|
176
|
+
help: { short: 'h', default: false },
|
|
177
|
+
apply: false,
|
|
178
|
+
'dry-run': false,
|
|
179
|
+
json: false,
|
|
180
|
+
},
|
|
181
|
+
knownString: {
|
|
182
|
+
repos: null, // raw comma-separated string; split below
|
|
183
|
+
from: null,
|
|
184
|
+
to: null,
|
|
185
|
+
},
|
|
186
|
+
});
|
|
187
|
+
} catch (err) {
|
|
188
|
+
if (err instanceof CliFlagError) {
|
|
189
|
+
// Preserve legacy prose so the existing test contracts at
|
|
190
|
+
// tests/scripts/migrate-vault-paths.test.mjs ("unknown argument",
|
|
191
|
+
// "--repos requires") keep passing — same exit-1 behaviour, same
|
|
192
|
+
// user-visible message shape, helper owns the actual parsing.
|
|
193
|
+
const legacy = err.message
|
|
194
|
+
.replace(/^Unknown option/, 'unknown argument')
|
|
195
|
+
.replace(/^Option '--repos <value>' argument missing$/, '--repos requires a comma-separated list')
|
|
196
|
+
.replace(/^Option '--from <value>' argument missing$/, '--from requires a literal source segment')
|
|
197
|
+
.replace(/^Option '--to <value>' argument missing$/, '--to requires a literal target segment');
|
|
198
|
+
process.stderr.write(`migrate-vault-paths: ${legacy}\n`);
|
|
199
|
+
process.exit(1);
|
|
200
|
+
}
|
|
201
|
+
throw err;
|
|
202
|
+
}
|
|
203
|
+
|
|
204
|
+
const values = parsedFlags.values;
|
|
205
|
+
|
|
206
|
+
// Mutex check: --dry-run and --apply on the same invocation is the
|
|
207
|
+
// canonical footgun (issue #509). Preserved verbatim.
|
|
208
|
+
const applyFlag = values.apply === true;
|
|
209
|
+
const dryRunFlag = values['dry-run'] === true;
|
|
210
|
+
if (applyFlag && dryRunFlag) {
|
|
211
|
+
process.stderr.write('migrate-vault-paths: --dry-run and --apply are mutually exclusive\n');
|
|
212
|
+
process.exit(1);
|
|
213
|
+
}
|
|
214
|
+
|
|
215
|
+
// Split --repos into a string[] at the use site, preserving the prior shape
|
|
216
|
+
// contract (the downstream code at main() iterates opts.repos as an array).
|
|
217
|
+
let repos = null;
|
|
218
|
+
if (values.repos !== undefined && values.repos !== null) {
|
|
219
|
+
repos = values.repos
|
|
220
|
+
.split(',')
|
|
221
|
+
.map((s) => s.trim())
|
|
222
|
+
.filter(Boolean);
|
|
223
|
+
}
|
|
224
|
+
|
|
225
|
+
return {
|
|
226
|
+
help: values.help === true,
|
|
227
|
+
apply: applyFlag, // --dry-run leaves this false (matches the prior shape)
|
|
228
|
+
json: values.json === true,
|
|
229
|
+
repos,
|
|
230
|
+
from: values.from ?? null,
|
|
231
|
+
to: values.to ?? null,
|
|
232
|
+
};
|
|
233
|
+
}
|
|
234
|
+
|
|
235
|
+
function printHelp() {
|
|
236
|
+
process.stdout.write(
|
|
237
|
+
`Usage: migrate-vault-paths.mjs --from <old/> --to <new/> [--dry-run|--apply] [--repos <comma,list>] [--json] [--help]
|
|
238
|
+
|
|
239
|
+
Rewrites a literal "from" path segment to a "to" segment across markdown files
|
|
240
|
+
under ~/Projects/, skipping tests/, fixtures/, .git/, vault-backups, and
|
|
241
|
+
historical contexts (decisions.md, history/, archive*).
|
|
242
|
+
|
|
243
|
+
Both --from and --to are required unless username-rewrites[0] is set in
|
|
244
|
+
${VAULT_MIGRATION_RULES_PATH}.
|
|
245
|
+
|
|
246
|
+
Options:
|
|
247
|
+
--from SEG Literal source segment (e.g. '/Users/oldname/')
|
|
248
|
+
--to SEG Literal target segment (e.g. '/Users/newname/')
|
|
249
|
+
--dry-run Preview changes without writing (DEFAULT)
|
|
250
|
+
--apply Write fixes in-place (atomic: tmp + rename)
|
|
251
|
+
--repos LIST Comma-separated repo names or paths to scan; falls back
|
|
252
|
+
to audited-repos: [...] in vault-migration-rules.yaml,
|
|
253
|
+
then to ~/Projects/** auto-discovery sweep
|
|
254
|
+
--json Emit JSONL records on stdout (one per hit)
|
|
255
|
+
--help, -h Show this help
|
|
256
|
+
|
|
257
|
+
Exit codes: 0 success 1 input error 2 I/O error
|
|
258
|
+
`,
|
|
259
|
+
);
|
|
260
|
+
}
|
|
261
|
+
|
|
262
|
+
// ---------------------------------------------------------------------------
|
|
263
|
+
// Repo discovery
|
|
264
|
+
// ---------------------------------------------------------------------------
|
|
265
|
+
|
|
266
|
+
/**
|
|
267
|
+
* Resolve a repo name to an absolute path by searching ~/Projects/**.
|
|
268
|
+
* If the input is already an absolute path that exists, returns it as-is.
|
|
269
|
+
* Returns null when no match is found.
|
|
270
|
+
*/
|
|
271
|
+
function resolveRepoPath(nameOrPath) {
|
|
272
|
+
if (path.isAbsolute(nameOrPath)) {
|
|
273
|
+
return existsSync(nameOrPath) ? nameOrPath : null;
|
|
274
|
+
}
|
|
275
|
+
// Search up to 4 levels deep under ~/Projects/
|
|
276
|
+
const result = spawnSync(
|
|
277
|
+
'find',
|
|
278
|
+
[PROJECTS_ROOT, '-maxdepth', '4', '-type', 'd', '-name', nameOrPath],
|
|
279
|
+
{ encoding: 'utf8' },
|
|
280
|
+
);
|
|
281
|
+
if (result.status !== 0) return null;
|
|
282
|
+
const lines = result.stdout.split('\n').map((l) => l.trim()).filter(Boolean);
|
|
283
|
+
// Prefer non-vault paths; the vault mirror under 01-projects/<name>/ is the wrong target.
|
|
284
|
+
const nonVault = lines.find((l) => !l.includes('/vault/'));
|
|
285
|
+
return nonVault ?? lines[0] ?? null;
|
|
286
|
+
}
|
|
287
|
+
|
|
288
|
+
/**
|
|
289
|
+
* Use grep -r -l -I to find every text file under any root that contains the
|
|
290
|
+
* literal old-username segment. Returns absolute file paths.
|
|
291
|
+
* The -I flag excludes binary files.
|
|
292
|
+
*/
|
|
293
|
+
function findCandidateFiles(roots) {
|
|
294
|
+
if (roots.length === 0) return [];
|
|
295
|
+
const result = spawnSync(
|
|
296
|
+
'grep',
|
|
297
|
+
[
|
|
298
|
+
'-r', // recursive
|
|
299
|
+
'-l', // list-files-with-matches
|
|
300
|
+
'-I', // skip binary
|
|
301
|
+
'--include=*.md', // markdown only (CLI runbooks live in .md; STATE.md and STATUS.md included)
|
|
302
|
+
OLD_SEGMENT,
|
|
303
|
+
...roots,
|
|
304
|
+
],
|
|
305
|
+
{ encoding: 'utf8' },
|
|
306
|
+
);
|
|
307
|
+
// ENOENT guard (GitLab #600 F2): when the `grep` binary is missing from PATH,
|
|
308
|
+
// spawnSync sets `result.error` (ENOENT) and `result.status` is null. The
|
|
309
|
+
// status check below treats null as a failure but reports an empty stderr
|
|
310
|
+
// ("grep failed: "), masking the real cause. Surface the spawn error first.
|
|
311
|
+
if (result.error) {
|
|
312
|
+
process.stderr.write(
|
|
313
|
+
`migrate-vault-paths: grep/find not found on PATH (${result.error.code ?? result.error.message})\n`,
|
|
314
|
+
);
|
|
315
|
+
process.exit(2);
|
|
316
|
+
}
|
|
317
|
+
// grep exits 1 when no matches; both 0 and 1 are valid responses
|
|
318
|
+
if (result.status !== 0 && result.status !== 1) {
|
|
319
|
+
process.stderr.write(`migrate-vault-paths: grep failed: ${result.stderr}\n`);
|
|
320
|
+
process.exit(2);
|
|
321
|
+
}
|
|
322
|
+
return result.stdout
|
|
323
|
+
.split('\n')
|
|
324
|
+
.map((l) => l.trim())
|
|
325
|
+
.filter(Boolean)
|
|
326
|
+
.filter((p) => !EXCLUDE_GLOBS.some((g) => p.includes(g)));
|
|
327
|
+
}
|
|
328
|
+
|
|
329
|
+
/**
|
|
330
|
+
* Discover files containing a missing-segment `vault-dir:` drift (GitLab #600
|
|
331
|
+
* D3) — i.e. a `vault-dir:` line pointing at `~/Projects/vault` (or
|
|
332
|
+
* `/Users/<user>/Projects/vault`) that lacks the canonical `/Bernhard/` owner
|
|
333
|
+
* segment. These files do NOT contain OLD_SEGMENT, so findCandidateFiles() (which
|
|
334
|
+
* greps OLD_SEGMENT) cannot find them.
|
|
335
|
+
*
|
|
336
|
+
* grep matches the literal `Projects/vault`; per-file classification then applies
|
|
337
|
+
* the precise MISSING_SEGMENT_RE (vault-dir context + path-boundary + not-canonical)
|
|
338
|
+
* to weed out false positives such as `cache: ~/Projects/vault-backups`. Test-dir
|
|
339
|
+
* and other EXCLUDE_GLOBS are filtered out so tests/** fixtures are never rewritten.
|
|
340
|
+
*/
|
|
341
|
+
function findMissingSegmentFiles(roots) {
|
|
342
|
+
if (roots.length === 0) return [];
|
|
343
|
+
const result = spawnSync(
|
|
344
|
+
'grep',
|
|
345
|
+
[
|
|
346
|
+
'-r',
|
|
347
|
+
'-l',
|
|
348
|
+
'-I',
|
|
349
|
+
'--include=*.md',
|
|
350
|
+
// Coarse literal pre-filter; precise vault-dir/idempotency check happens
|
|
351
|
+
// per-line in findMissingSegmentHits(). 'Projects/vault' also matches
|
|
352
|
+
// canonical 'Projects/Bernhard/vault' lines, but those are dropped later.
|
|
353
|
+
'Projects/vault',
|
|
354
|
+
...roots,
|
|
355
|
+
],
|
|
356
|
+
{ encoding: 'utf8' },
|
|
357
|
+
);
|
|
358
|
+
if (result.error) {
|
|
359
|
+
process.stderr.write(
|
|
360
|
+
`migrate-vault-paths: grep/find not found on PATH (${result.error.code ?? result.error.message})\n`,
|
|
361
|
+
);
|
|
362
|
+
process.exit(2);
|
|
363
|
+
}
|
|
364
|
+
if (result.status !== 0 && result.status !== 1) {
|
|
365
|
+
process.stderr.write(`migrate-vault-paths: grep failed: ${result.stderr}\n`);
|
|
366
|
+
process.exit(2);
|
|
367
|
+
}
|
|
368
|
+
return result.stdout
|
|
369
|
+
.split('\n')
|
|
370
|
+
.map((l) => l.trim())
|
|
371
|
+
.filter(Boolean)
|
|
372
|
+
.filter((p) => !EXCLUDE_GLOBS.some((g) => p.includes(g)));
|
|
373
|
+
}
|
|
374
|
+
|
|
375
|
+
/**
|
|
376
|
+
* Auto-discover ALL repos under ~/Projects/** that contain the OLD_SEGMENT
|
|
377
|
+
* literal OR a missing-segment `vault-dir:` drift. Used in tandem with the
|
|
378
|
+
* default-target list to find drift we don't already know about.
|
|
379
|
+
*/
|
|
380
|
+
function discoverAdditionalFiles() {
|
|
381
|
+
if (!existsSync(PROJECTS_ROOT)) return [];
|
|
382
|
+
const set = new Set(findCandidateFiles([PROJECTS_ROOT]));
|
|
383
|
+
for (const f of findMissingSegmentFiles([PROJECTS_ROOT])) set.add(f);
|
|
384
|
+
return [...set];
|
|
385
|
+
}
|
|
386
|
+
|
|
387
|
+
// ---------------------------------------------------------------------------
|
|
388
|
+
// Classification
|
|
389
|
+
// ---------------------------------------------------------------------------
|
|
390
|
+
|
|
391
|
+
/**
|
|
392
|
+
* Classify a single drift hit on a line.
|
|
393
|
+
* - vault-dir-drift: contains `vault-dir:` AND vault-path literal
|
|
394
|
+
* - path-drift: any other use of the old username
|
|
395
|
+
* - historical: file is in a decisions.md / history/ / archive context
|
|
396
|
+
*/
|
|
397
|
+
function classifyHit(filePath, line) {
|
|
398
|
+
if (isHistorical(filePath)) return 'historical';
|
|
399
|
+
const lower = line.toLowerCase();
|
|
400
|
+
if (lower.includes('vault-dir:') && line.includes(OLD_SEGMENT + 'Projects/vault')) {
|
|
401
|
+
return 'vault-dir-drift';
|
|
402
|
+
}
|
|
403
|
+
return 'path-drift';
|
|
404
|
+
}
|
|
405
|
+
|
|
406
|
+
/**
|
|
407
|
+
* Does this line carry a missing-segment `vault-dir:` drift (GitLab #600 D3)?
|
|
408
|
+
* Returns true only for a `vault-dir:` value at `~/Projects/vault` or
|
|
409
|
+
* `/Users/<user>/Projects/vault` that is NOT already canonical
|
|
410
|
+
* (`~/Projects/Bernhard/vault`) — see MISSING_SEGMENT_RE for the precise shape.
|
|
411
|
+
* `RegExp.test` advances lastIndex on a /g regex, so reset it before each probe.
|
|
412
|
+
*/
|
|
413
|
+
function lineHasMissingSegment(line) {
|
|
414
|
+
MISSING_SEGMENT_RE.lastIndex = 0;
|
|
415
|
+
return MISSING_SEGMENT_RE.test(line);
|
|
416
|
+
}
|
|
417
|
+
|
|
418
|
+
/**
|
|
419
|
+
* Is this line owned by the username-rewrite path (i.e. does it carry OLD_SEGMENT)?
|
|
420
|
+
*
|
|
421
|
+
* The missing-segment pass must never touch a line the username rewrite owns — a
|
|
422
|
+
* `vault-dir: /Users/oldname/Projects/vault` line is a username drift, not a
|
|
423
|
+
* missing-segment drift. This single predicate is the shared invariant used by
|
|
424
|
+
* BOTH findMissingSegmentHits() (skip the hit) and rewriteMissingSegment() (skip
|
|
425
|
+
* the rewrite, gated on the ORIGINAL line text). Extracted to one place so the
|
|
426
|
+
* two sites cannot drift apart (architect MED #607 D3).
|
|
427
|
+
*
|
|
428
|
+
* `line` may be undefined when an original-content array is shorter than the
|
|
429
|
+
* working-content array (chained transforms can change line count); an undefined
|
|
430
|
+
* line is, by definition, not owned by the username path → returns false.
|
|
431
|
+
*/
|
|
432
|
+
function isOwnedByUsernamePath(line) {
|
|
433
|
+
return line !== undefined && line.includes(OLD_SEGMENT);
|
|
434
|
+
}
|
|
435
|
+
|
|
436
|
+
/**
|
|
437
|
+
* Find missing-segment hits in a file's content, returning
|
|
438
|
+
* [{line, text, classification: MISSING_SEGMENT_CLASS}].
|
|
439
|
+
* Historical files (decisions.md, archive/, …) are reported but not rewritten,
|
|
440
|
+
* mirroring the OLD_SEGMENT path — classifyHit's isHistorical() guard.
|
|
441
|
+
*
|
|
442
|
+
* Lines that also contain OLD_SEGMENT are owned by the username-rewrite path and
|
|
443
|
+
* deliberately skipped here: a `vault-dir: /Users/oldname/Projects/vault` line is
|
|
444
|
+
* a username drift (→ /Users/newname/Projects/vault), NOT a missing-segment drift.
|
|
445
|
+
* Without this guard the two classes collide and the username output would gain a
|
|
446
|
+
* spurious /Bernhard/ segment.
|
|
447
|
+
*/
|
|
448
|
+
function findMissingSegmentHits(filePath, content) {
|
|
449
|
+
const historical = isHistorical(filePath);
|
|
450
|
+
const lines = content.split('\n');
|
|
451
|
+
const hits = [];
|
|
452
|
+
for (let i = 0; i < lines.length; i++) {
|
|
453
|
+
if (isOwnedByUsernamePath(lines[i])) continue; // owned by username-rewrite path
|
|
454
|
+
if (lineHasMissingSegment(lines[i])) {
|
|
455
|
+
hits.push({
|
|
456
|
+
line: i + 1, // 1-indexed
|
|
457
|
+
text: lines[i],
|
|
458
|
+
classification: historical ? 'historical' : MISSING_SEGMENT_CLASS,
|
|
459
|
+
});
|
|
460
|
+
}
|
|
461
|
+
}
|
|
462
|
+
return hits;
|
|
463
|
+
}
|
|
464
|
+
|
|
465
|
+
/**
|
|
466
|
+
* Rewrite missing-segment `vault-dir:` values in `content`: insert the canonical
|
|
467
|
+
* `/Bernhard/` owner segment between `Projects/` and `vault`. Only `vault-dir:`
|
|
468
|
+
* lines matching MISSING_SEGMENT_RE are touched — unrelated `~/Projects/...`
|
|
469
|
+
* paths (e.g. `cache: ~/Projects/vault-backups`) are preserved verbatim.
|
|
470
|
+
*
|
|
471
|
+
* `originalContent` (the file BEFORE rewriteContent ran) gates which lines are
|
|
472
|
+
* eligible: any line whose original carried OLD_SEGMENT is owned by the username
|
|
473
|
+
* path and is skipped, even though the username rewrite may have produced a
|
|
474
|
+
* `/Users/newname/Projects/vault` form that would otherwise match the regex.
|
|
475
|
+
* This keeps the two classes from colliding when both transforms chain.
|
|
476
|
+
*
|
|
477
|
+
* `originalContent` is REQUIRED (no default): the collision-gate is meaningless
|
|
478
|
+
* without the pre-rewrite text, and a `= content` default silently fails open if
|
|
479
|
+
* a future caller forgets it — passing the already-rewritten content as its own
|
|
480
|
+
* "original" would let a chained username rewrite leak a spurious /Bernhard/
|
|
481
|
+
* segment. Callers MUST pass the original content explicitly (architect MED #607 D3).
|
|
482
|
+
*/
|
|
483
|
+
function rewriteMissingSegment(content, originalContent) {
|
|
484
|
+
const origLines = originalContent.split('\n');
|
|
485
|
+
return content
|
|
486
|
+
.split('\n')
|
|
487
|
+
.map((line, i) => {
|
|
488
|
+
// Skip lines the username path owns (matched on the ORIGINAL line text).
|
|
489
|
+
if (isOwnedByUsernamePath(origLines[i])) return line;
|
|
490
|
+
MISSING_SEGMENT_RE.lastIndex = 0;
|
|
491
|
+
return line.replace(
|
|
492
|
+
MISSING_SEGMENT_RE,
|
|
493
|
+
(_m, prefix, root) => `${prefix}${root}/Projects/${MISSING_SEGMENT_OWNER}/vault`,
|
|
494
|
+
);
|
|
495
|
+
})
|
|
496
|
+
.join('\n');
|
|
497
|
+
}
|
|
498
|
+
|
|
499
|
+
// ---------------------------------------------------------------------------
|
|
500
|
+
// File processing
|
|
501
|
+
// ---------------------------------------------------------------------------
|
|
502
|
+
|
|
503
|
+
/**
|
|
504
|
+
* Replace OLD_SEGMENT with NEW_SEGMENT across the full text. Only that literal
|
|
505
|
+
* segment changes; the trailing path is preserved verbatim (e.g. '/Users/x/Foo'
|
|
506
|
+
* → '/Users/y/Foo' when the rewrite rule is '/Users/x/' → '/Users/y/').
|
|
507
|
+
*/
|
|
508
|
+
function rewriteContent(content) {
|
|
509
|
+
// Use split+join (no regex) so we never accidentally match a substring outside the literal.
|
|
510
|
+
return content.split(OLD_SEGMENT).join(NEW_SEGMENT);
|
|
511
|
+
}
|
|
512
|
+
|
|
513
|
+
/**
|
|
514
|
+
* Determine which lines in a file contain the literal, returning [{line: n, text, classification}].
|
|
515
|
+
*/
|
|
516
|
+
function findHits(filePath, content) {
|
|
517
|
+
const lines = content.split('\n');
|
|
518
|
+
const hits = [];
|
|
519
|
+
for (let i = 0; i < lines.length; i++) {
|
|
520
|
+
if (lines[i].includes(OLD_SEGMENT)) {
|
|
521
|
+
hits.push({
|
|
522
|
+
line: i + 1, // 1-indexed
|
|
523
|
+
text: lines[i],
|
|
524
|
+
classification: classifyHit(filePath, lines[i]),
|
|
525
|
+
});
|
|
526
|
+
}
|
|
527
|
+
}
|
|
528
|
+
return hits;
|
|
529
|
+
}
|
|
530
|
+
|
|
531
|
+
/**
|
|
532
|
+
* Atomic write: write to <path>.migrate-tmp, then rename.
|
|
533
|
+
*/
|
|
534
|
+
async function atomicWrite(filePath, content) {
|
|
535
|
+
const tmp = `${filePath}.migrate-tmp-${process.pid}-${Date.now()}`;
|
|
536
|
+
await fs.writeFile(tmp, content, 'utf8');
|
|
537
|
+
await fs.rename(tmp, filePath);
|
|
538
|
+
}
|
|
539
|
+
|
|
540
|
+
/**
|
|
541
|
+
* Shorten a path for display: ~/Projects/<short> from absolute.
|
|
542
|
+
*/
|
|
543
|
+
function abbrev(filePath) {
|
|
544
|
+
if (filePath.startsWith(HOME)) return '~' + filePath.slice(HOME.length);
|
|
545
|
+
return filePath;
|
|
546
|
+
}
|
|
547
|
+
|
|
548
|
+
// ---------------------------------------------------------------------------
|
|
549
|
+
// Main
|
|
550
|
+
// ---------------------------------------------------------------------------
|
|
551
|
+
|
|
552
|
+
async function main() {
|
|
553
|
+
const opts = parseArgs(process.argv);
|
|
554
|
+
|
|
555
|
+
if (opts.help) {
|
|
556
|
+
printHelp();
|
|
557
|
+
process.exit(0);
|
|
558
|
+
}
|
|
559
|
+
|
|
560
|
+
// ── Resolve --from / --to rewrite segments ────────────────────────────────
|
|
561
|
+
// Priority: CLI args > config username-rewrites[0] > fail.
|
|
562
|
+
const ruleset = loadVaultMigrationRules();
|
|
563
|
+
for (const e of ruleset.errors) {
|
|
564
|
+
process.stderr.write(`migrate-vault-paths: config: ${e}\n`);
|
|
565
|
+
}
|
|
566
|
+
if (opts.from && opts.to) {
|
|
567
|
+
OLD_SEGMENT = opts.from;
|
|
568
|
+
NEW_SEGMENT = opts.to;
|
|
569
|
+
} else if (opts.from || opts.to) {
|
|
570
|
+
process.stderr.write('migrate-vault-paths: --from and --to must be given together\n');
|
|
571
|
+
process.exit(1);
|
|
572
|
+
} else if (ruleset.config.usernameRewrites.length > 0) {
|
|
573
|
+
OLD_SEGMENT = ruleset.config.usernameRewrites[0].from;
|
|
574
|
+
NEW_SEGMENT = ruleset.config.usernameRewrites[0].to;
|
|
575
|
+
} else {
|
|
576
|
+
process.stderr.write(
|
|
577
|
+
'migrate-vault-paths: no rewrite rule found.\n' +
|
|
578
|
+
' Either pass --from <old/> --to <new/>, or set username-rewrites: in\n' +
|
|
579
|
+
` ${VAULT_MIGRATION_RULES_PATH}\n`
|
|
580
|
+
);
|
|
581
|
+
process.exit(1);
|
|
582
|
+
}
|
|
583
|
+
|
|
584
|
+
// Resolve the scan set: explicit --repos, or config audited-repos, or empty (auto-discover only)
|
|
585
|
+
const repoRoots = [];
|
|
586
|
+
const explicitRepos = opts.repos ?? (ruleset.config.auditedRepos.length > 0 ? ruleset.config.auditedRepos : null);
|
|
587
|
+
if (explicitRepos) {
|
|
588
|
+
for (const r of explicitRepos) {
|
|
589
|
+
const resolved = resolveRepoPath(r);
|
|
590
|
+
if (!resolved) {
|
|
591
|
+
process.stderr.write(`migrate-vault-paths: WARN repo not found: ${r}\n`);
|
|
592
|
+
continue;
|
|
593
|
+
}
|
|
594
|
+
repoRoots.push(resolved);
|
|
595
|
+
}
|
|
596
|
+
if (repoRoots.length === 0 && opts.repos) {
|
|
597
|
+
process.stderr.write('migrate-vault-paths: no valid repos to scan\n');
|
|
598
|
+
process.exit(1);
|
|
599
|
+
}
|
|
600
|
+
}
|
|
601
|
+
|
|
602
|
+
// Initial file discovery within named repos: OLD_SEGMENT files AND
|
|
603
|
+
// missing-segment vault-dir drift files (GitLab #600 D3 — the latter do not
|
|
604
|
+
// contain OLD_SEGMENT, so findCandidateFiles alone would miss them).
|
|
605
|
+
const candidateSet = new Set(findCandidateFiles(repoRoots));
|
|
606
|
+
for (const f of findMissingSegmentFiles(repoRoots)) candidateSet.add(f);
|
|
607
|
+
let candidateFiles = [...candidateSet];
|
|
608
|
+
|
|
609
|
+
// If NO explicit repo list was given (neither --repos nor config.audited-repos),
|
|
610
|
+
// expand scan to discover anything else under ~/Projects/**.
|
|
611
|
+
if (!explicitRepos) {
|
|
612
|
+
const additional = discoverAdditionalFiles();
|
|
613
|
+
const set = new Set(candidateFiles);
|
|
614
|
+
for (const f of additional) set.add(f);
|
|
615
|
+
candidateFiles = [...set];
|
|
616
|
+
}
|
|
617
|
+
|
|
618
|
+
candidateFiles.sort();
|
|
619
|
+
|
|
620
|
+
let totalScanned = 0;
|
|
621
|
+
let totalLinesFixed = 0;
|
|
622
|
+
let totalHistoricalSkipped = 0;
|
|
623
|
+
let totalPermissionErrors = 0;
|
|
624
|
+
|
|
625
|
+
for (const filePath of candidateFiles) {
|
|
626
|
+
// Skip if path matches an exclude glob (defense-in-depth; grep filter already applied)
|
|
627
|
+
if (EXCLUDE_GLOBS.some((g) => filePath.includes(g))) continue;
|
|
628
|
+
|
|
629
|
+
let content;
|
|
630
|
+
try {
|
|
631
|
+
// Skip symlinks (safer than dereferencing during a rewrite)
|
|
632
|
+
const lst = await fs.lstat(filePath);
|
|
633
|
+
if (lst.isSymbolicLink()) {
|
|
634
|
+
emit(opts, {
|
|
635
|
+
action: 'skipped',
|
|
636
|
+
reason: 'symlink',
|
|
637
|
+
file: filePath,
|
|
638
|
+
line: null,
|
|
639
|
+
classification: null,
|
|
640
|
+
});
|
|
641
|
+
continue;
|
|
642
|
+
}
|
|
643
|
+
content = await fs.readFile(filePath, 'utf8');
|
|
644
|
+
} catch (err) {
|
|
645
|
+
totalPermissionErrors++;
|
|
646
|
+
emit(opts, {
|
|
647
|
+
action: 'skipped',
|
|
648
|
+
reason: `read-error: ${err.message}`,
|
|
649
|
+
file: filePath,
|
|
650
|
+
line: null,
|
|
651
|
+
classification: null,
|
|
652
|
+
});
|
|
653
|
+
continue;
|
|
654
|
+
}
|
|
655
|
+
|
|
656
|
+
totalScanned++;
|
|
657
|
+
// Two independent drift classes, each discovered per-line:
|
|
658
|
+
// - OLD_SEGMENT username drift → rewriteContent (split+join)
|
|
659
|
+
// - missing /Bernhard/ segment → rewriteMissingSegment (#600 D3)
|
|
660
|
+
const hits = findHits(filePath, content);
|
|
661
|
+
const missingHits = findMissingSegmentHits(filePath, content);
|
|
662
|
+
if (hits.length === 0 && missingHits.length === 0) continue;
|
|
663
|
+
|
|
664
|
+
// Decide per-file: if every hit is historical → skip the whole file.
|
|
665
|
+
// Otherwise, rewrite the whole file (transforms are global) — historical hits
|
|
666
|
+
// in mixed files are rare; if any exist, classify each line and skip-count.
|
|
667
|
+
const fixableHits = hits.filter((h) => h.classification !== 'historical');
|
|
668
|
+
const historicalHits = hits.filter((h) => h.classification === 'historical');
|
|
669
|
+
const fixableMissing = missingHits.filter((h) => h.classification !== 'historical');
|
|
670
|
+
const historicalMissing = missingHits.filter((h) => h.classification === 'historical');
|
|
671
|
+
|
|
672
|
+
for (const h of [...historicalHits, ...historicalMissing]) {
|
|
673
|
+
totalHistoricalSkipped++;
|
|
674
|
+
emit(opts, {
|
|
675
|
+
action: 'skipped',
|
|
676
|
+
reason: 'historical',
|
|
677
|
+
file: filePath,
|
|
678
|
+
line: h.line,
|
|
679
|
+
classification: h.classification,
|
|
680
|
+
text: truncate(h.text),
|
|
681
|
+
});
|
|
682
|
+
}
|
|
683
|
+
|
|
684
|
+
if (fixableHits.length === 0 && fixableMissing.length === 0) continue;
|
|
685
|
+
|
|
686
|
+
// Defense-in-depth: if the entire file is in a historical context, skip
|
|
687
|
+
// rewriting even if any hit slipped past per-line classification.
|
|
688
|
+
if (isHistorical(filePath)) continue;
|
|
689
|
+
|
|
690
|
+
// Chain both transforms. rewriteContent (username) runs first; the
|
|
691
|
+
// missing-segment pass is then gated on the ORIGINAL content so it never
|
|
692
|
+
// touches a line the username rewrite already owns (see rewriteMissingSegment).
|
|
693
|
+
let newContent = content;
|
|
694
|
+
if (fixableHits.length > 0) newContent = rewriteContent(newContent);
|
|
695
|
+
if (fixableMissing.length > 0) newContent = rewriteMissingSegment(newContent, content);
|
|
696
|
+
|
|
697
|
+
for (const h of fixableHits) {
|
|
698
|
+
totalLinesFixed++;
|
|
699
|
+
emit(opts, {
|
|
700
|
+
action: opts.apply ? 'fixed' : 'would-fix',
|
|
701
|
+
reason: null,
|
|
702
|
+
file: filePath,
|
|
703
|
+
line: h.line,
|
|
704
|
+
classification: h.classification,
|
|
705
|
+
text: truncate(h.text),
|
|
706
|
+
});
|
|
707
|
+
}
|
|
708
|
+
for (const h of fixableMissing) {
|
|
709
|
+
totalLinesFixed++;
|
|
710
|
+
emit(opts, {
|
|
711
|
+
action: opts.apply ? `${MISSING_SEGMENT_CLASS}-fixed` : `${MISSING_SEGMENT_CLASS}-would-fix`,
|
|
712
|
+
reason: null,
|
|
713
|
+
file: filePath,
|
|
714
|
+
line: h.line,
|
|
715
|
+
classification: h.classification,
|
|
716
|
+
text: truncate(h.text),
|
|
717
|
+
});
|
|
718
|
+
}
|
|
719
|
+
|
|
720
|
+
if (opts.apply) {
|
|
721
|
+
try {
|
|
722
|
+
await atomicWrite(filePath, newContent);
|
|
723
|
+
} catch (err) {
|
|
724
|
+
totalPermissionErrors++;
|
|
725
|
+
process.stderr.write(
|
|
726
|
+
`migrate-vault-paths: ERROR failed to write ${filePath}: ${err.message}\n`,
|
|
727
|
+
);
|
|
728
|
+
// Continue with next file rather than aborting the whole run.
|
|
729
|
+
}
|
|
730
|
+
}
|
|
731
|
+
}
|
|
732
|
+
|
|
733
|
+
const modeLabel = opts.apply ? 'applied' : 'dry-run';
|
|
734
|
+
process.stderr.write(
|
|
735
|
+
`migrate-vault-paths: ${totalScanned} files scanned, ${totalLinesFixed} lines fixed, ` +
|
|
736
|
+
`${totalHistoricalSkipped} historical skipped` +
|
|
737
|
+
(totalPermissionErrors > 0 ? `, ${totalPermissionErrors} I/O errors` : '') +
|
|
738
|
+
` [${modeLabel}]\n`,
|
|
739
|
+
);
|
|
740
|
+
|
|
741
|
+
process.exit(totalPermissionErrors > 0 ? 2 : 0);
|
|
742
|
+
}
|
|
743
|
+
|
|
744
|
+
function truncate(s) {
|
|
745
|
+
if (typeof s !== 'string') return s;
|
|
746
|
+
return s.length > 200 ? s.slice(0, 197) + '...' : s;
|
|
747
|
+
}
|
|
748
|
+
|
|
749
|
+
/**
|
|
750
|
+
* Emit a single output record. JSON mode prints one JSONL object; text mode
|
|
751
|
+
* prints the canonical human line: "<abbrev>:<line>: <class> → <action>".
|
|
752
|
+
*/
|
|
753
|
+
function emit(opts, rec) {
|
|
754
|
+
if (opts.json) {
|
|
755
|
+
process.stdout.write(JSON.stringify(rec) + '\n');
|
|
756
|
+
return;
|
|
757
|
+
}
|
|
758
|
+
const where = `${abbrev(rec.file)}${rec.line ? ':' + rec.line : ''}`;
|
|
759
|
+
const cls = rec.classification ?? rec.reason ?? '?';
|
|
760
|
+
process.stdout.write(`migrate-vault-paths: ${where}: ${cls} → ${rec.action}\n`);
|
|
761
|
+
}
|
|
762
|
+
|
|
763
|
+
// ---------------------------------------------------------------------------
|
|
764
|
+
// Entry guard + exports (architect MED #607 D3)
|
|
765
|
+
//
|
|
766
|
+
// Run main() only when invoked directly as a CLI (node scripts/migrate-vault-paths.mjs).
|
|
767
|
+
// When imported by a test (or another module), the pure helpers below are
|
|
768
|
+
// available without firing the one-shot migration as a side effect.
|
|
769
|
+
//
|
|
770
|
+
// `process.argv[1]` is the invoked script path under `node <script>`, but it is
|
|
771
|
+
// `undefined` under `node -e "import(...)"` and some dynamic-import contexts —
|
|
772
|
+
// pathToFileURL(undefined) throws ERR_INVALID_ARG_TYPE. Guard the falsy case so
|
|
773
|
+
// importing the module is never a hard error regardless of how the importer runs.
|
|
774
|
+
// ---------------------------------------------------------------------------
|
|
775
|
+
|
|
776
|
+
const invokedDirectly =
|
|
777
|
+
process.argv[1] !== undefined && import.meta.url === pathToFileURL(process.argv[1]).href;
|
|
778
|
+
|
|
779
|
+
if (invokedDirectly) {
|
|
780
|
+
main().catch((err) => {
|
|
781
|
+
process.stderr.write(`migrate-vault-paths: FATAL: ${err.message}\n`);
|
|
782
|
+
process.exit(2);
|
|
783
|
+
});
|
|
784
|
+
}
|
|
785
|
+
|
|
786
|
+
export {
|
|
787
|
+
rewriteMissingSegment,
|
|
788
|
+
rewriteContent,
|
|
789
|
+
lineHasMissingSegment,
|
|
790
|
+
findMissingSegmentHits,
|
|
791
|
+
isHistorical,
|
|
792
|
+
classifyHit,
|
|
793
|
+
isOwnedByUsernamePath,
|
|
794
|
+
MISSING_SEGMENT_CLASS,
|
|
795
|
+
_setSegmentsForTest,
|
|
796
|
+
};
|