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,328 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
/**
|
|
3
|
+
* post-tool-batch-wave-signal.mjs — PostToolBatch hook.
|
|
4
|
+
*
|
|
5
|
+
* Hook event: PostToolBatch (issue #342).
|
|
6
|
+
* Fires after a batch of tool invocations completes within a single wave
|
|
7
|
+
* turn. Writes a deterministic `last_batch` signal into
|
|
8
|
+
* `.orchestrator/current-session.json` so skills and the coordinator can
|
|
9
|
+
* observe batch-resolution boundaries without parsing the full event log.
|
|
10
|
+
*
|
|
11
|
+
* Also refreshes the session-lock heartbeat (Epic #583, W3-P3 wiring of
|
|
12
|
+
* W2-I3 OQ2). The PostToolBatch event fires on a much more frequent cadence
|
|
13
|
+
* than session-start / inter-wave / session-end, which keeps the heartbeat
|
|
14
|
+
* within the TTL window even during long-running waves. The call is
|
|
15
|
+
* try/catch-wrapped — a heartbeat-refresh failure must never block the
|
|
16
|
+
* coordinator at a tool-batch boundary.
|
|
17
|
+
*
|
|
18
|
+
* Decision flow:
|
|
19
|
+
* 1. shouldRunHook gate — exit 0 immediately when the hook is disabled.
|
|
20
|
+
* 2. Read JSON payload from stdin:
|
|
21
|
+
* { batch_id, batch_size, batch_completed_at, agent_id?, parent_session_id? }
|
|
22
|
+
* 3. Atomic read-modify-write of .orchestrator/current-session.json:
|
|
23
|
+
* set `last_batch` to
|
|
24
|
+
* { batch_id, batch_size, completed_at, agent_id?, parent_session_id? }
|
|
25
|
+
* (always overwrites — last batch wins, one record per session file).
|
|
26
|
+
* 4. Best-effort: refresh session.lock `last_heartbeat` via updateHeartbeat()
|
|
27
|
+
* using the session_id read back from current-session.json (or stdin).
|
|
28
|
+
* 5. Output: nothing on stdout. Diagnostic errors to stderr only.
|
|
29
|
+
*
|
|
30
|
+
* Exit codes: 0 always (informational, never blocking).
|
|
31
|
+
*
|
|
32
|
+
* hooks.json wiring is managed separately (W3-C4 scope).
|
|
33
|
+
*/
|
|
34
|
+
|
|
35
|
+
import { readFile, writeFile, rename, mkdir } from 'node:fs/promises';
|
|
36
|
+
import path from 'node:path';
|
|
37
|
+
|
|
38
|
+
import { shouldRunHook } from './_lib/profile-gate.mjs';
|
|
39
|
+
// Exit 0 immediately when disabled via SO_HOOK_PROFILE / SO_DISABLED_HOOKS.
|
|
40
|
+
if (!shouldRunHook('post-tool-batch-wave-signal')) process.exit(0);
|
|
41
|
+
|
|
42
|
+
import { SO_PROJECT_DIR } from '../scripts/lib/platform.mjs';
|
|
43
|
+
import { emitEvent } from '../scripts/lib/events.mjs';
|
|
44
|
+
|
|
45
|
+
// ---------------------------------------------------------------------------
|
|
46
|
+
// Helpers
|
|
47
|
+
// ---------------------------------------------------------------------------
|
|
48
|
+
|
|
49
|
+
/**
|
|
50
|
+
* Read stdin to EOF (best-effort). Returns parsed JSON or null on failure.
|
|
51
|
+
* Uses a 5 s timeout consistent with Claude Code hook contract.
|
|
52
|
+
*
|
|
53
|
+
* @returns {Promise<object|null>}
|
|
54
|
+
*/
|
|
55
|
+
function readStdinJson() {
|
|
56
|
+
return new Promise((resolve) => {
|
|
57
|
+
if (process.stdin.readableEnded || process.stdin.closed) {
|
|
58
|
+
resolve(null);
|
|
59
|
+
return;
|
|
60
|
+
}
|
|
61
|
+
const chunks = [];
|
|
62
|
+
const timer = setTimeout(() => { resolve(null); }, 5_000);
|
|
63
|
+
process.stdin.setEncoding('utf8');
|
|
64
|
+
process.stdin.on('data', (c) => chunks.push(c));
|
|
65
|
+
process.stdin.on('end', () => {
|
|
66
|
+
clearTimeout(timer);
|
|
67
|
+
const raw = chunks.join('').trim();
|
|
68
|
+
if (!raw) { resolve(null); return; }
|
|
69
|
+
try { resolve(JSON.parse(raw)); } catch { resolve(null); }
|
|
70
|
+
});
|
|
71
|
+
process.stdin.on('error', () => { clearTimeout(timer); resolve(null); });
|
|
72
|
+
process.stdin.resume();
|
|
73
|
+
});
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
/**
|
|
77
|
+
* Atomic read-modify-write of a JSON file via temp + rename.
|
|
78
|
+
* Reads the existing file (or starts with `defaultValue` when absent),
|
|
79
|
+
* applies `mutate`, writes to a tmp file, then renames over the original.
|
|
80
|
+
* Atomic on POSIX (same-filesystem rename). Best-effort on Windows.
|
|
81
|
+
*
|
|
82
|
+
* @param {string} filePath
|
|
83
|
+
* @param {object} defaultValue — used when the file does not exist or is unparseable
|
|
84
|
+
* @param {function(object): object} mutate — synchronous pure transformer
|
|
85
|
+
*/
|
|
86
|
+
async function atomicMutateJson(filePath, defaultValue, mutate) {
|
|
87
|
+
let current = defaultValue;
|
|
88
|
+
try {
|
|
89
|
+
const raw = await readFile(filePath, 'utf8');
|
|
90
|
+
current = JSON.parse(raw);
|
|
91
|
+
} catch {
|
|
92
|
+
// File absent or unparseable — start from defaultValue.
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
const updated = mutate(current);
|
|
96
|
+
const tmp = `${filePath}.tmp-ptb-${process.pid}-${Date.now()}`;
|
|
97
|
+
await mkdir(path.dirname(filePath), { recursive: true });
|
|
98
|
+
await writeFile(tmp, JSON.stringify(updated, null, 2) + '\n', 'utf8');
|
|
99
|
+
await rename(tmp, filePath);
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
/**
|
|
103
|
+
* Resolve the session-id for the heartbeat refresh.
|
|
104
|
+
* Precedence:
|
|
105
|
+
* 1. stdin payload `session_id` / `sessionId` field (Claude Code contract).
|
|
106
|
+
* 2. parent_session_id from stdin (sub-agent batches).
|
|
107
|
+
* 3. `.orchestrator/current-session.json` (`session_id` written by
|
|
108
|
+
* on-session-start.mjs).
|
|
109
|
+
* Returns null when no id can be resolved.
|
|
110
|
+
*
|
|
111
|
+
* @param {object|null} input Parsed stdin payload (may be null).
|
|
112
|
+
* @param {string} sessionFile Absolute path to current-session.json.
|
|
113
|
+
* @returns {Promise<string|null>}
|
|
114
|
+
*/
|
|
115
|
+
async function resolveSessionIdForHeartbeat(input, sessionFile) {
|
|
116
|
+
if (input) {
|
|
117
|
+
if (typeof input.session_id === 'string' && input.session_id.length > 0) {
|
|
118
|
+
return input.session_id;
|
|
119
|
+
}
|
|
120
|
+
if (typeof input.sessionId === 'string' && input.sessionId.length > 0) {
|
|
121
|
+
return input.sessionId;
|
|
122
|
+
}
|
|
123
|
+
if (typeof input.parent_session_id === 'string' && input.parent_session_id.length > 0) {
|
|
124
|
+
return input.parent_session_id;
|
|
125
|
+
}
|
|
126
|
+
}
|
|
127
|
+
try {
|
|
128
|
+
const raw = await readFile(sessionFile, 'utf8');
|
|
129
|
+
const parsed = JSON.parse(raw);
|
|
130
|
+
if (typeof parsed.session_id === 'string' && parsed.session_id.length > 0) {
|
|
131
|
+
return parsed.session_id;
|
|
132
|
+
}
|
|
133
|
+
} catch { /* missing or unparseable is fine */ }
|
|
134
|
+
return null;
|
|
135
|
+
}
|
|
136
|
+
|
|
137
|
+
/**
|
|
138
|
+
* Resolve the current wave number from .claude/wave-scope.json `.wave`.
|
|
139
|
+
* Returns 0 when the file is absent or unparseable, mirroring the
|
|
140
|
+
* pre-bash-memory-propose-audit.mjs G5 precedent ("wave defaults to 0 when
|
|
141
|
+
* wave-scope.json absent"). The file is deleted mid-session at Quality phase
|
|
142
|
+
* transitions and final cleanup, so absence is an expected, non-error state.
|
|
143
|
+
*
|
|
144
|
+
* @param {string} projectDir
|
|
145
|
+
* @returns {Promise<number>}
|
|
146
|
+
*/
|
|
147
|
+
async function resolveWaveNumber(projectDir) {
|
|
148
|
+
const waveFile = path.join(projectDir, '.claude', 'wave-scope.json');
|
|
149
|
+
try {
|
|
150
|
+
const raw = await readFile(waveFile, 'utf8');
|
|
151
|
+
const data = JSON.parse(raw);
|
|
152
|
+
const wave = data?.wave;
|
|
153
|
+
return typeof wave === 'number' ? wave : 0;
|
|
154
|
+
} catch {
|
|
155
|
+
// Absent or unparseable — treat as 0 (no active wave).
|
|
156
|
+
return 0;
|
|
157
|
+
}
|
|
158
|
+
}
|
|
159
|
+
|
|
160
|
+
// ---------------------------------------------------------------------------
|
|
161
|
+
// Main
|
|
162
|
+
// ---------------------------------------------------------------------------
|
|
163
|
+
|
|
164
|
+
async function main() {
|
|
165
|
+
const input = await readStdinJson();
|
|
166
|
+
|
|
167
|
+
// Extract fields from the hook payload.
|
|
168
|
+
const batchId = typeof input?.batch_id === 'string' ? input.batch_id : null;
|
|
169
|
+
const batchSize =
|
|
170
|
+
typeof input?.batch_size === 'number' ? input.batch_size : null;
|
|
171
|
+
const completedAt =
|
|
172
|
+
typeof input?.batch_completed_at === 'string'
|
|
173
|
+
? input.batch_completed_at
|
|
174
|
+
: new Date().toISOString();
|
|
175
|
+
const agentId =
|
|
176
|
+
typeof input?.agent_id === 'string' ? input.agent_id : undefined;
|
|
177
|
+
const parentSessionId =
|
|
178
|
+
typeof input?.parent_session_id === 'string'
|
|
179
|
+
? input.parent_session_id
|
|
180
|
+
: undefined;
|
|
181
|
+
// wave_signal is set by the orchestrator when a wave completes.
|
|
182
|
+
const waveSignal =
|
|
183
|
+
typeof input?.wave_signal === 'string' ? input.wave_signal : null;
|
|
184
|
+
const nextWaveRole =
|
|
185
|
+
typeof input?.next_wave_role === 'string' ? input.next_wave_role : null;
|
|
186
|
+
const waveNumber =
|
|
187
|
+
typeof input?.wave_number === 'number' ? input.wave_number : null;
|
|
188
|
+
|
|
189
|
+
// Build the last_batch signal. Only include optional fields when present
|
|
190
|
+
// to keep the session file lean.
|
|
191
|
+
const lastBatch = {
|
|
192
|
+
batch_id: batchId,
|
|
193
|
+
batch_size: batchSize,
|
|
194
|
+
completed_at: completedAt,
|
|
195
|
+
...(agentId !== undefined ? { agent_id: agentId } : {}),
|
|
196
|
+
...(parentSessionId !== undefined ? { parent_session_id: parentSessionId } : {}),
|
|
197
|
+
...(waveSignal !== null ? { wave_signal: waveSignal } : {}),
|
|
198
|
+
};
|
|
199
|
+
|
|
200
|
+
const sessionFile = path.join(SO_PROJECT_DIR, '.orchestrator', 'current-session.json');
|
|
201
|
+
|
|
202
|
+
await atomicMutateJson(sessionFile, {}, (current) => ({
|
|
203
|
+
...current,
|
|
204
|
+
last_batch: lastBatch,
|
|
205
|
+
}));
|
|
206
|
+
|
|
207
|
+
// ----------------------------------------------------------------------
|
|
208
|
+
// Heartbeat refresh (Epic #583 W3-P3, wires W2-I3 OQ2).
|
|
209
|
+
// ----------------------------------------------------------------------
|
|
210
|
+
// The session.lock liveness rule is heartbeat-based (Epic #583, W2-I3):
|
|
211
|
+
// (now - last_heartbeat) < ttl_hours
|
|
212
|
+
// The default TTL is 4h; PostToolBatch fires far more often than that,
|
|
213
|
+
// so refreshing here keeps every active session perpetually live to
|
|
214
|
+
// discoverActiveSessions() while a coordinator is making tool calls.
|
|
215
|
+
// Resolve the session-id from stdin first, falling back to the just-
|
|
216
|
+
// written current-session.json so the refresh stays decoupled from the
|
|
217
|
+
// hook payload shape. Wrapped in try/catch — same defence-in-depth
|
|
218
|
+
// posture as the rest of this hook (must NEVER block).
|
|
219
|
+
try {
|
|
220
|
+
const sessionId = await resolveSessionIdForHeartbeat(input, sessionFile);
|
|
221
|
+
if (typeof sessionId === 'string' && sessionId.length > 0) {
|
|
222
|
+
const { updateHeartbeat } = await import('../scripts/lib/session-lock.mjs');
|
|
223
|
+
updateHeartbeat({ repoRoot: SO_PROJECT_DIR, sessionId });
|
|
224
|
+
}
|
|
225
|
+
} catch { /* best effort — hook must remain non-blocking */ }
|
|
226
|
+
|
|
227
|
+
// Emit wave-lifecycle events via the canonical stream when the orchestrator
|
|
228
|
+
// populates wave_signal ('wave-start' | 'wave-complete'). Mechanical seam —
|
|
229
|
+
// see docs/events-schema.md. Best-effort; never blocks the hook.
|
|
230
|
+
//
|
|
231
|
+
// Live path today: the `wave_signal === null` branch below provides the
|
|
232
|
+
// mechanical wave-lifecycle fallback (#612). It fires live by diffing
|
|
233
|
+
// `.claude/wave-scope.json` `.wave` against the persisted `last_wave` — no
|
|
234
|
+
// payload injection required. This explicit-signal branch remains as the
|
|
235
|
+
// preferred path for whenever the harness DOES inject `wave_signal` into the
|
|
236
|
+
// batch payload (it takes precedence over the fallback when present).
|
|
237
|
+
if (waveSignal === 'wave-start' || waveSignal === 'wave-complete') {
|
|
238
|
+
try {
|
|
239
|
+
await emitEvent(
|
|
240
|
+
waveSignal === 'wave-start'
|
|
241
|
+
? 'orchestrator.wave.started'
|
|
242
|
+
: 'orchestrator.wave.completed',
|
|
243
|
+
{
|
|
244
|
+
...(waveNumber !== null ? { wave_number: waveNumber } : {}),
|
|
245
|
+
...(nextWaveRole !== null ? { next_wave_role: nextWaveRole } : {}),
|
|
246
|
+
...(batchId !== null ? { batch_id: batchId } : {}),
|
|
247
|
+
...(batchSize !== null ? { batch_size: batchSize } : {}),
|
|
248
|
+
},
|
|
249
|
+
);
|
|
250
|
+
} catch { /* best-effort — hook must remain non-blocking */ }
|
|
251
|
+
} else if (waveSignal === null) {
|
|
252
|
+
// ------------------------------------------------------------------
|
|
253
|
+
// Mechanical wave-lifecycle fallback (#612, Option b).
|
|
254
|
+
// ------------------------------------------------------------------
|
|
255
|
+
// When the harness does NOT inject an explicit wave_signal (the common
|
|
256
|
+
// case today — nothing populates it), detect wave boundaries from the
|
|
257
|
+
// coordinator-written .claude/wave-scope.json `.wave` number, diffed
|
|
258
|
+
// against `last_wave` persisted in current-session.json. The explicit
|
|
259
|
+
// path above takes precedence — this branch only runs when wave_signal
|
|
260
|
+
// is absent (backward-compatible).
|
|
261
|
+
//
|
|
262
|
+
// Only a STRICT INCREASE (wave > last_wave AND wave > 0) is a real wave
|
|
263
|
+
// boundary. wave-scope.json is deleted mid-session at Quality phase
|
|
264
|
+
// transitions and final cleanup, so resolveWaveNumber() returns 0 in
|
|
265
|
+
// those windows — a drop to 0 (or any non-increase) is NOT a wave change
|
|
266
|
+
// and is ignored, preventing spurious emissions on every batch.
|
|
267
|
+
//
|
|
268
|
+
// Final-wave limitation: the LAST wave never receives a `completed` event
|
|
269
|
+
// here because there is no N+1 transition to trigger it. The coordinator
|
|
270
|
+
// emits the final orchestrator.wave.completed at session close.
|
|
271
|
+
try {
|
|
272
|
+
const wave = await resolveWaveNumber(SO_PROJECT_DIR);
|
|
273
|
+
if (wave > 0) {
|
|
274
|
+
// Read last_wave from the just-written session file (after the
|
|
275
|
+
// last_batch RMW above, so we observe the latest persisted value).
|
|
276
|
+
let lastWave = 0;
|
|
277
|
+
try {
|
|
278
|
+
const raw = await readFile(sessionFile, 'utf8');
|
|
279
|
+
const parsed = JSON.parse(raw);
|
|
280
|
+
if (typeof parsed.last_wave === 'number') lastWave = parsed.last_wave;
|
|
281
|
+
} catch { /* absent/unparseable → lastWave stays 0 */ }
|
|
282
|
+
|
|
283
|
+
if (wave > lastWave) {
|
|
284
|
+
// Close the prior wave first (only when there was one).
|
|
285
|
+
if (lastWave > 0) {
|
|
286
|
+
await emitEvent('orchestrator.wave.completed', {
|
|
287
|
+
wave_number: lastWave,
|
|
288
|
+
...(batchId !== null ? { batch_id: batchId } : {}),
|
|
289
|
+
...(batchSize !== null ? { batch_size: batchSize } : {}),
|
|
290
|
+
});
|
|
291
|
+
}
|
|
292
|
+
// Open the new wave.
|
|
293
|
+
await emitEvent('orchestrator.wave.started', {
|
|
294
|
+
wave_number: wave,
|
|
295
|
+
...(nextWaveRole !== null ? { next_wave_role: nextWaveRole } : {}),
|
|
296
|
+
...(batchId !== null ? { batch_id: batchId } : {}),
|
|
297
|
+
...(batchSize !== null ? { batch_size: batchSize } : {}),
|
|
298
|
+
});
|
|
299
|
+
// Persist the high-water mark so the next batch does not re-emit.
|
|
300
|
+
await atomicMutateJson(sessionFile, {}, (current) => ({
|
|
301
|
+
...current,
|
|
302
|
+
last_wave: wave,
|
|
303
|
+
}));
|
|
304
|
+
}
|
|
305
|
+
}
|
|
306
|
+
} catch { /* best-effort — hook must remain non-blocking */ }
|
|
307
|
+
}
|
|
308
|
+
|
|
309
|
+
// If a wave-complete signal is present, surface it as additionalContext so
|
|
310
|
+
// Claude sees the state change at the next turn boundary.
|
|
311
|
+
// PostToolBatch hookSpecificOutput shape per CC docs:
|
|
312
|
+
// { hookSpecificOutput: { hookEventName: "PostToolBatch", additionalContext: "<string>" } }
|
|
313
|
+
if (waveSignal === 'wave-complete') {
|
|
314
|
+
const waveLabel = waveNumber !== null ? `Wave ${waveNumber}` : 'Wave';
|
|
315
|
+
const nextRole = nextWaveRole ?? 'unknown';
|
|
316
|
+
const context = `${waveLabel} complete. Next agent role: ${nextRole}. ` +
|
|
317
|
+
`Batch ${batchId ?? 'n/a'} (${batchSize ?? 'n/a'} tools) resolved at ${completedAt}.`;
|
|
318
|
+
process.stdout.write(JSON.stringify({
|
|
319
|
+
hookSpecificOutput: {
|
|
320
|
+
hookEventName: 'PostToolBatch',
|
|
321
|
+
additionalContext: context,
|
|
322
|
+
},
|
|
323
|
+
}));
|
|
324
|
+
}
|
|
325
|
+
}
|
|
326
|
+
|
|
327
|
+
// Exit 0 always — informational hook must never block Claude.
|
|
328
|
+
main().catch(() => {}).finally(() => process.exit(0));
|
|
@@ -0,0 +1,248 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
/**
|
|
3
|
+
* post-tool-failure-corrective-context.mjs — PostToolUseFailure hook.
|
|
4
|
+
*
|
|
5
|
+
* Hook event: PostToolUseFailure (issue #342).
|
|
6
|
+
* Fires after a tool invocation exits with a non-zero status or produces an
|
|
7
|
+
* error payload. Appends a structured corrective-context note to the
|
|
8
|
+
* `corrective_context` array in `.orchestrator/current-session.json` so the
|
|
9
|
+
* coordinator and downstream skills can inspect recent tool failures without
|
|
10
|
+
* re-reading the full event log.
|
|
11
|
+
*
|
|
12
|
+
* Decision flow:
|
|
13
|
+
* 1. shouldRunHook gate — exit 0 immediately when the hook is disabled.
|
|
14
|
+
* 2. Read JSON payload from stdin: { tool_name, tool_input, error, exit_code }.
|
|
15
|
+
* 3. Build a compact note: { timestamp, tool_name, error_summary, exit_code }.
|
|
16
|
+
* error_summary is capped at 256 chars to keep the session file lean.
|
|
17
|
+
* 4. Atomic read-modify-write of .orchestrator/current-session.json:
|
|
18
|
+
* append to `corrective_context` array (create if absent), keep last 20
|
|
19
|
+
* entries to bound file growth.
|
|
20
|
+
* 5. Write to stderr only (stdout must remain clean — hook is informational).
|
|
21
|
+
*
|
|
22
|
+
* Exit codes: 0 always (informational, never blocking).
|
|
23
|
+
*
|
|
24
|
+
* hooks.json wiring is managed separately (W3-C4 scope).
|
|
25
|
+
*/
|
|
26
|
+
|
|
27
|
+
import { readFile, writeFile, mkdir, rename } from 'node:fs/promises';
|
|
28
|
+
import path from 'node:path';
|
|
29
|
+
|
|
30
|
+
import { shouldRunHook } from './_lib/profile-gate.mjs';
|
|
31
|
+
// Exit 0 immediately when disabled via SO_HOOK_PROFILE / SO_DISABLED_HOOKS.
|
|
32
|
+
if (!shouldRunHook('post-tool-failure-corrective-context')) process.exit(0);
|
|
33
|
+
|
|
34
|
+
import { SO_PROJECT_DIR } from '../scripts/lib/platform.mjs';
|
|
35
|
+
|
|
36
|
+
// ---------------------------------------------------------------------------
|
|
37
|
+
// Constants
|
|
38
|
+
// ---------------------------------------------------------------------------
|
|
39
|
+
|
|
40
|
+
/** Maximum number of corrective context entries retained per session. */
|
|
41
|
+
const MAX_ENTRIES = 20;
|
|
42
|
+
|
|
43
|
+
/** Maximum length of error_summary field (chars). */
|
|
44
|
+
const MAX_ERROR_SUMMARY_CHARS = 256;
|
|
45
|
+
|
|
46
|
+
// ---------------------------------------------------------------------------
|
|
47
|
+
// Helpers
|
|
48
|
+
// ---------------------------------------------------------------------------
|
|
49
|
+
|
|
50
|
+
/**
|
|
51
|
+
* Read stdin to EOF (best-effort). Returns parsed JSON or null on failure.
|
|
52
|
+
* Uses a 5 s timeout consistent with Claude Code hook contract.
|
|
53
|
+
*
|
|
54
|
+
* @returns {Promise<object|null>}
|
|
55
|
+
*/
|
|
56
|
+
function readStdinJson() {
|
|
57
|
+
return new Promise((resolve) => {
|
|
58
|
+
if (process.stdin.readableEnded || process.stdin.closed) {
|
|
59
|
+
resolve(null);
|
|
60
|
+
return;
|
|
61
|
+
}
|
|
62
|
+
const chunks = [];
|
|
63
|
+
const timer = setTimeout(() => { resolve(null); }, 5_000);
|
|
64
|
+
process.stdin.setEncoding('utf8');
|
|
65
|
+
process.stdin.on('data', (c) => chunks.push(c));
|
|
66
|
+
process.stdin.on('end', () => {
|
|
67
|
+
clearTimeout(timer);
|
|
68
|
+
const raw = chunks.join('').trim();
|
|
69
|
+
if (!raw) { resolve(null); return; }
|
|
70
|
+
try { resolve(JSON.parse(raw)); } catch { resolve(null); }
|
|
71
|
+
});
|
|
72
|
+
process.stdin.on('error', () => { clearTimeout(timer); resolve(null); });
|
|
73
|
+
process.stdin.resume();
|
|
74
|
+
});
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
/**
|
|
78
|
+
* Atomic read-modify-write of a JSON file.
|
|
79
|
+
* Reads the existing file (or starts with `defaultValue` when absent),
|
|
80
|
+
* applies `mutate`, writes to a tmp file, renames over the original.
|
|
81
|
+
*
|
|
82
|
+
* @param {string} filePath
|
|
83
|
+
* @param {object} defaultValue — used when the file does not exist
|
|
84
|
+
* @param {function(object): object} mutate — pure transformer
|
|
85
|
+
*/
|
|
86
|
+
async function atomicMutateJson(filePath, defaultValue, mutate) {
|
|
87
|
+
let current = defaultValue;
|
|
88
|
+
try {
|
|
89
|
+
const raw = await readFile(filePath, 'utf8');
|
|
90
|
+
current = JSON.parse(raw);
|
|
91
|
+
} catch {
|
|
92
|
+
// File absent or unparseable — start from defaultValue.
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
const updated = mutate(current);
|
|
96
|
+
const tmp = `${filePath}.tmp-ptf-${process.pid}-${Date.now()}`;
|
|
97
|
+
await mkdir(path.dirname(filePath), { recursive: true });
|
|
98
|
+
await writeFile(tmp, JSON.stringify(updated, null, 2) + '\n', 'utf8');
|
|
99
|
+
|
|
100
|
+
// Rename is atomic on POSIX (same-filesystem). On Windows this is best-effort
|
|
101
|
+
// via the fs.rename syscall (may fail if target is locked — swallowed by
|
|
102
|
+
// the catch in main()).
|
|
103
|
+
await rename(tmp, filePath);
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
// ---------------------------------------------------------------------------
|
|
107
|
+
// Main
|
|
108
|
+
// ---------------------------------------------------------------------------
|
|
109
|
+
|
|
110
|
+
/**
|
|
111
|
+
* Derive a short cause-hint and suggestion based on the tool name and
|
|
112
|
+
* error text. All strings are kept short to stay within the ~500-char
|
|
113
|
+
* additionalContext budget.
|
|
114
|
+
*
|
|
115
|
+
* @param {string|null} toolName
|
|
116
|
+
* @param {string|null} errorSummary
|
|
117
|
+
* @returns {{ cause: string, suggestion: string }}
|
|
118
|
+
*/
|
|
119
|
+
function deriveHints(toolName, errorSummary) {
|
|
120
|
+
const err = (errorSummary ?? '').toLowerCase();
|
|
121
|
+
|
|
122
|
+
if (toolName === 'Bash') {
|
|
123
|
+
if (err.includes('permission denied') || err.includes('eacces')) {
|
|
124
|
+
return {
|
|
125
|
+
cause: 'insufficient file permissions',
|
|
126
|
+
suggestion: 'check file mode with `ls -la` and adjust with `chmod` if needed',
|
|
127
|
+
};
|
|
128
|
+
}
|
|
129
|
+
if (err.includes('command not found') || err.includes('enoent')) {
|
|
130
|
+
return {
|
|
131
|
+
cause: 'command or binary not on PATH',
|
|
132
|
+
suggestion: 'verify the binary is installed and `which <cmd>` resolves it',
|
|
133
|
+
};
|
|
134
|
+
}
|
|
135
|
+
if (err.includes('timeout') || err.includes('timed out')) {
|
|
136
|
+
return {
|
|
137
|
+
cause: 'command exceeded time limit',
|
|
138
|
+
suggestion: 'increase the timeout parameter or break the command into smaller steps',
|
|
139
|
+
};
|
|
140
|
+
}
|
|
141
|
+
return {
|
|
142
|
+
cause: 'shell command exited non-zero',
|
|
143
|
+
suggestion: 'inspect the error output above, fix the root cause, and retry',
|
|
144
|
+
};
|
|
145
|
+
}
|
|
146
|
+
|
|
147
|
+
if (toolName === 'Edit' || toolName === 'Write') {
|
|
148
|
+
if (err.includes('no such file') || err.includes('enoent')) {
|
|
149
|
+
return {
|
|
150
|
+
cause: 'target file or parent directory does not exist',
|
|
151
|
+
suggestion: 'create the missing directory with `mkdir -p` before writing',
|
|
152
|
+
};
|
|
153
|
+
}
|
|
154
|
+
if (err.includes('old_string not found') || err.includes('not unique')) {
|
|
155
|
+
return {
|
|
156
|
+
cause: 'old_string did not match any text in the file',
|
|
157
|
+
suggestion: 're-read the file to confirm the exact text, then retry with a wider context window',
|
|
158
|
+
};
|
|
159
|
+
}
|
|
160
|
+
return {
|
|
161
|
+
cause: 'file edit failed',
|
|
162
|
+
suggestion: 're-read the file to verify its current state, then retry',
|
|
163
|
+
};
|
|
164
|
+
}
|
|
165
|
+
|
|
166
|
+
if (toolName === 'Read') {
|
|
167
|
+
return {
|
|
168
|
+
cause: 'file could not be read (missing or unreadable)',
|
|
169
|
+
suggestion: 'verify the path exists and is accessible before retrying',
|
|
170
|
+
};
|
|
171
|
+
}
|
|
172
|
+
|
|
173
|
+
if (toolName === 'Glob' || toolName === 'Grep') {
|
|
174
|
+
return {
|
|
175
|
+
cause: 'search returned an error (bad pattern or missing directory)',
|
|
176
|
+
suggestion: 'check the pattern syntax and confirm the search root exists',
|
|
177
|
+
};
|
|
178
|
+
}
|
|
179
|
+
|
|
180
|
+
// Fallback for unknown tools.
|
|
181
|
+
return {
|
|
182
|
+
cause: 'tool invocation failed',
|
|
183
|
+
suggestion: 'check the error details above and retry with corrected parameters',
|
|
184
|
+
};
|
|
185
|
+
}
|
|
186
|
+
|
|
187
|
+
async function main() {
|
|
188
|
+
const input = await readStdinJson();
|
|
189
|
+
|
|
190
|
+
// Extract fields from the hook payload. All optional — missing fields are
|
|
191
|
+
// represented as null in the stored note.
|
|
192
|
+
const toolName = typeof input?.tool_name === 'string' ? input.tool_name : null;
|
|
193
|
+
const exitCode = typeof input?.exit_code === 'number' ? input.exit_code : null;
|
|
194
|
+
|
|
195
|
+
const rawError =
|
|
196
|
+
typeof input?.error === 'string'
|
|
197
|
+
? input.error
|
|
198
|
+
: (input?.error !== null && input?.error !== undefined ? String(input.error) : null);
|
|
199
|
+
|
|
200
|
+
const errorSummary = rawError !== null
|
|
201
|
+
? rawError.slice(0, MAX_ERROR_SUMMARY_CHARS)
|
|
202
|
+
: null;
|
|
203
|
+
|
|
204
|
+
const note = {
|
|
205
|
+
timestamp: new Date().toISOString(),
|
|
206
|
+
tool_name: toolName,
|
|
207
|
+
error_summary: errorSummary,
|
|
208
|
+
exit_code: exitCode,
|
|
209
|
+
};
|
|
210
|
+
|
|
211
|
+
const sessionFile = path.join(SO_PROJECT_DIR, '.orchestrator', 'current-session.json');
|
|
212
|
+
|
|
213
|
+
await atomicMutateJson(sessionFile, {}, (current) => {
|
|
214
|
+
const existing = Array.isArray(current.corrective_context)
|
|
215
|
+
? current.corrective_context
|
|
216
|
+
: [];
|
|
217
|
+
// Append the new note and cap at MAX_ENTRIES (keep most-recent).
|
|
218
|
+
const updated = [...existing, note].slice(-MAX_ENTRIES);
|
|
219
|
+
return { ...current, corrective_context: updated };
|
|
220
|
+
});
|
|
221
|
+
|
|
222
|
+
// Surface corrective context to Claude via additionalContext on the next turn.
|
|
223
|
+
// PostToolUseFailure hookSpecificOutput shape per CC docs:
|
|
224
|
+
// { hookSpecificOutput: { hookEventName: "PostToolUseFailure", additionalContext: "<string>" } }
|
|
225
|
+
// Keep the message under ~500 chars so it stays readable.
|
|
226
|
+
const { cause, suggestion } = deriveHints(toolName, errorSummary);
|
|
227
|
+
const toolLabel = toolName ?? 'unknown tool';
|
|
228
|
+
const exitLabel = exitCode !== null ? ` (exit ${exitCode})` : '';
|
|
229
|
+
// Strip control chars (newlines, ANSI escapes) before forwarding to additionalContext —
|
|
230
|
+
// SEC-016 (Log Injection): tool errors may contain attacker-controlled bytes.
|
|
231
|
+
const safeSummary = errorSummary
|
|
232
|
+
? errorSummary.replace(/[\r\n]/g, ' ').split('').join(' ').slice(0, 120)
|
|
233
|
+
: '';
|
|
234
|
+
const summaryLabel = safeSummary ? ` Error: ${safeSummary}` : '';
|
|
235
|
+
const additionalContext =
|
|
236
|
+
`Tool failure: ${toolLabel}${exitLabel}.${summaryLabel} ` +
|
|
237
|
+
`Common cause: ${cause}. Try: ${suggestion}.`;
|
|
238
|
+
|
|
239
|
+
process.stdout.write(JSON.stringify({
|
|
240
|
+
hookSpecificOutput: {
|
|
241
|
+
hookEventName: 'PostToolUseFailure',
|
|
242
|
+
additionalContext: additionalContext.slice(0, 500),
|
|
243
|
+
},
|
|
244
|
+
}));
|
|
245
|
+
}
|
|
246
|
+
|
|
247
|
+
// Exit 0 always — informational hook must never block Claude.
|
|
248
|
+
main().catch(() => {}).finally(() => process.exit(0));
|