session-orchestrator 5.12.0 → 5.13.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 +2 -2
- package/.claude-plugin/plugin.json +4 -1
- package/.codex-plugin/agents/explorer.toml +1 -1
- package/.codex-plugin/agents/session-reviewer.toml +2 -2
- package/.codex-plugin/agents/wave-worker.toml +1 -1
- package/.codex-plugin/plugin.json +1 -1
- package/.cursor-plugin/plugin.json +1 -1
- package/.orchestrator/policy/blocked-commands.json +8 -0
- package/.orchestrator/policy/public-export.json +30 -0
- package/CHANGELOG.md +162 -4
- package/README.md +126 -127
- package/agents/analyst.md +7 -2
- package/agents/architect-reviewer.md +7 -2
- package/agents/code-implementer.md +4 -2
- package/agents/db-specialist.md +9 -2
- package/agents/dialectic-deriver.md +9 -3
- package/agents/docs-writer.md +7 -2
- package/agents/eval-judge.md +7 -1
- package/agents/qa-strategist.md +7 -2
- package/agents/security-reviewer.md +9 -2
- package/agents/session-reviewer.md +18 -2
- package/agents/skill-applied-judge.md +7 -1
- package/agents/test-writer.md +9 -2
- package/agents/ui-developer.md +9 -2
- package/agents/ux-evaluator.md +6 -1
- package/commands/session.md +1 -1
- package/commands/templates-ack.md +89 -18
- package/docs/agent-authoring.md +30 -11
- package/docs/ci-setup.md +38 -2
- package/docs/codex-setup.md +33 -9
- package/docs/components.md +13 -2
- package/docs/cursor-setup.md +3 -3
- package/docs/events-schema.md +5 -4
- package/docs/evidence-candidates.md +63 -0
- package/docs/fleet-checkin-audit.md +7 -4
- package/docs/github-mirror-protection.md +150 -3
- package/docs/install.md +90 -17
- package/docs/instruction-delivery.md +1 -1
- package/docs/migration-v4.md +2 -2
- package/docs/mods.md +118 -0
- package/docs/owner-config-schema.md +1 -0
- package/docs/plugin-architecture-v3.md +1 -1
- package/docs/pm-skills-marketplace.md +4 -3
- package/docs/rule-authoring.md +24 -13
- package/docs/scope-collision-guard.md +2 -1
- package/docs/session-config-reference.md +49 -15
- package/docs/session-config-template.md +30 -22
- package/docs/sessions-ledger-repair.md +137 -0
- package/docs/vault-docs-architecture.md +1 -1
- package/hooks/_lib/hook-import-set.json +406 -73
- package/hooks/_lib/hook-root.mjs +301 -0
- package/hooks/_lib/lock-bootstrap.mjs +434 -4
- package/hooks/_lib/orchestrator-ignore.mjs +3 -0
- package/hooks/_lib/subagent-transcript.mjs +2 -2
- package/hooks/_lib/vcs-create-matcher.mjs +13 -17
- package/hooks/cwd-change-restore.mjs +5 -1
- package/hooks/enforce-commands.mjs +4 -3
- package/hooks/enforce-scope.mjs +11 -1
- package/hooks/hooks-codex.json +48 -2
- package/hooks/hooks.json +31 -31
- package/hooks/on-session-end.mjs +38 -15
- package/hooks/on-session-start.mjs +139 -49
- package/hooks/on-stop.mjs +11 -6
- package/hooks/post-bash-issue-budget-refund.mjs +6 -1
- package/hooks/post-bash-write-verify.mjs +8 -4
- package/hooks/post-edit-validate.mjs +45 -52
- package/hooks/post-subagent-discovery-validator.mjs +2 -2
- package/hooks/post-tool-batch-wave-signal.mjs +8 -4
- package/hooks/post-tool-failure-corrective-context.mjs +5 -1
- package/hooks/pre-bash-destructive-guard.mjs +220 -8
- package/hooks/pre-bash-issue-budget.mjs +6 -1
- package/hooks/pre-bash-sessions-ledger-guard.mjs +691 -123
- package/hooks/pre-bash-staging-fence.mjs +258 -14
- package/hooks/pre-bash-templates-first.mjs +28 -27
- package/hooks/run-node.sh +1 -1
- package/hooks/skill-invocation-telemetry.mjs +15 -8
- package/hooks/subagent-telemetry.mjs +77 -14
- package/hooks/wave-scope-commit-guard.mjs +143 -14
- package/mods/so-hud/.claude-plugin/plugin.json +11 -0
- package/mods/so-hud/README.md +57 -0
- package/mods/so-hud/hooks/beacon-io.mjs +101 -0
- package/mods/so-hud/hooks/heredoc-scan.ts +394 -0
- package/mods/so-hud/hooks/hooks.json +1 -0
- package/mods/so-hud/hooks/logic.ts +182 -0
- package/mods/so-hud/hooks/register.tsx +670 -0
- package/mods/so-hud/hooks/rules.ts +2998 -0
- package/mods/so-hud/tsconfig.json +8 -0
- package/mods/so-hud/types/index.d.ts +36 -0
- package/package.json +4 -2
- package/rules/_index.md +1 -1
- package/rules/always-on/ask-via-tool.md +1 -1
- package/rules/always-on/loop-and-monitor.md +17 -194
- package/rules/always-on/parallel-sessions.md +3 -1
- package/rules/opt-in-stack/backend-data.md +1 -1
- package/rules/opt-in-stack/backend.md +1 -1
- package/rules/opt-in-stack/security-web.md +5 -1
- package/scripts/agent-usage-census.mjs +79 -0
- package/scripts/apply-session-learnings.mjs +34 -6
- package/scripts/archive-closed-prds.mjs +21 -5
- package/scripts/autopilot.mjs +11 -5
- package/scripts/backfill-abandoned-sessions.mjs +13 -3
- package/scripts/backfill-evidence-digest.mjs +2 -1
- package/scripts/backfill-learnings-from-vault.mjs +61 -6
- package/scripts/backfill-sessions.mjs +16 -2
- package/scripts/check-package-manager.mjs +26 -8
- package/scripts/check-sessions-integrity.mjs +4 -1
- package/scripts/ci/assert-vitest-green.mjs +3 -0
- package/scripts/ci/windows-smoke.mjs +201 -0
- package/scripts/close-precheck.mjs +55 -0
- package/scripts/dialectic-deriver.mjs +20 -12
- package/scripts/emit-event.mjs +2 -2
- package/scripts/emit-session.mjs +239 -20
- package/scripts/eval-session.mjs +17 -3
- package/scripts/export-hw-learnings.mjs +29 -2
- package/scripts/fleet-checkin-audit.mjs +8 -4
- package/scripts/fleet-checkin.mjs +39 -27
- package/scripts/gc-stale-worktrees.mjs +6 -1
- package/scripts/github-export.mjs +128 -0
- package/scripts/github-protection-audit.mjs +3 -1
- package/scripts/handover-queue-check.mjs +209 -0
- package/scripts/harness-audit.mjs +6 -4
- package/scripts/lib/agent-frontmatter.mjs +57 -10
- package/scripts/lib/agent-status.mjs +125 -7
- package/scripts/lib/agent-usage-census.mjs +394 -0
- package/scripts/lib/auto-dialectic.mjs +17 -10
- package/scripts/lib/auto-dream.mjs +22 -14
- package/scripts/lib/autopilot/loop.mjs +3 -1
- package/scripts/lib/autopilot/recent-runs.mjs +4 -2
- package/scripts/lib/autopilot/stall-sampler.mjs +2 -1
- package/scripts/lib/autopilot/telemetry.mjs +5 -1
- package/scripts/lib/autopilot/worktree-pipeline.mjs +5 -5
- package/scripts/lib/baseline-archetypes.mjs +10 -2
- package/scripts/lib/blocked-commands-policy.mjs +4 -3
- package/scripts/lib/build-live-signals.mjs +13 -9
- package/scripts/lib/ci-status-banner.mjs +329 -24
- package/scripts/lib/codex/plugin-contract.mjs +8 -0
- package/scripts/lib/cold-start-detector.mjs +5 -2
- package/scripts/lib/command-blocker.mjs +603 -43
- package/scripts/lib/common.mjs +3 -2
- package/scripts/lib/config/cross-repo.mjs +74 -7
- package/scripts/lib/config/host-paths.mjs +6 -2
- package/scripts/lib/config/section-extractor.mjs +65 -11
- package/scripts/lib/config/template-keys.mjs +153 -0
- package/scripts/lib/config/vault-integration.mjs +1 -0
- package/scripts/lib/config.mjs +2 -2
- package/scripts/lib/coordinator-snapshot.mjs +1 -1
- package/scripts/lib/discovery/triage-state.mjs +2 -1
- package/scripts/lib/dispatcher/rank.mjs +5 -7
- package/scripts/lib/ecosystem-health.mjs +2 -2
- package/scripts/lib/eval/engine.mjs +3 -2
- package/scripts/lib/eval/session-resolve.mjs +6 -2
- package/scripts/lib/eval/sink.mjs +4 -3
- package/scripts/lib/events-retention-banner.mjs +3 -2
- package/scripts/lib/events-schema.mjs +10 -0
- package/scripts/lib/events.mjs +23 -12
- package/scripts/lib/evolve/autonomy-verdict.mjs +4 -9
- package/scripts/lib/evolve/autopilot-effectiveness.mjs +8 -18
- package/scripts/lib/fleet-protocol.mjs +4 -0
- package/scripts/lib/gates/gate-full.mjs +172 -11
- package/scripts/lib/gates/gate-helpers.mjs +236 -23
- package/scripts/lib/git-config-drift.mjs +141 -7
- package/scripts/lib/github-export.mjs +447 -0
- package/scripts/lib/gitlab-portfolio/cli.mjs +16 -5
- package/scripts/lib/handover-queue-marks.mjs +159 -0
- package/scripts/lib/hardware-pattern-detector.mjs +103 -13
- package/scripts/lib/harness-audit/categories/category1.mjs +16 -4
- package/scripts/lib/harness-audit/categories/category4.mjs +11 -8
- package/scripts/lib/harness-audit/categories/category6.mjs +25 -5
- package/scripts/lib/harness-audit/categories/category9.mjs +10 -4
- package/scripts/lib/host-identity.mjs +9 -5
- package/scripts/lib/http-request.mjs +64 -0
- package/scripts/lib/instruction-budget-guard.mjs +30 -22
- package/scripts/lib/io.mjs +6 -3
- package/scripts/lib/issue-budget-reconcile.mjs +3 -2
- package/scripts/lib/issue-budget.mjs +36 -7
- package/scripts/lib/learnings/evidence-candidates.mjs +154 -0
- package/scripts/lib/learnings/expiry-sweep.mjs +99 -7
- package/scripts/lib/learnings/io.mjs +289 -15
- package/scripts/lib/learnings/select.mjs +53 -4
- package/scripts/lib/learnings/store-role.mjs +209 -0
- package/scripts/lib/learnings/surface.mjs +46 -7
- package/scripts/lib/ledger-path.mjs +113 -0
- package/scripts/lib/maintenance-due-banner.mjs +63 -12
- package/scripts/lib/memory-banner.mjs +5 -6
- package/scripts/lib/memory-proposals/collector.mjs +5 -2
- package/scripts/lib/memory-proposals/sink.mjs +7 -4
- package/scripts/lib/memory-proposals/store.mjs +22 -5
- package/scripts/lib/mode-selector-accuracy.mjs +2 -1
- package/scripts/lib/no-follow-open.mjs +57 -0
- package/scripts/lib/orphan-reaper/reaper-audit.mjs +4 -2
- package/scripts/lib/own-lock-root.mjs +95 -0
- package/scripts/lib/owner-interview.mjs +2 -1
- package/scripts/lib/owner-yaml.mjs +70 -7
- package/scripts/lib/platform.mjs +161 -9
- package/scripts/lib/plugin-update-banner.mjs +15 -11
- package/scripts/lib/pre-push-local-partial.mjs +117 -0
- package/scripts/lib/pre-push-remote-gate.mjs +151 -25
- package/scripts/lib/process-group.mjs +270 -34
- package/scripts/lib/project-hygiene.mjs +28 -0
- package/scripts/lib/quality-gate.mjs +33 -9
- package/scripts/lib/reconcile/backlog.mjs +29 -15
- package/scripts/lib/reconcile/engine.mjs +26 -2
- package/scripts/lib/reconcile/idempotency.mjs +65 -3
- package/scripts/lib/reconcile/rule-expiry-sweep.mjs +53 -12
- package/scripts/lib/reconcile/verdict-ledger.mjs +224 -0
- package/scripts/lib/reconcile-nudge-banner.mjs +40 -6
- package/scripts/lib/resource-probe/evaluate.mjs +32 -9
- package/scripts/lib/resource-probe/probe-platform.mjs +3 -1
- package/scripts/lib/rule-loader.mjs +20 -10
- package/scripts/lib/scope-baseline.mjs +123 -8
- package/scripts/lib/scope-echo.mjs +5 -2
- package/scripts/lib/scope-gate.mjs +55 -20
- package/scripts/lib/session-close-backfill.mjs +75 -7
- package/scripts/lib/session-end/close-precheck.mjs +151 -0
- package/scripts/lib/session-end/phase-skip.mjs +8 -7
- package/scripts/lib/session-end/tail-runner.mjs +7 -2
- package/scripts/lib/session-id.mjs +9 -1
- package/scripts/lib/session-identity/own-session.mjs +63 -0
- package/scripts/lib/session-lock.mjs +114 -0
- package/scripts/lib/session-record-repair.mjs +245 -35
- package/scripts/lib/session-registry.mjs +72 -9
- package/scripts/lib/session-schema/constants.mjs +9 -0
- package/scripts/lib/session-schema/filters.mjs +56 -1
- package/scripts/lib/session-schema/validator.mjs +15 -5
- package/scripts/lib/session-start-probes.mjs +149 -22
- package/scripts/lib/session-token-rollup.mjs +2 -1
- package/scripts/lib/session-work-trees.mjs +166 -0
- package/scripts/lib/sessions-canonical.mjs +61 -3
- package/scripts/lib/sessions-integrity-banner.mjs +17 -39
- package/scripts/lib/sessions-staleness-banner.mjs +8 -5
- package/scripts/lib/sh-quote.mjs +1 -0
- package/scripts/lib/skill-evidence-window.mjs +5 -1
- package/scripts/lib/skill-health/join.mjs +10 -9
- package/scripts/lib/skill-invocations-schema.mjs +13 -0
- package/scripts/lib/skill-judgments-schema.mjs +6 -2
- package/scripts/lib/snapshot-probe.mjs +414 -0
- package/scripts/lib/so-hud/collect.mjs +49 -5
- package/scripts/lib/so-hud/runtime.mjs +15 -4
- package/scripts/lib/spiral-carryover.mjs +8 -1
- package/scripts/lib/state-md/frontmatter-mutators.mjs +266 -8
- package/scripts/lib/state-md/idle-reset.mjs +14 -5
- package/scripts/lib/state-md/mission-status.mjs +5 -1
- package/scripts/lib/state-md-peer-guard.mjs +2 -0
- package/scripts/lib/state-md.mjs +31 -8
- package/scripts/lib/sunset/walker.mjs +4 -3
- package/scripts/lib/telemetry/consent.mjs +4 -2
- package/scripts/lib/telemetry/queue.mjs +3 -3
- package/scripts/lib/telemetry/sync.mjs +45 -9
- package/scripts/lib/test-runner/artifact-paths.mjs +10 -2
- package/scripts/lib/tmux-layout/layouts.mjs +20 -3
- package/scripts/lib/ux-grill/paths.mjs +5 -1
- package/scripts/lib/validate/check-agents.mjs +37 -6
- package/scripts/lib/validate/check-component-paths.mjs +25 -1
- package/scripts/lib/validate/check-guard-requires-parity.mjs +3 -3
- package/scripts/lib/validate/check-hooks-symmetry.mjs +53 -32
- package/scripts/lib/validate/check-json-files.mjs +3 -1
- package/scripts/lib/validate/check-learning-provenance.mjs +3 -2
- package/scripts/lib/validate/check-learnings-shell-writes.mjs +10 -4
- package/scripts/lib/validate/check-metrics-ledger-root.mjs +234 -0
- package/scripts/lib/validate/check-owner-leakage.mjs +159 -11
- package/scripts/lib/validate/check-test-posix-shell.mjs +246 -0
- package/scripts/lib/validate/check-unwired-features.mjs +4 -2
- package/scripts/lib/validate/check-vcs-repo-flag-positional.mjs +58 -0
- package/scripts/lib/validate/check-vcs-repo-flag.mjs +259 -73
- package/scripts/lib/validate/check-workflow-continue-on-error.mjs +63 -0
- package/scripts/lib/validate/confidential-names.mjs +3 -1
- package/scripts/lib/vault-mirror/migrate-ids.mjs +321 -0
- package/scripts/lib/vault-mirror/namespace.mjs +19 -7
- package/scripts/lib/vault-mirror/process.mjs +56 -27
- package/scripts/lib/vault-mirror/prune.mjs +5 -2
- package/scripts/lib/vault-mirror/render-learnings.mjs +3 -3
- package/scripts/lib/vault-mirror/render-sessions.mjs +36 -53
- package/scripts/lib/vault-mirror/retention.mjs +2 -2
- package/scripts/lib/vault-mirror/utils.mjs +79 -0
- package/scripts/lib/vault-staleness-banner.mjs +4 -7
- package/scripts/lib/vault-status/board-writer.mjs +2 -1
- package/scripts/lib/vault-status/narrative-mirror.mjs +66 -7
- package/scripts/lib/wave-executor/foreign-dispatch.mjs +20 -0
- package/scripts/lib/win-process-tree.mjs +137 -0
- package/scripts/lib/workspace.mjs +1 -1
- package/scripts/lib/worktree/constants.mjs +1 -1
- package/scripts/lib/worktree/lifecycle.mjs +1 -1
- package/scripts/lib/worktree/listing.mjs +1 -1
- package/scripts/lib/zx.mjs +50 -0
- package/scripts/mcp-server.mjs +16 -7
- package/scripts/mcp-server.sh +13 -83
- package/scripts/measure-policy-cache-effectiveness.mjs +7 -5
- package/scripts/memory-propose.mjs +10 -3
- package/scripts/migrate-cold-start-seed.mjs +7 -0
- package/scripts/migrate-legacy-learnings.sh +22 -0
- package/scripts/migrate-sessions-jsonl.mjs +16 -2
- package/scripts/migrate-vault-paths.mjs +29 -13
- package/scripts/parse-config.mjs +71 -14
- package/scripts/print-learnings-index.mjs +25 -2
- package/scripts/release-session-lock.mjs +94 -7
- package/scripts/release.mjs +451 -24
- package/scripts/relocate-vault-corpus.mjs +11 -7
- package/scripts/repair-invalid-sessions.mjs +68 -3
- package/scripts/resolve-session-invocation.mjs +10 -17
- package/scripts/run-migrate-v2-cross-repo.mjs +56 -1
- package/scripts/run-quality-gate.mjs +33 -7
- package/scripts/self-update.mjs +56 -7
- package/scripts/session-commit-files.mjs +133 -0
- package/scripts/site-numbers.mjs +52 -9
- package/scripts/snapshot-probe.mjs +156 -0
- package/scripts/so-hud.mjs +29 -6
- package/scripts/state-md-idle-reset.mjs +2 -1
- package/scripts/sweep-expired-learnings.mjs +54 -6
- package/scripts/sweep-expired-rules.mjs +4 -1
- package/scripts/validate-plugin.mjs +22 -0
- package/scripts/vault-integration-watcher.mjs +54 -6
- package/scripts/vault-mirror.mjs +9 -0
- package/skills/_shared/fleet-protocol.md +142 -1
- package/skills/_shared/instruction-file-resolution.md +1 -1
- package/skills/_shared/monitor-patterns.md +44 -25
- package/skills/_shared/platform-tools.md +7 -0
- package/skills/_shared/state-ownership.md +3 -2
- package/skills/architecture/references/domain-model.md +1 -1
- package/skills/bootstrap/SKILL.md +3 -1
- package/skills/bootstrap/_shared-template.md +4 -4
- package/skills/bootstrap/fast-template.md +1 -1
- package/skills/bootstrap/private-contract.md +1 -1
- package/skills/bootstrap/references/bootstrap-retroactive-flow.md +17 -9
- package/skills/bootstrap/templates/agents/project-code-review.md +5 -1
- package/skills/bootstrap/templates/agents/project-discovery.md +5 -1
- package/skills/bootstrap/templates/agents/project-quality-gate.md +5 -1
- package/skills/claude-md-drift-check/SKILL.md +3 -3
- package/skills/claude-md-drift-check/checker.mjs +61 -101
- package/skills/close/SKILL.md +20 -7
- package/skills/discovery/probes/docs-staleness.mjs +7 -5
- package/skills/discovery/probes/frontend-slop.mjs +7 -3
- package/skills/discovery/probes/ssot-code-diff.mjs +5 -3
- package/skills/discovery/probes/supply-chain-slopcheck.mjs +10 -8
- package/skills/discovery/probes/vault-narrative-staleness.mjs +30 -10
- package/skills/discovery/probes/vault-staleness.mjs +5 -3
- package/skills/discovery/probes-session.md +3 -0
- package/skills/discovery/probes-vault.md +2 -0
- package/skills/dispatcher/SKILL.md +1 -1
- package/skills/eval/SKILL.md +3 -1
- package/skills/evolve/SKILL.md +2 -0
- package/skills/evolve/references/evolve-analyze-mode.md +3 -3
- package/skills/evolve/references/evolve-dialectic-mode.md +1 -1
- package/skills/gitlab-ops/SKILL.md +7 -6
- package/skills/gitlab-portfolio/SKILL.md +2 -2
- package/skills/go/SKILL.md +13 -1
- package/skills/npm-publish/SKILL.md +5 -2
- package/skills/plan/SKILL.md +23 -10
- package/skills/plan/mode-feature.md +13 -8
- package/skills/plan/mode-new.md +6 -14
- package/skills/plan/prd-feature-template.md +19 -0
- package/skills/plan/prd-full-template.md +12 -5
- package/skills/plan/prd-premortem-panel.md +48 -0
- package/skills/plan/prd-reviewer-prompt.md +11 -3
- package/skills/plan/soul.md +1 -1
- package/skills/portfolio/SKILL.md +5 -6
- package/skills/quality-gates/SKILL.md +2 -4
- package/skills/reconcile/SKILL.md +27 -4
- package/skills/release/SKILL.md +16 -8
- package/skills/session-end/SKILL.md +94 -11
- package/skills/session-end/phase-3-2-docs-verification.md +1 -0
- package/skills/session-end/phase-3-6-tail.md +11 -7
- package/skills/session-end/phase-3-7a-recommendations.md +9 -2
- package/skills/session-end/plan-verification.md +16 -1
- package/skills/session-end/references/phase-3-documentation-updates.md +14 -12
- package/skills/session-end/references/phase-5-issue-cleanup.md +1 -1
- package/skills/session-end/references/session-summary-template.md +4 -0
- package/skills/session-end/session-metrics-write.md +7 -4
- package/skills/session-plan/SKILL.md +5 -3
- package/skills/session-start/SKILL.md +9 -3
- package/skills/session-start/phase-4-5-resource-health.md +11 -5
- package/skills/session-start/phase-8-5-express-path.md +4 -3
- package/skills/session-start/references/phase-1-2-session-lock.md +7 -5
- package/skills/session-start/references/phase-1-5-session-continuity.md +2 -2
- package/skills/session-start/references/phase-1-7-vault-status-board.md +1 -1
- package/skills/session-start/references/phase-4-ssot-environment-check.md +3 -2
- package/skills/tmux-layout/SKILL.md +2 -2
- package/skills/vault-sync/validator.mjs +3 -2
- package/skills/wave-executor/SKILL.md +5 -4
- package/skills/wave-executor/references/wave-executor-state-init.md +3 -3
- package/skills/wave-executor/references/wave-loop-dispatch.md +11 -5
- package/skills/wave-executor/references/wave-loop-review.md +13 -5
- package/skills/wave-executor/references/wave-loop-scope-manifest.md +1 -1
- package/assets/og-card.png +0 -0
- package/assets/og-card.svg +0 -47
- package/assets/wave-lifecycle.svg +0 -98
|
@@ -23,9 +23,10 @@
|
|
|
23
23
|
* queue (bounded, oldest-dropped) and the session closes with zero user-facing
|
|
24
24
|
* error.
|
|
25
25
|
*
|
|
26
|
-
* Node ESM.
|
|
26
|
+
* Node ESM. Networking uses the Stdlib HTTP helper.
|
|
27
27
|
*/
|
|
28
28
|
|
|
29
|
+
import { httpRequest } from '../http-request.mjs';
|
|
29
30
|
import path from 'node:path';
|
|
30
31
|
import os from 'node:os';
|
|
31
32
|
import fs from 'node:fs';
|
|
@@ -43,10 +44,11 @@ import { loadOwnerConfig } from '../owner-yaml.mjs';
|
|
|
43
44
|
import { readJsonlFile } from '../io.mjs';
|
|
44
45
|
import { readEventsWithRotations } from '../events.mjs';
|
|
45
46
|
import { resolveEventsLedgerRoot } from '../platform.mjs';
|
|
47
|
+
import { resolveLedgerPath } from '../ledger-path.mjs';
|
|
46
48
|
import { readCanonicalSessions } from '../sessions-canonical.mjs';
|
|
47
49
|
import { resolvePrivateConfigDir } from '../config/private-config-dir.mjs';
|
|
48
50
|
import { readSessionProfile } from '../state-md.mjs';
|
|
49
|
-
import {
|
|
51
|
+
import { resolveStateMdWriteTarget } from '../state-md/frontmatter-mutators.mjs';
|
|
50
52
|
|
|
51
53
|
// ---------------------------------------------------------------------------
|
|
52
54
|
// Constants
|
|
@@ -73,7 +75,7 @@ function resolveEndpoint(env) {
|
|
|
73
75
|
|
|
74
76
|
/**
|
|
75
77
|
* The default network sender: one POST carrying the whole batch array as the
|
|
76
|
-
* JSON body (the server accepts arrays), with
|
|
78
|
+
* JSON body (the server accepts arrays), with a wall-clock timeout. Resolves
|
|
77
79
|
* on a 2xx status, rejects on anything else (which routes the caller into the
|
|
78
80
|
* offline queue).
|
|
79
81
|
*
|
|
@@ -85,11 +87,12 @@ function resolveEndpoint(env) {
|
|
|
85
87
|
function defaultSender({ env, timeoutMs }) {
|
|
86
88
|
const endpoint = resolveEndpoint(env);
|
|
87
89
|
return async (batches) => {
|
|
88
|
-
const res = await
|
|
90
|
+
const res = await httpRequest(endpoint, {
|
|
89
91
|
method: 'POST',
|
|
90
92
|
headers: { 'Content-Type': 'application/json' },
|
|
91
93
|
body: JSON.stringify(batches),
|
|
92
|
-
|
|
94
|
+
timeoutMs,
|
|
95
|
+
maxBytes: 64 * 1024,
|
|
93
96
|
});
|
|
94
97
|
if (!res.ok) {
|
|
95
98
|
// The status travels ON the error: `flush` needs it to tell a TRANSPORT
|
|
@@ -307,7 +310,15 @@ export function readSessionProfileForMetricsDir(metricsDir) {
|
|
|
307
310
|
// metricsDir is `<repoRoot>/.orchestrator/metrics` by construction (every
|
|
308
311
|
// caller builds it that way); two levels up is the repo root.
|
|
309
312
|
const repoRoot = path.resolve(metricsDir, '..', '..');
|
|
310
|
-
|
|
313
|
+
// #1636: the write side's ownership rule — another platform's STATE.md not
|
|
314
|
+
// proven to be this process's is no profile source (ABSENT IS NOT EMPTY:
|
|
315
|
+
// null ⇒ the ping omits the field). Deliberately NO warnForeignStateMd line:
|
|
316
|
+
// this runs inside flush, which the hooks call in-process at SessionEnd or
|
|
317
|
+
// spawn detached with `stdio: 'ignore'` — the line would be hook noise or
|
|
318
|
+
// lost, and an omitted optional field needs no operator action.
|
|
319
|
+
const { path: stateMdPath, rejected } = resolveStateMdWriteTarget(repoRoot);
|
|
320
|
+
if (rejected) return null;
|
|
321
|
+
return readSessionProfile(fs.readFileSync(stateMdPath, 'utf8'));
|
|
311
322
|
} catch {
|
|
312
323
|
return null;
|
|
313
324
|
}
|
|
@@ -494,6 +505,23 @@ export function deriveSessionFromEvents(metricsDir) {
|
|
|
494
505
|
}
|
|
495
506
|
}
|
|
496
507
|
|
|
508
|
+
/**
|
|
509
|
+
* `skill-invocations.jsonl` for the metrics dir `metricsDir` (#1722): a
|
|
510
|
+
* `<root>/.orchestrator/metrics` dir resolves through {@link resolveLedgerPath}
|
|
511
|
+
* for `<root>` — the main checkout's ledger when `<root>` is a linked worktree
|
|
512
|
+
* that ignores it, else the dir's own — and any other dir is read as given.
|
|
513
|
+
*
|
|
514
|
+
* @param {string} metricsDir
|
|
515
|
+
* @returns {string}
|
|
516
|
+
*/
|
|
517
|
+
function skillInvocationsPathFor(metricsDir) {
|
|
518
|
+
const parent = path.dirname(metricsDir);
|
|
519
|
+
if (path.basename(metricsDir) === 'metrics' && path.basename(parent) === '.orchestrator') {
|
|
520
|
+
return resolveLedgerPath('.orchestrator/metrics/skill-invocations.jsonl', path.dirname(parent));
|
|
521
|
+
}
|
|
522
|
+
return path.join(metricsDir, 'skill-invocations.jsonl');
|
|
523
|
+
}
|
|
524
|
+
|
|
497
525
|
/**
|
|
498
526
|
* Build ONE whitelist-projected usage-ping record from the local JSONL streams.
|
|
499
527
|
*
|
|
@@ -537,11 +565,19 @@ export function buildBatch({
|
|
|
537
565
|
consentState,
|
|
538
566
|
} = {}) {
|
|
539
567
|
try {
|
|
568
|
+
// `dir` stays the caller's metrics dir: STATE.md and the events fallback
|
|
569
|
+
// below derive their own roots from it.
|
|
570
|
+
// ledger-root: the caller's dir as given; each ledger in it resolves below
|
|
540
571
|
const dir = metricsDir || path.join(process.cwd(), '.orchestrator', 'metrics');
|
|
541
572
|
const nowIso = now || new Date().toISOString();
|
|
542
573
|
|
|
543
|
-
|
|
544
|
-
|
|
574
|
+
// #1722: each ledger resolves on its own, an explicit metricsDir included
|
|
575
|
+
// (the SessionEnd flush hands over the LAUNCH dir's, which is a linked
|
|
576
|
+
// worktree for a session started there; deriveSessionFromEvents maps the
|
|
577
|
+
// events fallback the same way) — sessions via the one-ledger root
|
|
578
|
+
// (#1514/#1584), skill-invocations via skillInvocationsPathFor().
|
|
579
|
+
const sessions = readCanonicalSessions({ filePath: path.join(resolveEventsLedgerRoot(dir), 'sessions.jsonl') });
|
|
580
|
+
const invocations = readJsonlFile(skillInvocationsPathFor(dir), { skipInvalid: true });
|
|
545
581
|
|
|
546
582
|
const sessionRecord = mostRecentSession(sessions);
|
|
547
583
|
|
|
@@ -855,7 +891,7 @@ export function shouldDailyFlush({ statePath, queuePath, metricsDir, now = Date.
|
|
|
855
891
|
// (271 records / 292 KB in this repo, 2026-08-23) that is sub-millisecond
|
|
856
892
|
// and it only runs once the 24h gate above has already passed. Revisit
|
|
857
893
|
// with a tail-read if any repo's sessions.jsonl passes ~10 MB.
|
|
858
|
-
const dir = metricsDir || path.join(process.cwd(), '.orchestrator', 'metrics');
|
|
894
|
+
const dir = metricsDir || path.join(resolveEventsLedgerRoot(process.cwd()), '.orchestrator', 'metrics');
|
|
859
895
|
const sessions = readCanonicalSessions({ filePath: path.join(dir, 'sessions.jsonl') });
|
|
860
896
|
const last = mostRecentSession(sessions);
|
|
861
897
|
const completedMs = Date.parse(last?.completed_at);
|
|
@@ -17,6 +17,7 @@
|
|
|
17
17
|
*/
|
|
18
18
|
|
|
19
19
|
import path from 'node:path';
|
|
20
|
+
import { resolveLedgerPath } from '../ledger-path.mjs';
|
|
20
21
|
|
|
21
22
|
const ROLLUP_REL = '.orchestrator/metrics/test-runs.jsonl';
|
|
22
23
|
const TEST_RUNS_REL = '.orchestrator/metrics/test-runs';
|
|
@@ -62,6 +63,7 @@ export function makeRunId() {
|
|
|
62
63
|
* @throws {TypeError} on an invalid runId (see {@link assertRunId})
|
|
63
64
|
*/
|
|
64
65
|
export function runDirPath(runId) {
|
|
66
|
+
// ledger-root: per-run artifact dir of this working tree, kept local
|
|
65
67
|
return path.join(TEST_RUNS_REL, assertRunId(runId));
|
|
66
68
|
}
|
|
67
69
|
|
|
@@ -112,8 +114,14 @@ export function consoleLogPath(runId) {
|
|
|
112
114
|
|
|
113
115
|
/**
|
|
114
116
|
* Path to the shared JSONL rollup file (across all runs).
|
|
117
|
+
*
|
|
118
|
+
* Without `repoRoot` it is the repo-relative constant. With `repoRoot` it is
|
|
119
|
+
* the absolute ledger path, routed to the main checkout when `repoRoot` is a
|
|
120
|
+
* linked worktree of a repo that ignores the rollup (#1722).
|
|
121
|
+
*
|
|
122
|
+
* @param {string} [repoRoot]
|
|
115
123
|
* @returns {string}
|
|
116
124
|
*/
|
|
117
|
-
export function jsonlRollupPath() {
|
|
118
|
-
return ROLLUP_REL;
|
|
125
|
+
export function jsonlRollupPath(repoRoot) {
|
|
126
|
+
return typeof repoRoot === 'string' && repoRoot !== '' ? resolveLedgerPath(ROLLUP_REL, repoRoot) : ROLLUP_REL;
|
|
119
127
|
}
|
|
@@ -25,6 +25,8 @@
|
|
|
25
25
|
|
|
26
26
|
import path from 'node:path';
|
|
27
27
|
import { resolveStateDir } from '../platform.mjs';
|
|
28
|
+
import { resolveOwnLockRootFor } from '../own-lock-root.mjs';
|
|
29
|
+
import { readProcessLocalSessionIds } from '../session-identity/own-session.mjs';
|
|
28
30
|
import { shellQuote } from '../sh-quote.mjs';
|
|
29
31
|
import { detectVcsCommand } from './vcs-detector.mjs';
|
|
30
32
|
import { isSessionCollision } from './tmux-shell.mjs';
|
|
@@ -70,6 +72,11 @@ const STATUS_PANE_POLL_SECONDS = 2;
|
|
|
70
72
|
*
|
|
71
73
|
* Exported so a consumer test can run the render ONCE instead of the poll loop.
|
|
72
74
|
*
|
|
75
|
+
* `repoRoot` (#1730): the agent-status writer (`hooks/subagent-telemetry.mjs`)
|
|
76
|
+
* writes beside the session's OWN lock, which is not always the checkout the
|
|
77
|
+
* second terminal sits in. A given root is written into the command as a
|
|
78
|
+
* literal; without one the pane reads its own `process.cwd()`, as before.
|
|
79
|
+
*
|
|
73
80
|
* Shell-shape notes (both load-bearing):
|
|
74
81
|
* - the `-e` script is wrapped in SINGLE quotes, so the JS below uses only
|
|
75
82
|
* double-quoted strings: inside single quotes no `$`, backtick or interactive
|
|
@@ -77,15 +84,21 @@ const STATUS_PANE_POLL_SECONDS = 2;
|
|
|
77
84
|
* - the module path is resolved from THIS file's own URL (never a hardcoded home
|
|
78
85
|
* path), exactly as the sibling pane commands stay relative-to-cwd.
|
|
79
86
|
*
|
|
87
|
+
* @param {{ repoRoot?: string|null }} [opts]
|
|
80
88
|
* @returns {string} a single shell command; prints the pane body once and exits.
|
|
81
89
|
*/
|
|
82
|
-
export function buildStatusPaneRenderCommand() {
|
|
90
|
+
export function buildStatusPaneRenderCommand({ repoRoot = null } = {}) {
|
|
83
91
|
// `%27` guard: a single quote in the repo path would otherwise end the shell
|
|
84
92
|
// single-quoted `-e` argument.
|
|
85
93
|
const moduleHref = new URL('../agent-status.mjs', import.meta.url).href.replace(/'/g, '%27');
|
|
94
|
+
// Same guard for the root literal: a quote in the path is written as its
|
|
95
|
+
// JSON unicode escape, which the double-quoted JS string reads as the quote.
|
|
96
|
+
const rootExpr = typeof repoRoot === 'string' && repoRoot !== ''
|
|
97
|
+
? JSON.stringify(repoRoot).replace(/'/g, '\\u0027')
|
|
98
|
+
: 'process.cwd()';
|
|
86
99
|
const js = [
|
|
87
100
|
`import {readCurrentStatus} from ${JSON.stringify(moduleHref)};`,
|
|
88
|
-
|
|
101
|
+
`const v=readCurrentStatus({repoRoot:${rootExpr}});`,
|
|
89
102
|
'const ids=Object.keys(v.entries).sort();',
|
|
90
103
|
// An EMPTY channel (no ledger yet, no cache yet) is the normal fresh-repo
|
|
91
104
|
// state, not a degradation — marking it would fire the warning on every
|
|
@@ -141,7 +154,11 @@ async function _renderDefaultLayoutInner({ sessionName, force, projectRoot, vcsC
|
|
|
141
154
|
// file — so the pane can never present a stale `running` as live. The renderer
|
|
142
155
|
// prints its own "no agent-status yet" line when the channel is empty, and is
|
|
143
156
|
// exported (buildStatusPaneRenderCommand) so a test can run it once.
|
|
144
|
-
|
|
157
|
+
// #1730: the root is the one holding this session's own lock, resolved once here; none found → the pane's cwd.
|
|
158
|
+
const own = withStatusPane
|
|
159
|
+
? resolveOwnLockRootFor({ sessionId: readProcessLocalSessionIds()[0] ?? null, cwd: projectRoot })
|
|
160
|
+
: null;
|
|
161
|
+
const pane5Cmd = `while true; do clear; ${buildStatusPaneRenderCommand({ repoRoot: own && own.source !== 'none' ? own.root : null })}; sleep ${STATUS_PANE_POLL_SECONDS}; done`;
|
|
145
162
|
|
|
146
163
|
// 4. Session-collision check
|
|
147
164
|
const collision = isSessionCollision(sessionName);
|
|
@@ -26,6 +26,7 @@ import path from 'node:path';
|
|
|
26
26
|
import { randomBytes } from 'node:crypto';
|
|
27
27
|
|
|
28
28
|
import { digestSha256Short } from '../crypto-digest-utils.mjs';
|
|
29
|
+
import { resolveLedgerPath } from '../ledger-path.mjs';
|
|
29
30
|
import { RUN_ID_PATTERN, assertRunId } from '../test-runner/artifact-paths.mjs';
|
|
30
31
|
|
|
31
32
|
// Re-exported so existing `ux-grill/paths.mjs` importers keep working; the
|
|
@@ -106,6 +107,7 @@ function assertRepoRoot(repoRoot) {
|
|
|
106
107
|
* @throws {TypeError} on an invalid repoRoot or runId (path-traversal guard)
|
|
107
108
|
*/
|
|
108
109
|
export function runDirPath(repoRoot, runId) {
|
|
110
|
+
// ledger-root: per-run artifact dir of this working tree, kept local
|
|
109
111
|
return path.resolve(assertRepoRoot(repoRoot), UX_GRILL_METRICS_DIR, assertRunId(runId));
|
|
110
112
|
}
|
|
111
113
|
|
|
@@ -156,7 +158,9 @@ export function findingsPath(repoRoot, runId) {
|
|
|
156
158
|
* @returns {string}
|
|
157
159
|
*/
|
|
158
160
|
export function runRecordPath(repoRoot) {
|
|
159
|
-
|
|
161
|
+
// #1722: an ignored ledger in a linked worktree maps to the main checkout;
|
|
162
|
+
// a target repo that commits it keeps its own copy.
|
|
163
|
+
return resolveLedgerPath(UX_GRILL_LEDGER, path.resolve(assertRepoRoot(repoRoot)));
|
|
160
164
|
}
|
|
161
165
|
|
|
162
166
|
/**
|
|
@@ -6,9 +6,9 @@
|
|
|
6
6
|
|
|
7
7
|
import { readFileSync, readdirSync, existsSync } from 'node:fs';
|
|
8
8
|
import { join, resolve, dirname } from 'node:path';
|
|
9
|
-
import { fileURLToPath } from 'node:url';
|
|
9
|
+
import { fileURLToPath, pathToFileURL } from 'node:url';
|
|
10
10
|
import yaml from 'js-yaml';
|
|
11
|
-
import { ALLOWED_MODEL_ALIASES, MODEL_ID_RE } from '../agent-frontmatter.mjs';
|
|
11
|
+
import { ALLOWED_MODEL_ALIASES, MODEL_ID_RE, checkAgentEffort } from '../agent-frontmatter.mjs';
|
|
12
12
|
import { extractInitialFrontmatter } from './frontmatter-block.mjs';
|
|
13
13
|
import { parseFrontmatterDescription } from '../description-surface.mjs';
|
|
14
14
|
|
|
@@ -18,7 +18,7 @@ const __dirname = dirname(fileURLToPath(import.meta.url));
|
|
|
18
18
|
let _tierModule = null;
|
|
19
19
|
async function getTierModule() {
|
|
20
20
|
if (!_tierModule) {
|
|
21
|
-
_tierModule = await import(join(__dirname, 'tier-inference.mjs'));
|
|
21
|
+
_tierModule = await import(pathToFileURL(join(__dirname, 'tier-inference.mjs')).href);
|
|
22
22
|
}
|
|
23
23
|
return _tierModule;
|
|
24
24
|
}
|
|
@@ -103,13 +103,28 @@ function parseFrontmatterYaml(yamlText) {
|
|
|
103
103
|
|
|
104
104
|
/**
|
|
105
105
|
* Get a scalar field value from frontmatter text.
|
|
106
|
-
* Returns null if field not present.
|
|
106
|
+
* Returns null if field not present or its value is empty. `[ \t]*` instead of
|
|
107
|
+
* `\s*`: an empty `key:` must not read the NEXT line as its value.
|
|
107
108
|
*/
|
|
108
109
|
function getField(fm, name) {
|
|
109
|
-
const m = fm.match(new RegExp(`^${name}
|
|
110
|
+
const m = fm.match(new RegExp(`^${name}:[ \t]*(.+)$`, 'm'));
|
|
110
111
|
return m ? m[1].trim() : null;
|
|
111
112
|
}
|
|
112
113
|
|
|
114
|
+
/**
|
|
115
|
+
* Like getField, but for plain enum-like scalars (`model`, `effort`): strips one
|
|
116
|
+
* pair of surrounding quotes and a trailing YAML comment, so `effort: "medium"`
|
|
117
|
+
* and `effort: medium # reason` read as `medium`. Not for free text such as
|
|
118
|
+
* `description`, where ` #` may be content.
|
|
119
|
+
*/
|
|
120
|
+
function getScalarField(fm, name) {
|
|
121
|
+
const raw = getField(fm, name);
|
|
122
|
+
if (raw === null) return null;
|
|
123
|
+
const quoted = raw.match(/^(["'])(.*)\1(?:\s+#.*)?$/);
|
|
124
|
+
if (quoted) return quoted[2];
|
|
125
|
+
return raw.replace(/(^|\s+)#.*$/, '').trim();
|
|
126
|
+
}
|
|
127
|
+
|
|
113
128
|
/**
|
|
114
129
|
* Check whether a field key is present in frontmatter (even with empty/block value).
|
|
115
130
|
*/
|
|
@@ -323,7 +338,7 @@ if (mdFiles.length === 0) {
|
|
|
323
338
|
// do not re-declare the alias set or the model-ID regex here.
|
|
324
339
|
// Per https://code.claude.com/docs/en/sub-agents — the canonical doc accepts both.
|
|
325
340
|
if (hasField(frontmatter, 'model')) {
|
|
326
|
-
const modelVal =
|
|
341
|
+
const modelVal = getScalarField(frontmatter, 'model');
|
|
327
342
|
if (modelVal === null || (!ALLOWED_MODEL_ALIASES.has(modelVal) && !MODEL_ID_RE.test(modelVal))) {
|
|
328
343
|
const got = modelVal ?? '';
|
|
329
344
|
const aliasList = [...ALLOWED_MODEL_ALIASES].join('|');
|
|
@@ -331,6 +346,22 @@ if (mdFiles.length === 0) {
|
|
|
331
346
|
}
|
|
332
347
|
}
|
|
333
348
|
|
|
349
|
+
// effort: required when model ≠ inherit, enum low|medium|high|xhigh|max (#1614).
|
|
350
|
+
// Rule lives in agent-frontmatter.mjs (checkAgentEffort) — do not re-declare it here.
|
|
351
|
+
const effortError = checkAgentEffort(
|
|
352
|
+
hasField(frontmatter, 'model') ? (getScalarField(frontmatter, 'model') ?? '') : undefined,
|
|
353
|
+
hasField(frontmatter, 'effort') ? (getScalarField(frontmatter, 'effort') ?? '') : undefined,
|
|
354
|
+
);
|
|
355
|
+
if (effortError) fail(`${agentName}: ${effortError.message}`);
|
|
356
|
+
|
|
357
|
+
// omitClaudeMd: optional boolean (#1616) — a typo such as `yes` would be ignored silently.
|
|
358
|
+
if (hasField(frontmatter, 'omitClaudeMd')) {
|
|
359
|
+
const omitVal = getField(frontmatter, 'omitClaudeMd');
|
|
360
|
+
if (omitVal !== 'true' && omitVal !== 'false') {
|
|
361
|
+
fail(`${agentName}: omitClaudeMd must be true or false (got: '${omitVal ?? ''}')`);
|
|
362
|
+
}
|
|
363
|
+
}
|
|
364
|
+
|
|
334
365
|
// color: canonical Anthropic palette (red|blue|green|yellow|purple|orange|pink|cyan) plus magenta from plugin-dev SKILL.md.
|
|
335
366
|
// Source: https://code.claude.com/docs/en/sub-agents § Supported frontmatter fields.
|
|
336
367
|
if (hasField(frontmatter, 'color')) {
|
|
@@ -72,7 +72,22 @@ function stripLeadingDotSlash(p) {
|
|
|
72
72
|
* @param {boolean} optional - If true, missing conventional path is a PASS
|
|
73
73
|
*/
|
|
74
74
|
function checkComponentPath(field, conventional, optional = false) {
|
|
75
|
-
const
|
|
75
|
+
const value = json[field];
|
|
76
|
+
// hooks/mcpServers also accept inline config objects (plugins-reference §
|
|
77
|
+
// Component path forms). An inline object carries no path to resolve; arrays
|
|
78
|
+
// may mix paths and objects, so each string entry is checked as a path.
|
|
79
|
+
if (Array.isArray(value)) {
|
|
80
|
+
for (const entry of value) {
|
|
81
|
+
if (typeof entry === 'string') checkExplicitPath(field, entry);
|
|
82
|
+
else pass(`${field} inline config entry (no path to resolve)`);
|
|
83
|
+
}
|
|
84
|
+
return;
|
|
85
|
+
}
|
|
86
|
+
if (value && typeof value === 'object') {
|
|
87
|
+
pass(`${field} is an inline config object (no path to resolve)`);
|
|
88
|
+
return;
|
|
89
|
+
}
|
|
90
|
+
const relPath = value || '';
|
|
76
91
|
|
|
77
92
|
if (!relPath) {
|
|
78
93
|
// No explicit value — try conventional auto-discovery
|
|
@@ -87,6 +102,15 @@ function checkComponentPath(field, conventional, optional = false) {
|
|
|
87
102
|
return;
|
|
88
103
|
}
|
|
89
104
|
|
|
105
|
+
checkExplicitPath(field, relPath);
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
/**
|
|
109
|
+
* Check one explicit component path: must start with "./" and exist.
|
|
110
|
+
* @param {string} field
|
|
111
|
+
* @param {string} relPath
|
|
112
|
+
*/
|
|
113
|
+
function checkExplicitPath(field, relPath) {
|
|
90
114
|
// Explicit value — must start with "./"
|
|
91
115
|
if (!relPath.startsWith('./')) {
|
|
92
116
|
fail(`${field} path does not start with ./: ${relPath}`);
|
|
@@ -889,7 +889,7 @@ function loadModuleVersions(pluginRoot, modulePath, cache) {
|
|
|
889
889
|
|
|
890
890
|
const workingSource = readFileSync(modulePath, 'utf8');
|
|
891
891
|
const working = parseModule(workingSource, modulePath);
|
|
892
|
-
const gitPath = relative.
|
|
892
|
+
const gitPath = relative.replace(/\\/g, '/');
|
|
893
893
|
const env = {};
|
|
894
894
|
for (const key of GIT_ENV_ALLOWLIST) {
|
|
895
895
|
if (process.env[key] !== undefined) env[key] = process.env[key];
|
|
@@ -1027,8 +1027,8 @@ export function inspectGuardRequiresParity(pluginRoot) {
|
|
|
1027
1027
|
result.summary.requires += requires.requires.length;
|
|
1028
1028
|
|
|
1029
1029
|
const modulePath = path.resolve(pluginRoot, MODULE_RELATIVE_PREFIX, ...specifier.segments);
|
|
1030
|
-
const moduleRelative = path.relative(pluginRoot, modulePath);
|
|
1031
|
-
if (moduleRelative.startsWith('..') || path.isAbsolute(moduleRelative) || !moduleRelative.startsWith(`${MODULE_RELATIVE_PREFIX}
|
|
1030
|
+
const moduleRelative = path.relative(pluginRoot, modulePath).replace(/\\/g, '/');
|
|
1031
|
+
if (moduleRelative.startsWith('..') || path.isAbsolute(moduleRelative) || !moduleRelative.startsWith(`${MODULE_RELATIVE_PREFIX}/`)) {
|
|
1032
1032
|
report('dynamic-contract', `${namespace}: specifier resolves outside ${MODULE_RELATIVE_PREFIX}`, specifierValue);
|
|
1033
1033
|
continue;
|
|
1034
1034
|
}
|
|
@@ -10,7 +10,7 @@
|
|
|
10
10
|
// Stdout: ' PASS: ...' / ' FAIL: ...' lines + 'Results: N passed, M failed'
|
|
11
11
|
|
|
12
12
|
import { readFileSync, existsSync, readdirSync } from 'node:fs';
|
|
13
|
-
import { join } from 'node:path';
|
|
13
|
+
import { basename, join } from 'node:path';
|
|
14
14
|
|
|
15
15
|
// The pi and cursor projections are IMPORTED, not copied: the runtime bridges
|
|
16
16
|
// rewrite `hook_event_name` for every native event, so they are the source of
|
|
@@ -83,16 +83,48 @@ const DOCUMENTED_ASYMMETRIES = {
|
|
|
83
83
|
// shape ("PreToolUse hook returned permissionDecision:deny without a
|
|
84
84
|
// non-empty permissionDecisionReason").
|
|
85
85
|
//
|
|
86
|
-
//
|
|
87
|
-
// Codex
|
|
88
|
-
// `
|
|
89
|
-
//
|
|
90
|
-
//
|
|
91
|
-
//
|
|
92
|
-
//
|
|
93
|
-
//
|
|
94
|
-
// a
|
|
95
|
-
//
|
|
86
|
+
// 2026-09-06 (0.144.4) this block named the tool-name VOCABULARY as the
|
|
87
|
+
// blocker: Codex's model-facing tools are `shell` / `exec_command` /
|
|
88
|
+
// `unified_exec` / `apply_patch`, so a guard gating on
|
|
89
|
+
// `tool_name === 'Bash'` would run, match nothing and allow everything
|
|
90
|
+
// (#919-P2 class). For the SHELL tool that premise is refuted below; for
|
|
91
|
+
// the Edit/Write/MultiEdit gates (apply_patch) it is still unmeasured.
|
|
92
|
+
//
|
|
93
|
+
// REFUTED AGAIN for the shell tool (#1669), measured 2026-10-20 on
|
|
94
|
+
// codex-cli 0.160.1 with a logging PreToolUse hook under `codex exec`:
|
|
95
|
+
// a shell call arrives as `"tool_name":"Bash"` with
|
|
96
|
+
// `"tool_input":{"command":"<string>"}`, and `matcher: "Bash"` fires.
|
|
97
|
+
// The 2026-09-06 evidence (`strings -a <codex> | grep -c '"Bash"'` -> 0)
|
|
98
|
+
// counted a JSON-quoted literal Rust never stores.
|
|
99
|
+
//
|
|
100
|
+
// DENY IS HONOURED (#1691), measured 2026-10-20 on codex-cli 0.160.1:
|
|
101
|
+
// `codex exec` with pre-bash-destructive-guard as the only PreToolUse hook
|
|
102
|
+
// and the prompt `printf x > CLAUDE.md` logged `Command blocked by
|
|
103
|
+
// PreToolUse hook: Destructive command blocked: '> CLAUDE.md' (rule:
|
|
104
|
+
// redirect-truncate-protected)` and left the file unchanged (the rule
|
|
105
|
+
// protects CLAUDE.md and its Codex alias AGENTS.md alike); the control
|
|
106
|
+
// run with `hooks.PreToolUse=[]` executed it and the file became `x`.
|
|
107
|
+
// Measured payload: `session_id`, `turn_id`, `cwd` (= the session workdir,
|
|
108
|
+
// also the hook's process cwd), `hook_event_name`, `model`,
|
|
109
|
+
// `permission_mode`, `tool_name:"Bash"`, `tool_input.command`,
|
|
110
|
+
// `tool_use_id`, `transcript_path: null`; PostToolUse adds
|
|
111
|
+
// `tool_response: ""`. A real Codex session exports NO CLAUDE_* env.
|
|
112
|
+
//
|
|
113
|
+
// Every Bash hook was then fed that payload by hand under a clean env
|
|
114
|
+
// (PATH + HOME only, SO_PLATFORM=codex, mkdtemp git repo) and wired only
|
|
115
|
+
// where it enforces on it (2026-10-20): destructive-guard (deny on
|
|
116
|
+
// `> CLAUDE.md`), sessions-ledger-guard (deny on `>>
|
|
117
|
+
// .orchestrator/metrics/sessions.jsonl`), issue-budget (cap 1, second
|
|
118
|
+
// create denied and parked), enforce-commands (`.codex/wave-scope.json`
|
|
119
|
+
// `blockedCommands` → deny), memory-propose-audit (event appended,
|
|
120
|
+
// observe-only by design), post-bash-write-verify (warn on an out-of-scope
|
|
121
|
+
// write). They read `tool_input.command`, `cwd` and `session_id`; issue-budget also `tool_use_id`.
|
|
122
|
+
// Wiring contract: tests/hooks/codex-bash-guards-wiring.test.mjs.
|
|
123
|
+
//
|
|
124
|
+
// post-bash-issue-budget-refund is no gap on this list: it runs on
|
|
125
|
+
// PostToolUseFailure, an event hooks-codex.json does not carry, so a
|
|
126
|
+
// failed create on Codex keeps its slot (over-count, the fail-closed
|
|
127
|
+
// direction). Revisit when Codex gains a failure event.
|
|
96
128
|
PreToolUse: [
|
|
97
129
|
// pre-task-scope-disjoint (#1020): NOT ported by construction. The hook
|
|
98
130
|
// matches the `Agent` dispatch tool, which this platform does not have —
|
|
@@ -118,27 +150,17 @@ const DOCUMENTED_ASYMMETRIES = {
|
|
|
118
150
|
// `apply_patch`, a name this hook never matches. Wired, it would run and allow
|
|
119
151
|
// everything: a silent no-op, not enforcement. Gap registered, not faked.
|
|
120
152
|
'pre-edit-sessions-ledger-guard.mjs',
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
//
|
|
125
|
-
//
|
|
126
|
-
//
|
|
127
|
-
//
|
|
128
|
-
//
|
|
129
|
-
//
|
|
130
|
-
'pre-bash-sessions-ledger-guard.mjs',
|
|
153
|
+
// pre-bash-templates-first (#1691): runs on the Codex payload but can
|
|
154
|
+
// only DENY. Both allow routes are unreachable there: the template-Read
|
|
155
|
+
// evidence needs `transcript_path` (hooks/pre-bash-templates-first.mjs:
|
|
156
|
+
// 404-410), which Codex sends as `null` (codex-cli 0.160.1, 2026-10-20),
|
|
157
|
+
// and `/templates-ack` writes an acknowledgement only under a `claude`
|
|
158
|
+
// ancestor process (commands/templates-ack.md:18-23). Fed the Codex
|
|
159
|
+
// payload with `.gitlab/issue_templates/bug.md` present, `glab issue
|
|
160
|
+
// create` was denied with no way to satisfy it. A guard nobody can pass
|
|
161
|
+
// is a blocker, not enforcement; gap registered, not faked.
|
|
131
162
|
'pre-bash-templates-first.mjs',
|
|
132
|
-
'pre-bash-issue-budget.mjs',
|
|
133
|
-
'enforce-commands.mjs',
|
|
134
163
|
],
|
|
135
|
-
// post-bash-write-verify (#942): gates on tool_name === 'Bash', a value
|
|
136
|
-
// Codex never emits (its shell tools are `shell` / `exec_command` /
|
|
137
|
-
// `unified_exec`). Documented exception until a tool-name map exists —
|
|
138
|
-
// a manifest entry today would be a silent no-op, not enforcement. Same
|
|
139
|
-
// #919-P2 class as the PreToolUse block. `post-tool-use.command.input`
|
|
140
|
-
// at 0.144.4 does require `tool_name`, `tool_input` AND `tool_response`,
|
|
141
|
-
// so the port is a rename map, not a payload bridge.
|
|
142
164
|
PostToolUse: [
|
|
143
165
|
'post-edit-validate.mjs',
|
|
144
166
|
'post-tooluse-frontend-slop.mjs',
|
|
@@ -147,7 +169,6 @@ const DOCUMENTED_ASYMMETRIES = {
|
|
|
147
169
|
// tool_input.file_path, neither of which any Codex bridge delivers.
|
|
148
170
|
// A manifest entry would be a silent no-op, not enforcement.
|
|
149
171
|
'post-edit-import-probe.mjs',
|
|
150
|
-
'post-bash-write-verify.mjs',
|
|
151
172
|
],
|
|
152
173
|
SubagentStart: ['subagent-telemetry.mjs'],
|
|
153
174
|
SubagentStop: ['subagent-telemetry.mjs', 'post-subagent-discovery-validator.mjs'],
|
|
@@ -247,7 +268,7 @@ function loadJson(filePath, required = true) {
|
|
|
247
268
|
try {
|
|
248
269
|
return JSON.parse(readFileSync(filePath, 'utf8'));
|
|
249
270
|
} catch (e) {
|
|
250
|
-
fail(`${filePath
|
|
271
|
+
fail(`${basename(filePath)}: ${e.message}`);
|
|
251
272
|
process.exit(1);
|
|
252
273
|
}
|
|
253
274
|
}
|
|
@@ -85,7 +85,9 @@ if (hooksPath && hooksPath.endsWith('.json')) {
|
|
|
85
85
|
console.log('');
|
|
86
86
|
console.log('--- Check 5b: mcpServers JSON validity ---');
|
|
87
87
|
|
|
88
|
-
|
|
88
|
+
// mcpServers may also be an inline object or an array (plugins-reference §
|
|
89
|
+
// Component path forms); only a .json path string is a file to parse here.
|
|
90
|
+
const mcpPath = typeof pluginData.mcpServers === 'string' ? pluginData.mcpServers : '';
|
|
89
91
|
if (mcpPath && mcpPath.endsWith('.json')) {
|
|
90
92
|
const mcpAbs = join(pluginRoot, mcpPath.replace(/^\.\//, ''));
|
|
91
93
|
if (existsSync(mcpAbs)) {
|
|
@@ -134,6 +134,7 @@ import { readLearnings } from '../learnings/io.mjs';
|
|
|
134
134
|
// the emitter's formula.
|
|
135
135
|
import { learningKeyOf } from '../learnings/kebab.mjs';
|
|
136
136
|
import { isMainModule } from '../is-main-module.mjs';
|
|
137
|
+
import { resolveLedgerPath } from '../ledger-path.mjs';
|
|
137
138
|
|
|
138
139
|
/** Directory holding the rule corpus, relative to the plugin root. */
|
|
139
140
|
const RULES_REL = path.join('.claude', 'rules');
|
|
@@ -272,8 +273,8 @@ async function indexStore(absolutePath) {
|
|
|
272
273
|
export async function inspectLearningProvenance(pluginRoot, opts = {}) {
|
|
273
274
|
/** @type {Finding[]} */
|
|
274
275
|
const findings = [];
|
|
275
|
-
const livePath = opts.livePath ??
|
|
276
|
-
const archivePath = opts.archivePath ??
|
|
276
|
+
const livePath = opts.livePath ?? resolveLedgerPath(LIVE_STORE_REL, pluginRoot);
|
|
277
|
+
const archivePath = opts.archivePath ?? resolveLedgerPath(ARCHIVE_STORE_REL, pluginRoot);
|
|
277
278
|
|
|
278
279
|
/** @type {Inspection} */
|
|
279
280
|
const result = {
|
|
@@ -61,10 +61,16 @@ export const SCAN_DIRS = Object.freeze(['skills', 'commands', 'agents', 'docs',
|
|
|
61
61
|
/** Lines on either side of an R2 operator searched for an anchor. */
|
|
62
62
|
const WINDOW = 3;
|
|
63
63
|
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
64
|
+
/**
|
|
65
|
+
* The protected store filenames: the live store plus the #1516 inbox and its
|
|
66
|
+
* receipts — append-only stores written only through `learnings/io.mjs`.
|
|
67
|
+
*/
|
|
68
|
+
const STORE = String.raw`learnings(?:-inbox(?:-ingested)?)?\.jsonl`;
|
|
69
|
+
|
|
70
|
+
const MENTION_RE = new RegExp(STORE);
|
|
71
|
+
const ANCHOR_RE = new RegExp(String.raw`${STORE}|\b(?:the|learnings) store\b|\bresult back\b|\batomic rewrite\b`, 'i');
|
|
72
|
+
const SHELL_REDIRECT_RE = new RegExp(String.raw`(?:^|[\s|;&(])(>{1,2})[ \t]*["'\`]?[^\s"'\`<>]*${STORE}\b`, 'dg');
|
|
73
|
+
const TEE_RE = new RegExp(String.raw`\btee\b(?:[ \t]+-{1,2}[\w-]+)*[ \t]+["'\`]?[^\s"'\`|]*${STORE}\b`, 'g');
|
|
68
74
|
const PROSE_OPERATOR_RE = /`>{1,2}`/g;
|
|
69
75
|
const ANY_OPERATOR_RE = /`>{1,2}`|(?:^|[\s|;&(])>{1,2}|\btee\b/;
|
|
70
76
|
const PROHIBITION_RE = /\b(NEVER|[Nn]ever|DO NOT|[Dd]o not|[Dd]on't|FORBIDDEN|forbidden|verboten)\b/;
|