session-orchestrator 5.1.0 → 5.3.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/.agents/skills/architecture/SKILL.md +3 -1
- package/.agents/skills/autopilot/SKILL.md +6 -1
- package/.agents/skills/autopilot/agents/openai.yaml +5 -0
- package/.agents/skills/bootstrap/SKILL.md +7 -1
- package/.agents/skills/bootstrap/agents/openai.yaml +5 -0
- package/.agents/skills/brainstorm/SKILL.md +8 -1
- package/.agents/skills/brainstorm/agents/openai.yaml +5 -0
- package/.agents/skills/claude-md-drift-check/SKILL.md +3 -1
- package/.agents/skills/close/SKILL.md +21 -0
- package/.agents/skills/close/agents/openai.yaml +5 -0
- package/.agents/skills/convergence-monitoring/SKILL.md +4 -2
- package/.agents/skills/debug/SKILL.md +7 -1
- package/.agents/skills/debug/agents/openai.yaml +5 -0
- package/.agents/skills/discovery/SKILL.md +7 -2
- package/.agents/skills/discovery/agents/openai.yaml +5 -0
- package/.agents/skills/dispatcher/SKILL.md +7 -1
- package/.agents/skills/dispatcher/agents/openai.yaml +5 -0
- package/.agents/skills/docs-orchestrator/SKILL.md +3 -1
- package/.agents/skills/ecosystem-health/SKILL.md +3 -1
- package/.agents/skills/eli5/SKILL.md +7 -1
- package/.agents/skills/eli5/agents/openai.yaml +5 -0
- package/.agents/skills/eval/SKILL.md +7 -2
- package/.agents/skills/eval/agents/openai.yaml +5 -0
- package/.agents/skills/evolve/SKILL.md +8 -3
- package/.agents/skills/evolve/agents/openai.yaml +5 -0
- package/.agents/skills/frontmatter-guard/SKILL.md +3 -1
- package/.agents/skills/gitlab-ops/SKILL.md +3 -1
- package/.agents/skills/gitlab-portfolio/SKILL.md +3 -1
- package/.agents/skills/go/SKILL.md +22 -0
- package/.agents/skills/go/agents/openai.yaml +5 -0
- package/.agents/skills/grill/SKILL.md +7 -1
- package/.agents/skills/grill/agents/openai.yaml +5 -0
- package/.agents/skills/harness-audit/SKILL.md +20 -0
- package/.agents/skills/harness-audit/agents/openai.yaml +5 -0
- package/.agents/skills/hook-development/SKILL.md +3 -1
- package/.agents/skills/mcp-builder/SKILL.md +3 -1
- package/.agents/skills/memory-cleanup/SKILL.md +6 -1
- package/.agents/skills/memory-cleanup/agents/openai.yaml +5 -0
- package/.agents/skills/mode-selector/SKILL.md +3 -1
- package/.agents/skills/npm-publish/SKILL.md +4 -2
- package/.agents/skills/peekaboo-driver/SKILL.md +3 -1
- package/.agents/skills/persona-panel/SKILL.md +6 -1
- package/.agents/skills/persona-panel/agents/openai.yaml +5 -0
- package/.agents/skills/plan/SKILL.md +8 -2
- package/.agents/skills/plan/agents/openai.yaml +5 -0
- package/.agents/skills/playwright-driver/SKILL.md +3 -1
- package/.agents/skills/portfolio/SKILL.md +21 -0
- package/.agents/skills/portfolio/agents/openai.yaml +5 -0
- package/.agents/skills/quality-gates/SKILL.md +3 -1
- package/.agents/skills/reconcile/SKILL.md +6 -1
- package/.agents/skills/reconcile/agents/openai.yaml +5 -0
- package/.agents/skills/release/SKILL.md +22 -0
- package/.agents/skills/release/agents/openai.yaml +5 -0
- package/.agents/skills/remote-offload/SKILL.md +3 -1
- package/.agents/skills/repo-audit/SKILL.md +6 -1
- package/.agents/skills/repo-audit/agents/openai.yaml +5 -0
- package/.agents/skills/session/SKILL.md +21 -0
- package/.agents/skills/session/agents/openai.yaml +5 -0
- package/.agents/skills/session-end/SKILL.md +3 -1
- package/.agents/skills/session-plan/SKILL.md +3 -1
- package/.agents/skills/session-start/SKILL.md +3 -1
- package/.agents/skills/spinout/SKILL.md +6 -1
- package/.agents/skills/spinout/agents/openai.yaml +5 -0
- package/.agents/skills/sunset-review/SKILL.md +7 -1
- package/.agents/skills/sunset-review/agents/openai.yaml +5 -0
- package/.agents/skills/templates-ack/SKILL.md +21 -0
- package/.agents/skills/templates-ack/agents/openai.yaml +5 -0
- package/.agents/skills/test/SKILL.md +21 -0
- package/.agents/skills/test/agents/openai.yaml +5 -0
- package/.agents/skills/test-runner/SKILL.md +3 -1
- package/.agents/skills/tmux-layout/SKILL.md +3 -1
- package/.agents/skills/using-orchestrator/SKILL.md +3 -1
- package/.agents/skills/ux-grill/SKILL.md +7 -1
- package/.agents/skills/ux-grill/agents/openai.yaml +5 -0
- package/.agents/skills/vault-mirror/SKILL.md +3 -1
- package/.agents/skills/vault-sync/SKILL.md +3 -1
- package/.agents/skills/wave-executor/SKILL.md +3 -1
- package/.agents/skills/write-executable-plan/SKILL.md +3 -1
- package/.claude-plugin/marketplace.json +1 -1
- package/.claude-plugin/plugin.json +1 -1
- package/.codex-plugin/plugin.json +4 -4
- package/.codex-plugin/skills/autopilot/SKILL.md +5 -4
- package/.codex-plugin/skills/bootstrap/SKILL.md +8 -4
- package/.codex-plugin/skills/brainstorm/SKILL.md +11 -4
- package/.codex-plugin/skills/close/SKILL.md +3 -3
- package/.codex-plugin/skills/convergence-monitoring/SKILL.md +1 -1
- package/.codex-plugin/skills/debug/SKILL.md +11 -4
- package/.codex-plugin/skills/discovery/SKILL.md +8 -4
- package/.codex-plugin/skills/dispatcher/SKILL.md +4 -4
- package/.codex-plugin/skills/eli5/SKILL.md +9 -4
- package/.codex-plugin/skills/eval/SKILL.md +9 -4
- package/.codex-plugin/skills/evolve/SKILL.md +9 -4
- package/.codex-plugin/skills/go/SKILL.md +3 -3
- package/.codex-plugin/skills/grill/SKILL.md +11 -4
- package/.codex-plugin/skills/harness-audit/SKILL.md +4 -3
- package/.codex-plugin/skills/memory-cleanup/SKILL.md +9 -4
- package/.codex-plugin/skills/npm-publish/SKILL.md +1 -1
- package/.codex-plugin/skills/persona-panel/SKILL.md +5 -5
- package/.codex-plugin/skills/plan/SKILL.md +8 -4
- package/.codex-plugin/skills/portfolio/SKILL.md +3 -3
- package/.codex-plugin/skills/reconcile/SKILL.md +9 -4
- package/.codex-plugin/skills/release/SKILL.md +3 -3
- package/.codex-plugin/skills/repo-audit/SKILL.md +6 -4
- package/.codex-plugin/skills/session/SKILL.md +1 -1
- package/.codex-plugin/skills/spinout/SKILL.md +4 -4
- package/.codex-plugin/skills/sunset-review/SKILL.md +5 -4
- package/.codex-plugin/skills/test/SKILL.md +3 -3
- package/.codex-plugin/skills/ux-grill/SKILL.md +11 -4
- package/.cursor/commands/autopilot.md +4 -4
- package/.cursor/commands/bootstrap.md +5 -4
- package/.cursor/commands/brainstorm.md +5 -4
- package/.cursor/commands/close.md +4 -3
- package/.cursor/commands/debug.md +4 -4
- package/.cursor/commands/discovery.md +4 -4
- package/.cursor/commands/dispatcher.md +4 -4
- package/.cursor/commands/eli5.md +4 -4
- package/.cursor/commands/eval.md +4 -4
- package/.cursor/commands/evolve.md +4 -4
- package/.cursor/commands/go.md +4 -3
- package/.cursor/commands/grill.md +4 -4
- package/.cursor/commands/harness-audit.md +3 -3
- package/.cursor/commands/memory-cleanup.md +4 -4
- package/.cursor/commands/persona-panel.md +4 -4
- package/.cursor/commands/plan.md +5 -4
- package/.cursor/commands/portfolio.md +3 -3
- package/.cursor/commands/reconcile.md +4 -4
- package/.cursor/commands/release.md +4 -3
- package/.cursor/commands/repo-audit.md +4 -4
- package/.cursor/commands/session.md +1 -1
- package/.cursor/commands/spinout.md +4 -4
- package/.cursor/commands/sunset-review.md +4 -4
- package/.cursor/commands/test.md +3 -3
- package/.cursor/commands/ux-grill.md +4 -4
- package/.cursor/rules/000-session-orchestrator.mdc +0 -2
- package/.cursor/rules/010-session-workflow.mdc +2 -2
- package/.cursor/rules/050-plan.mdc +1 -1
- package/.cursor/skills/bootstrap/SKILL.md +1 -0
- package/.cursor/skills/close/SKILL.md +13 -0
- package/.cursor/skills/convergence-monitoring/SKILL.md +1 -0
- package/.cursor/skills/debug/SKILL.md +0 -1
- package/.cursor/skills/discovery/SKILL.md +0 -1
- package/.cursor/skills/dispatcher/SKILL.md +0 -1
- package/.cursor/skills/eli5/SKILL.md +0 -1
- package/.cursor/skills/eval/SKILL.md +1 -1
- package/.cursor/skills/evolve/SKILL.md +0 -1
- package/.cursor/skills/go/SKILL.md +13 -0
- package/.cursor/skills/grill/SKILL.md +0 -1
- package/.cursor/skills/harness-audit/SKILL.md +12 -0
- package/.cursor/skills/npm-publish/SKILL.md +1 -0
- package/.cursor/skills/portfolio/SKILL.md +12 -0
- package/.cursor/skills/release/SKILL.md +13 -0
- package/.cursor/skills/repo-audit/SKILL.md +0 -1
- package/.cursor/skills/sunset-review/SKILL.md +0 -1
- package/.cursor/skills/test/SKILL.md +12 -0
- package/.cursor/skills/ux-grill/SKILL.md +0 -1
- package/.cursor-plugin/plugin.json +1 -1
- package/.orchestrator/policy/blocked-commands.json +13 -4
- package/AGENTS.md +3 -2
- package/CHANGELOG.md +197 -0
- package/README.md +11 -9
- package/SECURITY.md +12 -0
- package/agents/dialectic-deriver.md +13 -10
- package/agents/eval-judge.md +67 -45
- package/agents/skill-applied-judge.md +34 -19
- package/commands/session.md +17 -3
- package/docs/baseline.md +12 -6
- package/docs/ci-setup.md +53 -0
- package/docs/codex-setup.md +15 -3
- package/docs/components.md +13 -6
- package/docs/events-schema.md +59 -9
- package/docs/install.md +16 -0
- package/docs/persona-panel.md +1 -1
- package/docs/pi-setup.md +1 -1
- package/docs/rule-authoring.md +135 -14
- package/docs/scope-collision-guard.md +2 -0
- package/docs/session-config-reference.md +106 -11
- package/docs/session-config-template.md +31 -2
- package/docs/telemetry.md +2 -0
- package/hooks/_lib/hook-import-set.json +125 -8
- package/hooks/_lib/subagent-paths.mjs +15 -0
- package/hooks/_lib/subagent-transcript.mjs +582 -31
- package/hooks/_lib/vcs-create-matcher.mjs +217 -62
- package/hooks/config-protection.mjs +11 -3
- package/hooks/cwd-change-restore.mjs +11 -3
- package/hooks/enforce-commands.mjs +70 -23
- package/hooks/enforce-scope.mjs +143 -33
- package/hooks/hooks-codex.json +1 -1
- package/hooks/hooks.json +1 -1
- package/hooks/loop-guard.mjs +11 -3
- package/hooks/on-session-end.mjs +72 -25
- package/hooks/on-session-start.mjs +48 -11
- package/hooks/on-stop.mjs +211 -23
- package/hooks/operator-steer.mjs +11 -3
- package/hooks/post-bash-issue-budget-refund.mjs +18 -8
- package/hooks/post-bash-write-verify.mjs +6 -2
- package/hooks/post-edit-import-probe.mjs +17 -9
- package/hooks/post-edit-validate.mjs +13 -5
- package/hooks/post-subagent-discovery-validator.mjs +98 -13
- package/hooks/post-tool-batch-wave-signal.mjs +200 -38
- package/hooks/post-tool-failure-corrective-context.mjs +11 -5
- package/hooks/post-tooluse-frontend-slop.mjs +10 -4
- package/hooks/pre-auq-clarity.mjs +18 -2
- package/hooks/pre-bash-destructive-guard.mjs +80 -9
- package/hooks/pre-bash-issue-budget.mjs +119 -28
- package/hooks/pre-bash-memory-propose-audit.mjs +86 -54
- package/hooks/pre-bash-sessions-ledger-guard.mjs +391 -20
- package/hooks/pre-bash-staging-fence.mjs +335 -31
- package/hooks/pre-bash-templates-first.mjs +19 -14
- package/hooks/pre-task-scope-disjoint.mjs +385 -5
- package/hooks/skill-invocation-telemetry.mjs +2 -1
- package/hooks/subagent-telemetry.mjs +15 -19
- package/hooks/wave-scope-commit-guard.mjs +197 -100
- package/monitors/monitors.json +1 -1
- package/output-styles/wave-summary.md +1 -1
- package/package.json +2 -1
- package/pi/prompts/autopilot.md +3 -3
- package/pi/prompts/bootstrap.md +3 -3
- package/pi/prompts/brainstorm.md +3 -3
- package/pi/prompts/close.md +2 -2
- package/pi/prompts/debug.md +3 -3
- package/pi/prompts/discovery.md +3 -3
- package/pi/prompts/dispatcher.md +3 -3
- package/pi/prompts/eli5.md +3 -3
- package/pi/prompts/eval.md +3 -3
- package/pi/prompts/evolve.md +3 -3
- package/pi/prompts/go.md +2 -2
- package/pi/prompts/grill.md +3 -3
- package/pi/prompts/harness-audit.md +2 -3
- package/pi/prompts/memory-cleanup.md +3 -3
- package/pi/prompts/persona-panel.md +3 -3
- package/pi/prompts/plan.md +3 -3
- package/pi/prompts/portfolio.md +2 -2
- package/pi/prompts/reconcile.md +3 -3
- package/pi/prompts/release.md +3 -3
- package/pi/prompts/repo-audit.md +3 -4
- package/pi/prompts/session.md +2 -2
- package/pi/prompts/spinout.md +3 -3
- package/pi/prompts/sunset-review.md +3 -3
- package/pi/prompts/templates-ack.md +1 -1
- package/pi/prompts/test.md +3 -3
- package/pi/prompts/ux-grill.md +3 -3
- package/rules/README.md +1 -1
- package/rules/opt-in-domain/prompt-caching.md +1 -1
- package/rules/opt-in-stack/backend-data.md +1 -1
- package/rules/opt-in-stack/backend.md +3 -3
- package/rules/opt-in-stack/frontend.md +1 -1
- package/rules/opt-in-stack/security-web.md +3 -3
- package/rules/opt-in-stack/swift.md +1 -1
- package/scripts/archive-closed-prds.mjs +2 -2
- package/scripts/auq-audit.mjs +2 -3
- package/scripts/autopilot.mjs +23 -2
- package/scripts/backfill-abandoned-sessions.mjs +171 -15
- package/scripts/backfill-evidence-digest.mjs +2 -1
- package/scripts/backfill-learnings-from-vault.mjs +2 -2
- package/scripts/check-package-manager.mjs +2 -2
- package/scripts/check-sessions-integrity.mjs +300 -0
- package/scripts/ci/assert-vitest-green.mjs +2 -1
- package/scripts/dialectic-deriver.mjs +50 -13
- package/scripts/emit-session.mjs +77 -32
- package/scripts/eval-session.mjs +65 -3
- package/scripts/export-hw-learnings.mjs +2 -1
- package/scripts/express-path.mjs +1 -1
- package/scripts/gc-stale-worktrees.mjs +2 -1
- package/scripts/generate-agents-skills.mjs +102 -29
- package/scripts/generate-codex-skills.mjs +48 -4
- package/scripts/generate-cursor-adapter.mjs +220 -11
- package/scripts/generate-hook-import-set.mjs +12 -27
- package/scripts/generate-pi-prompts.mjs +183 -13
- package/scripts/github-protection-audit.mjs +2 -3
- package/scripts/lib/agent-frontmatter.mjs +23 -1
- package/scripts/lib/agent-status.mjs +2 -31
- package/scripts/lib/auq/clarity.mjs +10 -2
- package/scripts/lib/auq/parse.mjs +12 -31
- package/scripts/lib/auq/schema.mjs +56 -41
- package/scripts/lib/auto-dialectic.mjs +304 -15
- package/scripts/lib/autopilot/flags.mjs +12 -1
- package/scripts/lib/autopilot/kill-switches.mjs +6 -3
- package/scripts/lib/autopilot/loop.mjs +14 -1
- package/scripts/lib/autopilot/stall-sampler.mjs +80 -23
- package/scripts/lib/ci-status-banner.mjs +376 -16
- package/scripts/lib/claude-md-budget-lint.mjs +2 -5
- package/scripts/lib/command-blocker.mjs +408 -33
- package/scripts/lib/config/dialectic.mjs +12 -3
- package/scripts/lib/config/drift-check.mjs +19 -0
- package/scripts/lib/config/gate.mjs +74 -0
- package/scripts/lib/config/reaper.mjs +162 -0
- package/scripts/lib/config.mjs +14 -0
- package/scripts/lib/convergence-monitor.mjs +76 -13
- package/scripts/lib/cursor-hook-bridge.mjs +2 -2
- package/scripts/lib/description-surface.mjs +2 -5
- package/scripts/lib/dispatcher/cli.mjs +2 -1
- package/scripts/lib/ecosystem-health.mjs +11 -0
- package/scripts/lib/ecosystem-wizard.mjs +2 -1
- package/scripts/lib/eval/engine.mjs +421 -53
- package/scripts/lib/eval/judge.mjs +463 -40
- package/scripts/lib/eval/schema.mjs +10 -1
- package/scripts/lib/events-rotation.mjs +221 -25
- package/scripts/lib/events-schema.mjs +114 -0
- package/scripts/lib/events.mjs +524 -5
- package/scripts/lib/fetch-baseline.mjs +3 -8
- package/scripts/lib/frontmatter-guard.mjs +21 -10
- package/scripts/lib/gates/gate-baseline.mjs +27 -2
- package/scripts/lib/gates/gate-full.mjs +28 -3
- package/scripts/lib/gates/gate-helpers.mjs +243 -21
- package/scripts/lib/gates/gate-incremental.mjs +28 -3
- package/scripts/lib/gates/gate-per-file.mjs +27 -2
- package/scripts/lib/gitlab-ops/stale-mr-sweep.mjs +2 -1
- package/scripts/lib/gitlab-portfolio/cli.mjs +2 -1
- package/scripts/lib/gitlab-portfolio/markdown-writer.mjs +6 -1
- package/scripts/lib/instruction-budget-guard.mjs +332 -50
- package/scripts/lib/io.mjs +42 -8
- package/scripts/lib/is-main-module.mjs +82 -0
- package/scripts/lib/issue-close-strip-labels.mjs +207 -49
- package/scripts/lib/js-mask.mjs +197 -0
- package/scripts/lib/learnings/evolve-telemetry.mjs +11 -7
- package/scripts/lib/locks/index.mjs +32 -25
- package/scripts/lib/maintenance-due-banner.mjs +122 -91
- package/scripts/lib/orphan-reaper.mjs +1588 -0
- package/scripts/lib/peer-cards/merger.mjs +48 -10
- package/scripts/lib/peer-cards/reader.mjs +78 -2
- package/scripts/lib/peer-discovery.mjs +2 -5
- package/scripts/lib/playwright-driver/runner.mjs +2 -1
- package/scripts/lib/process-group.mjs +899 -0
- package/scripts/lib/quality-gate.mjs +107 -28
- package/scripts/lib/reconcile/backlog.mjs +368 -0
- package/scripts/lib/reconcile/engine.mjs +55 -188
- package/scripts/lib/reconcile/rule-expiry-sweep.mjs +884 -0
- package/scripts/lib/reconcile/sanitize.mjs +69 -3
- package/scripts/lib/reconcile-nudge-banner.mjs +138 -45
- package/scripts/lib/resource-probe/parsers.mjs +31 -0
- package/scripts/lib/rule-loader.mjs +41 -12
- package/scripts/lib/rules-sync.mjs +2 -5
- package/scripts/lib/scope-echo.mjs +429 -7
- package/scripts/lib/scope-gate.mjs +605 -1
- package/scripts/lib/session-close-backfill.mjs +91 -12
- package/scripts/lib/session-id.mjs +9 -20
- package/scripts/lib/session-invocation.mjs +20 -0
- package/scripts/lib/session-schema/constants.mjs +30 -2
- package/scripts/lib/session-schema/normalizer.mjs +56 -4
- package/scripts/lib/session-schema.mjs +8 -3
- package/scripts/lib/session-start-probes.mjs +95 -10
- package/scripts/lib/sessions-canonical.mjs +23 -0
- package/scripts/lib/sessions-integrity-banner.mjs +7 -1
- package/scripts/lib/sessions-staleness-banner.mjs +193 -51
- package/scripts/lib/skill-evidence-window.mjs +891 -0
- package/scripts/lib/skill-evolution/candidate-intake.mjs +133 -12
- package/scripts/lib/skill-evolution/engine.mjs +18 -9
- package/scripts/lib/skill-judge.mjs +45 -3
- package/scripts/lib/state-md.mjs +84 -3
- package/scripts/lib/sunset/walker.mjs +31 -4
- package/scripts/lib/tail-window.mjs +56 -0
- package/scripts/lib/telemetry/schema.mjs +30 -0
- package/scripts/lib/telemetry/sync.mjs +61 -6
- package/scripts/lib/telemetry-flush-health-banner.mjs +4 -22
- package/scripts/lib/test-runner/issue-reconcile.mjs +48 -16
- package/scripts/lib/tests-src-ratio.mjs +2 -6
- package/scripts/lib/tmux-layout/telemetry-stats.mjs +74 -14
- package/scripts/lib/user-invocable-skills.mjs +205 -0
- package/scripts/lib/ux-grill/reconcile.mjs +48 -22
- package/scripts/lib/validate/check-agents-skills.mjs +26 -15
- package/scripts/lib/validate/check-banner-parity.mjs +2 -2
- package/scripts/lib/validate/check-cursor-adapter.mjs +3 -2
- package/scripts/lib/validate/check-dead-bridge.mjs +2 -2
- package/scripts/lib/validate/check-doc-cli-commands.mjs +2 -2
- package/scripts/lib/validate/check-entry-guard.mjs +329 -0
- package/scripts/lib/validate/check-guard-requires-parity.mjs +2 -2
- package/scripts/lib/validate/check-hook-entry-guards.mjs +636 -0
- package/scripts/lib/validate/check-hooks-emit-event-guard.mjs +2 -2
- package/scripts/lib/validate/check-learning-provenance.mjs +2 -2
- package/scripts/lib/validate/check-pi-prompts.mjs +1 -0
- package/scripts/lib/validate/check-rules.mjs +7 -5
- package/scripts/lib/validate/check-skill-links.mjs +35 -6
- package/scripts/lib/validate/check-skill-script-paths.mjs +241 -29
- package/scripts/lib/validate/check-test-git-config-target.mjs +26 -36
- package/scripts/lib/validate/check-unicode-safety.mjs +2 -2
- package/scripts/lib/validate/check-untracked-test-deps.mjs +9 -104
- package/scripts/lib/validate/check-unwired-features.mjs +220 -33
- package/scripts/lib/validate/check-validator-registration.mjs +36 -12
- package/scripts/lib/validate/check-vcs-repo-flag.mjs +2 -2
- package/scripts/lib/validate/confidential-names.mjs +10 -0
- package/scripts/lib/validate-vendored-rules.mjs +39 -12
- package/scripts/lib/vault-mirror/namespace.mjs +46 -8
- package/scripts/lib/vault-mirror/process.mjs +10 -3
- package/scripts/lib/vault-mirror/render-sessions.mjs +12 -2
- package/scripts/lib/vault-status/narrative-mirror.mjs +31 -7
- package/scripts/lib/vault-yaml.mjs +118 -0
- package/scripts/lib/wave-transcript-tail.mjs +2 -2
- package/scripts/lib/worktree/lifecycle.mjs +153 -1
- package/scripts/lock-reaper.mjs +2 -1
- package/scripts/materialize-wave-scope.mjs +87 -4
- package/scripts/migrate-sessions-jsonl.mjs +2 -1
- package/scripts/migrate-vault-paths.mjs +2 -3
- package/scripts/release-session-lock.mjs +305 -0
- package/scripts/release.mjs +109 -39
- package/scripts/relocate-vault-corpus.mjs +2 -3
- package/scripts/repair-invalid-sessions.mjs +2 -2
- package/scripts/resolve-session-invocation.mjs +59 -0
- package/scripts/run-quality-gate.mjs +156 -17
- package/scripts/session-shape.mjs +2 -2
- package/scripts/site-numbers.mjs +35 -11
- package/scripts/sweep-expired-rules.mjs +227 -0
- package/scripts/validate-plugin.mjs +21 -0
- package/scripts/validate-wave-scope.mjs +32 -105
- package/scripts/vault-consolidate.mjs +2 -2
- package/scripts/vault-mirror.mjs +11 -4
- package/scripts/wave-scope-binding.mjs +2 -3
- package/skills/_shared/bootstrap-gate.md +1 -1
- package/skills/_shared/monitor-patterns.md +1 -1
- package/skills/_shared/platform-tools.md +23 -11
- package/skills/_shared/research-evidence.md +53 -0
- package/skills/_shared/state-ownership.md +3 -0
- package/skills/autopilot/SKILL.md +80 -11
- package/skills/bootstrap/SKILL.md +51 -1
- package/skills/brainstorm/SKILL.md +16 -0
- package/skills/claude-md-drift-check/SKILL.md +1 -1
- package/skills/claude-md-drift-check/checker.mjs +49 -11
- package/{commands/close.md → skills/close/SKILL.md} +9 -3
- package/skills/convergence-monitoring/README.md +8 -1
- package/skills/convergence-monitoring/SIGNALS.md +50 -6
- package/skills/convergence-monitoring/SKILL.md +15 -6
- package/skills/debug/SKILL.md +10 -0
- package/skills/discovery/SKILL.md +24 -1
- package/skills/discovery/probes-session.md +2 -2
- package/skills/dispatcher/SKILL.md +38 -7
- package/skills/eli5/SKILL.md +11 -0
- package/skills/eval/SKILL.md +52 -23
- package/skills/eval/rubric-v1.md +1 -0
- package/skills/eval/rubric-v2.md +457 -0
- package/skills/evolve/SKILL.md +9 -2
- package/skills/evolve/references/evolve-dialectic-mode.md +46 -25
- package/skills/gitlab-ops/SKILL.md +3 -2
- package/{commands/go.md → skills/go/SKILL.md} +9 -1
- package/skills/grill/SKILL.md +19 -0
- package/{commands/harness-audit.md → skills/harness-audit/SKILL.md} +7 -2
- package/skills/hook-development/SKILL.md +46 -41
- package/skills/memory-cleanup/SKILL.md +7 -0
- package/skills/npm-publish/SKILL.md +2 -2
- package/skills/persona-panel/SKILL.md +56 -1
- package/skills/persona-panel/persona-format.md +1 -1
- package/skills/plan/SKILL.md +28 -1
- package/{commands/portfolio.md → skills/portfolio/SKILL.md} +8 -2
- package/skills/reconcile/SKILL.md +21 -0
- package/{commands/release.md → skills/release/SKILL.md} +16 -2
- package/skills/repo-audit/SKILL.md +7 -0
- package/skills/session-end/SKILL.md +13 -16
- package/skills/session-end/discovery-scan.md +1 -1
- package/skills/session-end/phase-3-6-tail.md +55 -9
- package/skills/session-end/plan-verification.md +2 -2
- package/skills/session-end/references/phase-5-issue-cleanup.md +9 -14
- package/skills/session-end/session-metrics-write.md +10 -0
- package/skills/session-plan/SKILL.md +18 -6
- package/skills/session-plan/references/session-plan-task-classification.md +2 -2
- package/skills/session-start/SKILL.md +5 -4
- package/skills/session-start/phase-8-5-express-path.md +6 -6
- package/skills/session-start/references/phase-1-5-session-continuity.md +1 -1
- package/skills/session-start/references/phase-2-7-portfolio-snapshot.md +1 -1
- package/skills/session-start/references/phase-4-ssot-environment-check.md +6 -4
- package/skills/spinout/SKILL.md +12 -1
- package/skills/sunset-review/SKILL.md +13 -0
- package/{commands/test.md → skills/test/SKILL.md} +10 -4
- package/skills/ux-grill/SKILL.md +20 -2
- package/skills/wave-executor/SKILL.md +14 -7
- package/skills/wave-executor/circuit-breaker.md +2 -0
- package/skills/wave-executor/references/wave-executor-state-init.md +18 -4
- package/skills/wave-executor/references/wave-loop-dispatch.md +5 -2
- package/skills/wave-executor/references/wave-loop-review.md +17 -1
- package/commands/autopilot.md +0 -80
- package/commands/bootstrap.md +0 -56
- package/commands/brainstorm.md +0 -48
- package/commands/debug.md +0 -36
- package/commands/discovery.md +0 -32
- package/commands/dispatcher.md +0 -59
- package/commands/eli5.md +0 -33
- package/commands/eval.md +0 -28
- package/commands/evolve.md +0 -10
- package/commands/grill.md +0 -45
- package/commands/memory-cleanup.md +0 -26
- package/commands/persona-panel.md +0 -121
- package/commands/plan.md +0 -15
- package/commands/reconcile.md +0 -23
- package/commands/repo-audit.md +0 -24
- package/commands/spinout.md +0 -15
- package/commands/sunset-review.md +0 -27
- package/commands/ux-grill.md +0 -51
|
@@ -173,6 +173,53 @@
|
|
|
173
173
|
* confused" as a bypass strategy. This is the one place the guard is closed
|
|
174
174
|
* rather than open, and it is closed because the alternative is a silent skip.
|
|
175
175
|
*
|
|
176
|
+
* ## Interpreter eval payloads (GitLab #1408)
|
|
177
|
+
*
|
|
178
|
+
* The blind spot this file used to NAME became the habitual shape. On
|
|
179
|
+
* 2026-09-19 a coordinator appended its own `sessions.jsonl` record with
|
|
180
|
+
* `node --input-type=module -e "… appendFileSync('.orchestrator/metrics/
|
|
181
|
+
* sessions.jsonl', JSON.stringify(rec) + '\n')"`; `emit-session.mjs` never ran
|
|
182
|
+
* that session, and the record landed with `ended_at` instead of `completed_at`
|
|
183
|
+
* and four required fields missing. Measured over 58 transcripts of this repo:
|
|
184
|
+
* 438 Bash commands mention the ledger, the SHELL write shapes above occurred
|
|
185
|
+
* **0 times**, and 46 are interpreter one-liners. Fleet-wide: 40 interpreter
|
|
186
|
+
* write calls, 38 with the ledger path as the FIRST argument. The guard was
|
|
187
|
+
* aimed at a shape nobody uses.
|
|
188
|
+
*
|
|
189
|
+
* What is covered now: ONE predicate (`findInterpreterLedgerWrite`), hung off
|
|
190
|
+
* the SAME per-segment loop as every other matcher here, so scoping is per
|
|
191
|
+
* STATEMENT — `node -e '<read>' ; echo ok` allows, `cmd && node -e '<append>'`
|
|
192
|
+
* denies on the second statement alone. It takes the eval-flag operand
|
|
193
|
+
* (`-e`/`--eval`/`-p`/`--print`, `python -c`, `perl -e`, `ruby -e`,
|
|
194
|
+
* `deno eval`, in separated / attached / `=`-joined spelling) and denies iff a
|
|
195
|
+
* write call from a CLOSED list (`appendFile(Sync)`, `writeFile(Sync)`,
|
|
196
|
+
* `createWriteStream`; Python `open(…, 'a'|'w'|'x'|…)`; Ruby `File.write` /
|
|
197
|
+
* `IO.write` / `File.open`; Perl's two `open` forms) has the literal basename
|
|
198
|
+
* `sessions.jsonl` in its FIRST ARGUMENT TEXT.
|
|
199
|
+
*
|
|
200
|
+
* The first-argument scoping is load-bearing, not an optimisation: keying on
|
|
201
|
+
* mere co-occurrence denies all three of the ledger READS measured in this
|
|
202
|
+
* repo's transcripts (each reads the ledger and writes
|
|
203
|
+
* `.orchestrator/tmp/session-entry.json` before piping into
|
|
204
|
+
* `emit-session.mjs`), and denies `readFileSync('…/sessions.jsonl')` +
|
|
205
|
+
* `writeFileSync('/tmp/x')` in one one-liner.
|
|
206
|
+
*
|
|
207
|
+
* KNOWN LIMITS — these stay limits, deliberately (BV-004):
|
|
208
|
+
* - The path held in a VARIABLE: `const p = '…/sessions.jsonl';
|
|
209
|
+
* appendFileSync(p, …)`. No constant propagation here; the first-argument
|
|
210
|
+
* text carries no basename.
|
|
211
|
+
* - A here-doc-fed interpreter (`node <<'EOF' … EOF`) — the body is skipped
|
|
212
|
+
* upstream by `scanCommand`, so no payload reaches this predicate.
|
|
213
|
+
* - An invoked SCRIPT FILE (`node tools/append.mjs`) — the ledger name is not
|
|
214
|
+
* in the command string at all.
|
|
215
|
+
* - A write API outside the closed list (`fs.open` + `write`, a stream from a
|
|
216
|
+
* helper, `Deno.writeTextFile`), and Python kwargs-only `open(file=…,
|
|
217
|
+
* mode='a')`.
|
|
218
|
+
* REVISIT TRIGGER: re-run the transcript census (`rg` for interpreter
|
|
219
|
+
* one-liners naming the ledger). If a shape from the four bullets above
|
|
220
|
+
* appears in ANY real transcript, widen the predicate then — not before. A
|
|
221
|
+
* guard widened against imagined shapes buys false positives at full price.
|
|
222
|
+
*
|
|
176
223
|
* ## What this does NOT catch (stated plainly — the bounds are the contract)
|
|
177
224
|
*
|
|
178
225
|
* - Indirection through a variable: `>> "$LEDGER"`, `tee "$LEDGER"`,
|
|
@@ -193,12 +240,29 @@
|
|
|
193
240
|
* buried in a quoted substitution is not the accident shape this guard is
|
|
194
241
|
* for — it is the "determined circumvention" line below.
|
|
195
242
|
* - In-place editors: `sed -i`, `perl -i`, `ed`, an interactive editor.
|
|
196
|
-
* - A write performed
|
|
197
|
-
* `
|
|
198
|
-
*
|
|
199
|
-
* `
|
|
200
|
-
*
|
|
201
|
-
*
|
|
243
|
+
* - A write performed by a SCRIPT FILE the command invokes
|
|
244
|
+
* (`node tools/append.mjs`) — the ledger name never appears in the command
|
|
245
|
+
* string at all, so nothing here can see it. Interpreter EVAL payloads
|
|
246
|
+
* (`node -e`, `python3 -c`) used to sit in this bullet too; since #1408 they
|
|
247
|
+
* are covered — see "Interpreter eval payloads" below for the exact bound.
|
|
248
|
+
* - A here-doc-fed interpreter (`node <<'EOF' … EOF`): here-doc BODIES are
|
|
249
|
+
* skipped as the data they are, so the payload never reaches the matcher.
|
|
250
|
+
* - A shell `-c` payload the segment does not NAME a shell for. Since
|
|
251
|
+
* 2026-09-20 a `-c` operand following a `bash`/`sh`/`zsh`/`dash`/`ksh`/`su`
|
|
252
|
+
* TOKEN is re-entered by the same matcher (`bash -c '… >> …/sessions.jsonl'`
|
|
253
|
+
* and `xargs -I{} sh -c '…'` both deny), but three shapes stay out:
|
|
254
|
+
* `ssh host '… >> …/sessions.jsonl'` (a REMOTE ledger — not this repo's
|
|
255
|
+
* file, so out of scope by nature, not by omission); a payload whose shell
|
|
256
|
+
* is only reachable through expansion (`"$SHELL" -c …`, `eval "$CMD"`);
|
|
257
|
+
* and a payload nested past MAX_PAYLOAD_DEPTH (2) — that cut is marked, not
|
|
258
|
+
* silent, when the dropped payload mentions the ledger (#998 item 2).
|
|
259
|
+
* This bullet REPLACES the one #1408 deleted (it named
|
|
260
|
+
* `bash -c '… >> …/sessions.jsonl'` as uncovered, which was true, and the
|
|
261
|
+
* replacement bullet narrowed the claim to script files and here-docs while
|
|
262
|
+
* the hole stayed open — a guard may have limits, it may not misstate them).
|
|
263
|
+
* REVISIT TRIGGER: re-run the transcript census for interpreter one-liners
|
|
264
|
+
* naming the ledger; widen only if one of these three shapes appears in a
|
|
265
|
+
* REAL transcript.
|
|
202
266
|
* - Obfuscation: `eval`, `base64 -d | sh`, a here-doc-fed shell. Here-doc
|
|
203
267
|
* BODIES are skipped as the data they are — `bash <<EOF … EOF` therefore
|
|
204
268
|
* hides its payload from this matcher by construction.
|
|
@@ -249,8 +313,7 @@ import path from 'node:path';
|
|
|
249
313
|
import { pathToFileURL } from 'node:url';
|
|
250
314
|
|
|
251
315
|
import { shouldRunHook } from './_lib/profile-gate.mjs';
|
|
252
|
-
|
|
253
|
-
if (!shouldRunHook('pre-bash-sessions-ledger-guard')) process.exit(0);
|
|
316
|
+
import { isMainModule } from '../scripts/lib/is-main-module.mjs';
|
|
254
317
|
|
|
255
318
|
// ---------------------------------------------------------------------------
|
|
256
319
|
// #993 — late-bound repo dependencies
|
|
@@ -324,8 +387,9 @@ const GUARD_CONSEQUENCE = {
|
|
|
324
387
|
' COMMITTED (HEAD) command lexer — any uncommitted change to that file is NOT in effect.',
|
|
325
388
|
],
|
|
326
389
|
inactive: [
|
|
327
|
-
' Consequence: a direct
|
|
328
|
-
' (>, >>, tee, dd of=, cp/mv destination
|
|
390
|
+
' Consequence: a direct write into .orchestrator/metrics/sessions.jsonl',
|
|
391
|
+
' (>, >>, tee, dd of=, cp/mv destination, or an interpreter payload such as',
|
|
392
|
+
' node -e / python -c — #1408) is NOT being blocked. This hook is a',
|
|
329
393
|
' fail-open nudge, not a security boundary — but repair it so the #958 corruption',
|
|
330
394
|
' class stays caught.',
|
|
331
395
|
],
|
|
@@ -859,6 +923,293 @@ function scanCommand(command) {
|
|
|
859
923
|
return { targets, sanitized: out, balanced: true };
|
|
860
924
|
}
|
|
861
925
|
|
|
926
|
+
// ---------------------------------------------------------------------------
|
|
927
|
+
// Interpreter eval payloads (#1408)
|
|
928
|
+
//
|
|
929
|
+
// ONE predicate, hung off the EXISTING per-segment loop in findWriteVerbTarget.
|
|
930
|
+
// Deliberately NOT a Bash parser and deliberately NOT in command-blocker.mjs:
|
|
931
|
+
// that module has five other consumers, and a JS payload re-entered by its SHELL
|
|
932
|
+
// matcher would be misread as shell.
|
|
933
|
+
// ---------------------------------------------------------------------------
|
|
934
|
+
|
|
935
|
+
/**
|
|
936
|
+
* Interpreter verbs whose eval payload is read, mapped to the DIALECT its code
|
|
937
|
+
* is judged in. `node`/`bun`/`deno` share the `fs` API surface, so one dialect
|
|
938
|
+
* covers all three.
|
|
939
|
+
*/
|
|
940
|
+
const INTERPRETER_FAMILY = new Map([
|
|
941
|
+
['node', 'js'],
|
|
942
|
+
['nodejs', 'js'],
|
|
943
|
+
['bun', 'js'],
|
|
944
|
+
['deno', 'js'],
|
|
945
|
+
['python', 'python'],
|
|
946
|
+
['python2', 'python'],
|
|
947
|
+
['python3', 'python'],
|
|
948
|
+
['perl', 'perl'],
|
|
949
|
+
['ruby', 'ruby'],
|
|
950
|
+
]);
|
|
951
|
+
|
|
952
|
+
/** Flags whose operand is CODE, not a script path — per dialect. */
|
|
953
|
+
const INTERPRETER_EVAL_FLAGS = {
|
|
954
|
+
js: new Set(['-e', '--eval', '-p', '--print']),
|
|
955
|
+
python: new Set(['-c']),
|
|
956
|
+
perl: new Set(['-e', '-E']),
|
|
957
|
+
ruby: new Set(['-e']),
|
|
958
|
+
};
|
|
959
|
+
|
|
960
|
+
/**
|
|
961
|
+
* CLOSED list of write calls per dialect. Nothing outside it is matched — a
|
|
962
|
+
* matcher keyed on "the payload mentions the ledger AND writes something" was
|
|
963
|
+
* measured to false-positive on all three real read one-liners in this repo's
|
|
964
|
+
* transcripts (they read the ledger and write `.orchestrator/tmp/…`).
|
|
965
|
+
*/
|
|
966
|
+
const INTERPRETER_WRITE_CALLS = {
|
|
967
|
+
js: ['appendFileSync', 'appendFile', 'writeFileSync', 'writeFile', 'createWriteStream'],
|
|
968
|
+
python: ['open'],
|
|
969
|
+
perl: ['open'],
|
|
970
|
+
ruby: ['File.write', 'IO.write', 'File.open'],
|
|
971
|
+
};
|
|
972
|
+
|
|
973
|
+
/**
|
|
974
|
+
* Collect the eval-payload operands of one interpreter invocation.
|
|
975
|
+
*
|
|
976
|
+
* `deno` is the odd one: its eval mode is a SUBCOMMAND (`deno eval '<code>'`),
|
|
977
|
+
* not a flag. Everything else takes `-e`/`-c`-style flags in three spellings —
|
|
978
|
+
* separated (`-e '<code>'`), attached (`-e'<code>'`) and `=`-joined
|
|
979
|
+
* (`--eval='<code>'`).
|
|
980
|
+
*
|
|
981
|
+
* A QUOTED `-e` token is still treated as the flag: bash passes `'-e'` and `-e`
|
|
982
|
+
* identically, so skipping the quoted spelling would be a one-character bypass.
|
|
983
|
+
*
|
|
984
|
+
* @param {string} verb
|
|
985
|
+
* @param {string} family
|
|
986
|
+
* @param {Array<{ text: string, quoted: boolean }>} args - tokens after the verb
|
|
987
|
+
* @returns {string[]}
|
|
988
|
+
*/
|
|
989
|
+
function collectCodeOperands(verb, family, args) {
|
|
990
|
+
const operands = [];
|
|
991
|
+
if (verb === 'deno') {
|
|
992
|
+
const at = args.findIndex((a) => a.text === 'eval');
|
|
993
|
+
if (at === -1) return operands;
|
|
994
|
+
for (const a of args.slice(at + 1)) {
|
|
995
|
+
if (!a.quoted && a.text.startsWith('-')) continue;
|
|
996
|
+
operands.push(a.text);
|
|
997
|
+
break;
|
|
998
|
+
}
|
|
999
|
+
return operands;
|
|
1000
|
+
}
|
|
1001
|
+
|
|
1002
|
+
const flags = INTERPRETER_EVAL_FLAGS[family];
|
|
1003
|
+
if (!flags) return operands;
|
|
1004
|
+
for (let i = 0; i < args.length; i++) {
|
|
1005
|
+
const text = args[i].text;
|
|
1006
|
+
const eq = text.indexOf('=');
|
|
1007
|
+
if (eq > 0 && flags.has(text.slice(0, eq))) { operands.push(text.slice(eq + 1)); continue; }
|
|
1008
|
+
if (flags.has(text)) {
|
|
1009
|
+
if (i + 1 < args.length) { operands.push(args[i + 1].text); i++; }
|
|
1010
|
+
continue;
|
|
1011
|
+
}
|
|
1012
|
+
for (const flag of flags) {
|
|
1013
|
+
if (flag.length === 2 && !text.startsWith('--') && text.length > 2 && text.startsWith(flag)) {
|
|
1014
|
+
operands.push(text.slice(2));
|
|
1015
|
+
break;
|
|
1016
|
+
}
|
|
1017
|
+
}
|
|
1018
|
+
}
|
|
1019
|
+
return operands;
|
|
1020
|
+
}
|
|
1021
|
+
|
|
1022
|
+
/**
|
|
1023
|
+
* Indices of the `(` that OPENS a call to `name` in `code`.
|
|
1024
|
+
*
|
|
1025
|
+
* Identifier-boundary aware on the left, so `appendFile` does not match inside
|
|
1026
|
+
* `appendFileSync` (which is a separate list entry) and `myWriteFile` is not a
|
|
1027
|
+
* `writeFile`. A receiver prefix is fine — `.` is not an identifier char, so
|
|
1028
|
+
* `fs.appendFileSync` and `require('fs').appendFileSync` both resolve.
|
|
1029
|
+
*
|
|
1030
|
+
* @param {string} code
|
|
1031
|
+
* @param {string} name
|
|
1032
|
+
* @returns {number[]}
|
|
1033
|
+
*/
|
|
1034
|
+
function findCallOpenings(code, name) {
|
|
1035
|
+
const out = [];
|
|
1036
|
+
for (let from = 0; ;) {
|
|
1037
|
+
const idx = code.indexOf(name, from);
|
|
1038
|
+
if (idx === -1) return out;
|
|
1039
|
+
from = idx + name.length;
|
|
1040
|
+
const before = idx > 0 ? code[idx - 1] : '';
|
|
1041
|
+
if (before && /[A-Za-z0-9_$]/.test(before)) continue;
|
|
1042
|
+
let j = from;
|
|
1043
|
+
while (j < code.length && (code[j] === ' ' || code[j] === '\t' || code[j] === '\n')) j++;
|
|
1044
|
+
if (code[j] === '(') out.push(j);
|
|
1045
|
+
}
|
|
1046
|
+
}
|
|
1047
|
+
|
|
1048
|
+
/**
|
|
1049
|
+
* Split the argument list opening at `open` on TOP-LEVEL commas.
|
|
1050
|
+
*
|
|
1051
|
+
* Quote- and nesting-aware, single pass, no backtracking regex — this runs on
|
|
1052
|
+
* every Bash call of every session, and a path that can throw or hang reads as
|
|
1053
|
+
* NO decision under the exit-0 protocol, i.e. fail-OPEN in the wrong direction.
|
|
1054
|
+
* Returns `null` when the list never closes (judged as "no hit", never a throw).
|
|
1055
|
+
*
|
|
1056
|
+
* @param {string} code
|
|
1057
|
+
* @param {number} open - index of the opening `(`
|
|
1058
|
+
* @returns {string[]|null}
|
|
1059
|
+
*/
|
|
1060
|
+
function splitCallArgs(code, open) {
|
|
1061
|
+
const args = [];
|
|
1062
|
+
let cur = '';
|
|
1063
|
+
let depth = 0;
|
|
1064
|
+
let quote = null;
|
|
1065
|
+
for (let i = open; i < code.length; i++) {
|
|
1066
|
+
const ch = code[i];
|
|
1067
|
+
if (quote !== null) {
|
|
1068
|
+
if (ch === '\\' && i + 1 < code.length) { cur += ch + code[i + 1]; i++; continue; }
|
|
1069
|
+
if (ch === quote) quote = null;
|
|
1070
|
+
cur += ch;
|
|
1071
|
+
continue;
|
|
1072
|
+
}
|
|
1073
|
+
if (ch === '"' || ch === "'" || ch === '`') { quote = ch; cur += ch; continue; }
|
|
1074
|
+
if (ch === '(' || ch === '[' || ch === '{') {
|
|
1075
|
+
depth++;
|
|
1076
|
+
if (depth > 1) cur += ch;
|
|
1077
|
+
continue;
|
|
1078
|
+
}
|
|
1079
|
+
if (ch === ')' || ch === ']' || ch === '}') {
|
|
1080
|
+
depth--;
|
|
1081
|
+
if (depth === 0) { args.push(cur); return args; }
|
|
1082
|
+
cur += ch;
|
|
1083
|
+
continue;
|
|
1084
|
+
}
|
|
1085
|
+
if (ch === ',' && depth === 1) { args.push(cur); cur = ''; continue; }
|
|
1086
|
+
cur += ch;
|
|
1087
|
+
}
|
|
1088
|
+
return null;
|
|
1089
|
+
}
|
|
1090
|
+
|
|
1091
|
+
/**
|
|
1092
|
+
* Does any operand after the first name a WRITE mode?
|
|
1093
|
+
*
|
|
1094
|
+
* `open(p, 'a')` / `open(p, mode='w')` / `File.open(p, 'a')` write; `open(p)`
|
|
1095
|
+
* and `open(p, 'r')` read. `+` counts (`'r+'` writes).
|
|
1096
|
+
*
|
|
1097
|
+
* @param {string[]} args
|
|
1098
|
+
* @returns {boolean}
|
|
1099
|
+
*/
|
|
1100
|
+
function hasWriteModeOperand(args) {
|
|
1101
|
+
for (let i = 1; i < args.length; i++) {
|
|
1102
|
+
const m = /^\s*(?:mode\s*=\s*)?(['"])([^'"]*)\1\s*$/.exec(args[i]);
|
|
1103
|
+
if (m && /[awx+]/.test(m[2])) return true;
|
|
1104
|
+
}
|
|
1105
|
+
return false;
|
|
1106
|
+
}
|
|
1107
|
+
|
|
1108
|
+
/**
|
|
1109
|
+
* Judge ONE resolved write call. The FIRST-ARGUMENT scoping is load-bearing:
|
|
1110
|
+
* keying on mere co-occurrence of a write verb and the ledger basename anywhere
|
|
1111
|
+
* in the payload denies `readFileSync('…/sessions.jsonl')` +
|
|
1112
|
+
* `writeFileSync('/tmp/x')` in one one-liner — the shape all three measured
|
|
1113
|
+
* ledger READS in this repo's transcripts actually have.
|
|
1114
|
+
*
|
|
1115
|
+
* Perl is the exception, by its own grammar: `open(FH, '>>', $p)` puts the path
|
|
1116
|
+
* THIRD and `open(FH, ">>$p")` glues mode to path. A read open never carries a
|
|
1117
|
+
* `>`, so "the ledger is an operand AND some operand carries `>`" is exact
|
|
1118
|
+
* rather than a guess.
|
|
1119
|
+
*
|
|
1120
|
+
* @param {string} family
|
|
1121
|
+
* @param {string} name
|
|
1122
|
+
* @param {string[]} args
|
|
1123
|
+
* @returns {string|null} the offending argument text, or null
|
|
1124
|
+
*/
|
|
1125
|
+
function judgeInterpreterCall(family, name, args) {
|
|
1126
|
+
if (args.length === 0) return null;
|
|
1127
|
+
if (family === 'perl') {
|
|
1128
|
+
const hit = args.find((a) => a.includes(LEDGER_BASENAME));
|
|
1129
|
+
if (!hit) return null;
|
|
1130
|
+
return args.some((a) => a.includes('>')) ? hit : null;
|
|
1131
|
+
}
|
|
1132
|
+
if (!args[0].includes(LEDGER_BASENAME)) return null;
|
|
1133
|
+
if (family === 'python' || name === 'File.open') {
|
|
1134
|
+
return hasWriteModeOperand(args) ? args[0] : null;
|
|
1135
|
+
}
|
|
1136
|
+
return args[0];
|
|
1137
|
+
}
|
|
1138
|
+
|
|
1139
|
+
/**
|
|
1140
|
+
* The #1408 predicate: does this interpreter invocation write the ledger from
|
|
1141
|
+
* INSIDE its eval payload?
|
|
1142
|
+
*
|
|
1143
|
+
* Called from the per-segment loop of {@link findWriteVerbTarget}, so scoping is
|
|
1144
|
+
* per STATEMENT: `node -e '<read>' ; echo ok` is judged on the statement that
|
|
1145
|
+
* hit, and `cmd && node -e '<append>'` denies on the second one alone.
|
|
1146
|
+
*
|
|
1147
|
+
* @param {string} verb - resolved segment verb (basename, wrappers already peeled)
|
|
1148
|
+
* @param {Array<{ text: string, quoted: boolean }>} args - tokens after the verb
|
|
1149
|
+
* @returns {string|null} the offending target, or null
|
|
1150
|
+
*/
|
|
1151
|
+
function findInterpreterLedgerWrite(verb, args) {
|
|
1152
|
+
const family = INTERPRETER_FAMILY.get(verb);
|
|
1153
|
+
if (!family) return null;
|
|
1154
|
+
for (const code of collectCodeOperands(verb, family, args)) {
|
|
1155
|
+
if (typeof code !== 'string' || !code.includes(LEDGER_BASENAME)) continue;
|
|
1156
|
+
for (const name of INTERPRETER_WRITE_CALLS[family]) {
|
|
1157
|
+
for (const open of findCallOpenings(code, name)) {
|
|
1158
|
+
const callArgs = splitCallArgs(code, open);
|
|
1159
|
+
if (!callArgs) continue;
|
|
1160
|
+
const hit = judgeInterpreterCall(family, name, callArgs);
|
|
1161
|
+
if (hit) return `${hit.trim()} (${name}(…) inside a ${verb} eval payload)`;
|
|
1162
|
+
}
|
|
1163
|
+
}
|
|
1164
|
+
}
|
|
1165
|
+
return null;
|
|
1166
|
+
}
|
|
1167
|
+
|
|
1168
|
+
/**
|
|
1169
|
+
* Shells whose `-c <payload>` operand is a command line the shell EXECUTES.
|
|
1170
|
+
*
|
|
1171
|
+
* DELIBERATE DUPLICATE of `DASH_C_SHELLS` in scripts/lib/command-blocker.mjs,
|
|
1172
|
+
* which does not export it (nor its `dashCPayloads`). Change one, change both.
|
|
1173
|
+
*/
|
|
1174
|
+
const DASH_C_SHELLS = new Set(['bash', 'sh', 'zsh', 'dash', 'ksh', 'su']);
|
|
1175
|
+
|
|
1176
|
+
/**
|
|
1177
|
+
* Collect the command lines a shell nested in this segment will EXECUTE, so
|
|
1178
|
+
* {@link findWriteVerbTarget} can re-enter the SAME matcher on them.
|
|
1179
|
+
*
|
|
1180
|
+
* `resolveSegmentVerb` reports WRAPPER payloads (`env -S '…'`) and this hook
|
|
1181
|
+
* has recursed on those since #982 — but a `bash -c` payload is not a wrapper
|
|
1182
|
+
* payload, and command-blocker's own `dedupedSegmentPayloads` (the piece that
|
|
1183
|
+
* adds them) is internal to that module. So the guard saw `bash -c` as the
|
|
1184
|
+
* verb `bash` with an inert quoted operand and ALLOWED, while the identical
|
|
1185
|
+
* unwrapped command DENIED — one word of bypass, measured 2026-09-20.
|
|
1186
|
+
*
|
|
1187
|
+
* Keyed on a shell TOKEN anywhere in the segment rather than on the resolved
|
|
1188
|
+
* verb: that is what reaches `xargs -I{} sh -c "…"` (verb `xargs`) and
|
|
1189
|
+
* `find … -exec sh -c "…" \;`, and it keeps `grep -c <pattern>` out, because
|
|
1190
|
+
* `grep` is not a shell name. Recursion is bounded by MAX_PAYLOAD_DEPTH, and a
|
|
1191
|
+
* payload that carries no write intent still ALLOWs — the false-positive floor
|
|
1192
|
+
* (`bash -c "jq . <ledger>"`, `bash -c "tail -1 <ledger> | jq ."`) is judged by
|
|
1193
|
+
* the same matcher as its unwrapped form, not by a second, weaker one.
|
|
1194
|
+
*
|
|
1195
|
+
* @param {Array<{ text: string, quoted: boolean }>} segment
|
|
1196
|
+
* @returns {string[]} distinct payload strings
|
|
1197
|
+
*/
|
|
1198
|
+
function dashCShellPayloads(segment) {
|
|
1199
|
+
const out = new Set();
|
|
1200
|
+
for (let i = 0; i < segment.length - 1; i++) {
|
|
1201
|
+
const tok = segment[i];
|
|
1202
|
+
if (tok.quoted || !DASH_C_SHELLS.has(path.basename(tok.text))) continue;
|
|
1203
|
+
for (let j = i + 1; j < segment.length - 1; j++) {
|
|
1204
|
+
if (/^-[A-Za-z]*c$/.test(segment[j].text)) {
|
|
1205
|
+
out.add(segment[j + 1].text);
|
|
1206
|
+
break;
|
|
1207
|
+
}
|
|
1208
|
+
}
|
|
1209
|
+
}
|
|
1210
|
+
return [...out];
|
|
1211
|
+
}
|
|
1212
|
+
|
|
862
1213
|
/**
|
|
863
1214
|
* Find a non-redirect write verb (`tee`, `dd of=`, `cp`/`mv` destination)
|
|
864
1215
|
* whose target is the ledger.
|
|
@@ -923,6 +1274,12 @@ function findWriteVerbTarget(command, depth = 0, marks = []) {
|
|
|
923
1274
|
}
|
|
924
1275
|
}
|
|
925
1276
|
|
|
1277
|
+
// A shell `-c` payload is a command line too, and until 2026-09-20 it was
|
|
1278
|
+
// the guard's one-word bypass: `bash -c "echo x >> <ledger>"` ALLOWED while
|
|
1279
|
+
// `echo x >> <ledger>` DENIED. See dashCShellPayloads for why this is keyed
|
|
1280
|
+
// on a shell token rather than the resolved verb.
|
|
1281
|
+
for (const p of dashCShellPayloads(segment)) payloadSet.add(p);
|
|
1282
|
+
|
|
926
1283
|
// `env -S 'tee -a <ledger>'` hides a whole command line in one operand.
|
|
927
1284
|
// Recurse on the payload with the SAME matcher rather than a second,
|
|
928
1285
|
// weaker one — bounded by MAX_PAYLOAD_DEPTH. Kept BEFORE the verb dispatch
|
|
@@ -951,6 +1308,12 @@ function findWriteVerbTarget(command, depth = 0, marks = []) {
|
|
|
951
1308
|
// other's (the lib docblock's positional-walk caveat).
|
|
952
1309
|
const args = segment.slice(index + 1);
|
|
953
1310
|
|
|
1311
|
+
// #1408 — the write happens INSIDE an interpreter payload (`node -e`,
|
|
1312
|
+
// `python3 -c`, …), where there is no shell write intent to resolve. One
|
|
1313
|
+
// predicate, judged on THIS statement; see findInterpreterLedgerWrite.
|
|
1314
|
+
const interpreterTarget = findInterpreterLedgerWrite(verb, args);
|
|
1315
|
+
if (interpreterTarget) return interpreterTarget;
|
|
1316
|
+
|
|
954
1317
|
// The repair CLI mutates the ledger from inside Node, so there is no
|
|
955
1318
|
// redirect target for the structural matcher to see. Match only its
|
|
956
1319
|
// effective apply contract at the resolved token seam: `node`, the
|
|
@@ -1163,8 +1526,9 @@ async function main() {
|
|
|
1163
1526
|
`Direct write to the sessions ledger blocked: '${shown}'`,
|
|
1164
1527
|
`The ledger is append-only through its validating writer:`,
|
|
1165
1528
|
` node scripts/emit-session.mjs --entry '<json>' (or pipe the JSON on stdin)`,
|
|
1166
|
-
`Hand-composing a record and appending it with a shell redirect
|
|
1167
|
-
`
|
|
1529
|
+
`Hand-composing a record and appending it — with a shell redirect, or from`,
|
|
1530
|
+
`inside an interpreter (node -e / python -c) — skips schema validation. That is`,
|
|
1531
|
+
`exactly how the malformed records in GitLab #958 and #1408 landed.`,
|
|
1168
1532
|
`Override (intentional maintenance only): run the session with`,
|
|
1169
1533
|
`SO_DISABLED_HOOKS=pre-bash-sessions-ledger-guard`,
|
|
1170
1534
|
`See: GitLab #958, skills/session-end/session-metrics-write.md`,
|
|
@@ -1203,11 +1567,18 @@ try {
|
|
|
1203
1567
|
process.exit(0); // fail-open, but no longer fail-silent
|
|
1204
1568
|
}
|
|
1205
1569
|
|
|
1206
|
-
//
|
|
1207
|
-
//
|
|
1208
|
-
|
|
1209
|
-
|
|
1210
|
-
|
|
1211
|
-
|
|
1212
|
-
|
|
1213
|
-
|
|
1570
|
+
// Entry guard (#1393): run only as the node script the harness execs — a bare
|
|
1571
|
+
// `import()` must run no handler and must not exit the importing process.
|
|
1572
|
+
if (isMainModule(import.meta.url)) {
|
|
1573
|
+
// #211: exit 0 immediately (silent allow) when this hook is disabled via profile/env
|
|
1574
|
+
if (!shouldRunHook('pre-bash-sessions-ledger-guard')) process.exit(0);
|
|
1575
|
+
|
|
1576
|
+
// Top-level error handler — fail-OPEN (see the module docblock). Never let a
|
|
1577
|
+
// non-zero exit leak: on this protocol exit 0 + empty stdout is "no decision".
|
|
1578
|
+
main().catch((e) => {
|
|
1579
|
+
process.stderr.write(
|
|
1580
|
+
`⚠ pre-bash-sessions-ledger-guard: internal error — ${e?.message || e}\n`,
|
|
1581
|
+
);
|
|
1582
|
+
process.exit(0);
|
|
1583
|
+
});
|
|
1584
|
+
}
|