@event4u/agent-config 11.0.0 → 12.1.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 +1 -1
- package/CHANGELOG.md +173 -229
- package/agents/templates/.ai-council.yml.example +46 -3
- package/dist/agent-src/commands/agent-handoff.md +0 -1
- package/dist/agent-src/commands/agent-status.md +0 -1
- package/dist/agent-src/commands/agents/audit.md +0 -1
- package/dist/agent-src/commands/agents/init.md +0 -1
- package/dist/agent-src/commands/agents/optimize.md +0 -1
- package/dist/agent-src/commands/agents/user/accept.md +0 -1
- package/dist/agent-src/commands/agents/user/delete.md +0 -1
- package/dist/agent-src/commands/agents/user/init.md +0 -1
- package/dist/agent-src/commands/agents/user/review.md +0 -1
- package/dist/agent-src/commands/agents/user/show.md +0 -1
- package/dist/agent-src/commands/agents/user/update.md +0 -1
- package/dist/agent-src/commands/agents/user.md +0 -1
- package/dist/agent-src/commands/agents.md +0 -1
- package/dist/agent-src/commands/analytics/prune.md +0 -1
- package/dist/agent-src/commands/analytics/show.md +0 -1
- package/dist/agent-src/commands/analytics.md +0 -1
- package/dist/agent-src/commands/analyze/conformance.md +0 -1
- package/dist/agent-src/commands/analyze/decision.md +0 -1
- package/dist/agent-src/commands/analyze/inbox.md +0 -1
- package/dist/agent-src/commands/analyze/incident.md +0 -1
- package/dist/agent-src/commands/analyze/near-miss.md +0 -1
- package/dist/agent-src/commands/analyze/postmortem.md +0 -1
- package/dist/agent-src/commands/analyze/premortem.md +0 -1
- package/dist/agent-src/commands/analyze/reference-repo.md +0 -1
- package/dist/agent-src/commands/analyze.md +0 -1
- package/dist/agent-src/commands/brand/identity.md +0 -1
- package/dist/agent-src/commands/brand/review.md +0 -1
- package/dist/agent-src/commands/brand/strategy.md +0 -1
- package/dist/agent-src/commands/brand/tokens.md +0 -1
- package/dist/agent-src/commands/brand/voice.md +0 -1
- package/dist/agent-src/commands/brand.md +0 -1
- package/dist/agent-src/commands/bug/fix.md +0 -1
- package/dist/agent-src/commands/bug/investigate.md +0 -1
- package/dist/agent-src/commands/bug.md +0 -1
- package/dist/agent-src/commands/challenge-me/vision.md +0 -1
- package/dist/agent-src/commands/challenge-me/with-docs.md +0 -1
- package/dist/agent-src/commands/challenge-me.md +0 -1
- package/dist/agent-src/commands/chat-history/import.md +0 -1
- package/dist/agent-src/commands/chat-history.md +0 -1
- package/dist/agent-src/commands/check-current-md.md +0 -1
- package/dist/agent-src/commands/commit/in-chunks.md +0 -1
- package/dist/agent-src/commands/commit.md +0 -1
- package/dist/agent-src/commands/condense.md +0 -1
- package/dist/agent-src/commands/context/create.md +0 -1
- package/dist/agent-src/commands/context/refactor.md +0 -1
- package/dist/agent-src/commands/context.md +0 -1
- package/dist/agent-src/commands/contribution-precheck.md +0 -1
- package/dist/agent-src/commands/cost/profile.md +0 -1
- package/dist/agent-src/commands/cost/report.md +0 -1
- package/dist/agent-src/commands/cost.md +0 -1
- package/dist/agent-src/commands/council/analysis.md +0 -1
- package/dist/agent-src/commands/council/debate.md +0 -1
- package/dist/agent-src/commands/council/default.md +0 -1
- package/dist/agent-src/commands/council/design.md +0 -1
- package/dist/agent-src/commands/council/optimize.md +0 -1
- package/dist/agent-src/commands/council/pr.md +0 -1
- package/dist/agent-src/commands/council.md +0 -1
- package/dist/agent-src/commands/design-system/capture.md +0 -1
- package/dist/agent-src/commands/design-system/generate.md +0 -1
- package/dist/agent-src/commands/design-system/import.md +0 -1
- package/dist/agent-src/commands/design-system.md +0 -1
- package/dist/agent-src/commands/estimate-ticket.md +0 -1
- package/dist/agent-src/commands/explain-run.md +0 -1
- package/dist/agent-src/commands/feature/dev.md +0 -1
- package/dist/agent-src/commands/feature/explore.md +0 -1
- package/dist/agent-src/commands/feature/plan.md +0 -1
- package/dist/agent-src/commands/feature/refactor.md +0 -1
- package/dist/agent-src/commands/feature/roadmap.md +0 -1
- package/dist/agent-src/commands/feature.md +0 -1
- package/dist/agent-src/commands/fix/ci.md +0 -1
- package/dist/agent-src/commands/fix/comments.md +0 -1
- package/dist/agent-src/commands/fix/portability.md +0 -1
- package/dist/agent-src/commands/fix/pr-comments-loop.md +0 -1
- package/dist/agent-src/commands/fix/pr-comments.md +0 -1
- package/dist/agent-src/commands/fix/quality.md +0 -1
- package/dist/agent-src/commands/fix/refs.md +0 -1
- package/dist/agent-src/commands/fix/route.md +0 -1
- package/dist/agent-src/commands/fix/seeder.md +0 -1
- package/dist/agent-src/commands/fix.md +0 -1
- package/dist/agent-src/commands/ghostwriter/delete.md +0 -1
- package/dist/agent-src/commands/ghostwriter/fetch.md +0 -1
- package/dist/agent-src/commands/ghostwriter/list.md +0 -1
- package/dist/agent-src/commands/ghostwriter/show.md +0 -1
- package/dist/agent-src/commands/ghostwriter/write.md +0 -1
- package/dist/agent-src/commands/ghostwriter.md +0 -1
- package/dist/agent-src/commands/grill-me.md +0 -1
- package/dist/agent-src/commands/humanize.md +0 -1
- package/dist/agent-src/commands/image/analyse.md +0 -1
- package/dist/agent-src/commands/image/create.md +0 -1
- package/dist/agent-src/commands/image/verify.md +0 -1
- package/dist/agent-src/commands/image.md +0 -1
- package/dist/agent-src/commands/implement-ticket.md +0 -1
- package/dist/agent-src/commands/jira-ticket.md +0 -1
- package/dist/agent-src/commands/judge/on-diff.md +0 -1
- package/dist/agent-src/commands/judge/solo.md +0 -1
- package/dist/agent-src/commands/judge/steps.md +0 -1
- package/dist/agent-src/commands/judge.md +0 -1
- package/dist/agent-src/commands/knowledge/cross-repo.md +0 -1
- package/dist/agent-src/commands/knowledge/forget.md +0 -1
- package/dist/agent-src/commands/knowledge/ingest.md +0 -1
- package/dist/agent-src/commands/knowledge/list.md +0 -1
- package/dist/agent-src/commands/knowledge.md +0 -1
- package/dist/agent-src/commands/memory/add.md +0 -1
- package/dist/agent-src/commands/memory/learn-low-impact.md +0 -1
- package/dist/agent-src/commands/memory/load.md +0 -1
- package/dist/agent-src/commands/memory/mine-session.md +0 -1
- package/dist/agent-src/commands/memory/promote.md +0 -1
- package/dist/agent-src/commands/memory/propose.md +0 -1
- package/dist/agent-src/commands/memory.md +0 -1
- package/dist/agent-src/commands/mission/upgrade.md +0 -1
- package/dist/agent-src/commands/mode.md +0 -1
- package/dist/agent-src/commands/module/create.md +0 -1
- package/dist/agent-src/commands/module/explore.md +0 -1
- package/dist/agent-src/commands/module.md +0 -1
- package/dist/agent-src/commands/optimize/agents-dir.md +0 -1
- package/dist/agent-src/commands/optimize/augmentignore.md +0 -1
- package/dist/agent-src/commands/optimize/deep.md +0 -1
- package/dist/agent-src/commands/optimize/project.md +0 -1
- package/dist/agent-src/commands/optimize/prompt.md +0 -1
- package/dist/agent-src/commands/optimize/rtk.md +0 -1
- package/dist/agent-src/commands/optimize/skills.md +0 -1
- package/dist/agent-src/commands/optimize.md +0 -1
- package/dist/agent-src/commands/orchestrate.md +0 -1
- package/dist/agent-src/commands/override/create.md +0 -1
- package/dist/agent-src/commands/override/manage.md +0 -1
- package/dist/agent-src/commands/override.md +0 -1
- package/dist/agent-src/commands/package/reset.md +0 -1
- package/dist/agent-src/commands/package/test.md +0 -1
- package/dist/agent-src/commands/package.md +0 -1
- package/dist/agent-src/commands/post-as/ghostwriter.md +0 -1
- package/dist/agent-src/commands/post-as/me.md +0 -1
- package/dist/agent-src/commands/post-as.md +0 -1
- package/dist/agent-src/commands/pr/create/description-only.md +0 -1
- package/dist/agent-src/commands/pr/create.md +23 -9
- package/dist/agent-src/commands/prediction-pool.md +0 -1
- package/dist/agent-src/commands/prepare-for-review.md +0 -1
- package/dist/agent-src/commands/profile/activate.md +0 -1
- package/dist/agent-src/commands/profile/deactivate.md +0 -1
- package/dist/agent-src/commands/profile/show.md +0 -1
- package/dist/agent-src/commands/profile.md +0 -1
- package/dist/agent-src/commands/project/analyze.md +0 -1
- package/dist/agent-src/commands/project/health.md +0 -1
- package/dist/agent-src/commands/project.md +0 -1
- package/dist/agent-src/commands/refine-ticket.md +0 -1
- package/dist/agent-src/commands/research/deep.md +0 -1
- package/dist/agent-src/commands/research/report.md +0 -1
- package/dist/agent-src/commands/research.md +0 -1
- package/dist/agent-src/commands/review/changes.md +0 -1
- package/dist/agent-src/commands/review/routing.md +0 -1
- package/dist/agent-src/commands/review.md +0 -1
- package/dist/agent-src/commands/roadmap/ai-council.md +0 -1
- package/dist/agent-src/commands/roadmap/create.md +0 -1
- package/dist/agent-src/commands/roadmap/materialize.md +0 -1
- package/dist/agent-src/commands/roadmap/next.md +0 -1
- package/dist/agent-src/commands/roadmap/process-full.md +0 -1
- package/dist/agent-src/commands/roadmap/process-phase.md +0 -1
- package/dist/agent-src/commands/roadmap/process-step.md +0 -1
- package/dist/agent-src/commands/roadmap.md +0 -1
- package/dist/agent-src/commands/rule-compliance-audit.md +0 -1
- package/dist/agent-src/commands/security-audit-config.md +0 -1
- package/dist/agent-src/commands/skill/preview.md +0 -1
- package/dist/agent-src/commands/skill.md +0 -1
- package/dist/agent-src/commands/skills/discover.md +0 -1
- package/dist/agent-src/commands/skills.md +0 -1
- package/dist/agent-src/commands/sync/agent-settings.md +0 -1
- package/dist/agent-src/commands/sync/gitignore/fix.md +0 -1
- package/dist/agent-src/commands/sync/gitignore.md +0 -1
- package/dist/agent-src/commands/sync.md +0 -1
- package/dist/agent-src/commands/tdd/green.md +0 -1
- package/dist/agent-src/commands/tdd/red.md +0 -1
- package/dist/agent-src/commands/tdd/refactor.md +0 -1
- package/dist/agent-src/commands/tdd.md +0 -1
- package/dist/agent-src/commands/team/adversarial.md +0 -1
- package/dist/agent-src/commands/team/delegate.md +0 -1
- package/dist/agent-src/commands/team/review.md +0 -1
- package/dist/agent-src/commands/team/status.md +0 -1
- package/dist/agent-src/commands/team-knowledge/bootstrap.md +0 -1
- package/dist/agent-src/commands/team-knowledge/consolidate.md +0 -1
- package/dist/agent-src/commands/team-knowledge.md +0 -1
- package/dist/agent-src/commands/team.md +0 -1
- package/dist/agent-src/commands/tests/create.md +0 -1
- package/dist/agent-src/commands/tests/e2e-heal.md +0 -1
- package/dist/agent-src/commands/tests/e2e-plan.md +0 -1
- package/dist/agent-src/commands/tests/execute.md +0 -1
- package/dist/agent-src/commands/tests.md +0 -1
- package/dist/agent-src/commands/threat-model.md +0 -1
- package/dist/agent-src/commands/update-form-request-messages.md +0 -1
- package/dist/agent-src/commands/upstream-contribute.md +0 -1
- package/dist/agent-src/commands/video/from-script.md +0 -1
- package/dist/agent-src/commands/video/from-song.md +0 -1
- package/dist/agent-src/commands/video/scene.md +0 -1
- package/dist/agent-src/commands/video/stitch.md +0 -1
- package/dist/agent-src/commands/video/storyboard.md +0 -1
- package/dist/agent-src/commands/video.md +0 -1
- package/dist/agent-src/commands/work.md +0 -1
- package/dist/agent-src/commands/worktree/cleanup.md +0 -1
- package/dist/agent-src/commands/worktree/create.md +0 -1
- package/dist/agent-src/commands/worktree/status.md +0 -1
- package/dist/agent-src/commands/worktree/verify.md +0 -1
- package/dist/agent-src/commands/worktree.md +0 -1
- package/dist/agent-src/contexts/model-recommendations.md +6 -4
- package/dist/agent-src/rules/low-impact-corpus-privacy-floor.md +1 -0
- package/dist/agent-src/scripts/roadmap_gates.ts +30 -2
- package/dist/agent-src/scripts/update_roadmap_progress.ts +32 -1
- package/dist/agent-src/skills/docx-authoring/SKILL.md +1 -0
- package/dist/agent-src/skills/git-workflow/SKILL.md +9 -1
- package/dist/agent-src/skills/pdf-tools/SKILL.md +1 -0
- package/dist/agent-src/skills/roadmap-writing/SKILL.md +7 -7
- package/dist/agent-src/skills/skill-writing/SKILL.md +35 -0
- package/dist/agent-src/templates/agents/agent-project-settings.example.yml +1 -1
- package/dist/agent-src/templates/roadmaps.md +41 -3
- package/dist/cli/commands/commands.candidates.test.js +1 -1
- package/dist/cli/commands/commands.candidates.test.js.map +1 -1
- package/dist/cli/commands/commands.js +9 -15
- package/dist/cli/commands/commands.js.map +1 -1
- package/dist/cli/commands/profiles.test.js +7 -7
- package/dist/cli/commands/profiles.test.js.map +1 -1
- package/dist/cli/discovery/loadManifest.js.map +1 -1
- package/dist/cli/main.js +2 -2
- package/dist/cli/main.js.map +1 -1
- package/dist/cli/registry.js +1 -0
- package/dist/cli/registry.js.map +1 -1
- package/dist/cli-delegate/chunk-K34QTSD5.js +3895 -0
- package/dist/cli-delegate/{chunk-L565YFXH.js → chunk-LKZ5DOYW.js} +557 -297
- package/dist/cli-delegate/{chunk-NN4KW4HZ.js → chunk-O52B4F5S.js} +123 -2338
- package/dist/cli-delegate/cmd_conformance.js +18 -6
- package/dist/cli-delegate/cmd_doctor.js +2 -2
- package/dist/cli-delegate/cmd_export.js +1 -1
- package/dist/cli-delegate/cmd_handoff.js +1 -1
- package/dist/cli-delegate/cmd_prune.js +1 -1
- package/dist/cli-delegate/cmd_settings_migrate.js +1 -1
- package/dist/cli-delegate/cmd_sync.js +1 -1
- package/dist/cli-delegate/cmd_uninstall.js +1 -1
- package/dist/cli-delegate/cmd_validate.js +1 -1
- package/dist/discovery/deprecation-report.md +1 -1
- package/dist/discovery/discovery-manifest.json +213 -412
- package/dist/discovery/discovery-manifest.json.sha256 +1 -1
- package/dist/discovery/discovery-manifest.summary.md +2 -2
- package/dist/discovery/dormancy-report.md +1 -1
- package/dist/discovery/orphan-report.md +1 -1
- package/dist/discovery/packs.json +3 -3
- package/dist/discovery/trust-report.md +1 -1
- package/dist/discovery/workspaces.json +3 -3
- package/dist/hooks/dispatch.js +2588 -1082
- package/dist/install/install.mjs +744 -482
- package/dist/mcp/registry-manifest.json +3 -3
- package/dist/mcp/server.json +2 -2
- package/dist/mcp/server.mjs +159 -15
- package/dist/router.json +1 -1
- package/dist/scripts/_lib/model_tier.js +9 -0
- package/dist/scripts/_lib/model_tier.js.map +1 -1
- package/dist/scripts/condense.js +50 -0
- package/dist/scripts/condense.js.map +1 -1
- package/docs/MIGRATION.md +21 -1
- package/docs/contracts/settings-classes.md +41 -0
- package/package.json +1 -1
- package/src/agent-src/scripts/roadmap_gates.ts +30 -2
- package/src/agent-src/scripts/update_roadmap_progress.ts +32 -1
- package/src/config/agent-settings.template.yml +34 -0
- package/src/config/evaluator-budgets.json +4 -4
- package/src/config/gate-coverage.yml +39 -9
- package/src/config/gate-violation-baselines.json +9 -3
- package/src/config/hook-token-budget.json +7 -1
- package/src/config/preamble-payload-budget.json +28 -0
- package/src/scripts/_dispatch.bash +52 -5
- package/src/scripts/_lib/git_common_dir.ts +173 -0
- package/src/scripts/_lib/model_tier.ts +9 -0
- package/src/scripts/_lib/release_highlights.ts +108 -7
- package/src/scripts/_lib/skill_catalogue.ts +862 -0
- package/src/scripts/ai_council/clients.ts +182 -3
- package/src/scripts/ai_council/config.ts +16 -1
- package/src/scripts/ai_council/low_impact.ts +14 -1
- package/src/scripts/ai_council/orchestrator.ts +14 -1
- package/src/scripts/ai_council/transport_resolver.ts +32 -2
- package/src/scripts/audit_command_surface.ts +21 -15
- package/src/scripts/build_catalog_index.ts +1 -1
- package/src/scripts/build_discovery_manifest.ts +30 -34
- package/src/scripts/capture_skill_catalogue.ts +185 -328
- package/src/scripts/check_branch_freshness.ts +217 -62
- package/src/scripts/check_release_published.ts +6 -2
- package/src/scripts/check_release_trunk_sync.ts +18 -2
- package/src/scripts/condense.ts +50 -1
- package/src/scripts/dispatch_r2_reviewer.ts +15 -5
- package/src/scripts/evidence_report.ts +15 -4
- package/src/scripts/gen_discovery_baseline.ts +3 -3
- package/src/scripts/hook_manifest.yaml +67 -13
- package/src/scripts/hooks/concern_registry.ts +2 -0
- package/src/scripts/hooks/orchestration_record_hook.ts +21 -0
- package/src/scripts/hooks/subagent_ledger_hook.ts +17 -4
- package/src/scripts/hooks/tool_result_bytes_hook.ts +219 -0
- package/src/scripts/hot_context_hook.ts +13 -1
- package/src/scripts/install.ts +129 -0
- package/src/scripts/janitor.ts +77 -2
- package/src/scripts/lint_command_tiers.ts +29 -75
- package/src/scripts/lint_originality.ts +101 -14
- package/src/scripts/lint_plan_risk_register.ts +22 -9
- package/src/scripts/lint_roadmap_blockers.ts +109 -6
- package/src/scripts/lint_scheduled_deprecations.ts +738 -0
- package/src/scripts/migration_status.ts +17 -1
- package/src/scripts/probe_review_binding_drift.ts +731 -0
- package/src/scripts/release.ts +95 -1
- package/src/scripts/schemas/command.schema.json +6 -11
- package/src/scripts/schemas/skill.schema.json +12 -2
- package/src/scripts/schemas/subagent.schema.json +2 -2
- package/src/scripts/session_eol_report.ts +81 -0
- package/src/scripts/skill_linter.ts +82 -2
- package/src/scripts/workspace_doctor.ts +464 -0
- package/src/scripts/worktree_cleanup_check.ts +113 -8
- package/dist/cli-delegate/chunk-AYKQGIBG.js +0 -1594
|
@@ -247,19 +247,19 @@ export function main(): number {
|
|
|
247
247
|
out.push('');
|
|
248
248
|
|
|
249
249
|
out.push('## Sample commands (first 10)\n');
|
|
250
|
-
out.push('| File | name |
|
|
250
|
+
out.push('| File | name | visibility | cluster |');
|
|
251
251
|
out.push('|---|---|---|---|');
|
|
252
252
|
for (const c of commands.slice(0, 10)) {
|
|
253
253
|
const fm = _frontmatter(c);
|
|
254
254
|
const rel = _relativeTo(c, SRC);
|
|
255
|
-
out.push(`| \`${rel}\` | ${fm.name ?? ''} | ${fm.
|
|
255
|
+
out.push(`| \`${rel}\` | ${fm.name ?? ''} | ${fm.visibility ?? ''} | ${fm.cluster ?? ''} |`);
|
|
256
256
|
}
|
|
257
257
|
out.push('');
|
|
258
258
|
|
|
259
259
|
out.push('## Notes\n');
|
|
260
260
|
out.push('- Every skill carries `domain:` today — Phase 4 mapping uses it as primary signal.');
|
|
261
261
|
out.push('- Rules use `type` (always | auto) + `tier` (safety-floor | kernel | …); no `domain:` today.');
|
|
262
|
-
out.push('- Commands use `
|
|
262
|
+
out.push('- Commands use `visibility` + `cluster`; no `domain:` today.');
|
|
263
263
|
out.push('- Templates have no consistent frontmatter; Phase 4 quarantines them in `unassigned-artefacts.yml`.');
|
|
264
264
|
out.push('');
|
|
265
265
|
|
|
@@ -556,6 +556,30 @@ concerns:
|
|
|
556
556
|
args: []
|
|
557
557
|
fail_closed: false
|
|
558
558
|
severity: advisory
|
|
559
|
+
# road-to-inbox-harvest-2026-08-d-context-ledger Phase 1 Step 1.1 — the
|
|
560
|
+
# tool-result byte counter. `hook-token-budget.json` registers the gap it
|
|
561
|
+
# closes in its own words: bytes-into-context from tool RESULTS are
|
|
562
|
+
# instrumented nowhere, so every context-cost claim in this tree is made
|
|
563
|
+
# against a denominator missing its dominant term.
|
|
564
|
+
#
|
|
565
|
+
# CAPTURE ONLY. It appends `{ts, tool, bytes, measurable}` to a gitignored
|
|
566
|
+
# census file and emits NOTHING to the model — zero injection bytes, which
|
|
567
|
+
# is why it carries a metric registration rather than a per-concern byte
|
|
568
|
+
# cap. The threshold question (when is a result too large) is deliberately
|
|
569
|
+
# unanswered: the distribution has never been observed, and the house rule
|
|
570
|
+
# is baseline before mechanism.
|
|
571
|
+
#
|
|
572
|
+
# The record type has four fields and none can hold free text — a tool
|
|
573
|
+
# NAME, a byte COUNT, a timestamp and a measurable flag. Tool results
|
|
574
|
+
# routinely carry file contents and command output; none of it can reach
|
|
575
|
+
# this output because the output has no field able to carry it.
|
|
576
|
+
#
|
|
577
|
+
# Never blocks, never warns: exit 0 on every path. fail_closed: false.
|
|
578
|
+
tool-result-bytes:
|
|
579
|
+
script: src/scripts/hooks/tool_result_bytes_hook.ts
|
|
580
|
+
args: []
|
|
581
|
+
fail_closed: false
|
|
582
|
+
severity: advisory
|
|
559
583
|
# road-to-subagent-lifecycle-integrity Phase 3 Step 1 — spawn guard, SHADOW
|
|
560
584
|
# ONLY, per docs/contracts/concern-activation-policy.md. On every Agent/Task
|
|
561
585
|
# call it appends one `spawn_guard_shadow` line recording what a guard WOULD
|
|
@@ -590,6 +614,26 @@ concerns:
|
|
|
590
614
|
# blocker: in-process Agent-tool subagents inherit the host process env and
|
|
591
615
|
# cannot be marked per-spawn; the axis binds where the suite launches a CLI
|
|
592
616
|
# session itself.
|
|
617
|
+
#
|
|
618
|
+
# Superseded in part, 2026-08-14 (road-to-subagent-lifecycle-integrity Phase 4
|
|
619
|
+
# Step 2). The env half above still stands — nothing marks an in-process spawn
|
|
620
|
+
# per-process. What has changed is that env is no longer the only candidate
|
|
621
|
+
# channel: `subagent_start` / `subagent_stop` are bound on this host
|
|
622
|
+
# (2.1.229, Phase 0 Step 1) and their payload carries `agent_id`, which
|
|
623
|
+
# `subagent-ledger` correlates on today. So a payload-bound role axis —
|
|
624
|
+
# `agent_id` present ⇒ role `worker` — is a live proposal (Phase 4 Step 1),
|
|
625
|
+
# not a closed question.
|
|
626
|
+
# It is NOT implemented, and the reason is a missing measurement rather than a
|
|
627
|
+
# missing decision: whether `agent_id` reaches a `PreToolUse` / `PostToolUse`
|
|
628
|
+
# payload inside a subagent is unverified on this host. That is Phase 0 Step 4,
|
|
629
|
+
# blocked on `raw-capture-needs-host-env` (the capture itself is already
|
|
630
|
+
# shipped — `dispatch_hook._maybe_capture_payload`). A neighbouring assertion
|
|
631
|
+
# from the same spike already came back negative: `agent_type` does NOT arrive
|
|
632
|
+
# on `SubagentStop` (18 of 25 stop records null), so the documented-field
|
|
633
|
+
# presence is not assumed for `agent_id` either.
|
|
634
|
+
# Until that spike lands the env path is the only one wired, and the
|
|
635
|
+
# pre_tool_use exemption above is unaffected either way — a payload-resolved
|
|
636
|
+
# `worker` still cannot drop a pre_tool_use concern.
|
|
593
637
|
roles:
|
|
594
638
|
worker:
|
|
595
639
|
# Orchestrator-only concerns (Phase 2.3): a worker never talks to the
|
|
@@ -603,7 +647,7 @@ platforms:
|
|
|
603
647
|
session_end: [chat-history, memory-learn, session-register]
|
|
604
648
|
stop: [chat-history, hot-context, verify-before-complete, self-repair, session-register]
|
|
605
649
|
pre_tool_use: [block-no-verify, block-unauthorized-git, evidence-independence, block-kernel-rule-writes, block-config-weakening, rtk-wrap, design-slop, ui-route-nudge, code-graph-nudge, reread-guard]
|
|
606
|
-
post_tool_use: [chat-history, roadmap-progress, context-hygiene, verify-before-complete, minimal-safe-diff, injection-scan, pr-url-reminder, orchestration-record, edit-shape]
|
|
650
|
+
post_tool_use: [chat-history, roadmap-progress, context-hygiene, verify-before-complete, minimal-safe-diff, injection-scan, pr-url-reminder, orchestration-record, tool-result-bytes, edit-shape]
|
|
607
651
|
|
|
608
652
|
claude:
|
|
609
653
|
session_start: [chat-history, hot-context, handoff-context, session-canary, council-availability, first-run-gate, onboarding-gate, verify-before-complete, minimal-safe-diff, profile-staleness, wrapper-freshness, surface-probe, session-register]
|
|
@@ -611,14 +655,24 @@ platforms:
|
|
|
611
655
|
stop: [chat-history, hot-context, verify-before-complete, team-review-gate, end-review-nudge, turn-end-gate, self-repair, session-register, session-eol]
|
|
612
656
|
user_prompt_submit: [chat-history, verify-before-complete, minimal-safe-diff, language-mirror, delegation-nudge, git-authorization, session-canary, self-repair, session-register]
|
|
613
657
|
pre_tool_use: [block-no-verify, block-unauthorized-git, evidence-independence, block-kernel-rule-writes, block-config-weakening, rtk-wrap, design-slop, ui-route-nudge, code-graph-nudge, reread-guard, spawn-guard-shadow]
|
|
614
|
-
post_tool_use: [chat-history, roadmap-progress, context-hygiene, verify-before-complete, minimal-safe-diff, injection-scan, pr-url-reminder, orchestration-record, edit-shape, language-mirror]
|
|
615
|
-
# round-5 § 6.1
|
|
616
|
-
#
|
|
617
|
-
#
|
|
618
|
-
#
|
|
619
|
-
#
|
|
620
|
-
#
|
|
621
|
-
|
|
658
|
+
post_tool_use: [chat-history, roadmap-progress, context-hygiene, verify-before-complete, minimal-safe-diff, injection-scan, pr-url-reminder, orchestration-record, tool-result-bytes, edit-shape, language-mirror]
|
|
659
|
+
# `language-mirror` — round-5 § 6.1. `pre_compact` sets a pin-lost marker;
|
|
660
|
+
# the `post_tool_use` entry above re-emits the pin exactly once while that
|
|
661
|
+
# marker is set, then clears it. One extra injection per compaction EVENT —
|
|
662
|
+
# never one per tool call, which is the shape § 6.2 refuses. claude only: it
|
|
663
|
+
# is the sole verified platform, and the measured compaction defect is a
|
|
664
|
+
# Claude Code transcript fact. (It was the ONLY binding on this slot until
|
|
665
|
+
# the entry below; that sentence is retired rather than left to rot.)
|
|
666
|
+
#
|
|
667
|
+
# `hot-context` — road-to-inbox-harvest-2026-08-d-context-ledger Step 2.1.
|
|
668
|
+
# This is the one slot that fires WHILE state is being destroyed, and it
|
|
669
|
+
# carried no WRITER at all: the cache a post-compaction `session_start`
|
|
670
|
+
# restores was written at the last `stop`, so everything the session did
|
|
671
|
+
# between that stop and the compaction was lost with it. The concern is
|
|
672
|
+
# already bound on `stop` and `session_start`; this adds the write at the
|
|
673
|
+
# moment the loss actually happens. Deterministic rebuild, emits nothing
|
|
674
|
+
# here — the 4,096-byte row it carries is for its session_start restore.
|
|
675
|
+
pre_compact: [language-mirror, hot-context]
|
|
622
676
|
# road-to-subagent-lifecycle-integrity Phase 1 Step 1. `claude` is the only
|
|
623
677
|
# platform where both native events were measured present on the installed
|
|
624
678
|
# binary (2.1.229 — see the evidence page cited on the alias rows below).
|
|
@@ -655,7 +709,7 @@ platforms:
|
|
|
655
709
|
stop: [chat-history, hot-context, verify-before-complete, self-repair, session-register]
|
|
656
710
|
user_prompt_submit: [chat-history, verify-before-complete, minimal-safe-diff, language-mirror, git-authorization, session-canary, self-repair, session-register]
|
|
657
711
|
pre_tool_use: [block-no-verify, block-unauthorized-git, evidence-independence, block-kernel-rule-writes, block-config-weakening, rtk-wrap, design-slop, ui-route-nudge, code-graph-nudge, reread-guard, spawn-guard-shadow]
|
|
658
|
-
post_tool_use: [chat-history, roadmap-progress, context-hygiene, verify-before-complete, minimal-safe-diff, injection-scan, pr-url-reminder, orchestration-record, edit-shape]
|
|
712
|
+
post_tool_use: [chat-history, roadmap-progress, context-hygiene, verify-before-complete, minimal-safe-diff, injection-scan, pr-url-reminder, orchestration-record, tool-result-bytes, edit-shape]
|
|
659
713
|
# subagent-ledger IS bound here, unlike the two nudges above, and the
|
|
660
714
|
# difference is the delivery channel rather than the platform: the nudges
|
|
661
715
|
# depend on stdout reaching the model, which this trampoline discards by
|
|
@@ -678,7 +732,7 @@ platforms:
|
|
|
678
732
|
session_end: [chat-history, memory-learn, session-register]
|
|
679
733
|
stop: [chat-history, hot-context, verify-before-complete, self-repair, session-register]
|
|
680
734
|
user_prompt_submit: [chat-history, verify-before-complete, minimal-safe-diff, language-mirror, git-authorization, session-canary, self-repair, session-register]
|
|
681
|
-
post_tool_use: [chat-history, roadmap-progress, context-hygiene, verify-before-complete, minimal-safe-diff, injection-scan, pr-url-reminder, orchestration-record, edit-shape]
|
|
735
|
+
post_tool_use: [chat-history, roadmap-progress, context-hygiene, verify-before-complete, minimal-safe-diff, injection-scan, pr-url-reminder, orchestration-record, tool-result-bytes, edit-shape]
|
|
682
736
|
|
|
683
737
|
# Phase 7.6 — Cline. Hooks live under `.clinerules/hooks/<HookName>`
|
|
684
738
|
# (project, no file extension, must be executable per Cline docs) or
|
|
@@ -693,7 +747,7 @@ platforms:
|
|
|
693
747
|
session_end: [chat-history, memory-learn, session-register]
|
|
694
748
|
stop: [chat-history, hot-context, verify-before-complete, self-repair, session-register]
|
|
695
749
|
user_prompt_submit: [chat-history, verify-before-complete, minimal-safe-diff, language-mirror, git-authorization, session-canary, self-repair, session-register]
|
|
696
|
-
post_tool_use: [chat-history, roadmap-progress, context-hygiene, verify-before-complete, minimal-safe-diff, injection-scan, pr-url-reminder, orchestration-record, edit-shape]
|
|
750
|
+
post_tool_use: [chat-history, roadmap-progress, context-hygiene, verify-before-complete, minimal-safe-diff, injection-scan, pr-url-reminder, orchestration-record, tool-result-bytes, edit-shape]
|
|
697
751
|
|
|
698
752
|
# Phase 7.7 — Windsurf (Cascade). Hooks live at `.windsurf/hooks.json`
|
|
699
753
|
# (project) or `~/.codeium/windsurf/hooks.json` (user). Cascade has
|
|
@@ -729,7 +783,7 @@ platforms:
|
|
|
729
783
|
session_end: [chat-history, memory-learn, session-register]
|
|
730
784
|
stop: [chat-history, hot-context, verify-before-complete, self-repair, session-register]
|
|
731
785
|
user_prompt_submit: [chat-history, verify-before-complete, minimal-safe-diff, language-mirror, git-authorization, session-canary, self-repair, session-register]
|
|
732
|
-
post_tool_use: [chat-history, roadmap-progress, context-hygiene, verify-before-complete, minimal-safe-diff, injection-scan, pr-url-reminder, orchestration-record, edit-shape]
|
|
786
|
+
post_tool_use: [chat-history, roadmap-progress, context-hygiene, verify-before-complete, minimal-safe-diff, injection-scan, pr-url-reminder, orchestration-record, tool-result-bytes, edit-shape]
|
|
733
787
|
|
|
734
788
|
# Phase 7.9 — Copilot has no hook surface. Concerns route through
|
|
735
789
|
# rule-only fallback; the dispatcher silently no-ops on --platform copilot.
|
|
@@ -63,6 +63,7 @@ import { main as editShapeMain } from './edit_shape_hook.js';
|
|
|
63
63
|
import { main as rereadGuardMain } from './reread_guard_hook.js';
|
|
64
64
|
import { main as sessionEolMain } from './session_eol_hook.js';
|
|
65
65
|
import { main as subagentLedgerMain } from './subagent_ledger_hook.js';
|
|
66
|
+
import { main as toolResultBytesMain } from './tool_result_bytes_hook.js';
|
|
66
67
|
import { main as spawnGuardShadowMain } from './spawn_guard_shadow_hook.js';
|
|
67
68
|
|
|
68
69
|
/** A concern `main` — argv-taking or not; both shapes exist. */
|
|
@@ -109,5 +110,6 @@ export const CONCERN_REGISTRY: Readonly<Record<string, ConcernMain>> = {
|
|
|
109
110
|
'src/scripts/hooks/reread_guard_hook.ts': rereadGuardMain as ConcernMain,
|
|
110
111
|
'src/scripts/hooks/session_eol_hook.ts': sessionEolMain as ConcernMain,
|
|
111
112
|
'src/scripts/hooks/subagent_ledger_hook.ts': subagentLedgerMain as ConcernMain,
|
|
113
|
+
'src/scripts/hooks/tool_result_bytes_hook.ts': toolResultBytesMain as ConcernMain,
|
|
112
114
|
'src/scripts/hooks/spawn_guard_shadow_hook.ts': spawnGuardShadowMain as ConcernMain,
|
|
113
115
|
};
|
|
@@ -243,6 +243,27 @@ export function buildRecordInput(facts: DispatchFacts, ts: string, id: string):
|
|
|
243
243
|
|
|
244
244
|
if (!facts.isAsync) {
|
|
245
245
|
if (facts.resolvedModel) {
|
|
246
|
+
// road-to-inbox-harvest-2026-08-d-top-band-model-economy Step 1.1.
|
|
247
|
+
//
|
|
248
|
+
// The raw id goes into the field the schema already reserves for it
|
|
249
|
+
// ("the model id the provider reported SERVING"). Without it, a
|
|
250
|
+
// dispatch that ran on a band the three-tier vocabulary cannot
|
|
251
|
+
// express is byte-identical in the record to one where the host
|
|
252
|
+
// reported no model at all: `extractModelFamily` returns null for
|
|
253
|
+
// both, so `tiers` is omitted for both.
|
|
254
|
+
//
|
|
255
|
+
// That collapse is precisely what blocks ADR-035's reopen
|
|
256
|
+
// condition. "Did a vendor ship a band the three tiers cannot
|
|
257
|
+
// express" is answerable only if an unmapped id is distinguishable
|
|
258
|
+
// from an absent one, and recording the served id separates them:
|
|
259
|
+
// unmapped ⇒ `model_served` set with `tiers` absent; unreported ⇒
|
|
260
|
+
// both absent.
|
|
261
|
+
//
|
|
262
|
+
// Vendor-neutral by construction: the id is stored verbatim as an
|
|
263
|
+
// opaque host string. No band above `high` is named and no mapping
|
|
264
|
+
// is invented — naming a fourth band is the decision this
|
|
265
|
+
// measurement exists to inform, never one it may pre-empt.
|
|
266
|
+
input.model_served = facts.resolvedModel;
|
|
246
267
|
const family = extractModelFamily(facts.resolvedModel);
|
|
247
268
|
if (family) input.tiers = [family];
|
|
248
269
|
}
|
|
@@ -73,8 +73,9 @@
|
|
|
73
73
|
* a `subagent_reaped` line is written for it. That line is not bookkeeping —
|
|
74
74
|
* it is the closest thing this instrument has to a direct observation of a
|
|
75
75
|
* never-returning dispatch, which is why the leak is reported rather than
|
|
76
|
-
* quietly swept. `session_id` rides on the record
|
|
77
|
-
* by session; nothing reads it yet and it is not
|
|
76
|
+
* quietly swept. `session_id` rides on the open record AND on both dispatch
|
|
77
|
+
* lines so a rate can be scoped by session; nothing reads it yet and it is not
|
|
78
|
+
* claimed to.
|
|
78
79
|
*
|
|
79
80
|
* ── Depth and concurrency (Step 3) ────────────────────────────────────────
|
|
80
81
|
*
|
|
@@ -598,6 +599,13 @@ function handleStart(root: string, payload: JsonObject, nowIso: string, sessionI
|
|
|
598
599
|
event: 'subagent_start',
|
|
599
600
|
ts: nowIso,
|
|
600
601
|
ref,
|
|
602
|
+
// Phase 1 Step 4 correction (b): without this the dispatch denominator
|
|
603
|
+
// is not per-session. Three sessions share one ledger file, so a rate
|
|
604
|
+
// computed over the file aggregates strangers — the measured window
|
|
605
|
+
// held 7 starts against 25 stops, one of them an agent type this
|
|
606
|
+
// session never dispatched. The open record already carried it; the
|
|
607
|
+
// appended line did not, and the line is what a rate is computed from.
|
|
608
|
+
session_id: sessionId,
|
|
601
609
|
agent_type: rec.agent_type,
|
|
602
610
|
parent_ref: parentRef,
|
|
603
611
|
depth,
|
|
@@ -606,7 +614,7 @@ function handleStart(root: string, payload: JsonObject, nowIso: string, sessionI
|
|
|
606
614
|
});
|
|
607
615
|
}
|
|
608
616
|
|
|
609
|
-
function handleStop(root: string, payload: JsonObject, nowIso: string): void {
|
|
617
|
+
function handleStop(root: string, payload: JsonObject, nowIso: string, sessionId: string | null): void {
|
|
610
618
|
const agentId = str(payload, 'agent_id', 'agentId');
|
|
611
619
|
if (agentId === null) {
|
|
612
620
|
appendUnidentified(root, 'subagent_stop', nowIso, payload);
|
|
@@ -631,6 +639,11 @@ function handleStop(root: string, payload: JsonObject, nowIso: string): void {
|
|
|
631
639
|
event: 'subagent_stop',
|
|
632
640
|
ts: nowIso,
|
|
633
641
|
ref,
|
|
642
|
+
// The session this stop was OBSERVED in, read from the same envelope
|
|
643
|
+
// position as the start's — never back-filled from `rec`, which would
|
|
644
|
+
// relabel a cross-session stop as belonging to the dispatching session
|
|
645
|
+
// and hide exactly the mismatch this field exists to expose.
|
|
646
|
+
session_id: sessionId,
|
|
634
647
|
agent_type: rec?.agent_type ?? str(payload, 'agent_type', 'agentType', 'subagent_type', 'subagentType'),
|
|
635
648
|
depth: rec?.depth ?? null,
|
|
636
649
|
depth_basis: rec?.depth_basis ?? null,
|
|
@@ -661,7 +674,7 @@ export function processEnvelope(envelope: JsonValue, consumerRoot: string): numb
|
|
|
661
674
|
reapStaleOpenRecords(consumerRoot, nowIso);
|
|
662
675
|
|
|
663
676
|
if (event === 'subagent_start') handleStart(consumerRoot, payload, nowIso, sessionId);
|
|
664
|
-
else handleStop(consumerRoot, payload, nowIso);
|
|
677
|
+
else handleStop(consumerRoot, payload, nowIso, sessionId);
|
|
665
678
|
} catch {
|
|
666
679
|
// Malformed payload, unreadable disk, anything — never disturb the run.
|
|
667
680
|
return EXIT_ALLOW;
|
|
@@ -0,0 +1,219 @@
|
|
|
1
|
+
#!/usr/bin/env tsx
|
|
2
|
+
/**
|
|
3
|
+
* Tool-result byte counter — capture only, no behaviour change
|
|
4
|
+
* (road-to-inbox-harvest-2026-08-d-context-ledger Phase 1, Step 1.1).
|
|
5
|
+
*
|
|
6
|
+
* Why this exists. `hook-token-budget.json` registers the gap in its own
|
|
7
|
+
* words: *"bytes-into-context from tool RESULTS is not instrumented
|
|
8
|
+
* anywhere (the census covers hook payloads only)"*. The injection census
|
|
9
|
+
* measures what HOOKS put into context — a few kilobytes per turn under a
|
|
10
|
+
* per-slot cap. It does not measure what TOOLS put into context, which is
|
|
11
|
+
* the larger number by any plausible margin and the one nobody can cite.
|
|
12
|
+
* Every claim about context cost in this tree is therefore made against a
|
|
13
|
+
* denominator that excludes its own dominant term.
|
|
14
|
+
*
|
|
15
|
+
* This concern is the instrument and ONLY the instrument. It appends; it
|
|
16
|
+
* never decides, never advises, and emits nothing to the model. The
|
|
17
|
+
* threshold question — when is a tool result too large — is deliberately
|
|
18
|
+
* NOT answered here: the distribution has never been observed, and a
|
|
19
|
+
* threshold set before the distribution is a number invented rather than
|
|
20
|
+
* measured. That ordering is the house rule this repository has paid to
|
|
21
|
+
* learn more than once.
|
|
22
|
+
*
|
|
23
|
+
* ── What is recorded, and what is refused ──────────────────────────────
|
|
24
|
+
*
|
|
25
|
+
* PRIVACY BY CONSTRUCTION — never widen this file to record content.
|
|
26
|
+
*
|
|
27
|
+
* The record type has exactly three fields and NONE of them can hold free
|
|
28
|
+
* text: a timestamp, a tool NAME (an id-shaped enum — `Read`, `Bash`,
|
|
29
|
+
* `Grep` — the same single host string `orchestration_record_hook` already
|
|
30
|
+
* records), and a BYTE COUNT. A tool result routinely carries file
|
|
31
|
+
* contents, command output, secrets in an env dump, and customer data from
|
|
32
|
+
* an API response. None of it reaches this file's output, because the
|
|
33
|
+
* output has no field able to carry it. A record shape that cannot hold a
|
|
34
|
+
* secret has no scrubber that can fail — the same construction
|
|
35
|
+
* `domain-safety-pii` § Surface 2 prescribes for logs and the reason the
|
|
36
|
+
* sibling `subagent-ledger` refuses `last_assistant_message` in any form.
|
|
37
|
+
*
|
|
38
|
+
* The result is measured, never read: `_resultBytes` computes a length and
|
|
39
|
+
* discards the value in the same expression.
|
|
40
|
+
*
|
|
41
|
+
* ── Always exit 0 ─────────────────────────────────────────────────────
|
|
42
|
+
*
|
|
43
|
+
* A `warn` (exit 2) is read as a hard BLOCK on this host, and this concern
|
|
44
|
+
* has nothing to say to the model. Every path returns EXIT_ALLOW —
|
|
45
|
+
* malformed envelope, unparseable payload, unwritable disk. An instrument
|
|
46
|
+
* that can fail a turn is a mechanism, and this is not one yet.
|
|
47
|
+
*
|
|
48
|
+
* ── Absence is recorded, not inferred ─────────────────────────────────
|
|
49
|
+
*
|
|
50
|
+
* A payload carrying no readable result writes a line with `bytes: null`
|
|
51
|
+
* and `measurable: false` rather than writing nothing. The two cases are
|
|
52
|
+
* otherwise byte-identical in the output — "this host does not carry tool
|
|
53
|
+
* results in its PostToolUse payload" and "no tools ran" would produce the
|
|
54
|
+
* same empty file, and the first is a finding about the instrument's own
|
|
55
|
+
* coverage that the baseline has to be able to see. This is the
|
|
56
|
+
* instrument-goes-quiet failure the sibling ledger's round-1 review fixed
|
|
57
|
+
* one layer down, applied here before it can be shipped.
|
|
58
|
+
*/
|
|
59
|
+
import * as fs from 'node:fs';
|
|
60
|
+
import * as path from 'node:path';
|
|
61
|
+
import { fileURLToPath, pathToFileURL } from 'node:url';
|
|
62
|
+
|
|
63
|
+
import { is_replay_mode } from './state_io.js';
|
|
64
|
+
import { readHookStdin } from './hook_stdin.js';
|
|
65
|
+
|
|
66
|
+
const EXIT_ALLOW = 0;
|
|
67
|
+
|
|
68
|
+
type JsonValue = string | number | boolean | null | JsonValue[] | { [k: string]: JsonValue };
|
|
69
|
+
type JsonObject = { [k: string]: JsonValue };
|
|
70
|
+
|
|
71
|
+
function isObject(v: unknown): v is JsonObject {
|
|
72
|
+
return typeof v === 'object' && v !== null && !Array.isArray(v);
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
/**
|
|
76
|
+
* Census file, relative to the consumer repo root. Sibling of
|
|
77
|
+
* `injection-census.jsonl` in the same directory and the same
|
|
78
|
+
* `{ts, …, bytes}` line shape — one family, two units: that file counts
|
|
79
|
+
* bytes this suite injects, this one counts bytes the host's tools do.
|
|
80
|
+
* Gitignored via `/agents/runtime/`.
|
|
81
|
+
*/
|
|
82
|
+
export const CENSUS_FILE = path.join('agents', 'runtime', 'state', 'tool-result-census.jsonl');
|
|
83
|
+
|
|
84
|
+
/** The payload keys a host may use for a tool's result, widest first. */
|
|
85
|
+
const RESULT_KEYS = ['tool_response', 'toolResponse', 'tool_result', 'toolUseResult'] as const;
|
|
86
|
+
|
|
87
|
+
/**
|
|
88
|
+
* UTF-8 byte length of a tool result, or `null` when the payload carries
|
|
89
|
+
* none under any known key.
|
|
90
|
+
*
|
|
91
|
+
* A result is an object on most tools and a bare string on some (`Bash`
|
|
92
|
+
* returns its stdout directly — see the sibling hook's own test corpus), so
|
|
93
|
+
* both shapes are measured. The sibling's `extractToolResult` returns
|
|
94
|
+
* `JsonObject | null` and would report a bare-string result as absent,
|
|
95
|
+
* which is correct for its purpose (it reads named usage fields) and wrong
|
|
96
|
+
* for this one: a 400 KB command output is exactly the case this counter
|
|
97
|
+
* exists to see, and it is precisely the shape that extractor drops.
|
|
98
|
+
*
|
|
99
|
+
* Bytes, not characters: a multibyte result costs what it costs on the
|
|
100
|
+
* wire, and `String.length` would under-report it.
|
|
101
|
+
*/
|
|
102
|
+
export function _resultBytes(payload: JsonObject): number | null {
|
|
103
|
+
for (const key of RESULT_KEYS) {
|
|
104
|
+
const v = payload[key];
|
|
105
|
+
if (v === undefined || v === null) continue;
|
|
106
|
+
if (typeof v === 'string') return Buffer.byteLength(v, 'utf8');
|
|
107
|
+
try {
|
|
108
|
+
return Buffer.byteLength(JSON.stringify(v), 'utf8');
|
|
109
|
+
} catch {
|
|
110
|
+
// Circular or otherwise unserialisable — unmeasurable, not zero.
|
|
111
|
+
return null;
|
|
112
|
+
}
|
|
113
|
+
}
|
|
114
|
+
return null;
|
|
115
|
+
}
|
|
116
|
+
|
|
117
|
+
/** The tool's name, read from the same positions the sibling hook reads. */
|
|
118
|
+
export function _toolName(payload: JsonObject, envelope: JsonObject): string | null {
|
|
119
|
+
const candidates = [
|
|
120
|
+
payload['tool_name'],
|
|
121
|
+
payload['toolName'],
|
|
122
|
+
payload['tool'],
|
|
123
|
+
envelope['tool_name'],
|
|
124
|
+
envelope['tool'],
|
|
125
|
+
];
|
|
126
|
+
for (const v of candidates) {
|
|
127
|
+
if (typeof v === 'string' && v) return v;
|
|
128
|
+
}
|
|
129
|
+
return null;
|
|
130
|
+
}
|
|
131
|
+
|
|
132
|
+
function unwrapPayload(envelope: JsonObject): JsonObject {
|
|
133
|
+
const inner = envelope['payload'];
|
|
134
|
+
return isObject(inner) ? inner : envelope;
|
|
135
|
+
}
|
|
136
|
+
|
|
137
|
+
/** `${ts.slice(0,7)}` is NOT used here — the census is one flat file, as its sibling is. */
|
|
138
|
+
function censusFile(consumerRoot: string): string {
|
|
139
|
+
return path.join(consumerRoot, CENSUS_FILE);
|
|
140
|
+
}
|
|
141
|
+
|
|
142
|
+
export interface CensusLine {
|
|
143
|
+
ts: string;
|
|
144
|
+
tool: string | null;
|
|
145
|
+
bytes: number | null;
|
|
146
|
+
/** `false` ⇒ the payload carried no readable result. See the header. */
|
|
147
|
+
measurable: boolean;
|
|
148
|
+
}
|
|
149
|
+
|
|
150
|
+
export function _appendCensusLine(consumerRoot: string, line: CensusLine): void {
|
|
151
|
+
if (is_replay_mode()) return;
|
|
152
|
+
const file = censusFile(consumerRoot);
|
|
153
|
+
fs.mkdirSync(path.dirname(file), { recursive: true });
|
|
154
|
+
fs.appendFileSync(file, `${JSON.stringify(line)}\n`, 'utf8');
|
|
155
|
+
}
|
|
156
|
+
|
|
157
|
+
/** Process an ALREADY-PARSED dispatcher envelope. Always returns EXIT_ALLOW. */
|
|
158
|
+
export function processEnvelope(envelope: JsonValue, consumerRoot: string): number {
|
|
159
|
+
try {
|
|
160
|
+
if (!isObject(envelope)) return EXIT_ALLOW;
|
|
161
|
+
if (envelope['event'] !== 'post_tool_use') return EXIT_ALLOW;
|
|
162
|
+
|
|
163
|
+
const payload = unwrapPayload(envelope);
|
|
164
|
+
const bytes = _resultBytes(payload);
|
|
165
|
+
|
|
166
|
+
_appendCensusLine(consumerRoot, {
|
|
167
|
+
ts: new Date().toISOString(),
|
|
168
|
+
tool: _toolName(payload, envelope),
|
|
169
|
+
bytes,
|
|
170
|
+
measurable: bytes !== null,
|
|
171
|
+
});
|
|
172
|
+
} catch {
|
|
173
|
+
// Malformed payload, unwritable disk, anything — never disturb the run.
|
|
174
|
+
return EXIT_ALLOW;
|
|
175
|
+
}
|
|
176
|
+
return EXIT_ALLOW;
|
|
177
|
+
}
|
|
178
|
+
|
|
179
|
+
/** Resolve the consumer repo root, same precedence as the sibling ledger. */
|
|
180
|
+
export function resolveConsumerRoot(envelope: JsonValue): string {
|
|
181
|
+
if (isObject(envelope)) {
|
|
182
|
+
for (const key of ['workspace_root', 'project_root']) {
|
|
183
|
+
const v = envelope[key];
|
|
184
|
+
if (typeof v === 'string' && v) return v;
|
|
185
|
+
}
|
|
186
|
+
const cwd = unwrapPayload(envelope)['cwd'];
|
|
187
|
+
if (typeof cwd === 'string' && cwd) return cwd;
|
|
188
|
+
}
|
|
189
|
+
return process.cwd();
|
|
190
|
+
}
|
|
191
|
+
|
|
192
|
+
export function main(): number {
|
|
193
|
+
const raw = readHookStdin();
|
|
194
|
+
let envelope: JsonValue = {};
|
|
195
|
+
try {
|
|
196
|
+
envelope = raw.trim() ? (JSON.parse(raw) as JsonValue) : {};
|
|
197
|
+
} catch {
|
|
198
|
+
return EXIT_ALLOW;
|
|
199
|
+
}
|
|
200
|
+
return processEnvelope(envelope, resolveConsumerRoot(envelope));
|
|
201
|
+
}
|
|
202
|
+
|
|
203
|
+
// Bundle-safety: never auto-run when inlined into an esbuild bundle, where
|
|
204
|
+
// every module shares the bundle's `import.meta.url`.
|
|
205
|
+
declare const __AGENT_CONFIG_BUNDLE__: boolean | undefined;
|
|
206
|
+
function _isCliEntry(): boolean {
|
|
207
|
+
if (typeof __AGENT_CONFIG_BUNDLE__ !== 'undefined' && __AGENT_CONFIG_BUNDLE__) {
|
|
208
|
+
return false;
|
|
209
|
+
}
|
|
210
|
+
if (process.argv[1] === undefined) return false;
|
|
211
|
+
const argvUrl = pathToFileURL(path.resolve(process.argv[1])).href;
|
|
212
|
+
if (import.meta.url === argvUrl) return true;
|
|
213
|
+
try {
|
|
214
|
+
return fs.realpathSync(fileURLToPath(import.meta.url)) === fs.realpathSync(path.resolve(process.argv[1]));
|
|
215
|
+
} catch {
|
|
216
|
+
return false;
|
|
217
|
+
}
|
|
218
|
+
}
|
|
219
|
+
if (_isCliEntry()) process.exit(main());
|
|
@@ -379,7 +379,19 @@ export function main(): number {
|
|
|
379
379
|
if (_is_replay_mode()) {
|
|
380
380
|
return 0; // replay fixtures: read-only, no state mutation
|
|
381
381
|
}
|
|
382
|
-
|
|
382
|
+
// `pre_compact` joins the write set (road-to-inbox-harvest-2026-08-d-
|
|
383
|
+
// context-ledger Step 2.1). It is the only slot that fires WHILE state
|
|
384
|
+
// is being destroyed, and until now it carried no writer at all: the
|
|
385
|
+
// cache a post-compaction `session_start` restores was written at the
|
|
386
|
+
// last `stop`, so everything the session did since then was lost with
|
|
387
|
+
// the compaction. Writing here makes the restore reflect the moment
|
|
388
|
+
// before the loss instead of the moment before the last turn.
|
|
389
|
+
//
|
|
390
|
+
// Cheap and idempotent by construction: `_write_hot_context` is a
|
|
391
|
+
// deterministic rebuild from the history file, so an extra fire costs
|
|
392
|
+
// one rebuild and can never produce a worse cache than the older one it
|
|
393
|
+
// replaces. It emits nothing — the restore side is unchanged.
|
|
394
|
+
if (event === 'stop' || event === 'session_end' || event === 'pre_compact') {
|
|
383
395
|
_write_hot_context(root);
|
|
384
396
|
} else if (event === 'session_start') {
|
|
385
397
|
const source = String(payload.source ?? '');
|
package/src/scripts/install.ts
CHANGED
|
@@ -81,6 +81,12 @@ import * as rule_layer_overlap from './_lib/rule_layer_overlap.js';
|
|
|
81
81
|
import * as installed_tools from './_lib/installed_tools.js';
|
|
82
82
|
import { collect_drift, format_drift_report } from './_lib/install_drift.js';
|
|
83
83
|
import * as user_global_paths from './_lib/user_global_paths.js';
|
|
84
|
+
// The LIBRARY half, deliberately not `capture_skill_catalogue.js`: that module
|
|
85
|
+
// is a CLI entry with a top-level `process.exit()`, and esbuild would bundle
|
|
86
|
+
// that exit into the installer a consumer loads (`check_installer_import_purity`
|
|
87
|
+
// catches exactly this). The lib has no entry and no exit.
|
|
88
|
+
import * as catalogue from './_lib/skill_catalogue.js';
|
|
89
|
+
import { isInteractiveSession, type SessionProbe } from '../shared/interactiveContext.js';
|
|
84
90
|
import * as claude_desktop_bundler from './_lib/claude_desktop_bundler.js';
|
|
85
91
|
import * as claude_settings_hooks from './_lib/claude_settings_hooks.js';
|
|
86
92
|
import { find_project_root_with_anchor, load_agent_settings } from './_lib/agent_settings.js';
|
|
@@ -3899,6 +3905,32 @@ function install_global(
|
|
|
3899
3905
|
if (!state.QUIET) warn(`settings-surface snapshot failed — ${String(e)}`);
|
|
3900
3906
|
}
|
|
3901
3907
|
|
|
3908
|
+
// Catalogue-budget warning (road-to-skill-catalogue-budget Phase 1).
|
|
3909
|
+
// Some hosts silently truncate the skill catalogue they were handed: the
|
|
3910
|
+
// deploy reports success, and the model never sees most of what was
|
|
3911
|
+
// deployed. A host that publishes its own dropped count has a MEASURED
|
|
3912
|
+
// ceiling; one that does not gets no warning and no invented number.
|
|
3913
|
+
// Warn only — over-shipping is the safe direction, and a consumer who
|
|
3914
|
+
// accepts truncation is making a legitimate choice.
|
|
3915
|
+
if (!state.QUIET) {
|
|
3916
|
+
try {
|
|
3917
|
+
for (const line of _catalogue_truncation_warnings(deploy_results, project_root)) {
|
|
3918
|
+
warn(line);
|
|
3919
|
+
}
|
|
3920
|
+
const notice = _scoped_migration_notice(deploy_results, project_root, package_root, {
|
|
3921
|
+
env: process.env,
|
|
3922
|
+
stdinTty: process.stdin.isTTY === true,
|
|
3923
|
+
stdoutTty: process.stdout.isTTY === true,
|
|
3924
|
+
});
|
|
3925
|
+
if (notice.length > 0) {
|
|
3926
|
+
process.stdout.write('\n');
|
|
3927
|
+
for (const line of notice) info(line);
|
|
3928
|
+
}
|
|
3929
|
+
} catch {
|
|
3930
|
+
// A measurement must never sink an install that otherwise worked.
|
|
3931
|
+
}
|
|
3932
|
+
}
|
|
3933
|
+
|
|
3902
3934
|
if (!state.QUIET) {
|
|
3903
3935
|
process.stdout.write('\n');
|
|
3904
3936
|
success(`Global install completed (v${installed_version}).`);
|
|
@@ -3907,6 +3939,103 @@ function install_global(
|
|
|
3907
3939
|
return 0;
|
|
3908
3940
|
}
|
|
3909
3941
|
|
|
3942
|
+
/**
|
|
3943
|
+
* One line per deployed host whose catalogue exceeds a ceiling this machine has
|
|
3944
|
+
* actually measured. Empty when nothing has been measured — the observation log
|
|
3945
|
+
* lives where captures accumulate, and an install on a machine that never ran
|
|
3946
|
+
* one has no basis to claim a limit.
|
|
3947
|
+
*
|
|
3948
|
+
* Returns lines rather than printing them so the decision is testable without
|
|
3949
|
+
* capturing stdout.
|
|
3950
|
+
*/
|
|
3951
|
+
export function _catalogue_truncation_warnings(
|
|
3952
|
+
deploy_results: Record<string, DeployResult>,
|
|
3953
|
+
project_root: string | null,
|
|
3954
|
+
): string[] {
|
|
3955
|
+
const log_candidates = [
|
|
3956
|
+
path.join(user_global_paths.event4u_root(), 'state', 'skill-catalogue.jsonl'),
|
|
3957
|
+
...(project_root ? [path.join(project_root, catalogue.OBSERVATION_LOG)] : []),
|
|
3958
|
+
];
|
|
3959
|
+
const records = log_candidates.flatMap((p) => catalogue.readObservationLog(p));
|
|
3960
|
+
if (records.length === 0) return [];
|
|
3961
|
+
|
|
3962
|
+
const limits = catalogue.knownHostLimits(records);
|
|
3963
|
+
if (limits.size === 0) return [];
|
|
3964
|
+
|
|
3965
|
+
const lines: string[] = [];
|
|
3966
|
+
for (const tool_id of Object.keys(deploy_results).sort()) {
|
|
3967
|
+
const [, , status] = deploy_results[tool_id] as DeployResult;
|
|
3968
|
+
if (status !== 'deployed') continue;
|
|
3969
|
+
const limit = limits.get(tool_id);
|
|
3970
|
+
if (limit === undefined) continue;
|
|
3971
|
+
const anchor_raw = USER_SCOPE_PATHS[tool_id];
|
|
3972
|
+
if (!anchor_raw) continue;
|
|
3973
|
+
|
|
3974
|
+
const volume = catalogue.measureCatalogueVolume(tool_id, expanduser(anchor_raw));
|
|
3975
|
+
const warning = catalogue.catalogueLimitWarning(volume, limit);
|
|
3976
|
+
if (warning !== null) lines.push(warning);
|
|
3977
|
+
}
|
|
3978
|
+
return lines;
|
|
3979
|
+
}
|
|
3980
|
+
|
|
3981
|
+
/**
|
|
3982
|
+
* The scoped-projection migration notice for an EXISTING install.
|
|
3983
|
+
*
|
|
3984
|
+
* Shape decided by AI council 2026-08-15 (2/2 converged, both members present):
|
|
3985
|
+
* option B — the existing warning PLUS an interactive notice that persists
|
|
3986
|
+
* NOTHING and names the manual edit and the GUI route. `projection.mode` is
|
|
3987
|
+
* settings class C, so no agent may write it; a notice that offered a CLI
|
|
3988
|
+
* write would send the reader to a command guaranteed to refuse them.
|
|
3989
|
+
*
|
|
3990
|
+
* Eligibility and delivery are separate on purpose. A non-interactive run can
|
|
3991
|
+
* be fully eligible; it simply gets the warning and no notice, and the two
|
|
3992
|
+
* outcomes stay distinguishable instead of collapsing into "nothing printed".
|
|
3993
|
+
*
|
|
3994
|
+
* Known and accepted: with nothing persisted, a qualifying install sees this
|
|
3995
|
+
* on every deploy. The council named that as the strongest argument for doing
|
|
3996
|
+
* nothing at all, and it does not outweigh making a measured, silent
|
|
3997
|
+
* capability loss an explicit human decision.
|
|
3998
|
+
*/
|
|
3999
|
+
export function _scoped_migration_notice(
|
|
4000
|
+
deploy_results: Record<string, DeployResult>,
|
|
4001
|
+
project_root: string | null,
|
|
4002
|
+
package_root: string,
|
|
4003
|
+
probe: SessionProbe,
|
|
4004
|
+
): string[] {
|
|
4005
|
+
if (!isInteractiveSession(probe)) return [];
|
|
4006
|
+
|
|
4007
|
+
const log_candidates = [
|
|
4008
|
+
path.join(user_global_paths.event4u_root(), 'state', 'skill-catalogue.jsonl'),
|
|
4009
|
+
...(project_root ? [path.join(project_root, catalogue.OBSERVATION_LOG)] : []),
|
|
4010
|
+
];
|
|
4011
|
+
const limits = catalogue.knownHostLimits(log_candidates.flatMap((p) => catalogue.readObservationLog(p)));
|
|
4012
|
+
if (limits.size === 0) return [];
|
|
4013
|
+
|
|
4014
|
+
const resolved = _resolve_scoped_projection(package_root);
|
|
4015
|
+
const settings_path =
|
|
4016
|
+
_resolve_global_settings_path() ??
|
|
4017
|
+
path.join(user_global_paths.event4u_root(), 'settings', SETTINGS_FILE);
|
|
4018
|
+
|
|
4019
|
+
for (const tool_id of Object.keys(deploy_results).sort()) {
|
|
4020
|
+
const [, , status] = deploy_results[tool_id] as DeployResult;
|
|
4021
|
+
if (status !== 'deployed') continue;
|
|
4022
|
+
const anchor_raw = USER_SCOPE_PATHS[tool_id];
|
|
4023
|
+
if (!anchor_raw) continue;
|
|
4024
|
+
|
|
4025
|
+
const volume = catalogue.measureCatalogueVolume(tool_id, expanduser(anchor_raw));
|
|
4026
|
+
const eligibility = catalogue.migrationEligibility(
|
|
4027
|
+
tool_id,
|
|
4028
|
+
resolved.mode,
|
|
4029
|
+
volume.skillEntries,
|
|
4030
|
+
limits,
|
|
4031
|
+
);
|
|
4032
|
+
if (eligibility.eligible) {
|
|
4033
|
+
return catalogue.migrationPromptLines(tool_id, eligibility, settings_path);
|
|
4034
|
+
}
|
|
4035
|
+
}
|
|
4036
|
+
return [];
|
|
4037
|
+
}
|
|
4038
|
+
|
|
3910
4039
|
const SETTINGS_SURFACE_REL = path.join('state', 'settings-surface.json');
|
|
3911
4040
|
const SETTINGS_DELTA_REL = path.join('state', 'settings-delta.json');
|
|
3912
4041
|
|