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,371 @@
|
|
|
1
|
+
// @ts-check
|
|
2
|
+
/**
|
|
3
|
+
* coordinator-snapshot.mjs — pre-dispatch coordinator snapshot helpers for session-orchestrator.
|
|
4
|
+
*
|
|
5
|
+
* Saves git stash-format commit objects under refs/so-snapshots/<sessionId>/wave-<N>-<label>
|
|
6
|
+
* without touching the working tree, using `git update-ref` to persist them.
|
|
7
|
+
*
|
|
8
|
+
* `git stash create` alone captures only tracked modifications. It silently ignores
|
|
9
|
+
* `-u`/`--include-untracked` on git 2.53 (the flag is accepted but has no effect on
|
|
10
|
+
* `create` — only `push` and `save` honor it), so any new untracked files produced
|
|
11
|
+
* during a wave were dropped from the snapshot. Fix (#221): after building the
|
|
12
|
+
* tracked stash commit we also build an untracked-tree commit from a temp index and
|
|
13
|
+
* attach it as a third parent, matching git-stash's 3-parent format so
|
|
14
|
+
* `git stash apply` restores both tracked and untracked content on recovery.
|
|
15
|
+
*
|
|
16
|
+
* Part of v3.1.0 env-aware sessions (issue #196).
|
|
17
|
+
*/
|
|
18
|
+
|
|
19
|
+
import { promises as fs } from 'node:fs';
|
|
20
|
+
import path from 'node:path';
|
|
21
|
+
import os from 'node:os';
|
|
22
|
+
|
|
23
|
+
import { $, nothrow } from 'zx';
|
|
24
|
+
|
|
25
|
+
// Do not spam stdout/stderr with git command echoes.
|
|
26
|
+
$.verbose = false;
|
|
27
|
+
$.quiet = true;
|
|
28
|
+
|
|
29
|
+
// ---------------------------------------------------------------------------
|
|
30
|
+
// Internal helpers
|
|
31
|
+
// ---------------------------------------------------------------------------
|
|
32
|
+
|
|
33
|
+
const MODULE = 'coordinator-snapshot';
|
|
34
|
+
const REF_PREFIX = 'refs/so-snapshots/';
|
|
35
|
+
|
|
36
|
+
/**
|
|
37
|
+
* Sanitize a session ID for use in a git ref namespace.
|
|
38
|
+
* Replaces any character outside [A-Za-z0-9._-] with '-'.
|
|
39
|
+
*
|
|
40
|
+
* @param {string} sessionId
|
|
41
|
+
* @returns {string}
|
|
42
|
+
*/
|
|
43
|
+
function _sanitizeSessionId(sessionId) {
|
|
44
|
+
return sessionId.replace(/[^A-Za-z0-9._-]/g, '-');
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
/**
|
|
48
|
+
* Build the full ref name for a snapshot.
|
|
49
|
+
*
|
|
50
|
+
* @param {string} sanitizedSessionId
|
|
51
|
+
* @param {number} waveN
|
|
52
|
+
* @param {string} label
|
|
53
|
+
* @returns {string}
|
|
54
|
+
*/
|
|
55
|
+
function _refName(sanitizedSessionId, waveN, label) {
|
|
56
|
+
return `${REF_PREFIX}${sanitizedSessionId}/wave-${waveN}-${label}`;
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
/**
|
|
60
|
+
* Build a snapshot commit that captures both tracked changes and untracked files.
|
|
61
|
+
*
|
|
62
|
+
* Strategy:
|
|
63
|
+
* 1. `git stash create` produces a 2-parent commit over tracked/index changes.
|
|
64
|
+
* 2. `_buildUntrackedParent` produces an optional commit whose tree is the set
|
|
65
|
+
* of untracked, non-gitignored files (done via a temp GIT_INDEX_FILE).
|
|
66
|
+
* 3. If both are present, we splice the untracked commit in as a third parent
|
|
67
|
+
* of the stash — matching the format `git stash push -u` writes, so
|
|
68
|
+
* `git stash apply` can restore both tracked and untracked content.
|
|
69
|
+
* 4. If only one is present, return that SHA; if neither, return empty string.
|
|
70
|
+
*
|
|
71
|
+
* @param {ReturnType<typeof $>} git
|
|
72
|
+
* @param {string} cwd
|
|
73
|
+
* @param {string} stashMessage
|
|
74
|
+
* @returns {Promise<string>} snapshot commit SHA or '' if nothing to capture
|
|
75
|
+
*/
|
|
76
|
+
async function _buildSnapshotCommit(git, cwd, stashMessage) {
|
|
77
|
+
const stashResult = await git`git stash create ${stashMessage}`;
|
|
78
|
+
const trackedSha = stashResult.stdout.trim();
|
|
79
|
+
const untrackedSha = await _buildUntrackedParent(git, cwd, stashMessage);
|
|
80
|
+
|
|
81
|
+
if (!trackedSha && !untrackedSha) return '';
|
|
82
|
+
if (!untrackedSha) return trackedSha;
|
|
83
|
+
if (!trackedSha) return untrackedSha;
|
|
84
|
+
|
|
85
|
+
// Splice untracked commit as a 3rd parent of the tracked stash commit.
|
|
86
|
+
const trackedTree = (await git`git rev-parse ${trackedSha + '^{tree}'}`).stdout.trim();
|
|
87
|
+
const parentsRaw = (await git`git rev-list --parents -n 1 ${trackedSha}`).stdout.trim().split(' ');
|
|
88
|
+
const [, ...stashParents] = parentsRaw;
|
|
89
|
+
const parentArgs = [];
|
|
90
|
+
for (const p of stashParents) parentArgs.push('-p', p);
|
|
91
|
+
parentArgs.push('-p', untrackedSha);
|
|
92
|
+
const combined = await git`git commit-tree ${trackedTree} ${parentArgs} -m ${stashMessage}`;
|
|
93
|
+
return combined.stdout.trim();
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
/**
|
|
97
|
+
* Build a commit object whose tree contains only the current untracked,
|
|
98
|
+
* non-gitignored files. Returns '' when the tree has no such files.
|
|
99
|
+
*
|
|
100
|
+
* Uses a temporary GIT_INDEX_FILE so the real index and working tree are never
|
|
101
|
+
* touched. The commit has HEAD as its sole parent, matching git-stash's
|
|
102
|
+
* "untracked files on ..." conventions.
|
|
103
|
+
*
|
|
104
|
+
* @param {ReturnType<typeof $>} git
|
|
105
|
+
* @param {string} cwd
|
|
106
|
+
* @param {string} stashMessage — reused as commit subject for provenance
|
|
107
|
+
* @returns {Promise<string>} untracked-commit SHA, or '' if no untracked files
|
|
108
|
+
*/
|
|
109
|
+
async function _buildUntrackedParent(git, cwd, stashMessage) {
|
|
110
|
+
const lsRes = await git`git ls-files --others --exclude-standard -z`;
|
|
111
|
+
const paths = lsRes.stdout.split('\0').filter(Boolean);
|
|
112
|
+
if (paths.length === 0) return '';
|
|
113
|
+
|
|
114
|
+
const tempIndex = path.join(
|
|
115
|
+
os.tmpdir(),
|
|
116
|
+
`so-snap-untracked-${process.pid}-${Date.now()}`,
|
|
117
|
+
);
|
|
118
|
+
const gitIdx = $({
|
|
119
|
+
cwd,
|
|
120
|
+
env: { ...process.env, GIT_INDEX_FILE: tempIndex },
|
|
121
|
+
});
|
|
122
|
+
gitIdx.verbose = false;
|
|
123
|
+
gitIdx.quiet = true;
|
|
124
|
+
|
|
125
|
+
try {
|
|
126
|
+
// `read-tree --empty` initializes the temp index file.
|
|
127
|
+
await gitIdx`git read-tree --empty`;
|
|
128
|
+
await gitIdx`git update-index --add -- ${paths}`;
|
|
129
|
+
const treeSha = (await gitIdx`git write-tree`).stdout.trim();
|
|
130
|
+
if (!treeSha) return '';
|
|
131
|
+
const head = (await git`git rev-parse HEAD`).stdout.trim();
|
|
132
|
+
const subject = `untracked files on ${stashMessage}`;
|
|
133
|
+
const commitRes = await git`git commit-tree ${treeSha} -p ${head} -m ${subject}`;
|
|
134
|
+
return commitRes.stdout.trim();
|
|
135
|
+
} finally {
|
|
136
|
+
try {
|
|
137
|
+
await fs.unlink(tempIndex);
|
|
138
|
+
} catch {
|
|
139
|
+
// Temp index may never have been created if read-tree failed — ignore.
|
|
140
|
+
}
|
|
141
|
+
}
|
|
142
|
+
}
|
|
143
|
+
|
|
144
|
+
// ---------------------------------------------------------------------------
|
|
145
|
+
// saveSnapshot
|
|
146
|
+
// ---------------------------------------------------------------------------
|
|
147
|
+
|
|
148
|
+
/**
|
|
149
|
+
* Create a git-stash commit object and save it under refs/so-snapshots/<sessionId>/wave-<N>-<label>.
|
|
150
|
+
* No-op when working tree is clean (no uncommitted changes). Does NOT touch the working tree
|
|
151
|
+
* (uses `git stash create -u` which produces a commit object including untracked files
|
|
152
|
+
* without modifying files). The `-u` flag is load-bearing — without it, untracked files
|
|
153
|
+
* generated between waves (e.g. new test files) are silently dropped from the snapshot
|
|
154
|
+
* and cannot be recovered on crash resume (#221).
|
|
155
|
+
*
|
|
156
|
+
* @param {Object} opts
|
|
157
|
+
* @param {string} opts.sessionId — session identifier (sanitized for ref namespace)
|
|
158
|
+
* @param {number} opts.waveN — wave number, 1-indexed
|
|
159
|
+
* @param {string} [opts.label='pre'] — short suffix for the ref (e.g. 'pre-dispatch', 'post-impl')
|
|
160
|
+
* @returns {Promise<{ok: boolean, ref: string|null, sha: string|null, skipped?: true, error?: string}>}
|
|
161
|
+
*/
|
|
162
|
+
export async function saveSnapshot({ sessionId, waveN, label = 'pre' }) {
|
|
163
|
+
if (!sessionId || sessionId.trim() === '') {
|
|
164
|
+
return { ok: false, ref: null, sha: null, error: `${MODULE}: sessionId must not be empty` };
|
|
165
|
+
}
|
|
166
|
+
|
|
167
|
+
const cwd = process.cwd();
|
|
168
|
+
const git = $({ cwd });
|
|
169
|
+
|
|
170
|
+
try {
|
|
171
|
+
// Step 1: Check if working tree is clean.
|
|
172
|
+
const statusResult = await git`git status --porcelain`;
|
|
173
|
+
if (statusResult.stdout.trim() === '') {
|
|
174
|
+
return { ok: true, ref: null, sha: null, skipped: true };
|
|
175
|
+
}
|
|
176
|
+
|
|
177
|
+
// Step 2: Build the snapshot commit without modifying the working tree.
|
|
178
|
+
// Tracked changes come from `git stash create`; untracked files are attached
|
|
179
|
+
// via a third parent commit built from a temp index (see _buildUntrackedParent).
|
|
180
|
+
const sanitized = _sanitizeSessionId(sessionId);
|
|
181
|
+
const stashMessage = `so-snapshot ${sessionId} wave ${waveN} ${label}`;
|
|
182
|
+
const sha = await _buildSnapshotCommit(git, cwd, stashMessage);
|
|
183
|
+
|
|
184
|
+
if (!sha) {
|
|
185
|
+
// Race condition guard: status showed changes but git stash create produced
|
|
186
|
+
// nothing and there are no untracked files — treat as clean.
|
|
187
|
+
return { ok: true, ref: null, sha: null, skipped: true };
|
|
188
|
+
}
|
|
189
|
+
|
|
190
|
+
// Step 3: Persist the stash commit object under our custom ref namespace.
|
|
191
|
+
const ref = _refName(sanitized, waveN, label);
|
|
192
|
+
await git`git update-ref ${ref} ${sha}`;
|
|
193
|
+
|
|
194
|
+
return { ok: true, ref, sha };
|
|
195
|
+
} catch (err) {
|
|
196
|
+
const msg = err && typeof err === 'object' && 'stderr' in err
|
|
197
|
+
? /** @type {any} */ (err).stderr.trim()
|
|
198
|
+
: String(err);
|
|
199
|
+
return { ok: false, ref: null, sha: null, error: `${MODULE}: saveSnapshot failed — ${msg}` };
|
|
200
|
+
}
|
|
201
|
+
}
|
|
202
|
+
|
|
203
|
+
// ---------------------------------------------------------------------------
|
|
204
|
+
// listSnapshots
|
|
205
|
+
// ---------------------------------------------------------------------------
|
|
206
|
+
|
|
207
|
+
/**
|
|
208
|
+
* List all coordinator snapshot refs, optionally filtered by sessionId.
|
|
209
|
+
*
|
|
210
|
+
* @param {Object} [opts]
|
|
211
|
+
* @param {string} [opts.sessionId] — if set, only return this session's snapshots
|
|
212
|
+
* @returns {Promise<Array<{ref: string, sha: string, sessionId: string, waveN: number, label: string, createdAt: string}>>}
|
|
213
|
+
* Sorted by waveN DESC (most recent wave first).
|
|
214
|
+
*/
|
|
215
|
+
export async function listSnapshots({ sessionId } = {}) {
|
|
216
|
+
const cwd = process.cwd();
|
|
217
|
+
const git = $({ cwd });
|
|
218
|
+
|
|
219
|
+
let output;
|
|
220
|
+
try {
|
|
221
|
+
// zx interpolates each template-literal word as a separate argv entry, so the
|
|
222
|
+
// format string must be a single interpolated value to stay as one --format arg.
|
|
223
|
+
const fmt = '%(refname) %(objectname) %(committerdate:iso8601)';
|
|
224
|
+
const result = await nothrow(
|
|
225
|
+
git`git for-each-ref --format=${fmt} ${REF_PREFIX}`
|
|
226
|
+
);
|
|
227
|
+
output = result.stdout;
|
|
228
|
+
} catch {
|
|
229
|
+
return [];
|
|
230
|
+
}
|
|
231
|
+
|
|
232
|
+
/** @type {Array<{ref: string, sha: string, sessionId: string, waveN: number, label: string, createdAt: string}>} */
|
|
233
|
+
const snapshots = [];
|
|
234
|
+
|
|
235
|
+
for (const rawLine of output.split('\n')) {
|
|
236
|
+
const line = rawLine.trim();
|
|
237
|
+
if (!line) continue;
|
|
238
|
+
|
|
239
|
+
// Format: "<refname> <sha> <iso8601-date> <iso8601-time> <iso8601-offset>"
|
|
240
|
+
// The date portion from --format=%(committerdate:iso8601) looks like:
|
|
241
|
+
// "refs/so-snapshots/sess/wave-1-pre abc123 2026-04-21 10:00:00 +0200"
|
|
242
|
+
// We split on the first two spaces to get refname and sha, then take the rest as date.
|
|
243
|
+
const spaceIdx1 = line.indexOf(' ');
|
|
244
|
+
if (spaceIdx1 === -1) continue;
|
|
245
|
+
const spaceIdx2 = line.indexOf(' ', spaceIdx1 + 1);
|
|
246
|
+
if (spaceIdx2 === -1) continue;
|
|
247
|
+
|
|
248
|
+
const ref = line.slice(0, spaceIdx1);
|
|
249
|
+
const sha = line.slice(spaceIdx1 + 1, spaceIdx2);
|
|
250
|
+
const dateStr = line.slice(spaceIdx2 + 1).trim();
|
|
251
|
+
|
|
252
|
+
// Parse ref: strip prefix, then split on '/' to get sessionId and wave part.
|
|
253
|
+
if (!ref.startsWith(REF_PREFIX)) continue;
|
|
254
|
+
const remainder = ref.slice(REF_PREFIX.length);
|
|
255
|
+
// remainder is "<sessionId>/wave-<N>-<label>"
|
|
256
|
+
const slashIdx = remainder.indexOf('/');
|
|
257
|
+
if (slashIdx === -1) {
|
|
258
|
+
console.error(`${MODULE}: listSnapshots — unexpected ref format (no slash after sessionId): ${ref}`);
|
|
259
|
+
continue;
|
|
260
|
+
}
|
|
261
|
+
|
|
262
|
+
const refSessionId = remainder.slice(0, slashIdx);
|
|
263
|
+
const wavePart = remainder.slice(slashIdx + 1);
|
|
264
|
+
|
|
265
|
+
// wavePart must match wave-<int>-<label>
|
|
266
|
+
const waveMatch = wavePart.match(/^wave-(\d+)-(.+)$/);
|
|
267
|
+
if (!waveMatch) {
|
|
268
|
+
console.error(`${MODULE}: listSnapshots — skipping ref with unexpected wave format: ${ref}`);
|
|
269
|
+
continue;
|
|
270
|
+
}
|
|
271
|
+
|
|
272
|
+
const waveN = parseInt(waveMatch[1], 10);
|
|
273
|
+
const label = waveMatch[2];
|
|
274
|
+
|
|
275
|
+
// Convert "2026-04-21 10:00:00 +0200" to ISO 8601 with T separator.
|
|
276
|
+
const createdAt = dateStr.replace(' ', 'T').replace(' ', '');
|
|
277
|
+
|
|
278
|
+
snapshots.push({ ref, sha, sessionId: refSessionId, waveN, label, createdAt });
|
|
279
|
+
}
|
|
280
|
+
|
|
281
|
+
snapshots.sort((a, b) => b.waveN - a.waveN);
|
|
282
|
+
|
|
283
|
+
if (sessionId) {
|
|
284
|
+
const sanitized = _sanitizeSessionId(sessionId);
|
|
285
|
+
return snapshots.filter((s) => s.sessionId === sanitized);
|
|
286
|
+
}
|
|
287
|
+
|
|
288
|
+
return snapshots;
|
|
289
|
+
}
|
|
290
|
+
|
|
291
|
+
// ---------------------------------------------------------------------------
|
|
292
|
+
// deleteSnapshot
|
|
293
|
+
// ---------------------------------------------------------------------------
|
|
294
|
+
|
|
295
|
+
/**
|
|
296
|
+
* Delete a single snapshot ref. Idempotent (returns ok:true if ref already gone).
|
|
297
|
+
*
|
|
298
|
+
* @param {Object} opts
|
|
299
|
+
* @param {string} opts.refName — full ref name, e.g. "refs/so-snapshots/sess-123/wave-2-pre"
|
|
300
|
+
* @returns {Promise<{ok: boolean, error?: string}>}
|
|
301
|
+
*/
|
|
302
|
+
export async function deleteSnapshot({ refName }) {
|
|
303
|
+
const cwd = process.cwd();
|
|
304
|
+
const git = $({ cwd });
|
|
305
|
+
|
|
306
|
+
try {
|
|
307
|
+
// git update-ref -d exits 0 whether or not the ref existed.
|
|
308
|
+
await git`git update-ref -d ${refName}`;
|
|
309
|
+
return { ok: true };
|
|
310
|
+
} catch (err) {
|
|
311
|
+
const msg = err && typeof err === 'object' && 'stderr' in err
|
|
312
|
+
? /** @type {any} */ (err).stderr.trim()
|
|
313
|
+
: String(err);
|
|
314
|
+
return { ok: false, error: `${MODULE}: deleteSnapshot failed for '${refName}' — ${msg}` };
|
|
315
|
+
}
|
|
316
|
+
}
|
|
317
|
+
|
|
318
|
+
// ---------------------------------------------------------------------------
|
|
319
|
+
// gcSnapshots
|
|
320
|
+
// ---------------------------------------------------------------------------
|
|
321
|
+
|
|
322
|
+
/**
|
|
323
|
+
* Delete all snapshots whose commit committer-date is older than olderThanDays.
|
|
324
|
+
* Non-throwing; errors per-ref are logged to stderr but don't abort.
|
|
325
|
+
*
|
|
326
|
+
* @param {Object} [opts]
|
|
327
|
+
* @param {number} [opts.olderThanDays=14]
|
|
328
|
+
* @returns {Promise<{ok: boolean, deletedCount: number, scanned: number, error?: string}>}
|
|
329
|
+
*/
|
|
330
|
+
export async function gcSnapshots({ olderThanDays = 14 } = {}) {
|
|
331
|
+
let snapshots;
|
|
332
|
+
try {
|
|
333
|
+
snapshots = await listSnapshots();
|
|
334
|
+
} catch (err) {
|
|
335
|
+
const msg = err instanceof Error ? err.message : String(err);
|
|
336
|
+
return { ok: false, deletedCount: 0, scanned: 0, error: `${MODULE}: gcSnapshots — listSnapshots failed: ${msg}` };
|
|
337
|
+
}
|
|
338
|
+
|
|
339
|
+
const scanned = snapshots.length;
|
|
340
|
+
let deletedCount = 0;
|
|
341
|
+
const now = Date.now();
|
|
342
|
+
const msPerDay = 24 * 60 * 60 * 1000;
|
|
343
|
+
|
|
344
|
+
for (const snapshot of snapshots) {
|
|
345
|
+
let ageMs;
|
|
346
|
+
try {
|
|
347
|
+
ageMs = now - new Date(snapshot.createdAt).getTime();
|
|
348
|
+
} catch {
|
|
349
|
+
// Unparseable date — skip this snapshot.
|
|
350
|
+
console.error(`${MODULE}: gcSnapshots — could not parse createdAt '${snapshot.createdAt}' for ref ${snapshot.ref}, skipping`);
|
|
351
|
+
continue;
|
|
352
|
+
}
|
|
353
|
+
|
|
354
|
+
if (isNaN(ageMs)) {
|
|
355
|
+
console.error(`${MODULE}: gcSnapshots — invalid date for ref ${snapshot.ref}, skipping`);
|
|
356
|
+
continue;
|
|
357
|
+
}
|
|
358
|
+
|
|
359
|
+
const ageDays = ageMs / msPerDay;
|
|
360
|
+
if (ageDays > olderThanDays) {
|
|
361
|
+
const result = await deleteSnapshot({ refName: snapshot.ref });
|
|
362
|
+
if (result.ok) {
|
|
363
|
+
deletedCount++;
|
|
364
|
+
} else {
|
|
365
|
+
console.error(`${MODULE}: gcSnapshots — failed to delete ${snapshot.ref}: ${result.error}`);
|
|
366
|
+
}
|
|
367
|
+
}
|
|
368
|
+
}
|
|
369
|
+
|
|
370
|
+
return { ok: true, deletedCount, scanned };
|
|
371
|
+
}
|
|
@@ -0,0 +1,91 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* crypto-digest-utils.mjs — Shared SHA-256 digest helpers.
|
|
3
|
+
*
|
|
4
|
+
* Replaces inline `createHash('sha256')...` patterns in 6 modules.
|
|
5
|
+
* Pure functions, no side effects, no async.
|
|
6
|
+
*
|
|
7
|
+
* Exports:
|
|
8
|
+
* digestSha256Short(input, options?) → string (truncated hex, default 8 chars)
|
|
9
|
+
* digestSha256(input, options?) → string (full hex digest)
|
|
10
|
+
* digestSha256WithSalt(value, options) → string (salt + \x00 + value pattern)
|
|
11
|
+
* digestMultiBufferSha256(buffers, options?) → string (sequential multi-buffer update)
|
|
12
|
+
*/
|
|
13
|
+
|
|
14
|
+
import { createHash } from 'node:crypto';
|
|
15
|
+
|
|
16
|
+
/**
|
|
17
|
+
* Compute a SHA-256 digest of `input` and return a truncated hex string.
|
|
18
|
+
* The most common pattern: an 8-character hex prefix suitable as a stable
|
|
19
|
+
* short identifier (e.g. schema hash, task dedup key).
|
|
20
|
+
*
|
|
21
|
+
* @param {string|Buffer} input - Value to hash; coerced to string when not a Buffer.
|
|
22
|
+
* @param {{ length?: number, encoding?: string }} [options]
|
|
23
|
+
* - `length` Number of leading characters to return (default 8).
|
|
24
|
+
* - `encoding` Digest encoding passed to `hash.digest()` (default 'hex').
|
|
25
|
+
* @returns {string} Truncated digest string.
|
|
26
|
+
*/
|
|
27
|
+
export function digestSha256Short(input, options = {}) {
|
|
28
|
+
const length = options.length ?? 8;
|
|
29
|
+
const encoding = options.encoding ?? 'hex';
|
|
30
|
+
return createHash('sha256').update(String(input ?? '')).digest(encoding).slice(0, length);
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
/**
|
|
34
|
+
* Compute the full SHA-256 digest of `input` without truncation.
|
|
35
|
+
*
|
|
36
|
+
* @param {string|Buffer} input - Value to hash; coerced to string when not a Buffer.
|
|
37
|
+
* @param {{ encoding?: string }} [options]
|
|
38
|
+
* - `encoding` Digest encoding passed to `hash.digest()` (default 'hex').
|
|
39
|
+
* @returns {string} Full digest string.
|
|
40
|
+
*/
|
|
41
|
+
export function digestSha256(input, options = {}) {
|
|
42
|
+
const encoding = options.encoding ?? 'hex';
|
|
43
|
+
return createHash('sha256').update(String(input ?? '')).digest(encoding);
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
/**
|
|
47
|
+
* Compute a SHA-256 digest using the `salt + \x00 + value` pattern.
|
|
48
|
+
* Used for hostname hashing where a per-installation salt prevents
|
|
49
|
+
* cross-installation correlation without knowing the salt.
|
|
50
|
+
*
|
|
51
|
+
* @param {string} value - The value to hash (e.g. hostname).
|
|
52
|
+
* @param {{ salt: string, encoding?: string }} options
|
|
53
|
+
* - `salt` Required. The salt string prepended before a NUL separator.
|
|
54
|
+
* - `encoding` Digest encoding passed to `hash.digest()` (default 'hex').
|
|
55
|
+
* @returns {string} Full digest string.
|
|
56
|
+
* @throws {TypeError} When `options.salt` is not a string.
|
|
57
|
+
*/
|
|
58
|
+
export function digestSha256WithSalt(value, options) {
|
|
59
|
+
if (!options || typeof options.salt !== 'string') {
|
|
60
|
+
throw new TypeError('digestSha256WithSalt requires options.salt (string)');
|
|
61
|
+
}
|
|
62
|
+
const encoding = options.encoding ?? 'hex';
|
|
63
|
+
return createHash('sha256')
|
|
64
|
+
.update(options.salt)
|
|
65
|
+
.update('\x00')
|
|
66
|
+
.update(String(value ?? ''))
|
|
67
|
+
.digest(encoding);
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
/**
|
|
71
|
+
* Compute a SHA-256 digest by sequentially updating over an array of
|
|
72
|
+
* buffers or strings. Preserves the exact update order of the original
|
|
73
|
+
* multi-step `hash.update(a).update(b)...` patterns.
|
|
74
|
+
*
|
|
75
|
+
* @param {Array<string|Buffer>} buffers - Ordered list of values to feed into the hash.
|
|
76
|
+
* @param {{ encoding?: string }} [options]
|
|
77
|
+
* - `encoding` Digest encoding passed to `hash.digest()` (default 'hex').
|
|
78
|
+
* @returns {string} Full digest string.
|
|
79
|
+
* @throws {TypeError} When `buffers` is not an array.
|
|
80
|
+
*/
|
|
81
|
+
export function digestMultiBufferSha256(buffers, options = {}) {
|
|
82
|
+
if (!Array.isArray(buffers)) {
|
|
83
|
+
throw new TypeError('digestMultiBufferSha256 requires buffers (array)');
|
|
84
|
+
}
|
|
85
|
+
const encoding = options.encoding ?? 'hex';
|
|
86
|
+
const h = createHash('sha256');
|
|
87
|
+
for (const b of buffers) {
|
|
88
|
+
h.update(b);
|
|
89
|
+
}
|
|
90
|
+
return h.digest(encoding);
|
|
91
|
+
}
|
|
@@ -0,0 +1,127 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* discovery-helpers.mjs — Shared utilities for /discovery and /test --since filtering.
|
|
3
|
+
*
|
|
4
|
+
* Pure ESM, Node stdlib only. No shell injection: uses execFile throughout.
|
|
5
|
+
*
|
|
6
|
+
* Exports:
|
|
7
|
+
* changedFilesSince(ref) → Promise<string[]>
|
|
8
|
+
*
|
|
9
|
+
* Part of the Clawpatch Borrow Cluster (issue #420).
|
|
10
|
+
*/
|
|
11
|
+
|
|
12
|
+
import { execFile } from 'node:child_process';
|
|
13
|
+
import { promisify } from 'node:util';
|
|
14
|
+
|
|
15
|
+
const execFileAsync = promisify(execFile);
|
|
16
|
+
|
|
17
|
+
// ---------------------------------------------------------------------------
|
|
18
|
+
// Input validation
|
|
19
|
+
// ---------------------------------------------------------------------------
|
|
20
|
+
|
|
21
|
+
/**
|
|
22
|
+
* Characters that should never appear in a git ref passed to execFile args.
|
|
23
|
+
*
|
|
24
|
+
* NOTE — layered defence: `execFile` is the primary injection guard (it passes
|
|
25
|
+
* args directly to the OS without invoking a shell, so none of these characters
|
|
26
|
+
* can trigger shell interpretation). This regex is a secondary sanity-check
|
|
27
|
+
* that catches clearly-malformed refs early, before the process is spawned, and
|
|
28
|
+
* provides a human-readable error message. Do NOT rely on this regex as the
|
|
29
|
+
* sole injection barrier.
|
|
30
|
+
*
|
|
31
|
+
* Includes `\n` and `\r` to reject pathological env-injected refs that contain
|
|
32
|
+
* embedded newlines (can occur in certain CI variable-expansion scenarios).
|
|
33
|
+
*
|
|
34
|
+
* @type {RegExp}
|
|
35
|
+
*/
|
|
36
|
+
const UNSAFE_REF_CHARS = /[;|&`$<>\\\n\r]/;
|
|
37
|
+
|
|
38
|
+
/**
|
|
39
|
+
* Validate that `ref` does not contain shell-injection-relevant characters.
|
|
40
|
+
* Throws a TypeError if the ref is invalid.
|
|
41
|
+
*
|
|
42
|
+
* @param {string} ref
|
|
43
|
+
*/
|
|
44
|
+
function validateRef(ref) {
|
|
45
|
+
if (UNSAFE_REF_CHARS.test(ref)) {
|
|
46
|
+
throw new TypeError(
|
|
47
|
+
`Invalid ref '${ref}': contains shell-unsafe characters (;|&\`$<>\\, newlines). ` +
|
|
48
|
+
`Use a plain git ref like HEAD~3, a commit hash, or a branch name.`,
|
|
49
|
+
);
|
|
50
|
+
}
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
// ---------------------------------------------------------------------------
|
|
54
|
+
// Public API
|
|
55
|
+
// ---------------------------------------------------------------------------
|
|
56
|
+
|
|
57
|
+
/**
|
|
58
|
+
* Return the list of files changed between `ref` and HEAD.
|
|
59
|
+
*
|
|
60
|
+
* Resolution:
|
|
61
|
+
* 1. Validates the ref with git rev-parse (confirms it is resolvable).
|
|
62
|
+
* 2. Runs git diff --name-only <ref>..HEAD.
|
|
63
|
+
* 3. Returns sorted, de-duplicated relative paths (no leading ./ or /).
|
|
64
|
+
*
|
|
65
|
+
* Empty result (no files changed) → returns [] — the CALLER decides whether
|
|
66
|
+
* to skip or proceed with a full scan.
|
|
67
|
+
*
|
|
68
|
+
* @param {string} ref - Git ref (commit hash, branch name, tag, HEAD~N, …).
|
|
69
|
+
* @returns {Promise<string[]>} Sorted array of relative file paths.
|
|
70
|
+
* @throws {Error} If the ref cannot be resolved or git is unavailable.
|
|
71
|
+
*/
|
|
72
|
+
export async function changedFilesSince(ref) {
|
|
73
|
+
if (!ref || typeof ref !== 'string' || !ref.trim()) {
|
|
74
|
+
throw new TypeError(`changedFilesSince: ref must be a non-empty string, got: ${JSON.stringify(ref)}`);
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
const trimmedRef = ref.trim();
|
|
78
|
+
validateRef(trimmedRef);
|
|
79
|
+
|
|
80
|
+
// Step 1: verify the ref is resolvable.
|
|
81
|
+
try {
|
|
82
|
+
await execFileAsync('git', ['rev-parse', '--verify', trimmedRef], {
|
|
83
|
+
encoding: 'utf8',
|
|
84
|
+
// No shell — args are passed directly to git.
|
|
85
|
+
});
|
|
86
|
+
} catch (err) {
|
|
87
|
+
const detail = err.stderr ? err.stderr.trim() : String(err);
|
|
88
|
+
throw new Error(
|
|
89
|
+
`Cannot resolve ref '${trimmedRef}'. Common causes: shallow clone ` +
|
|
90
|
+
`(run 'git fetch --unshallow'), local-only refs that have not been ` +
|
|
91
|
+
`fetched, or a typo. Use HEAD~N or 'git log --oneline' to list ` +
|
|
92
|
+
`available refs. Original error: ${detail}`,
|
|
93
|
+
{ cause: err },
|
|
94
|
+
);
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
// Step 2: compute changed files.
|
|
98
|
+
let rawOutput;
|
|
99
|
+
try {
|
|
100
|
+
const result = await execFileAsync('git', ['diff', '--name-only', `${trimmedRef}..HEAD`], {
|
|
101
|
+
encoding: 'utf8',
|
|
102
|
+
});
|
|
103
|
+
rawOutput = result.stdout ?? result;
|
|
104
|
+
} catch (err) {
|
|
105
|
+
// A non-zero exit from git diff --name-only is unusual (the ref already
|
|
106
|
+
// validated above) but surface as a clear error.
|
|
107
|
+
const detail = err.stderr ? err.stderr.trim() : String(err);
|
|
108
|
+
throw new Error(
|
|
109
|
+
`git diff --name-only ${trimmedRef}..HEAD failed: ${detail}`,
|
|
110
|
+
{ cause: err },
|
|
111
|
+
);
|
|
112
|
+
}
|
|
113
|
+
|
|
114
|
+
// Step 3: normalise paths.
|
|
115
|
+
const paths = String(rawOutput)
|
|
116
|
+
.split('\n')
|
|
117
|
+
.map((p) => p.trim())
|
|
118
|
+
// Remove empty lines (trailing newline, etc.).
|
|
119
|
+
.filter(Boolean)
|
|
120
|
+
// Strip leading ./ if somehow present.
|
|
121
|
+
.map((p) => (p.startsWith('./') ? p.slice(2) : p))
|
|
122
|
+
// Strip leading / if somehow present (safety net).
|
|
123
|
+
.map((p) => (p.startsWith('/') ? p.slice(1) : p));
|
|
124
|
+
|
|
125
|
+
// Deduplicate + sort alphabetically.
|
|
126
|
+
return [...new Set(paths)].sort();
|
|
127
|
+
}
|