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,332 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* instruction-budget-guard.mjs — #687
|
|
3
|
+
*
|
|
4
|
+
* Lightweight directive-budget guard for always-on `.claude/rules/*.md`.
|
|
5
|
+
*
|
|
6
|
+
* Sums the always-on directive count across the rule files that the
|
|
7
|
+
* rule-loader classifies as always-on (no `globs:` frontmatter, and no
|
|
8
|
+
* `paths:` alias either — issue #795: `paths:`-scoped rules are NOT
|
|
9
|
+
* always-on and must not inflate this count) and warns when the total
|
|
10
|
+
* exceeds a ceiling. "Mechanism over discipline" — the #668 instruction-
|
|
11
|
+
* budget audit recommends this as a silent-now growth ratchet that only
|
|
12
|
+
* fires when NEW always-on directives are added.
|
|
13
|
+
*
|
|
14
|
+
* Plain-JS — no Zod dependency. Never throws.
|
|
15
|
+
* - `computeInstructionBudget` always returns the full shape (never null).
|
|
16
|
+
* - `checkInstructionBudget` returns a banner object or null (session-start
|
|
17
|
+
* Phase 4 convention, mirroring checkQgCommandDrift / checkCiStatus).
|
|
18
|
+
*
|
|
19
|
+
* Always-on membership is delegated to `loadApplicableRules` from
|
|
20
|
+
* `./rule-loader.mjs` (single SSOT) — we do NOT hard-code the file list.
|
|
21
|
+
*
|
|
22
|
+
* Cross-references:
|
|
23
|
+
* - "2026-06-20 instruction-budget audit" (#668 / #687; archived in the private Meta-Vault)
|
|
24
|
+
* - scripts/lib/rule-loader.mjs (always-on classification SSOT)
|
|
25
|
+
* - scripts/lib/qg-command-drift-banner.mjs (banner-shape convention)
|
|
26
|
+
* - scripts/lib/ci-status-banner.mjs (never-throws convention)
|
|
27
|
+
*/
|
|
28
|
+
|
|
29
|
+
import { existsSync, readFileSync } from 'node:fs';
|
|
30
|
+
import { basename, join } from 'node:path';
|
|
31
|
+
import { loadApplicableRules } from './rule-loader.mjs';
|
|
32
|
+
|
|
33
|
+
/** Default directive ceiling (operator-chosen growth ratchet just above the ~457 baseline). */
|
|
34
|
+
export const DEFAULT_CEILING = 480;
|
|
35
|
+
|
|
36
|
+
/**
|
|
37
|
+
* Read the `instruction-budget:` nested block from the `## Session Config`
|
|
38
|
+
* section of CLAUDE.md (or AGENTS.md) at `repoRoot`. Synchronous + never throws.
|
|
39
|
+
*
|
|
40
|
+
* The block lives inside `## Session Config`, e.g.:
|
|
41
|
+
*
|
|
42
|
+
* instruction-budget:
|
|
43
|
+
* enabled: true
|
|
44
|
+
* ceiling: 480
|
|
45
|
+
* mode: warn
|
|
46
|
+
*
|
|
47
|
+
* Behaviour:
|
|
48
|
+
* - Config-load failure (no instruction file / unreadable) → returns the
|
|
49
|
+
* graceful fallback `{ enabled: true, ceiling: DEFAULT_CEILING, mode: 'warn' }`
|
|
50
|
+
* so the probe still computes (mirrors the other session-start probes).
|
|
51
|
+
* - Absent block → same fallback (the feature is on-by-default, growth-ratchet).
|
|
52
|
+
* - Malformed individual values silently fall back to the per-key default.
|
|
53
|
+
*
|
|
54
|
+
* @param {string} repoRoot
|
|
55
|
+
* @returns {{ enabled: boolean, ceiling: number, mode: 'warn' | 'off' }}
|
|
56
|
+
*/
|
|
57
|
+
export function loadInstructionBudgetConfig(repoRoot) {
|
|
58
|
+
const fallback = { enabled: true, ceiling: DEFAULT_CEILING, mode: 'warn' };
|
|
59
|
+
|
|
60
|
+
let content = null;
|
|
61
|
+
for (const name of ['CLAUDE.md', 'AGENTS.md']) {
|
|
62
|
+
const file = join(repoRoot ?? process.cwd(), name);
|
|
63
|
+
try {
|
|
64
|
+
if (existsSync(file)) {
|
|
65
|
+
content = readFileSync(file, 'utf8');
|
|
66
|
+
break;
|
|
67
|
+
}
|
|
68
|
+
} catch {
|
|
69
|
+
// unreadable — try the next candidate
|
|
70
|
+
}
|
|
71
|
+
}
|
|
72
|
+
if (typeof content !== 'string') return fallback;
|
|
73
|
+
|
|
74
|
+
try {
|
|
75
|
+
return _parseInstructionBudget(content, fallback);
|
|
76
|
+
} catch {
|
|
77
|
+
return fallback;
|
|
78
|
+
}
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
/**
|
|
82
|
+
* Parse the `instruction-budget:` block out of raw markdown content.
|
|
83
|
+
* Independent helper (testable without disk IO).
|
|
84
|
+
*
|
|
85
|
+
* @param {string} content - full file contents
|
|
86
|
+
* @param {{ enabled: boolean, ceiling: number, mode: 'warn' | 'off' }} [defaults]
|
|
87
|
+
* @returns {{ enabled: boolean, ceiling: number, mode: 'warn' | 'off' }}
|
|
88
|
+
*/
|
|
89
|
+
export function _parseInstructionBudget(content, defaults) {
|
|
90
|
+
const base = defaults ?? { enabled: true, ceiling: DEFAULT_CEILING, mode: 'warn' };
|
|
91
|
+
if (typeof content !== 'string' || content === '') return { ...base };
|
|
92
|
+
|
|
93
|
+
const lines = content.split(/\r?\n/);
|
|
94
|
+
let inBlock = false;
|
|
95
|
+
let keyIndent = 0;
|
|
96
|
+
const blockLines = [];
|
|
97
|
+
|
|
98
|
+
for (const rawLine of lines) {
|
|
99
|
+
const line = rawLine.replace(/\r$/, '');
|
|
100
|
+
if (!inBlock) {
|
|
101
|
+
// The block key normally sits at column 0 inside `## Session Config`,
|
|
102
|
+
// but tolerate a leading indent (e.g. nested under another mapping).
|
|
103
|
+
const m = line.match(/^(\s*)instruction-budget:\s*$/);
|
|
104
|
+
if (m) {
|
|
105
|
+
inBlock = true;
|
|
106
|
+
keyIndent = m[1].length;
|
|
107
|
+
}
|
|
108
|
+
continue;
|
|
109
|
+
}
|
|
110
|
+
// Blank lines stay inside the block (mid-block spacing is tolerated).
|
|
111
|
+
if (line.trim() === '') {
|
|
112
|
+
blockLines.push(line);
|
|
113
|
+
continue;
|
|
114
|
+
}
|
|
115
|
+
// A child line must be indented STRICTLY DEEPER than the block key.
|
|
116
|
+
// Any line at or below the key's indent (incl. column 0) closes the block.
|
|
117
|
+
const indent = line.length - line.replace(/^\s+/, '').length;
|
|
118
|
+
if (indent <= keyIndent) break;
|
|
119
|
+
blockLines.push(line);
|
|
120
|
+
}
|
|
121
|
+
|
|
122
|
+
if (blockLines.length === 0) return { ...base };
|
|
123
|
+
|
|
124
|
+
let enabled = base.enabled;
|
|
125
|
+
let ceiling = base.ceiling;
|
|
126
|
+
let mode = base.mode;
|
|
127
|
+
|
|
128
|
+
for (const rawLine of blockLines) {
|
|
129
|
+
const clean = rawLine.replace(/\s*#.*$/, '').replace(/\s+$/, '');
|
|
130
|
+
if (!clean.trim()) continue;
|
|
131
|
+
|
|
132
|
+
const kvMatch = clean.match(/^\s+([a-zA-Z_-]+):\s*(.*)/);
|
|
133
|
+
if (!kvMatch) continue;
|
|
134
|
+
|
|
135
|
+
const k = kvMatch[1];
|
|
136
|
+
let v = kvMatch[2].trim();
|
|
137
|
+
if (v.startsWith('"') && v.endsWith('"') && v.length >= 2) v = v.slice(1, -1);
|
|
138
|
+
else if (v.startsWith("'") && v.endsWith("'") && v.length >= 2) v = v.slice(1, -1);
|
|
139
|
+
|
|
140
|
+
switch (k) {
|
|
141
|
+
case 'enabled':
|
|
142
|
+
// Default on → only flip to false on explicit "false".
|
|
143
|
+
enabled = v.toLowerCase() !== 'false';
|
|
144
|
+
break;
|
|
145
|
+
case 'ceiling': {
|
|
146
|
+
if (/^-?\d+$/.test(v)) {
|
|
147
|
+
const n = Number.parseInt(v, 10);
|
|
148
|
+
if (Number.isFinite(n) && n > 0) ceiling = n;
|
|
149
|
+
}
|
|
150
|
+
break;
|
|
151
|
+
}
|
|
152
|
+
case 'mode':
|
|
153
|
+
// Only `off` silences; any other value (incl. `warn`) surfaces the banner.
|
|
154
|
+
mode = v.toLowerCase() === 'off' ? 'off' : 'warn';
|
|
155
|
+
break;
|
|
156
|
+
}
|
|
157
|
+
}
|
|
158
|
+
|
|
159
|
+
return { enabled, ceiling, mode };
|
|
160
|
+
}
|
|
161
|
+
|
|
162
|
+
/**
|
|
163
|
+
* Count always-on directives in a single rule file's content.
|
|
164
|
+
*
|
|
165
|
+
* Deterministic heuristic — counts lines that represent a directive:
|
|
166
|
+
* - bullets: /^\s*[-*+]\s/
|
|
167
|
+
* - ordered items: /^\s*\d+[.)]\s/
|
|
168
|
+
* - headings ≥2: /^#{2,}\s/
|
|
169
|
+
*
|
|
170
|
+
* Fenced code blocks (``` … ```) are excluded entirely, and a leading
|
|
171
|
+
* `---` … `---` YAML frontmatter block is skipped before counting.
|
|
172
|
+
*
|
|
173
|
+
* @param {string} content - raw file contents
|
|
174
|
+
* @returns {number}
|
|
175
|
+
*/
|
|
176
|
+
function countDirectives(content) {
|
|
177
|
+
if (typeof content !== 'string' || content === '') return 0;
|
|
178
|
+
|
|
179
|
+
const lines = content.split(/\r?\n/);
|
|
180
|
+
let i = 0;
|
|
181
|
+
|
|
182
|
+
// Skip a leading YAML frontmatter block: `---` … `---`.
|
|
183
|
+
if (lines[0] !== undefined && lines[0].trim() === '---') {
|
|
184
|
+
let j = 1;
|
|
185
|
+
while (j < lines.length && lines[j].trim() !== '---') j++;
|
|
186
|
+
// Only skip if a closing fence was found; otherwise leave i at 0.
|
|
187
|
+
if (j < lines.length) i = j + 1;
|
|
188
|
+
}
|
|
189
|
+
|
|
190
|
+
let count = 0;
|
|
191
|
+
let inFence = false;
|
|
192
|
+
|
|
193
|
+
for (; i < lines.length; i++) {
|
|
194
|
+
const line = lines[i];
|
|
195
|
+
|
|
196
|
+
// Toggle code-fence state on any line that opens/closes a fence.
|
|
197
|
+
if (/^\s*```/.test(line)) {
|
|
198
|
+
inFence = !inFence;
|
|
199
|
+
continue;
|
|
200
|
+
}
|
|
201
|
+
if (inFence) continue;
|
|
202
|
+
|
|
203
|
+
if (
|
|
204
|
+
/^\s*[-*+]\s/.test(line) ||
|
|
205
|
+
/^\s*\d+[.)]\s/.test(line) ||
|
|
206
|
+
/^#{2,}\s/.test(line)
|
|
207
|
+
) {
|
|
208
|
+
count++;
|
|
209
|
+
}
|
|
210
|
+
}
|
|
211
|
+
|
|
212
|
+
return count;
|
|
213
|
+
}
|
|
214
|
+
|
|
215
|
+
/**
|
|
216
|
+
* Pure computation — always returns the full shape (never null, never throws).
|
|
217
|
+
*
|
|
218
|
+
* @param {object} [opts]
|
|
219
|
+
* @param {string} [opts.repoRoot] project root (defaults to process.cwd()).
|
|
220
|
+
* @param {string} [opts.rulesDir] rules directory (defaults to <repoRoot>/.claude/rules).
|
|
221
|
+
* @param {number} [opts.ceiling] directive ceiling (defaults to DEFAULT_CEILING).
|
|
222
|
+
* @returns {{
|
|
223
|
+
* totalDirectives: number,
|
|
224
|
+
* perFile: Array<{ file: string, count: number }>,
|
|
225
|
+
* ceiling: number,
|
|
226
|
+
* overBudget: boolean,
|
|
227
|
+
* severity: 'ok' | 'warn',
|
|
228
|
+
* }}
|
|
229
|
+
* perFile is sorted DESC by count. On missing/unreadable dir →
|
|
230
|
+
* { totalDirectives: 0, perFile: [], ceiling, overBudget: false, severity: 'ok' }.
|
|
231
|
+
*/
|
|
232
|
+
export function computeInstructionBudget(opts = {}) {
|
|
233
|
+
const repoRoot = opts.repoRoot ?? process.cwd();
|
|
234
|
+
const rulesDir = opts.rulesDir ?? join(repoRoot, '.claude/rules');
|
|
235
|
+
const ceiling = typeof opts.ceiling === 'number' ? opts.ceiling : DEFAULT_CEILING;
|
|
236
|
+
|
|
237
|
+
const empty = {
|
|
238
|
+
totalDirectives: 0,
|
|
239
|
+
perFile: [],
|
|
240
|
+
ceiling,
|
|
241
|
+
overBudget: false,
|
|
242
|
+
severity: 'ok',
|
|
243
|
+
};
|
|
244
|
+
|
|
245
|
+
let entries;
|
|
246
|
+
try {
|
|
247
|
+
// Empty scopePaths → only always-on rules (no glob matches) are returned.
|
|
248
|
+
entries = loadApplicableRules({ rulesDir, scopePaths: [] });
|
|
249
|
+
} catch {
|
|
250
|
+
return empty;
|
|
251
|
+
}
|
|
252
|
+
|
|
253
|
+
if (!Array.isArray(entries)) return empty;
|
|
254
|
+
|
|
255
|
+
const perFile = [];
|
|
256
|
+
let totalDirectives = 0;
|
|
257
|
+
|
|
258
|
+
for (const entry of entries) {
|
|
259
|
+
if (!entry || entry.alwaysOn !== true) continue;
|
|
260
|
+
const count = countDirectives(entry.content);
|
|
261
|
+
totalDirectives += count;
|
|
262
|
+
perFile.push({ file: basename(entry.path), count });
|
|
263
|
+
}
|
|
264
|
+
|
|
265
|
+
// Sort DESC by count; tie-break by filename for deterministic output.
|
|
266
|
+
perFile.sort((a, b) => b.count - a.count || a.file.localeCompare(b.file));
|
|
267
|
+
|
|
268
|
+
const overBudget = totalDirectives > ceiling;
|
|
269
|
+
|
|
270
|
+
return {
|
|
271
|
+
totalDirectives,
|
|
272
|
+
perFile,
|
|
273
|
+
ceiling,
|
|
274
|
+
overBudget,
|
|
275
|
+
severity: overBudget ? 'warn' : 'ok',
|
|
276
|
+
};
|
|
277
|
+
}
|
|
278
|
+
|
|
279
|
+
/**
|
|
280
|
+
* Banner wrapper — session-start Phase 4 convention.
|
|
281
|
+
*
|
|
282
|
+
* Reads `instruction-budget.{enabled,ceiling,mode}` from Session Config
|
|
283
|
+
* (CLAUDE.md / AGENTS.md at `opts.repoRoot`, default process.cwd()):
|
|
284
|
+
* - `enabled: false` OR `mode: off` → returns null (silent no-op).
|
|
285
|
+
* - The config `ceiling` is used unless `opts.ceiling` is explicitly supplied
|
|
286
|
+
* (an explicit opt wins, keeping callers that pin a ceiling deterministic).
|
|
287
|
+
* - Config-load failure → graceful fallback `{enabled:true, ceiling:480,
|
|
288
|
+
* mode:warn}` so the probe still computes (mirrors the other probes).
|
|
289
|
+
* Never throws.
|
|
290
|
+
*
|
|
291
|
+
* @param {object} [opts] forwarded to computeInstructionBudget.
|
|
292
|
+
* @param {string} [opts.repoRoot] project root for the config read.
|
|
293
|
+
* @param {number} [opts.ceiling] explicit ceiling override (wins over config).
|
|
294
|
+
* @returns {{ severity: 'warn', message: string } | null}
|
|
295
|
+
* null when disabled / off / at-or-under ceiling OR on any read failure.
|
|
296
|
+
*/
|
|
297
|
+
export function checkInstructionBudget(opts = {}) {
|
|
298
|
+
let cfg;
|
|
299
|
+
try {
|
|
300
|
+
cfg = loadInstructionBudgetConfig(opts.repoRoot);
|
|
301
|
+
} catch {
|
|
302
|
+
cfg = { enabled: true, ceiling: DEFAULT_CEILING, mode: 'warn' };
|
|
303
|
+
}
|
|
304
|
+
|
|
305
|
+
// Opt-out gates — return null without computing.
|
|
306
|
+
if (!cfg.enabled || cfg.mode === 'off') return null;
|
|
307
|
+
|
|
308
|
+
// An explicit ceiling opt wins over the config ceiling; otherwise use config.
|
|
309
|
+
const ceiling = typeof opts.ceiling === 'number' ? opts.ceiling : cfg.ceiling;
|
|
310
|
+
|
|
311
|
+
let budget;
|
|
312
|
+
try {
|
|
313
|
+
budget = computeInstructionBudget({ ...opts, ceiling });
|
|
314
|
+
} catch {
|
|
315
|
+
return null; // never throw out of the banner wrapper
|
|
316
|
+
}
|
|
317
|
+
|
|
318
|
+
if (!budget || !budget.overBudget) return null;
|
|
319
|
+
|
|
320
|
+
const top = budget.perFile
|
|
321
|
+
.slice(0, 3)
|
|
322
|
+
.map((f) => `${f.file} (${f.count})`)
|
|
323
|
+
.join(', ');
|
|
324
|
+
|
|
325
|
+
const message = [
|
|
326
|
+
`⚠ Instruction budget: ${budget.totalDirectives} always-on directives across ${budget.perFile.length} rules — over ceiling ${budget.ceiling}.`,
|
|
327
|
+
` Top files: ${top}`,
|
|
328
|
+
' See the instruction-budget audit (#687; archived in the private Meta-Vault) for the prune/demote list.',
|
|
329
|
+
].join('\n');
|
|
330
|
+
|
|
331
|
+
return { severity: 'warn', message };
|
|
332
|
+
}
|
|
@@ -0,0 +1,304 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* io.mjs — stdin/stdout helpers implementing the Claude Code hook I/O contract,
|
|
3
|
+
* plus shared atomic file-write helpers used by skills that need crash-safe
|
|
4
|
+
* sidecar JSON output (e.g. persona-panel, issue #457).
|
|
5
|
+
*
|
|
6
|
+
* Provides promise-based stdin JSON reading and structured exit helpers used by
|
|
7
|
+
* PreToolUse / PostToolUse hooks in the session-orchestrator v3 migration, and
|
|
8
|
+
* `writeJsonAtomic` (async) + `writeJsonAtomicSync` (sync) for tmp+rename JSON
|
|
9
|
+
* writes. The async variant supports optional pre-write validation; the sync
|
|
10
|
+
* variant is hot-path-friendly for hooks and session-lock writers that cannot
|
|
11
|
+
* await.
|
|
12
|
+
*
|
|
13
|
+
* No external dependencies — Node 20+ stdlib only.
|
|
14
|
+
*
|
|
15
|
+
* Part of v3.0.0 migration (Epic #124, issue #131); writeJsonAtomic added for
|
|
16
|
+
* #457; writeJsonAtomicSync extracted for #558 M1.
|
|
17
|
+
*/
|
|
18
|
+
|
|
19
|
+
import { writeFile, rename, mkdir } from 'node:fs/promises';
|
|
20
|
+
import { mkdirSync, writeFileSync, renameSync, readFileSync, existsSync } from 'node:fs';
|
|
21
|
+
import path, { dirname } from 'node:path';
|
|
22
|
+
import { randomBytes, randomUUID } from 'node:crypto';
|
|
23
|
+
|
|
24
|
+
// ---------------------------------------------------------------------------
|
|
25
|
+
// Internal helpers
|
|
26
|
+
// ---------------------------------------------------------------------------
|
|
27
|
+
|
|
28
|
+
/**
|
|
29
|
+
* Build the deny reason string, optionally appending a suggestion.
|
|
30
|
+
* @param {string} reason
|
|
31
|
+
* @param {string|undefined} suggestion
|
|
32
|
+
* @returns {string}
|
|
33
|
+
*/
|
|
34
|
+
function _formatReason(reason, suggestion) {
|
|
35
|
+
return suggestion ? `${reason} — ${suggestion}` : reason;
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
// ---------------------------------------------------------------------------
|
|
39
|
+
// Exports
|
|
40
|
+
// ---------------------------------------------------------------------------
|
|
41
|
+
|
|
42
|
+
/**
|
|
43
|
+
* Read process.stdin to EOF and parse as JSON.
|
|
44
|
+
* @returns {Promise<object|null>} Parsed JSON object, or null on empty stream.
|
|
45
|
+
* @throws {SyntaxError} If stdin contains non-empty, non-JSON data.
|
|
46
|
+
* @throws {Error} If the 1 MB size limit or 5 s timeout is exceeded.
|
|
47
|
+
*/
|
|
48
|
+
export async function readStdin() {
|
|
49
|
+
const MAX_BYTES = 1_048_576; // 1 MB guard
|
|
50
|
+
const TIMEOUT_MS = 5_000; // 5 s guard
|
|
51
|
+
|
|
52
|
+
return new Promise((resolve, reject) => {
|
|
53
|
+
const controller = new AbortController();
|
|
54
|
+
const timer = setTimeout(() => {
|
|
55
|
+
controller.abort();
|
|
56
|
+
reject(new Error('io.mjs: readStdin timed out after 5 s'));
|
|
57
|
+
}, TIMEOUT_MS);
|
|
58
|
+
|
|
59
|
+
const chunks = [];
|
|
60
|
+
let totalBytes = 0;
|
|
61
|
+
|
|
62
|
+
// If stdin is already closed (e.g. not a TTY with no data), handle cleanly.
|
|
63
|
+
if (process.stdin.readableEnded) {
|
|
64
|
+
clearTimeout(timer);
|
|
65
|
+
resolve(null);
|
|
66
|
+
return;
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
process.stdin.setEncoding('utf8');
|
|
70
|
+
|
|
71
|
+
const onData = (chunk) => {
|
|
72
|
+
if (controller.signal.aborted) return;
|
|
73
|
+
totalBytes += Buffer.byteLength(chunk, 'utf8');
|
|
74
|
+
if (totalBytes > MAX_BYTES) {
|
|
75
|
+
cleanup();
|
|
76
|
+
reject(new Error(`io.mjs: stdin payload exceeds 1 MB limit (${totalBytes} bytes read)`));
|
|
77
|
+
return;
|
|
78
|
+
}
|
|
79
|
+
chunks.push(chunk);
|
|
80
|
+
};
|
|
81
|
+
|
|
82
|
+
const onEnd = () => {
|
|
83
|
+
if (controller.signal.aborted) return;
|
|
84
|
+
cleanup();
|
|
85
|
+
const raw = chunks.join('').trim();
|
|
86
|
+
if (raw === '') {
|
|
87
|
+
resolve(null);
|
|
88
|
+
return;
|
|
89
|
+
}
|
|
90
|
+
try {
|
|
91
|
+
resolve(JSON.parse(raw));
|
|
92
|
+
} catch {
|
|
93
|
+
reject(new SyntaxError(`io.mjs: stdin is not valid JSON — got: ${raw.slice(0, 120)}`));
|
|
94
|
+
}
|
|
95
|
+
};
|
|
96
|
+
|
|
97
|
+
const onError = (err) => {
|
|
98
|
+
cleanup();
|
|
99
|
+
reject(err);
|
|
100
|
+
};
|
|
101
|
+
|
|
102
|
+
const cleanup = () => {
|
|
103
|
+
clearTimeout(timer);
|
|
104
|
+
process.stdin.off('data', onData);
|
|
105
|
+
process.stdin.off('end', onEnd);
|
|
106
|
+
process.stdin.off('error', onError);
|
|
107
|
+
};
|
|
108
|
+
|
|
109
|
+
process.stdin.on('data', onData);
|
|
110
|
+
process.stdin.on('end', onEnd);
|
|
111
|
+
process.stdin.on('error', onError);
|
|
112
|
+
|
|
113
|
+
// Resume in case the stream is paused (e.g. in flowing mode was never started).
|
|
114
|
+
process.stdin.resume();
|
|
115
|
+
});
|
|
116
|
+
}
|
|
117
|
+
|
|
118
|
+
/**
|
|
119
|
+
* Allow the current hook invocation — exits 0 silently.
|
|
120
|
+
* @returns {never}
|
|
121
|
+
*/
|
|
122
|
+
export function emitAllow() {
|
|
123
|
+
process.exit(0);
|
|
124
|
+
}
|
|
125
|
+
|
|
126
|
+
/**
|
|
127
|
+
* Deny the current hook invocation with a structured JSON reason on stdout, then exit 2.
|
|
128
|
+
* @param {string} reason Non-empty human-readable denial reason.
|
|
129
|
+
* @param {string} [suggestion] Optional remediation hint appended after " — ".
|
|
130
|
+
* @returns {never}
|
|
131
|
+
*/
|
|
132
|
+
export function emitDeny(reason, suggestion) {
|
|
133
|
+
if (!reason) throw new TypeError('io.mjs: emitDeny requires a non-empty reason string');
|
|
134
|
+
console.log(JSON.stringify({ permissionDecision: 'deny', reason: _formatReason(reason, suggestion) }));
|
|
135
|
+
process.exit(2);
|
|
136
|
+
}
|
|
137
|
+
|
|
138
|
+
/**
|
|
139
|
+
* Emit a warning message to stderr and exit 0 (allow with notice).
|
|
140
|
+
* @param {string} message Warning text written to stderr prefixed with "⚠ ".
|
|
141
|
+
* @returns {never}
|
|
142
|
+
*/
|
|
143
|
+
export function emitWarn(message) {
|
|
144
|
+
console.error(`⚠ ${message}`);
|
|
145
|
+
process.exit(0);
|
|
146
|
+
}
|
|
147
|
+
|
|
148
|
+
/**
|
|
149
|
+
* Inject a system message into the hook response without exiting.
|
|
150
|
+
* @param {string} msg Message text wrapped in a systemMessage JSON envelope on stdout.
|
|
151
|
+
*/
|
|
152
|
+
export function emitSystemMessage(msg) {
|
|
153
|
+
console.log(JSON.stringify({ systemMessage: msg }));
|
|
154
|
+
}
|
|
155
|
+
|
|
156
|
+
/**
|
|
157
|
+
* Atomically write a JSON value to filePath. Creates parent directories as needed.
|
|
158
|
+
*
|
|
159
|
+
* Crash-safe pattern: write to `<filePath>.<rand>.tmp`, then `rename()` over the
|
|
160
|
+
* target. Same-filesystem rename is atomic on POSIX, so partial-write states are
|
|
161
|
+
* impossible — observers see either the previous contents or the new ones, never
|
|
162
|
+
* a half-written file.
|
|
163
|
+
*
|
|
164
|
+
* When `validatorFn` is provided, the value is validated BEFORE any disk write.
|
|
165
|
+
* Validation failure throws an Error with `.validationErrors` attached and leaves
|
|
166
|
+
* the target file untouched.
|
|
167
|
+
*
|
|
168
|
+
* Caller is responsible for path-confinement (see scripts/lib/path-utils.mjs#validatePathInsideProject)
|
|
169
|
+
* — this helper does NOT validate that filePath lives inside the project.
|
|
170
|
+
*
|
|
171
|
+
* Added for persona-panel sidecar writes (issue #457).
|
|
172
|
+
*
|
|
173
|
+
* @param {string} filePath Target path; parent dirs created with mkdir -p semantics.
|
|
174
|
+
* @param {*} value JSON-serializable value.
|
|
175
|
+
* @param {object} [opts]
|
|
176
|
+
* @param {(value: *) => {ok: boolean, errors?: Array<object>}} [opts.validatorFn]
|
|
177
|
+
* Pre-write validator. Throws (without writing) on `ok=false`.
|
|
178
|
+
* @param {number} [opts.indent=2] JSON.stringify indent.
|
|
179
|
+
* @returns {Promise<{ path: string, bytes: number }>}
|
|
180
|
+
* @throws {Error} If validatorFn rejects the value (with `.validationErrors` array attached).
|
|
181
|
+
*/
|
|
182
|
+
export async function writeJsonAtomic(filePath, value, opts = {}) {
|
|
183
|
+
const { validatorFn, indent = 2 } = opts;
|
|
184
|
+
|
|
185
|
+
if (typeof validatorFn === 'function') {
|
|
186
|
+
const result = validatorFn(value);
|
|
187
|
+
if (!result || result.ok !== true) {
|
|
188
|
+
const err = new Error('writeJsonAtomic: validation failed before write');
|
|
189
|
+
err.validationErrors = (result && result.errors) || [];
|
|
190
|
+
throw err;
|
|
191
|
+
}
|
|
192
|
+
}
|
|
193
|
+
|
|
194
|
+
await mkdir(dirname(filePath), { recursive: true });
|
|
195
|
+
|
|
196
|
+
const tmp = `${filePath}.${randomUUID().slice(0, 8)}.tmp`;
|
|
197
|
+
const content = JSON.stringify(value, null, indent);
|
|
198
|
+
await writeFile(tmp, content, 'utf8');
|
|
199
|
+
await rename(tmp, filePath);
|
|
200
|
+
|
|
201
|
+
return { path: filePath, bytes: Buffer.byteLength(content, 'utf8') };
|
|
202
|
+
}
|
|
203
|
+
|
|
204
|
+
/**
|
|
205
|
+
* Atomically replace a file via tmp + renameSync. Synchronous companion to
|
|
206
|
+
* {@link writeJsonAtomic} for hot-path code that cannot await — session-lock
|
|
207
|
+
* acquire/replace, staging-fence intent logs, and other crash-safe sidecar
|
|
208
|
+
* writes invoked from PreToolUse hooks where async would add a microtask hop.
|
|
209
|
+
*
|
|
210
|
+
* Crash-safe pattern: write to `<dir>/<tmpPrefix>.<rand>` then renameSync over
|
|
211
|
+
* the target. Same-filesystem rename is atomic on POSIX, so partial-write
|
|
212
|
+
* states are impossible — observers see either the previous contents or the
|
|
213
|
+
* new ones, never a half-written file.
|
|
214
|
+
*
|
|
215
|
+
* Caller is responsible for path-confinement — this helper does NOT validate
|
|
216
|
+
* that filePath lives inside the project (mirrors the async {@link writeJsonAtomic}
|
|
217
|
+
* contract).
|
|
218
|
+
*
|
|
219
|
+
* Hook-safety: io.mjs MUST NOT reverse-import from `hooks/` — this helper is a
|
|
220
|
+
* pure Node-stdlib utility (`node:fs`, `node:path`, `node:crypto`) so it can be
|
|
221
|
+
* used from any layer without violating the layering rule in
|
|
222
|
+
* `scripts/lib/hardening.mjs`.
|
|
223
|
+
*
|
|
224
|
+
* @param {string} filePath Target path; parent dirs created with mkdir -p semantics.
|
|
225
|
+
* @param {*} data JSON-serializable value.
|
|
226
|
+
* @param {object} [opts]
|
|
227
|
+
* @param {number} [opts.indent=2] JSON.stringify indent.
|
|
228
|
+
* @param {string} [opts.tmpPrefix='.tmp'] Tmp-file prefix (callers pick their domain prefix).
|
|
229
|
+
* @returns {{ ok: true } | { ok: false, reason: 'fs-error', error: string }}
|
|
230
|
+
*/
|
|
231
|
+
export function writeJsonAtomicSync(filePath, data, opts = {}) {
|
|
232
|
+
const { indent = 2, tmpPrefix = '.tmp' } = opts;
|
|
233
|
+
try {
|
|
234
|
+
const dir = dirname(filePath);
|
|
235
|
+
mkdirSync(dir, { recursive: true });
|
|
236
|
+
const tmpSuffix = randomBytes(6).toString('hex');
|
|
237
|
+
const tmpFile = path.join(dir, `${tmpPrefix}.${tmpSuffix}`);
|
|
238
|
+
writeFileSync(tmpFile, JSON.stringify(data, null, indent) + '\n', { encoding: 'utf8' });
|
|
239
|
+
renameSync(tmpFile, filePath);
|
|
240
|
+
return { ok: true };
|
|
241
|
+
} catch (err) {
|
|
242
|
+
return { ok: false, reason: 'fs-error', error: err?.message ?? String(err) };
|
|
243
|
+
}
|
|
244
|
+
}
|
|
245
|
+
|
|
246
|
+
/**
|
|
247
|
+
* Parse newline-delimited JSON (JSONL) into an array of parsed objects.
|
|
248
|
+
*
|
|
249
|
+
* Splits `raw` on '\n', drops blank / whitespace-only lines, and JSON.parses
|
|
250
|
+
* each remaining line. This is the shared, tested replacement for the inline
|
|
251
|
+
* `raw.split('\n').filter(l => l.trim().length > 0).map(JSON.parse)` idiom that
|
|
252
|
+
* is re-implemented across ~30 metrics / event readers.
|
|
253
|
+
*
|
|
254
|
+
* Empty or whitespace-only input returns `[]` and never throws.
|
|
255
|
+
*
|
|
256
|
+
* @param {string} raw Raw JSONL text.
|
|
257
|
+
* @param {object} [opts]
|
|
258
|
+
* @param {boolean} [opts.skipInvalid=false] When `true`, silently skip lines
|
|
259
|
+
* that fail JSON.parse. When `false` (default), throw an Error naming the
|
|
260
|
+
* 1-based source line number plus a short snippet of the offending line,
|
|
261
|
+
* so callers get actionable diagnostics.
|
|
262
|
+
* @returns {object[]} Parsed objects, in source order.
|
|
263
|
+
* @throws {Error} When `skipInvalid` is false and a non-blank line is not valid JSON.
|
|
264
|
+
*/
|
|
265
|
+
export function readJsonlLines(raw, opts = {}) {
|
|
266
|
+
const { skipInvalid = false } = opts;
|
|
267
|
+
if (typeof raw !== 'string' || raw.trim().length === 0) return [];
|
|
268
|
+
|
|
269
|
+
const out = [];
|
|
270
|
+
const lines = raw.split('\n');
|
|
271
|
+
for (let i = 0; i < lines.length; i++) {
|
|
272
|
+
const line = lines[i];
|
|
273
|
+
if (line.trim().length === 0) continue;
|
|
274
|
+
try {
|
|
275
|
+
out.push(JSON.parse(line));
|
|
276
|
+
} catch (err) {
|
|
277
|
+
if (skipInvalid) continue;
|
|
278
|
+
const snippet = line.length > 80 ? `${line.slice(0, 80)}…` : line;
|
|
279
|
+
throw new Error(
|
|
280
|
+
`io.mjs: readJsonlLines failed to parse JSON on line ${i + 1}: ${snippet} (${err?.message ?? String(err)})`,
|
|
281
|
+
{ cause: err },
|
|
282
|
+
);
|
|
283
|
+
}
|
|
284
|
+
}
|
|
285
|
+
return out;
|
|
286
|
+
}
|
|
287
|
+
|
|
288
|
+
/**
|
|
289
|
+
* Read a UTF-8 JSONL file and parse it via {@link readJsonlLines}.
|
|
290
|
+
*
|
|
291
|
+
* A missing file returns `[]` rather than throwing — JSONL metrics / events
|
|
292
|
+
* sidecars (e.g. `.orchestrator/metrics/*.jsonl`) are routinely absent on first
|
|
293
|
+
* run, and callers should treat "no file" identically to "empty file".
|
|
294
|
+
*
|
|
295
|
+
* @param {string} filePath Path to a UTF-8 JSONL file.
|
|
296
|
+
* @param {object} [opts] Forwarded to {@link readJsonlLines} (e.g. `{ skipInvalid: true }`).
|
|
297
|
+
* @returns {object[]} Parsed objects, or `[]` when the file does not exist.
|
|
298
|
+
* @throws {Error} When the file exists but a non-blank line is invalid and `skipInvalid` is false.
|
|
299
|
+
*/
|
|
300
|
+
export function readJsonlFile(filePath, opts = {}) {
|
|
301
|
+
if (!existsSync(filePath)) return [];
|
|
302
|
+
const raw = readFileSync(filePath, 'utf8');
|
|
303
|
+
return readJsonlLines(raw, opts);
|
|
304
|
+
}
|