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,660 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
/**
|
|
3
|
+
* on-session-start.mjs — SessionStart hook: emit event + optional host/resource banner.
|
|
4
|
+
*
|
|
5
|
+
* Node.js port of hooks/on-session-start.sh. Part of v3.0.0 migration
|
|
6
|
+
* (Epic #124, issue #140). Extended in v3.1.0 (Epic #157):
|
|
7
|
+
* - #164: host-identity + resource-probe banner surfaced via systemMessage.
|
|
8
|
+
* - #168: multi-session registry — register this session, detect peers, sweep
|
|
9
|
+
* zombies. Peer summary is appended to the banner and surfaced in the event.
|
|
10
|
+
*
|
|
11
|
+
* Behaviour:
|
|
12
|
+
* 1. Resolves project name and current git branch.
|
|
13
|
+
* 2. When env-aware libs are available AND enable-host-banner config is true,
|
|
14
|
+
* collects host fingerprint + resource snapshot and emits a one-line
|
|
15
|
+
* systemMessage banner. Populates .orchestrator/host.json for skills.
|
|
16
|
+
* 3. Reads session_id from stdin (Claude Code hook contract). Falls back to
|
|
17
|
+
* a generated uuid-v4 and caches it at .orchestrator/current-session.json
|
|
18
|
+
* so on-stop can deregister cleanly.
|
|
19
|
+
* 4. Sweeps zombie heartbeats older than the configured threshold, registers
|
|
20
|
+
* this session, detects live peers on the same host.
|
|
21
|
+
* 5. Emits "orchestrator.session.started" event to .orchestrator/metrics/events.jsonl.
|
|
22
|
+
* 6. Optionally POSTs to Clank Event Bus if CLANK_EVENT_SECRET is set.
|
|
23
|
+
*
|
|
24
|
+
* Exit codes:
|
|
25
|
+
* 0 — always (informational, never blocking)
|
|
26
|
+
*
|
|
27
|
+
* hooks.json wiring (SessionStart, async: true, timeout: 5s) is managed separately.
|
|
28
|
+
* stdin: optional JSON payload from Claude Code containing session_id.
|
|
29
|
+
*/
|
|
30
|
+
|
|
31
|
+
import { readFile, writeFile, mkdir } from 'node:fs/promises';
|
|
32
|
+
import { execFile } from 'node:child_process';
|
|
33
|
+
import { promisify } from 'node:util';
|
|
34
|
+
import { randomUUID } from 'node:crypto';
|
|
35
|
+
import path from 'node:path';
|
|
36
|
+
|
|
37
|
+
import { shouldRunHook } from './_lib/profile-gate.mjs';
|
|
38
|
+
// #211: exit 0 immediately (silent allow) when this hook is disabled via profile/env
|
|
39
|
+
if (!shouldRunHook('on-session-start')) process.exit(0);
|
|
40
|
+
|
|
41
|
+
import { emitEvent, eventsFilePath } from '../scripts/lib/events.mjs';
|
|
42
|
+
import { maybeRotate } from '../scripts/lib/events-rotation.mjs';
|
|
43
|
+
import { readConfigFile, parseSessionConfig } from '../scripts/lib/config.mjs';
|
|
44
|
+
import { SO_PLATFORM, resolveProjectDir } from '../scripts/lib/platform.mjs';
|
|
45
|
+
import {
|
|
46
|
+
registerSelf,
|
|
47
|
+
detectPeers,
|
|
48
|
+
sweepZombies,
|
|
49
|
+
logSweepEvent,
|
|
50
|
+
} from '../scripts/lib/session-registry.mjs';
|
|
51
|
+
import { detectColdStart, consumeMarker } from '../scripts/lib/cold-start-detector.mjs';
|
|
52
|
+
|
|
53
|
+
const execFileAsync = promisify(execFile);
|
|
54
|
+
|
|
55
|
+
// ---------------------------------------------------------------------------
|
|
56
|
+
// Helpers
|
|
57
|
+
// ---------------------------------------------------------------------------
|
|
58
|
+
|
|
59
|
+
/**
|
|
60
|
+
* Run a git command in cwd; return trimmed stdout. Returns null on failure.
|
|
61
|
+
* @param {string[]} args
|
|
62
|
+
* @param {string} cwd
|
|
63
|
+
* @returns {Promise<string|null>}
|
|
64
|
+
*/
|
|
65
|
+
async function gitOutput(args, cwd) {
|
|
66
|
+
try {
|
|
67
|
+
const { stdout } = await execFileAsync('git', args, { cwd });
|
|
68
|
+
return stdout.trim();
|
|
69
|
+
} catch {
|
|
70
|
+
return null;
|
|
71
|
+
}
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
/**
|
|
75
|
+
* Read `enable-host-banner` from CLAUDE.md / AGENTS.md Session Config. Returns
|
|
76
|
+
* true as the default — missing config or parse errors fall back to enabled,
|
|
77
|
+
* matching the documented behaviour for issue #166.
|
|
78
|
+
*/
|
|
79
|
+
async function isHostBannerEnabled(projectRoot) {
|
|
80
|
+
for (const name of ['CLAUDE.md', 'AGENTS.md']) {
|
|
81
|
+
try {
|
|
82
|
+
const raw = await readFile(path.join(projectRoot, name), 'utf8');
|
|
83
|
+
const m = raw.match(/^\s*enable-host-banner:\s*(true|false)\b/im);
|
|
84
|
+
if (m) return m[1].toLowerCase() === 'true';
|
|
85
|
+
} catch { /* file missing is fine */ }
|
|
86
|
+
}
|
|
87
|
+
return true;
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
/**
|
|
91
|
+
* Read `cold-start.*` block from Session Config with PRD defaults applied.
|
|
92
|
+
*
|
|
93
|
+
* Uses `parseSessionConfig(md)['cold-start']` (wired via I6). Any parse
|
|
94
|
+
* failure returns defaults so the cold-start nudge is never blocked.
|
|
95
|
+
*
|
|
96
|
+
* All keys are optional. Returns:
|
|
97
|
+
* { enabled: boolean, 'nudge-after-hours': number, 'silence-after-sessions': number }
|
|
98
|
+
*
|
|
99
|
+
* PRD defaults (F1.3): enabled=true, nudge-after-hours=1,
|
|
100
|
+
* silence-after-sessions=1. Any parse failure → defaults.
|
|
101
|
+
*/
|
|
102
|
+
export async function readColdStartConfig(projectRoot) {
|
|
103
|
+
const defaults = {
|
|
104
|
+
enabled: true,
|
|
105
|
+
'nudge-after-hours': 1,
|
|
106
|
+
'silence-after-sessions': 1,
|
|
107
|
+
};
|
|
108
|
+
|
|
109
|
+
try {
|
|
110
|
+
const md = await readConfigFile(projectRoot);
|
|
111
|
+
try {
|
|
112
|
+
const config = parseSessionConfig(md);
|
|
113
|
+
const block = config['cold-start'];
|
|
114
|
+
if (block && typeof block === 'object') {
|
|
115
|
+
return {
|
|
116
|
+
enabled: block.enabled !== false,
|
|
117
|
+
'nudge-after-hours':
|
|
118
|
+
typeof block['nudge-after-hours'] === 'number'
|
|
119
|
+
? block['nudge-after-hours']
|
|
120
|
+
: defaults['nudge-after-hours'],
|
|
121
|
+
'silence-after-sessions':
|
|
122
|
+
typeof block['silence-after-sessions'] === 'number'
|
|
123
|
+
? block['silence-after-sessions']
|
|
124
|
+
: defaults['silence-after-sessions'],
|
|
125
|
+
};
|
|
126
|
+
}
|
|
127
|
+
} catch { return defaults; }
|
|
128
|
+
} catch {
|
|
129
|
+
return defaults;
|
|
130
|
+
}
|
|
131
|
+
return defaults;
|
|
132
|
+
}
|
|
133
|
+
|
|
134
|
+
/**
|
|
135
|
+
* Read `resource-thresholds.concurrent-sessions-warn` from Session Config.
|
|
136
|
+
* Used to decide when the peer banner should show the WARN icon. Defaults to
|
|
137
|
+
* 5 — matches the documented default in docs/session-config-reference.md.
|
|
138
|
+
*/
|
|
139
|
+
async function peerWarnThreshold(projectRoot) {
|
|
140
|
+
const DEFAULT = 5;
|
|
141
|
+
for (const name of ['CLAUDE.md', 'AGENTS.md']) {
|
|
142
|
+
try {
|
|
143
|
+
const raw = await readFile(path.join(projectRoot, name), 'utf8');
|
|
144
|
+
// Match `concurrent-sessions-warn: <int>` inside resource-thresholds or top-level.
|
|
145
|
+
const m = raw.match(/^\s*concurrent-sessions-warn:\s*(\d+)\b/im);
|
|
146
|
+
if (m) {
|
|
147
|
+
const n = parseInt(m[1], 10);
|
|
148
|
+
if (!Number.isNaN(n) && n > 0) return n;
|
|
149
|
+
}
|
|
150
|
+
} catch { /* file missing is fine */ }
|
|
151
|
+
}
|
|
152
|
+
return DEFAULT;
|
|
153
|
+
}
|
|
154
|
+
|
|
155
|
+
/**
|
|
156
|
+
* Non-blocking stdin read for Claude Code hook payload. Returns parsed JSON or
|
|
157
|
+
* null if stdin is closed, empty, unparseable, or times out.
|
|
158
|
+
*/
|
|
159
|
+
async function readStdinJson(timeoutMs = 500) {
|
|
160
|
+
return new Promise((resolve) => {
|
|
161
|
+
if (process.stdin.readableEnded || process.stdin.closed) {
|
|
162
|
+
resolve(null);
|
|
163
|
+
return;
|
|
164
|
+
}
|
|
165
|
+
const chunks = [];
|
|
166
|
+
const timer = setTimeout(() => { resolve(null); }, timeoutMs);
|
|
167
|
+
process.stdin.setEncoding('utf8');
|
|
168
|
+
process.stdin.on('data', (c) => chunks.push(c));
|
|
169
|
+
process.stdin.on('end', () => {
|
|
170
|
+
clearTimeout(timer);
|
|
171
|
+
const raw = chunks.join('').trim();
|
|
172
|
+
if (!raw) { resolve(null); return; }
|
|
173
|
+
try { resolve(JSON.parse(raw)); } catch { resolve(null); }
|
|
174
|
+
});
|
|
175
|
+
process.stdin.on('error', () => { clearTimeout(timer); resolve(null); });
|
|
176
|
+
process.stdin.resume();
|
|
177
|
+
});
|
|
178
|
+
}
|
|
179
|
+
|
|
180
|
+
/**
|
|
181
|
+
* Resolve a session_id for this run. Preference order:
|
|
182
|
+
* 1. stdin payload (`session_id` or `sessionId`)
|
|
183
|
+
* 2. generated uuid-v4 via crypto.randomUUID()
|
|
184
|
+
*
|
|
185
|
+
* In both cases, persist the resolved id to
|
|
186
|
+
* `.orchestrator/current-session.json` so on-stop can deregister even when the
|
|
187
|
+
* stop-hook stdin does not carry the same id (Claude Code guarantees this, but
|
|
188
|
+
* Codex/Cursor may not).
|
|
189
|
+
*
|
|
190
|
+
* Best-effort: any persistence failure is swallowed (hook must remain non-blocking).
|
|
191
|
+
*/
|
|
192
|
+
async function resolveSessionId(input, projectRoot) {
|
|
193
|
+
const fromStdin = (input && (input.session_id || input.sessionId)) ?? null;
|
|
194
|
+
|
|
195
|
+
// Mode normalization is shared by both branches (stdin-UUID + semantic-gen)
|
|
196
|
+
// so the semantic-id derivation works identically regardless of how
|
|
197
|
+
// sessionId was sourced. Epic #583 D4 #587: a semantic id is ALWAYS surfaced.
|
|
198
|
+
const rawMode = (input && (input.mode || input.session_type)) || 'session';
|
|
199
|
+
const normalizedMode =
|
|
200
|
+
String(rawMode).toLowerCase().replace(/[^a-z-]/g, '-').replace(/-+/g, '-').replace(/^-|-$/g, '') || 'session';
|
|
201
|
+
|
|
202
|
+
let sessionId;
|
|
203
|
+
let source;
|
|
204
|
+
// Epic #583 D4 #587: semanticSessionId is computed on BOTH branches so the
|
|
205
|
+
// session.lock + downstream consumers always see the semantic form, even
|
|
206
|
+
// when Claude Code's stdin payload provided a UUID-v4. Defaults to null
|
|
207
|
+
// and is populated below; the lock-bootstrap helper falls back to mirroring
|
|
208
|
+
// sessionId when this stays null.
|
|
209
|
+
let semanticSessionId = null;
|
|
210
|
+
|
|
211
|
+
if (typeof fromStdin === 'string' && fromStdin.length > 0) {
|
|
212
|
+
sessionId = fromStdin;
|
|
213
|
+
source = 'stdin';
|
|
214
|
+
|
|
215
|
+
// D4 #587: even on the stdin-UUID path, ALSO compute a semantic
|
|
216
|
+
// session-id so the lock and registry can surface it downstream.
|
|
217
|
+
// Best-effort: any failure leaves semanticSessionId = null which is
|
|
218
|
+
// handled gracefully by the lock-bootstrap helper.
|
|
219
|
+
try {
|
|
220
|
+
const semCandidate = await deriveSemanticCandidate({
|
|
221
|
+
projectRoot,
|
|
222
|
+
mode: normalizedMode,
|
|
223
|
+
});
|
|
224
|
+
if (semCandidate) semanticSessionId = semCandidate;
|
|
225
|
+
} catch { /* best effort — leave semanticSessionId = null */ }
|
|
226
|
+
} else {
|
|
227
|
+
// No stdin id — generate a semantic session-id via P2.2 #573 (PRD §3 P2).
|
|
228
|
+
// Falls back to randomUUID() on any failure to keep the hook non-blocking.
|
|
229
|
+
try {
|
|
230
|
+
const candidate = await deriveSemanticCandidate({
|
|
231
|
+
projectRoot,
|
|
232
|
+
mode: normalizedMode,
|
|
233
|
+
});
|
|
234
|
+
if (!candidate) throw new Error('semantic-derivation-empty');
|
|
235
|
+
|
|
236
|
+
// Race-free host-wide uniqueness: try to atomically claim the registry slot
|
|
237
|
+
// for this sessionId via O_CREAT|O_EXCL. If another process won the race
|
|
238
|
+
// (EEXIST), fall back to UUID-v4 to guarantee distinct sessionIds across
|
|
239
|
+
// parallel hook invocations. The empty file is immediately overwritten by
|
|
240
|
+
// registerSelf() with the real entry — _validEntry() in readers filters
|
|
241
|
+
// any zero-content artifact during the millisecond gap.
|
|
242
|
+
try {
|
|
243
|
+
// entryPath() is NOT exported from session-registry.mjs (it is an
|
|
244
|
+
// internal helper); replicate its body inline so the race-free slot
|
|
245
|
+
// claim actually succeeds. Without this inline build, the
|
|
246
|
+
// destructuring on `entryPath` returns undefined and calling it
|
|
247
|
+
// throws TypeError → silent UUID-fallback, defeating the semantic-id
|
|
248
|
+
// generation path entirely. Pre-existing bug pre-dating Epic #583 P3.
|
|
249
|
+
const { activeDir } = await import('../scripts/lib/session-registry.mjs');
|
|
250
|
+
const fsAsync = await import('node:fs/promises');
|
|
251
|
+
await fsAsync.mkdir(activeDir(), { recursive: true });
|
|
252
|
+
const claimPath = path.join(activeDir(), `${candidate}.json`);
|
|
253
|
+
const handle = await fsAsync.open(claimPath, 'wx'); // O_CREAT|O_EXCL — fails with EEXIST on collision
|
|
254
|
+
await handle.close();
|
|
255
|
+
sessionId = candidate;
|
|
256
|
+
semanticSessionId = candidate;
|
|
257
|
+
source = 'generated-semantic';
|
|
258
|
+
} catch (claimErr) {
|
|
259
|
+
if (claimErr.code === 'EEXIST') {
|
|
260
|
+
// Collision with another parallel hook — fall back to UUID-v4 silently.
|
|
261
|
+
// Hook is informational-only; never write to stderr per existing convention.
|
|
262
|
+
sessionId = randomUUID();
|
|
263
|
+
source = 'generated-uuid-fallback-collision';
|
|
264
|
+
// We still surface the semantic candidate as semanticSessionId so the
|
|
265
|
+
// lock reflects what this session would have been called — useful for
|
|
266
|
+
// forensics even when the UUID won the registry-slot race.
|
|
267
|
+
semanticSessionId = candidate;
|
|
268
|
+
} else {
|
|
269
|
+
throw claimErr; // surfaces to outer catch → UUID fallback
|
|
270
|
+
}
|
|
271
|
+
}
|
|
272
|
+
} catch {
|
|
273
|
+
// Semantic id generation failed for any reason — silently fall back to UUID-v4.
|
|
274
|
+
// Hook is informational-only; never write to stderr per existing convention.
|
|
275
|
+
sessionId = randomUUID();
|
|
276
|
+
source = 'generated-uuid-fallback';
|
|
277
|
+
}
|
|
278
|
+
}
|
|
279
|
+
|
|
280
|
+
try {
|
|
281
|
+
const dir = path.join(projectRoot, '.orchestrator');
|
|
282
|
+
await mkdir(dir, { recursive: true });
|
|
283
|
+
const sessionFilePath = path.join(dir, 'current-session.json');
|
|
284
|
+
|
|
285
|
+
// High-water-mark preservation (#612 root-cause fix).
|
|
286
|
+
// SessionStart fires on startup|clear|compact|resume of the SAME logical
|
|
287
|
+
// session. On clear/compact/resume the UUID `session_id` changes but the
|
|
288
|
+
// `semantic_session_id` (branch+date+mode+n) stays stable. A naive full
|
|
289
|
+
// overwrite of current-session.json drops the `last_wave` / `last_batch`
|
|
290
|
+
// markers written mid-session by post-tool-batch-wave-signal.mjs, which
|
|
291
|
+
// makes the next PostToolBatch re-read last_wave as absent→0 and re-emit a
|
|
292
|
+
// duplicate orchestrator.wave.started{N} with no intervening
|
|
293
|
+
// wave.completed. To prevent that, PRESERVE last_wave/last_batch across a
|
|
294
|
+
// SessionStart of the SAME logical session (matching semantic id), while
|
|
295
|
+
// still RESETTING them for a genuinely new session (different/absent
|
|
296
|
+
// semantic id, or an unparseable prior file). Best-effort: a read failure
|
|
297
|
+
// must never throw — we simply fall through to the reset path.
|
|
298
|
+
const preserved = {};
|
|
299
|
+
if (typeof semanticSessionId === 'string' && semanticSessionId.length > 0) {
|
|
300
|
+
try {
|
|
301
|
+
const prevRaw = await readFile(sessionFilePath, 'utf8');
|
|
302
|
+
const prev = JSON.parse(prevRaw);
|
|
303
|
+
if (prev && prev.semantic_session_id === semanticSessionId) {
|
|
304
|
+
if (Object.prototype.hasOwnProperty.call(prev, 'last_wave')) {
|
|
305
|
+
preserved.last_wave = prev.last_wave;
|
|
306
|
+
}
|
|
307
|
+
if (Object.prototype.hasOwnProperty.call(prev, 'last_batch')) {
|
|
308
|
+
preserved.last_batch = prev.last_batch;
|
|
309
|
+
}
|
|
310
|
+
}
|
|
311
|
+
} catch { /* absent / unparseable → no preservation (reset) */ }
|
|
312
|
+
}
|
|
313
|
+
|
|
314
|
+
// Epic #583 W5-F1c — surface semantic_session_id (Q5 H1 / Issue #587 completion).
|
|
315
|
+
// current-session.json now carries BOTH the UUID session_id (Claude Code default)
|
|
316
|
+
// AND the semantic_session_id derived from branch+date+mode+history.
|
|
317
|
+
const payload = {
|
|
318
|
+
session_id: sessionId,
|
|
319
|
+
semantic_session_id: semanticSessionId,
|
|
320
|
+
pid: process.pid,
|
|
321
|
+
source,
|
|
322
|
+
timestamp: new Date().toISOString(),
|
|
323
|
+
// Spread AFTER the base fields so the preserved high-water marks survive
|
|
324
|
+
// (and never clobber the identity fields above).
|
|
325
|
+
...preserved,
|
|
326
|
+
};
|
|
327
|
+
await writeFile(
|
|
328
|
+
sessionFilePath,
|
|
329
|
+
JSON.stringify(payload, null, 2) + '\n',
|
|
330
|
+
'utf8',
|
|
331
|
+
);
|
|
332
|
+
} catch { /* best effort */ }
|
|
333
|
+
|
|
334
|
+
return { sessionId, semanticSessionId, mode: normalizedMode };
|
|
335
|
+
}
|
|
336
|
+
|
|
337
|
+
/**
|
|
338
|
+
* Shared helper: derive a semantic session-id candidate for the current
|
|
339
|
+
* (branch, date, mode) tuple by consulting resolveSemanticSessionId() with
|
|
340
|
+
* a merged active-sessions view (worktree-local + host-wide registry).
|
|
341
|
+
*
|
|
342
|
+
* Returns the candidate string on success, null on any failure. Never throws.
|
|
343
|
+
*
|
|
344
|
+
* Used by both branches of resolveSessionId() so the semantic_session_id
|
|
345
|
+
* field on the session.lock is consistently populated regardless of whether
|
|
346
|
+
* the SessionStart stdin payload provided a UUID (Claude Code) or nothing
|
|
347
|
+
* (Codex / Cursor).
|
|
348
|
+
*
|
|
349
|
+
* @param {{ projectRoot: string, mode: string }} opts
|
|
350
|
+
* @returns {Promise<string|null>}
|
|
351
|
+
*/
|
|
352
|
+
async function deriveSemanticCandidate({ projectRoot, mode }) {
|
|
353
|
+
try {
|
|
354
|
+
const { resolveSemanticSessionId } = await import('../scripts/lib/session-id.mjs');
|
|
355
|
+
const { discoverActiveSessions } = await import('../scripts/lib/session-discovery.mjs');
|
|
356
|
+
const { execSync } = await import('node:child_process');
|
|
357
|
+
|
|
358
|
+
// Derive branch from git (cheap, ~5ms). Fall back to 'main' if outside a repo.
|
|
359
|
+
let branch = 'main';
|
|
360
|
+
try {
|
|
361
|
+
branch = execSync('git rev-parse --abbrev-ref HEAD', {
|
|
362
|
+
cwd: projectRoot,
|
|
363
|
+
stdio: ['ignore', 'pipe', 'ignore'],
|
|
364
|
+
encoding: 'utf8',
|
|
365
|
+
timeout: 1000,
|
|
366
|
+
}).trim();
|
|
367
|
+
if (!branch || branch === 'HEAD') branch = 'main';
|
|
368
|
+
} catch { /* keep default 'main' */ }
|
|
369
|
+
|
|
370
|
+
// Discover other active sessions for n-incrementing. We merge two sources:
|
|
371
|
+
// 1. worktree-local: discoverActiveSessions(projectRoot) reads each worktree's session.lock
|
|
372
|
+
// 2. host-wide: readRegistry() reads the per-host multi-session registry
|
|
373
|
+
// The merge is necessary because the host-wide registry is the single source
|
|
374
|
+
// of truth for cross-repo uniqueness — two unrelated repos on the same host
|
|
375
|
+
// would otherwise collide on the same default `<branch>-<date>-<mode>-1` id.
|
|
376
|
+
// Best-effort: both sources may fail; we proceed with whatever we get.
|
|
377
|
+
const { readRegistry } = await import('../scripts/lib/session-registry.mjs');
|
|
378
|
+
const [localSessions, registryEntries] = await Promise.all([
|
|
379
|
+
discoverActiveSessions(projectRoot).catch(() => []),
|
|
380
|
+
readRegistry().catch(() => []),
|
|
381
|
+
]);
|
|
382
|
+
const registrySessions = registryEntries.map((r) => ({
|
|
383
|
+
sessionId: r.session_id,
|
|
384
|
+
mode: r.mode ?? 'session',
|
|
385
|
+
}));
|
|
386
|
+
const activeSessions = [...localSessions, ...registrySessions];
|
|
387
|
+
|
|
388
|
+
return await resolveSemanticSessionId({
|
|
389
|
+
branch,
|
|
390
|
+
mode,
|
|
391
|
+
activeSessions,
|
|
392
|
+
repoRoot: projectRoot,
|
|
393
|
+
});
|
|
394
|
+
} catch {
|
|
395
|
+
return null;
|
|
396
|
+
}
|
|
397
|
+
}
|
|
398
|
+
|
|
399
|
+
/**
|
|
400
|
+
* Attempt to collect and emit the host + resource banner. Pure best-effort:
|
|
401
|
+
* any failure (missing lib, no home dir, probe throws) is swallowed so the
|
|
402
|
+
* session-start hook never blocks.
|
|
403
|
+
* @returns {Promise<{host: object, resources: object}|null>}
|
|
404
|
+
*/
|
|
405
|
+
async function emitHostBanner(projectRoot) {
|
|
406
|
+
try {
|
|
407
|
+
const [{ getHostFingerprint }, { probe }] = await Promise.all([
|
|
408
|
+
import('../scripts/lib/host-identity.mjs'),
|
|
409
|
+
import('../scripts/lib/resource-probe.mjs'),
|
|
410
|
+
]);
|
|
411
|
+
const [host, resources] = await Promise.all([
|
|
412
|
+
getHostFingerprint(projectRoot).catch(() => null),
|
|
413
|
+
probe({ skipProcessCounts: false }).catch(() => null),
|
|
414
|
+
]);
|
|
415
|
+
if (!host || !resources) return null;
|
|
416
|
+
|
|
417
|
+
const hostLine = `🖥️ Host: ${host.host_class} · ${host.ram_total_gb} GB RAM · ${host.platform ?? 'unknown'} · ${host.is_ssh ? 'ssh' : 'local'}`;
|
|
418
|
+
const procSuffix = resources.claude_processes_count === null
|
|
419
|
+
? ''
|
|
420
|
+
: ` · ${resources.claude_processes_count} Claude process${resources.claude_processes_count === 1 ? '' : 'es'} running`;
|
|
421
|
+
const resourceLine = `📊 Resources: ${resources.ram_free_gb.toFixed(1)} GB free · CPU ${resources.cpu_load_pct}%${procSuffix}`;
|
|
422
|
+
const banner = `${hostLine}\n${resourceLine}`;
|
|
423
|
+
|
|
424
|
+
// systemMessage envelope is the Claude Code hook contract; ignored by
|
|
425
|
+
// consumers that do not read stdout, harmless in all cases.
|
|
426
|
+
console.log(JSON.stringify({ systemMessage: banner }));
|
|
427
|
+
|
|
428
|
+
return { host, resources };
|
|
429
|
+
} catch {
|
|
430
|
+
return null;
|
|
431
|
+
}
|
|
432
|
+
}
|
|
433
|
+
|
|
434
|
+
// ---------------------------------------------------------------------------
|
|
435
|
+
// Main
|
|
436
|
+
// ---------------------------------------------------------------------------
|
|
437
|
+
|
|
438
|
+
async function main() {
|
|
439
|
+
const projectRoot = resolveProjectDir();
|
|
440
|
+
|
|
441
|
+
// Read optional stdin payload in parallel with git info so we don't stall
|
|
442
|
+
// the hook. Both are best-effort and the promise races against a 500 ms cap
|
|
443
|
+
// inside readStdinJson itself.
|
|
444
|
+
const stdinPromise = readStdinJson();
|
|
445
|
+
|
|
446
|
+
// Resolve project name: basename of git toplevel, falling back to cwd basename.
|
|
447
|
+
const topLevel = await gitOutput(['rev-parse', '--show-toplevel'], projectRoot);
|
|
448
|
+
const projectName = topLevel
|
|
449
|
+
? topLevel.split(/[/\\]/).filter(Boolean).pop() ?? 'unknown'
|
|
450
|
+
: projectRoot.split(/[/\\]/).filter(Boolean).pop() ?? 'unknown';
|
|
451
|
+
|
|
452
|
+
// Resolve current branch; fall back to "unknown" when detached HEAD or no git.
|
|
453
|
+
const branch = (await gitOutput(['branch', '--show-current'], projectRoot)) ?? 'unknown';
|
|
454
|
+
|
|
455
|
+
// v3.1.0 env-aware banner (opt-out via enable-host-banner: false in Session Config).
|
|
456
|
+
// The ask-via-tool nudge rides the same opt-out flag — both are coordinator
|
|
457
|
+
// reminders shown at session start; users who silence one expect silence.
|
|
458
|
+
let bannerData = null;
|
|
459
|
+
if (await isHostBannerEnabled(projectRoot)) {
|
|
460
|
+
bannerData = await emitHostBanner(projectRoot);
|
|
461
|
+
// Always-on nudge: user decisions must go through AskUserQuestion, not inline
|
|
462
|
+
// markdown lists. The coordinator chat stream is dense and prose questions
|
|
463
|
+
// are reliably missed. Full rationale + exceptions in .claude/rules/ask-via-tool.md.
|
|
464
|
+
try {
|
|
465
|
+
console.log(JSON.stringify({
|
|
466
|
+
systemMessage: '🎯 User decisions → AskUserQuestion tool. Inline choice lists = bug (.claude/rules/ask-via-tool.md).',
|
|
467
|
+
}));
|
|
468
|
+
} catch { /* best effort */ }
|
|
469
|
+
}
|
|
470
|
+
|
|
471
|
+
// F1.3 cold-start abandonment fix (PRD 2026-05-21). Emit a one-shot
|
|
472
|
+
// "first session not yet" banner when bootstrap.lock is older than
|
|
473
|
+
// cold-start.nudge-after-hours AND sessions.jsonl has fewer than
|
|
474
|
+
// cold-start.silence-after-sessions entries. Auto-silences after the
|
|
475
|
+
// first session. Master switch: cold-start.enabled (default true).
|
|
476
|
+
//
|
|
477
|
+
// Config keys read with PRD-default fallbacks — I6 wires these into
|
|
478
|
+
// parseSessionConfig(). Until then the detector uses the documented
|
|
479
|
+
// defaults so this code is safe to ship before the config schema lands.
|
|
480
|
+
try {
|
|
481
|
+
const coldStartCfg = await readColdStartConfig(projectRoot);
|
|
482
|
+
if (coldStartCfg.enabled !== false) {
|
|
483
|
+
const decision = await detectColdStart({
|
|
484
|
+
repoRoot: projectRoot,
|
|
485
|
+
nudgeAfterHours: coldStartCfg['nudge-after-hours'] ?? 1,
|
|
486
|
+
silenceAfterSessions: coldStartCfg['silence-after-sessions'] ?? 1,
|
|
487
|
+
enabled: coldStartCfg.enabled !== false,
|
|
488
|
+
});
|
|
489
|
+
if (decision.shouldEmit) {
|
|
490
|
+
try {
|
|
491
|
+
console.log(JSON.stringify({
|
|
492
|
+
systemMessage: decision.bannerLines.join('\n'),
|
|
493
|
+
}));
|
|
494
|
+
} catch { /* best effort — stdout may be closed */ }
|
|
495
|
+
if (decision.markerPath) {
|
|
496
|
+
await consumeMarker(decision.markerPath).catch(() => false);
|
|
497
|
+
}
|
|
498
|
+
}
|
|
499
|
+
}
|
|
500
|
+
} catch { /* silent — cold-start nudge must never block the hook */ }
|
|
501
|
+
|
|
502
|
+
// v3.1.0 multi-session registry (#168). All steps best-effort — failures
|
|
503
|
+
// must never break the hook, which is informational-only.
|
|
504
|
+
const input = await stdinPromise;
|
|
505
|
+
const { sessionId, semanticSessionId, mode } = await resolveSessionId(input, projectRoot);
|
|
506
|
+
const platform = process.env.SO_PLATFORM ?? SO_PLATFORM;
|
|
507
|
+
|
|
508
|
+
// Epic #583 P3 — mechanical session.lock writer (closes D1+D2+D4 gaps).
|
|
509
|
+
// Bootstrap the session-lock so discoverActiveSessions() picks us up even
|
|
510
|
+
// when the coordinator-LLM skips the prose Phase 1.2 acquire-call. The
|
|
511
|
+
// semantic-id is surfaced even when stdin provided a UUID (D4 fix #587).
|
|
512
|
+
// Wrapped in try/catch as a defence-in-depth: bootstrapLock() is already
|
|
513
|
+
// best-effort, but this outer guard ensures the hook stays non-blocking
|
|
514
|
+
// regardless of any future regression in the helper itself.
|
|
515
|
+
try {
|
|
516
|
+
const { bootstrapLock } = await import('./_lib/lock-bootstrap.mjs');
|
|
517
|
+
await bootstrapLock({
|
|
518
|
+
repoRoot: projectRoot,
|
|
519
|
+
sessionId,
|
|
520
|
+
semanticSessionId,
|
|
521
|
+
mode,
|
|
522
|
+
ttlHours: 4,
|
|
523
|
+
});
|
|
524
|
+
} catch { /* hook must remain non-blocking */ }
|
|
525
|
+
|
|
526
|
+
// Epic #724 C7 — own-repo orphaned-lock reaper (reconciliation net). If a
|
|
527
|
+
// dead-lease session.lock survived a crash-before-SessionEnd (the case that
|
|
528
|
+
// blocked this repo's start this morning until a manual force-takeover),
|
|
529
|
+
// archive-move it so it stops blocking. SINGLE-REPO only: no host-wide scan
|
|
530
|
+
// here (that stays CLI-only, scripts/lock-reaper.mjs) to keep hook latency
|
|
531
|
+
// small. Our own freshly-bootstrapped lock (written just above) is protected
|
|
532
|
+
// by the reaper's live-lock guard (fresh heartbeat) AND the currentSessionId
|
|
533
|
+
// guard, so this can never touch it. Best-effort: any failure is swallowed —
|
|
534
|
+
// the hook is informational-only and must never block.
|
|
535
|
+
try {
|
|
536
|
+
const { reapRepoLock } = await import('../scripts/lib/lock-reaper.mjs');
|
|
537
|
+
await reapRepoLock({
|
|
538
|
+
repoRoot: projectRoot,
|
|
539
|
+
currentSessionId: sessionId,
|
|
540
|
+
dryRun: false,
|
|
541
|
+
reapMode: 'auto-own-repo',
|
|
542
|
+
});
|
|
543
|
+
} catch { /* hook must remain non-blocking */ }
|
|
544
|
+
|
|
545
|
+
let peers = [];
|
|
546
|
+
try {
|
|
547
|
+
await sweepZombies().catch(() => ({ removed: [], logged: 0 }));
|
|
548
|
+
try {
|
|
549
|
+
await registerSelf({
|
|
550
|
+
sessionId,
|
|
551
|
+
semanticSessionId, // Epic #583 W5-F1c — registry entry carries semantic id (Q5 H1)
|
|
552
|
+
projectRoot,
|
|
553
|
+
branch,
|
|
554
|
+
platform,
|
|
555
|
+
mode,
|
|
556
|
+
hostClass: bannerData?.host?.host_class ?? null,
|
|
557
|
+
});
|
|
558
|
+
} catch (err) {
|
|
559
|
+
// Registration failed — emit an observability breadcrumb to sweep.log.
|
|
560
|
+
// Do NOT throw, do NOT write to stderr: the hook is informational-only.
|
|
561
|
+
logSweepEvent({ event: 'register-failed', session_id: sessionId, error: err?.message ?? String(err) });
|
|
562
|
+
}
|
|
563
|
+
peers = await detectPeers({ sessionId }).catch(() => []);
|
|
564
|
+
} catch { /* swallow — hook must remain non-blocking */ }
|
|
565
|
+
|
|
566
|
+
// Append a peer line to the host banner when a banner was already emitted
|
|
567
|
+
// and at least one peer is live on this host.
|
|
568
|
+
if (bannerData && peers.length > 0) {
|
|
569
|
+
const threshold = await peerWarnThreshold(projectRoot);
|
|
570
|
+
const icon = peers.length >= threshold ? '⚠️ ' : '';
|
|
571
|
+
const summary = peers
|
|
572
|
+
.map((p) => `${p.repo_name ?? 'unknown'}:${p.branch ?? 'unknown'}:wave-${p.current_wave ?? 0}`)
|
|
573
|
+
.slice(0, 5)
|
|
574
|
+
.join(', ');
|
|
575
|
+
const overflow = peers.length > 5 ? ` +${peers.length - 5} more` : '';
|
|
576
|
+
const peerLine = `${icon}👥 Peers: ${peers.length} active (${summary}${overflow})`;
|
|
577
|
+
try {
|
|
578
|
+
console.log(JSON.stringify({ systemMessage: peerLine }));
|
|
579
|
+
} catch { /* best effort */ }
|
|
580
|
+
}
|
|
581
|
+
|
|
582
|
+
// Epic #583 W3-P3 — Mechanical peer-detection banner (independent of the
|
|
583
|
+
// v3.1.0 registry-driven detectPeers() output). discoverActiveSessions()
|
|
584
|
+
// reads every worktree's session.lock and the host registry; this is the
|
|
585
|
+
// canonical source of truth and fires even when the v3.1.0 path returned
|
|
586
|
+
// an empty set (e.g., the peer's registry write failed but its session.lock
|
|
587
|
+
// was successfully bootstrapped by the lock-bootstrap hook). The two
|
|
588
|
+
// banners coexist: the v3.1.0 line above is per-host; this one is per-repo
|
|
589
|
+
// (mechanical, worktree-scoped) and uses the merged lock + registry source.
|
|
590
|
+
//
|
|
591
|
+
// Gated on `bannerData` (i.e., enable-host-banner: true) to respect the
|
|
592
|
+
// documented opt-out semantics — a user who silenced session-start
|
|
593
|
+
// banners has opted out of ALL session-start banners, including this one.
|
|
594
|
+
// The mechanical lock + STATE.md peer-guard remain active regardless;
|
|
595
|
+
// only the operator-visibility banner is silenced.
|
|
596
|
+
// Best-effort: any failure (import, listWorktrees timeout, etc.) is
|
|
597
|
+
// swallowed — the banner is purely informational.
|
|
598
|
+
if (bannerData) {
|
|
599
|
+
try {
|
|
600
|
+
const { discoverActiveSessions } = await import('../scripts/lib/session-discovery.mjs');
|
|
601
|
+
const allActive = await discoverActiveSessions(projectRoot);
|
|
602
|
+
const mechanicalPeers = allActive.filter((s) => s.sessionId !== sessionId);
|
|
603
|
+
if (mechanicalPeers.length > 0) {
|
|
604
|
+
const summary = mechanicalPeers
|
|
605
|
+
.slice(0, 3)
|
|
606
|
+
.map((p) => `${p.sessionId}:${p.mode ?? 'session'}`)
|
|
607
|
+
.join(', ');
|
|
608
|
+
const overflow = mechanicalPeers.length > 3 ? ` +${mechanicalPeers.length - 3} more` : '';
|
|
609
|
+
const mechanicalLine = `🔍 Mechanical peer-detection: ${mechanicalPeers.length} active in same repo (${summary}${overflow})`;
|
|
610
|
+
try {
|
|
611
|
+
console.log(JSON.stringify({ systemMessage: mechanicalLine }));
|
|
612
|
+
} catch { /* best effort */ }
|
|
613
|
+
}
|
|
614
|
+
} catch { /* best effort — banner is informational, never blocks */ }
|
|
615
|
+
}
|
|
616
|
+
|
|
617
|
+
const payload = {
|
|
618
|
+
platform,
|
|
619
|
+
project: projectName,
|
|
620
|
+
branch,
|
|
621
|
+
session_id: sessionId,
|
|
622
|
+
peer_count: peers.length,
|
|
623
|
+
};
|
|
624
|
+
if (bannerData) {
|
|
625
|
+
payload.host_class = bannerData.host.host_class;
|
|
626
|
+
payload.ram_free_gb = bannerData.resources.ram_free_gb;
|
|
627
|
+
payload.cpu_load_pct = bannerData.resources.cpu_load_pct;
|
|
628
|
+
payload.claude_processes_count = bannerData.resources.claude_processes_count;
|
|
629
|
+
}
|
|
630
|
+
await emitEvent('orchestrator.session.started', payload);
|
|
631
|
+
|
|
632
|
+
// Size-based rotation of events.jsonl (#251). Session-start is the single
|
|
633
|
+
// rotation trigger — per-append overhead is rejected design. Any failure
|
|
634
|
+
// is swallowed: rotation must NEVER block the hook.
|
|
635
|
+
try {
|
|
636
|
+
let rotCfg = { enabled: true, 'max-size-mb': 10, 'max-backups': 5 };
|
|
637
|
+
try {
|
|
638
|
+
const md = await readConfigFile(projectRoot);
|
|
639
|
+
const config = parseSessionConfig(md);
|
|
640
|
+
if (config['events-rotation']) rotCfg = config['events-rotation'];
|
|
641
|
+
} catch { /* missing config → defaults */ }
|
|
642
|
+
|
|
643
|
+
const result = maybeRotate({
|
|
644
|
+
logPath: eventsFilePath(),
|
|
645
|
+
maxSizeMb: rotCfg['max-size-mb'] ?? 10,
|
|
646
|
+
maxBackups: rotCfg['max-backups'] ?? 5,
|
|
647
|
+
enabled: rotCfg.enabled !== false,
|
|
648
|
+
});
|
|
649
|
+
if (result.rotated) {
|
|
650
|
+
console.error(`events-rotation: archived ${result.archivedAs} (${result.sizeBefore} bytes)`);
|
|
651
|
+
}
|
|
652
|
+
} catch (err) {
|
|
653
|
+
console.error(`events-rotation: skipped (${err?.message ?? err})`);
|
|
654
|
+
}
|
|
655
|
+
}
|
|
656
|
+
|
|
657
|
+
// Top-level guard — always exit 0 (non-blocking informational hook).
|
|
658
|
+
main().catch(() => {}).finally(() => {
|
|
659
|
+
process.exit(0);
|
|
660
|
+
});
|