@event4u/agent-config 16.0.0 → 16.2.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/.augment-plugin/marketplace.json +3 -3
- package/.augment-plugin/plugin.json +1 -1
- package/.claude-plugin/marketplace.json +1 -1
- package/CHANGELOG.md +289 -140
- package/CONTRIBUTING.md +9 -0
- package/README.md +2 -2
- package/dist/agent-src/commands/analyze/inbox.md +26 -0
- package/dist/agent-src/commands/analyze/roadmap-repos.md +27 -0
- package/dist/agent-src/commands/challenge-me/closure.md +178 -0
- package/dist/agent-src/commands/challenge-me.md +9 -2
- package/dist/agent-src/commands/council/default.md +25 -5
- package/dist/agent-src/commands/feature/plan.md +28 -1
- package/dist/agent-src/commands/feature/roadmap.md +28 -1
- package/dist/agent-src/commands/implement-ticket.md +27 -0
- package/dist/agent-src/commands/jira-ticket.md +27 -0
- package/dist/agent-src/commands/pr/create.md +29 -0
- package/dist/agent-src/commands/pr/merge.md +60 -9
- package/dist/agent-src/commands/roadmap/ai-council.md +7 -1
- package/dist/agent-src/commands/roadmap/create.md +74 -27
- package/dist/agent-src/commands/roadmap/materialize.md +27 -0
- package/dist/agent-src/commands/roadmap/process-full.md +123 -18
- package/dist/agent-src/commands/team.md +22 -0
- package/dist/agent-src/commands/work.md +4 -0
- package/dist/agent-src/contexts/authority/scope-growth-ownership.md +80 -0
- package/dist/agent-src/contexts/communication/rules-auto/user-interaction-mechanics.md +47 -0
- package/dist/agent-src/contexts/execution/auto-orchestration-activation.md +44 -3
- package/dist/agent-src/contexts/execution/autonomy-mechanics.md +19 -23
- package/dist/agent-src/contexts/execution/decision-closure-execution.md +93 -0
- package/dist/agent-src/contexts/execution/fix-loop-ladder.md +108 -0
- package/dist/agent-src/contexts/execution/plan-confidence-gate.md +4 -2
- package/dist/agent-src/contexts/execution/roadmap-process-loop.md +75 -6
- package/dist/agent-src/guidelines/agent-infra/active-remediation-mechanics.md +57 -0
- package/dist/agent-src/guidelines/agent-infra/evaluator-independence-mechanics.md +102 -0
- package/dist/agent-src/guidelines/design-fidelity-routing.md +29 -0
- package/dist/agent-src/rules/autonomous-execution.md +15 -8
- package/dist/agent-src/rules/delegation-policy.md +23 -45
- package/dist/agent-src/rules/design-fidelity.md +32 -37
- package/dist/agent-src/rules/evaluator-independence.md +43 -49
- package/dist/agent-src/rules/test-first.md +41 -0
- package/dist/agent-src/rules/ui-audit-gate.md +1 -2
- package/dist/agent-src/skills/accessibility-auditor/data/aria-patterns.csv +1 -1
- package/dist/agent-src/skills/accessibility-auditor/data/manifest.json +3 -3
- package/dist/agent-src/skills/ai-council/SKILL.md +11 -3
- package/dist/agent-src/skills/api-design/data/manifest.json +3 -3
- package/dist/agent-src/skills/database/data/manifest.json +4 -4
- package/dist/agent-src/skills/database/data/query-tuning.csv +3 -3
- package/dist/agent-src/skills/design-review/SKILL.md +80 -4
- package/dist/agent-src/skills/design-review/references/verification-automation.md +32 -0
- package/dist/agent-src/skills/existing-ui-audit/SKILL.md +30 -5
- package/dist/agent-src/skills/existing-ui-audit/references/component-taxonomy-detection.md +56 -0
- package/dist/agent-src/skills/experiment-loop/SKILL.md +31 -1
- package/dist/agent-src/skills/markitdown/SKILL.md +4 -0
- package/dist/agent-src/skills/playwright-testing/SKILL.md +30 -6
- package/dist/agent-src/skills/roadmap-writing/SKILL.md +1 -1
- package/dist/agent-src/skills/storybook-workshop/SKILL.md +42 -3
- package/dist/agent-src/skills/threat-modeling/SKILL.md +9 -4
- package/dist/agent-src/skills/threat-modeling/data/manifest.json +4 -4
- package/dist/agent-src/skills/ui-component-architect/SKILL.md +23 -0
- package/dist/agent-src/skills/verify-repair-loop/SKILL.md +37 -3
- package/dist/agent-src/templates/agent-settings.md +1 -1
- package/dist/agent-src/templates/agents/agent-project-settings.example.yml +1 -1
- package/dist/agent-src/templates/roadmaps.md +218 -0
- package/dist/agent-src/templates/scripts/work_engine/directives/backend/_self_fix.ts +62 -2
- package/dist/agent-src/templates/scripts/work_engine/directives/backend/test.ts +1 -0
- package/dist/agent-src/templates/scripts/work_engine/directives/backend/verify.ts +1 -0
- package/dist/agent-src/templates/scripts/work_engine/directives/ui/app_spec.ts +10 -4
- package/dist/agent-src/templates/scripts/work_engine/directives/ui/apply.ts +260 -21
- package/dist/agent-src/templates/scripts/work_engine/directives/ui/audit.ts +22 -3
- package/dist/agent-src/templates/scripts/work_engine/directives/ui/scaffold.ts +6 -1
- package/dist/agent-src/templates/scripts/work_engine/state.ts +57 -7
- package/dist/agent-src/templates/scripts/work_engine/taxonomy/detect.ts +500 -0
- package/dist/agent-src/templates/scripts/work_engine/taxonomy/place.ts +183 -0
- package/dist/cli/python/knowledge_ingest.js +1 -1
- package/dist/cli/python/workspace_drive.js +1 -1
- package/dist/cli-delegate/check_update_banner.js +40 -2
- package/dist/cli-delegate/chunk-4E2W63DA.js +335 -0
- package/dist/cli-delegate/{chunk-S3VFXMTM.js → chunk-4XFP2Y3E.js} +1 -1
- package/dist/cli-delegate/chunk-5R7HOLAF.js +99 -0
- package/dist/cli-delegate/{chunk-IVBPRCQO.js → chunk-6KFEC3G7.js} +1 -1
- package/dist/cli-delegate/{chunk-2TAQ2K4V.js → chunk-6KM6ORRY.js} +55 -386
- package/dist/cli-delegate/{chunk-AK6HODNQ.js → chunk-7XGBX6ZH.js} +973 -555
- package/dist/cli-delegate/{chunk-2XY7ILBT.js → chunk-FC75PGJE.js} +42 -2
- package/dist/cli-delegate/{chunk-55ACLDUE.js → chunk-FP36SWJD.js} +587 -141
- package/dist/cli-delegate/{chunk-3FKR64R5.js → chunk-IGD6ERX6.js} +62 -7
- package/dist/cli-delegate/{chunk-GYC3VRYN.js → chunk-JNMVLWLR.js} +99 -14
- package/dist/cli-delegate/{chunk-H32DGUJH.js → chunk-P6ZHP5W2.js} +1 -1
- package/dist/cli-delegate/{chunk-57PN4GYJ.js → chunk-RGNIWTEA.js} +1 -1
- package/dist/cli-delegate/chunk-ROYYTEZU.js +39 -0
- package/dist/cli-delegate/{chunk-2DK2VJWG.js → chunk-WGXTVKCQ.js} +20 -6
- package/dist/cli-delegate/chunk-YDPILCOL.js +152 -0
- package/dist/cli-delegate/{chunk-QJWAUMJW.js → chunk-YJ72UIAK.js} +730 -15
- package/dist/cli-delegate/cmd_analyze_session.js +1 -1
- package/dist/cli-delegate/cmd_brand_status.js +1 -1
- package/dist/cli-delegate/cmd_conformance.js +985 -831
- package/dist/cli-delegate/cmd_doctor.js +7 -5
- package/dist/cli-delegate/cmd_explain.js +2 -2
- package/dist/cli-delegate/cmd_export.js +9 -7
- package/dist/cli-delegate/cmd_fleet.js +4 -2
- package/dist/cli-delegate/cmd_handoff.js +7 -7
- package/dist/cli-delegate/cmd_mcp_available.js +1 -1
- package/dist/cli-delegate/cmd_memory_get.js +1 -1
- package/dist/cli-delegate/cmd_migrate.js +2 -2
- package/dist/cli-delegate/cmd_packs_active.js +2 -2
- package/dist/cli-delegate/cmd_preflight.js +4 -2
- package/dist/cli-delegate/cmd_prune.js +10 -8
- package/dist/cli-delegate/cmd_route_audit.js +3 -3
- package/dist/cli-delegate/cmd_route_explain.js +1 -1
- package/dist/cli-delegate/cmd_session_recycle.js +4 -4
- package/dist/cli-delegate/cmd_settings_get.js +28 -2
- package/dist/cli-delegate/cmd_settings_migrate.js +9 -7
- package/dist/cli-delegate/cmd_settings_set.js +1 -1
- package/dist/cli-delegate/cmd_sync.js +10 -8
- package/dist/cli-delegate/cmd_uninstall.js +16 -13
- package/dist/cli-delegate/cmd_update.js +1 -1
- package/dist/cli-delegate/cmd_upgrade.js +1 -1
- package/dist/cli-delegate/cmd_validate.js +12 -10
- package/dist/cli-delegate/cmd_versions.js +1 -1
- package/dist/discovery/deprecation-report.md +1 -1
- package/dist/discovery/discovery-manifest.json +90 -43
- package/dist/discovery/discovery-manifest.json.sha256 +1 -1
- package/dist/discovery/discovery-manifest.summary.md +4 -4
- package/dist/discovery/dormancy-report.md +1 -1
- package/dist/discovery/orphan-report.md +1 -1
- package/dist/discovery/packs.json +10 -8
- package/dist/discovery/trust-report.md +3 -3
- package/dist/discovery/workspaces.json +8 -6
- package/dist/hooks/dispatch.js +7682 -2772
- package/dist/hooks/dispatch.sha256 +1 -0
- package/dist/install/claudeRuleRewrite.js +14 -3
- package/dist/install/conflict.js +60 -10
- package/dist/install/conflictTracker.js +218 -0
- package/dist/install/fsPrimitives.js +54 -0
- package/dist/install/install.mjs +2200 -1559
- package/dist/install/manifestFiles.js +259 -0
- package/dist/install/preflight.js +14 -2
- package/dist/install/preserve.js +157 -0
- package/dist/install/recordedOwnership.js +132 -0
- package/dist/install/txlog.js +11 -2
- package/dist/mcp/registry-manifest.json +3 -3
- package/dist/mcp/server.json +2 -2
- package/dist/mcp/server.mjs +3066 -1470
- package/dist/router.json +1 -1
- package/dist/scripts/_lib/agent_settings.js +3 -2
- package/dist/scripts/_lib/fs_atomic.js +167 -0
- package/dist/scripts/_lib/installed_tools.js +460 -0
- package/dist/scripts/_lib/lean_projection_mode.js +3 -2
- package/dist/scripts/_lib/settings_renamed_keys.js +78 -0
- package/dist/server/routes/install.js +8 -0
- package/dist/server/routes/wizard.js +9 -3
- package/dist/server/schemas/settings.js +40 -5
- package/dist/shared/missionExecution.js +97 -0
- package/dist/ui/assets/index-Cwq4d0hX.js +23 -0
- package/dist/ui/assets/index-Cyjy8bWO.css +1 -0
- package/dist/ui/index.html +2 -2
- package/docs/MIGRATION.md +103 -0
- package/docs/contracts/settings-classes.md +28 -7
- package/docs/guidelines/agent-infra/active-remediation-mechanics.md +57 -0
- package/docs/guidelines/agent-infra/evaluator-independence-mechanics.md +102 -0
- package/docs/guidelines/design-fidelity-routing.md +29 -0
- package/package.json +4 -4
- package/src/agent-src/templates/scripts/work_engine/directives/backend/_self_fix.ts +62 -2
- package/src/agent-src/templates/scripts/work_engine/directives/backend/test.ts +1 -0
- package/src/agent-src/templates/scripts/work_engine/directives/backend/verify.ts +1 -0
- package/src/agent-src/templates/scripts/work_engine/directives/ui/app_spec.ts +10 -4
- package/src/agent-src/templates/scripts/work_engine/directives/ui/apply.ts +260 -21
- package/src/agent-src/templates/scripts/work_engine/directives/ui/audit.ts +22 -3
- package/src/agent-src/templates/scripts/work_engine/directives/ui/scaffold.ts +6 -1
- package/src/agent-src/templates/scripts/work_engine/state.ts +57 -7
- package/src/agent-src/templates/scripts/work_engine/taxonomy/detect.ts +500 -0
- package/src/agent-src/templates/scripts/work_engine/taxonomy/place.ts +183 -0
- package/src/config/agent-settings.template.yml +168 -27
- package/src/config/auto-rule-budget.json +16 -0
- package/src/config/ci-local-parity.yml +36 -5
- package/src/config/continuity-surface.json +23 -2
- package/src/config/gate-coverage.yml +377 -13
- package/src/config/gate-violation-baselines.json +10 -6
- package/src/config/hook-latency-budget.json +21 -0
- package/src/config/hook-token-budget.json +4 -2
- package/src/config/loop-surfaces.yaml +215 -0
- package/src/config/metric-registry.yml +114 -0
- package/src/config/placeholder-drift-budget.json +10 -0
- package/src/config/platform-anchor.json +3 -2
- package/src/config/release-gate-locality.yml +11 -0
- package/src/config/routing-coverage-seed.json +33 -5
- package/src/config/rule-enforcement-baseline.json +0 -1
- package/src/install/claudeRuleRewrite.ts +16 -2
- package/src/install/conflict.ts +65 -9
- package/src/install/conflictTracker.ts +299 -0
- package/src/install/fsPrimitives.ts +54 -0
- package/src/install/manifestFiles.ts +312 -0
- package/src/install/preflight.ts +16 -2
- package/src/install/preserve.ts +205 -0
- package/src/install/recordedOwnership.ts +149 -0
- package/src/install/txlog.ts +11 -2
- package/src/install/types.ts +19 -4
- package/src/scripts/_cli/cmd_conformance.ts +88 -15
- package/src/scripts/_cli/cmd_doctor.ts +7 -20
- package/src/scripts/_cli/cmd_route_explain.ts +9 -2
- package/src/scripts/_cli/cmd_settings_get.ts +44 -0
- package/src/scripts/_cli/doctor_execution.ts +173 -0
- package/src/scripts/_cli/doctor_network_posture.ts +226 -0
- package/src/scripts/_lib/activation_payload.ts +247 -0
- package/src/scripts/_lib/agent_settings.ts +4 -2
- package/src/scripts/_lib/authority_path.ts +102 -0
- package/src/scripts/_lib/body_portable.ts +102 -0
- package/src/scripts/_lib/cascade_base.ts +94 -0
- package/src/scripts/_lib/cc_transcript.ts +132 -1
- package/src/scripts/_lib/changelog_eras.ts +7 -2
- package/src/scripts/_lib/concern_sla_window.ts +200 -0
- package/src/scripts/_lib/continuation_ladder.ts +149 -2
- package/src/scripts/_lib/council_transport.ts +129 -0
- package/src/scripts/_lib/delivery_ready.ts +112 -0
- package/src/scripts/_lib/forge_protection.ts +381 -0
- package/src/scripts/_lib/forge_reader.ts +558 -0
- package/src/scripts/_lib/hook_platform_keys.ts +27 -0
- package/src/scripts/_lib/host_env_write.ts +111 -0
- package/src/scripts/_lib/json_python_parity.ts +127 -0
- package/src/scripts/_lib/lean_projection_mode.ts +14 -3
- package/src/scripts/_lib/llm_proposer_transport.ts +14 -4
- package/src/scripts/_lib/loop_surfaces.ts +382 -0
- package/src/scripts/_lib/mission_record.ts +190 -0
- package/src/scripts/_lib/obligation_frequency.ts +217 -0
- package/src/scripts/_lib/obligations.ts +513 -0
- package/src/scripts/_lib/orchestration_record.ts +11 -20
- package/src/scripts/_lib/probe_inputs.ts +271 -0
- package/src/scripts/_lib/read_surface_scan.ts +322 -0
- package/src/scripts/_lib/release_highlights.ts +201 -11
- package/src/scripts/_lib/release_holds.ts +493 -0
- package/src/scripts/_lib/release_migration_gate.ts +184 -0
- package/src/scripts/_lib/retrieval_sanitize.ts +77 -16
- package/src/scripts/_lib/rides_along.ts +102 -0
- package/src/scripts/_lib/settings_renamed_keys.ts +82 -0
- package/src/scripts/_lib/shell_write_shapes.ts +108 -0
- package/src/scripts/_lib/structural_hiding.ts +622 -0
- package/src/scripts/_lib/test_delta.ts +166 -0
- package/src/scripts/_lib/test_provenance.ts +171 -0
- package/src/scripts/_lib/turn_end_refusals.ts +525 -4
- package/src/scripts/_lib/turn_end_transcript.ts +216 -0
- package/src/scripts/_lib/typed_op_grant.ts +246 -0
- package/src/scripts/_lib/typed_op_watch.ts +185 -0
- package/src/scripts/_lib/verification_command.ts +324 -0
- package/src/scripts/_lib/verification_evidence.ts +647 -0
- package/src/scripts/_lib/verify_clause.ts +280 -0
- package/src/scripts/ai_council/config.ts +8 -11
- package/src/scripts/ai_council/fallback_config.ts +5 -3
- package/src/scripts/ai_council/ownership_classes.ts +118 -0
- package/src/scripts/ai_team/team_dispatch.ts +4 -0
- package/src/scripts/ask_block_census.ts +285 -15
- package/src/scripts/before_complete_hook.ts +340 -23
- package/src/scripts/bench_hook_latency.ts +412 -5
- package/src/scripts/cache_realization_report.ts +61 -1
- package/src/scripts/check_argument_hint.ts +287 -0
- package/src/scripts/check_claims.ts +125 -1
- package/src/scripts/check_enforcement_coverage.ts +38 -9
- package/src/scripts/check_enforcement_matrix.ts +866 -0
- package/src/scripts/check_finding_dispositions.ts +22 -3
- package/src/scripts/check_gate_reachability.ts +173 -10
- package/src/scripts/check_generator_sync.ts +1025 -0
- package/src/scripts/check_host_docs_digest.ts +786 -0
- package/src/scripts/check_host_format_column.ts +559 -0
- package/src/scripts/check_input_references.ts +228 -0
- package/src/scripts/check_inputs_parity.ts +235 -0
- package/src/scripts/check_kernel_edit_ratified.ts +47 -5
- package/src/scripts/check_kill_switch_table.ts +294 -0
- package/src/scripts/check_placeholder_drift.ts +221 -0
- package/src/scripts/check_preamble_payload_budget.ts +75 -2
- package/src/scripts/check_read_surface_coverage.ts +515 -0
- package/src/scripts/check_release_highlights.ts +33 -1
- package/src/scripts/check_release_holds.ts +568 -0
- package/src/scripts/check_routing_coverage.ts +378 -11
- package/src/scripts/check_rule_projection_integrity.ts +22 -12
- package/src/scripts/check_static_parity.ts +17 -0
- package/src/scripts/check_test_delta.ts +294 -0
- package/src/scripts/check_test_weakening.ts +299 -0
- package/src/scripts/ci_settle.ts +145 -19
- package/src/scripts/closure_scan.ts +447 -0
- package/src/scripts/compile_hook_manifest.ts +102 -18
- package/src/scripts/council_record_shape.ts +209 -0
- package/src/scripts/external_sources_denylist.json +29 -4
- package/src/scripts/hook_manifest.json +1 -1
- package/src/scripts/hook_manifest.yaml +127 -11
- package/src/scripts/hooks/block_config_weakening.ts +229 -4
- package/src/scripts/hooks/block_kernel_rule_writes.ts +9 -78
- package/src/scripts/hooks/block_plumbing_writes.ts +314 -0
- package/src/scripts/hooks/bundle_integrity.ts +291 -0
- package/src/scripts/hooks/chain_nudge_hook.ts +1 -2
- package/src/scripts/hooks/code_graph_context_hook.ts +1 -2
- package/src/scripts/hooks/comment_discipline_hook.ts +1 -3
- package/src/scripts/hooks/concern_registry.ts +6 -0
- package/src/scripts/hooks/concern_timings.ts +94 -0
- package/src/scripts/hooks/delegation_nudge_hook.ts +1 -2
- package/src/scripts/hooks/design_pass_hook.ts +221 -5
- package/src/scripts/hooks/design_slop_hook.ts +1 -2
- package/src/scripts/hooks/dispatch_hook.ts +25 -19
- package/src/scripts/hooks/edit_shape_hook.ts +1 -2
- package/src/scripts/hooks/end_review_nudge_hook.ts +1 -1
- package/src/scripts/hooks/evidence_independence.ts +97 -2
- package/src/scripts/hooks/exit_codes.ts +128 -0
- package/src/scripts/hooks/host_lowering.json +1 -0
- package/src/scripts/hooks/host_lowering.ts +128 -3
- package/src/scripts/hooks/host_lowering.yaml +262 -46
- package/src/scripts/hooks/host_semantics.ts +30 -2
- package/src/scripts/hooks/injection_budget.ts +9 -2
- package/src/scripts/hooks/interruption_ledger_hook.ts +1 -2
- package/src/scripts/hooks/journal_record_hook.ts +1 -2
- package/src/scripts/hooks/memory_recall_hook.ts +539 -0
- package/src/scripts/hooks/obligation_settle_hook.ts +328 -0
- package/src/scripts/hooks/one_question_per_ask_hook.ts +25 -4
- package/src/scripts/hooks/orchestration_record_hook.ts +1 -2
- package/src/scripts/hooks/permission_gate.ts +1 -2
- package/src/scripts/hooks/push_settle_hook.ts +1 -3
- package/src/scripts/hooks/replay_hook.ts +2 -1
- package/src/scripts/hooks/reread_guard_hook.ts +1 -2
- package/src/scripts/hooks/review_baseline_hook.ts +1 -2
- package/src/scripts/hooks/rtk_wrap_hook.ts +1 -2
- package/src/scripts/hooks/rule_inject_hook.ts +55 -4
- package/src/scripts/hooks/run_continuation_hook.ts +30 -35
- package/src/scripts/hooks/skill_route_hook.ts +1 -2
- package/src/scripts/hooks/source_first_gate_hook.ts +1 -2
- package/src/scripts/hooks/spawn_guard_shadow_hook.ts +1 -2
- package/src/scripts/hooks/subagent_ledger_hook.ts +1 -2
- package/src/scripts/hooks/suggestion_capture_hook.ts +1 -1
- package/src/scripts/hooks/table_fingerprint.ts +49 -0
- package/src/scripts/hooks/telemetry_flush_hook.ts +1 -2
- package/src/scripts/hooks/telemetry_usage_hook.ts +1 -2
- package/src/scripts/hooks/tool_result_bytes_hook.ts +93 -9
- package/src/scripts/hooks/turn_end_gate_hook.ts +337 -324
- package/src/scripts/hooks/ui_route_nudge_hook.ts +1 -2
- package/src/scripts/hooks_doctor.ts +85 -0
- package/src/scripts/hooks_status.ts +19 -0
- package/src/scripts/install.ts +109 -210
- package/src/scripts/janitor.ts +7 -0
- package/src/scripts/lint_decision_classes.ts +464 -0
- package/src/scripts/lint_deny_text.ts +419 -0
- package/src/scripts/lint_exit_codes.ts +262 -0
- package/src/scripts/lint_hook_manifest.ts +66 -0
- package/src/scripts/lint_major_migration_sections.ts +450 -0
- package/src/scripts/lint_plan_risk_register.ts +47 -3
- package/src/scripts/lint_roadmap_blockers.ts +139 -12
- package/src/scripts/lint_roadmap_producers.ts +202 -0
- package/src/scripts/lint_skill_descriptions.ts +1 -1
- package/src/scripts/lint_skill_scripts_readonly.ts +1 -1
- package/src/scripts/mcp_server/prompts.ts +142 -1
- package/src/scripts/measure_rule_budget.ts +101 -5
- package/src/scripts/measure_skill_ranker_baseline.ts +305 -51
- package/src/scripts/measure_turn_end_gate.ts +193 -0
- package/src/scripts/memory_lookup.ts +1 -1
- package/src/scripts/new_roadmap.ts +37 -0
- package/src/scripts/orchestration_record.ts +0 -5
- package/src/scripts/prepack-check.mjs +53 -0
- package/src/scripts/release.ts +14 -98
- package/src/scripts/release_drill.ts +267 -1
- package/src/scripts/release_publication.ts +64 -1
- package/src/scripts/report_enforcement_drift.ts +566 -0
- package/src/scripts/report_host_injection_effect.ts +3 -11
- package/src/scripts/report_invocation_surface.ts +383 -0
- package/src/scripts/report_obligation_carriers.ts +699 -0
- package/src/scripts/report_obligation_writer_split.ts +236 -0
- package/src/scripts/report_skill_menu_census.ts +208 -0
- package/src/scripts/report_standing_payload_by_host.ts +26 -152
- package/src/scripts/roadmap_verify_share.ts +213 -0
- package/src/scripts/schemas/agent-settings.schema.json +23 -0
- package/src/scripts/schemas/command.schema.json +48 -0
- package/src/scripts/schemas/review-findings.schema.json +4 -0
- package/src/scripts/schemas/skill.schema.json +44 -0
- package/src/scripts/scope_growth.ts +219 -0
- package/src/scripts/self_review_gate.ts +359 -10
- package/src/scripts/semantic_parity_census.ts +314 -0
- package/src/scripts/session_eol_report.ts +54 -1
- package/src/scripts/sweep_dead_scan_roots.ts +1 -1
- package/src/scripts/trigger_eval_grandfather.json +0 -3
- package/src/scripts/trigger_eval_rotation.ts +122 -27
- package/src/scripts/ui_conformance_probe.ts +774 -0
- package/src/scripts/update_prices.ts +85 -3
- package/src/scripts/write_bundle_digest.mjs +74 -0
- package/src/server/schemas/settings.ts +62 -7
- package/src/shared/missionExecution.ts +126 -0
- package/src/templates/consumer-settings/ONBOARDING.md +1 -0
- package/dist/cli-delegate/chunk-523HCFYO.js +0 -90
- package/dist/ui/assets/index-D0jaCRPM.js +0 -29
- package/dist/ui/assets/index-jSfMW8_t.css +0 -1
- /package/dist/cli-delegate/{chunk-YUM5KURG.js → chunk-7RQP4UDH.js} +0 -0
|
@@ -0,0 +1,102 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The strictest path, reserved for authority —
|
|
3
|
+
* `road-to-adversarial-verification-and-long-runs` 11.1.
|
|
4
|
+
*
|
|
5
|
+
* An authority-expanding change takes four independent passes — an independent
|
|
6
|
+
* TEST author, an independent GOVERNANCE reviewer, a COUNCIL pass, and a
|
|
7
|
+
* PROVIDER-DIVERSE reviewer — plus the ratification artefact. Until the artefact
|
|
8
|
+
* carries `verdict: ratified`, the new authority is **inert**.
|
|
9
|
+
*
|
|
10
|
+
* ```
|
|
11
|
+
* NEW AUTHORITY IS INERT UNTIL RATIFIED. AN AGENT MAY AUTHOR THE CHANGE
|
|
12
|
+
* AND MAY NEVER RATIFY ITS OWN INCREASE IN POWER (ADR-268 § 4).
|
|
13
|
+
* FOUR PASSES, ALL FOUR, AND THEY MAY NOT BE THE SAME REVIEWER TWICE.
|
|
14
|
+
* ```
|
|
15
|
+
*
|
|
16
|
+
* **Inert-until-ratified is the property that makes the rest safe.** Without it
|
|
17
|
+
* the four passes are a process an author could complete and then merge; with it
|
|
18
|
+
* the change can land, be read, and still do nothing — so the ratification is a
|
|
19
|
+
* separate act on a separate turn by a party that is not the author.
|
|
20
|
+
*
|
|
21
|
+
* Pure.
|
|
22
|
+
*/
|
|
23
|
+
|
|
24
|
+
/** The four passes. All four, and the list is closed. */
|
|
25
|
+
export const REQUIRED_PASSES = [
|
|
26
|
+
'independent-test-author',
|
|
27
|
+
'independent-governance-reviewer',
|
|
28
|
+
'council',
|
|
29
|
+
'provider-diverse-reviewer',
|
|
30
|
+
] as const;
|
|
31
|
+
export type Pass = (typeof REQUIRED_PASSES)[number];
|
|
32
|
+
|
|
33
|
+
export interface PassRecord {
|
|
34
|
+
readonly pass: Pass;
|
|
35
|
+
/** Who performed it. Two passes by the same identity is not two passes. */
|
|
36
|
+
readonly by: string;
|
|
37
|
+
/** The provider, so provider-diversity is checkable rather than asserted. */
|
|
38
|
+
readonly provider: string;
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
export interface RatificationArtefact {
|
|
42
|
+
/** Anything other than `ratified` leaves the authority inert. */
|
|
43
|
+
readonly verdict: string;
|
|
44
|
+
readonly passes: readonly PassRecord[];
|
|
45
|
+
/** The author of the change under ratification. */
|
|
46
|
+
readonly author: string;
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
export type AuthorityState = 'inert' | 'active';
|
|
50
|
+
|
|
51
|
+
export interface AuthorityVerdict {
|
|
52
|
+
readonly state: AuthorityState;
|
|
53
|
+
readonly blockers: readonly string[];
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
/**
|
|
57
|
+
* Is the new authority active yet?
|
|
58
|
+
*
|
|
59
|
+
* Returns EVERY blocker rather than the first: the artefact is a checklist a
|
|
60
|
+
* human completes, and handing them one item at a time turns four passes into
|
|
61
|
+
* four round trips.
|
|
62
|
+
*/
|
|
63
|
+
export function authorityState(a: RatificationArtefact | null): AuthorityVerdict {
|
|
64
|
+
if (a === null) {
|
|
65
|
+
return { state: 'inert', blockers: ['no ratification artefact exists'] };
|
|
66
|
+
}
|
|
67
|
+
const blockers: string[] = [];
|
|
68
|
+
|
|
69
|
+
for (const required of REQUIRED_PASSES) {
|
|
70
|
+
if (!a.passes.some((p) => p.pass === required)) {
|
|
71
|
+
blockers.push(`missing pass: ${required}`);
|
|
72
|
+
}
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
// The author may perform NONE of the four. This is the ADR-268 § 4 clause
|
|
76
|
+
// and the one a process can satisfy on paper while violating in substance.
|
|
77
|
+
for (const p of a.passes) {
|
|
78
|
+
if (p.by === a.author) {
|
|
79
|
+
blockers.push(`${p.pass} was performed by the author (${p.by}) — an agent may not ratify its own increase in power`);
|
|
80
|
+
}
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
// Provider diversity is a property of the SET, so it is checked over the set
|
|
84
|
+
// rather than trusted from the pass that carries the name.
|
|
85
|
+
const providers = new Set(a.passes.map((p) => p.provider));
|
|
86
|
+
if (a.passes.length > 1 && providers.size < 2) {
|
|
87
|
+
blockers.push(
|
|
88
|
+
`every pass ran on one provider (${[...providers].join(', ')}) — provider-diverse means at least two`,
|
|
89
|
+
);
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
if (a.verdict !== 'ratified') {
|
|
93
|
+
blockers.push(`verdict is "${a.verdict}", not "ratified"`);
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
return { state: blockers.length === 0 ? 'active' : 'inert', blockers };
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
/** `true` only for an artefact that clears everything. */
|
|
100
|
+
export function isRatified(a: RatificationArtefact | null): boolean {
|
|
101
|
+
return authorityState(a).state === 'active';
|
|
102
|
+
}
|
|
@@ -0,0 +1,102 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* `body-portable` — does a skill's declared frontmatter survive the MCP-lite
|
|
3
|
+
* carrier whole?
|
|
4
|
+
*
|
|
5
|
+
* road-to-semantic-parity-before-off-menu, Phase 2.1.
|
|
6
|
+
*
|
|
7
|
+
* WHAT THIS DECIDES, AND WHAT IT DOES NOT
|
|
8
|
+
*
|
|
9
|
+
* It decides ELIGIBILITY only. It marks nothing, excludes nothing, and is not a
|
|
10
|
+
* verdict about which skills matter. A committed list of skills this returns
|
|
11
|
+
* false for is one step from a menu-exclusion discharged by citing it, which is
|
|
12
|
+
* precisely the laundering the parked menu-economy roadmap's Risk 1 names — a
|
|
13
|
+
* measurement of carrier coverage reused as a judgement about worth. Nothing in
|
|
14
|
+
* the tree consumes this as an exclusion trigger, and building the half that
|
|
15
|
+
* could is a separate decision that has not been taken.
|
|
16
|
+
*
|
|
17
|
+
* THE RULE
|
|
18
|
+
*
|
|
19
|
+
* A skill is body-portable when every key it declares is one the carrier
|
|
20
|
+
* transports whole. Three ways to fail, and they are deliberately not
|
|
21
|
+
* distinguished in the boolean:
|
|
22
|
+
*
|
|
23
|
+
* - a DROPPED key — the carrier never reads it, so the semantic stays on disk.
|
|
24
|
+
* - a PARTIAL key — the carrier reads it and transports part of the value.
|
|
25
|
+
* `triggers` is the only one: `keyword` and `phrase` travel, while
|
|
26
|
+
* `file_pattern`, `path_prefix`, `command` and `reason` do not. Counting it
|
|
27
|
+
* as portable would make a path-triggered skill look complete when the very
|
|
28
|
+
* routing that selects it never leaves the disk.
|
|
29
|
+
* - an UNKNOWN key — one the schema does not declare at all. It fails CLOSED.
|
|
30
|
+
* The alternative is to ignore what you cannot classify, and an unknown key
|
|
31
|
+
* is exactly the case where the carrier's behaviour is unestablished, so
|
|
32
|
+
* returning true there would assert portability from ignorance.
|
|
33
|
+
*
|
|
34
|
+
* SCOPE: one carrier, `ContentEntry` in `src/cli/mcp/content.ts`. A second
|
|
35
|
+
* delivery path drops a different set and needs its own predicate rather than a
|
|
36
|
+
* widened reading of this one.
|
|
37
|
+
*/
|
|
38
|
+
|
|
39
|
+
import { INDEXED_TRIGGER_KEYS } from '../../shared/skillRanking.js';
|
|
40
|
+
|
|
41
|
+
/**
|
|
42
|
+
* Frontmatter keys `buildEntry` reads and transports whole
|
|
43
|
+
* (`src/cli/mcp/content.ts` — `fm.name`, `fm.description`, `fm.source`,
|
|
44
|
+
* `fm.personas`).
|
|
45
|
+
*
|
|
46
|
+
* Derived from the carrier source, never from the schema: a schema property the
|
|
47
|
+
* carrier does not read is dropped no matter what the schema says about it.
|
|
48
|
+
*/
|
|
49
|
+
export const CARRIED_KEYS: readonly string[] = ['name', 'description', 'source', 'personas'];
|
|
50
|
+
|
|
51
|
+
/**
|
|
52
|
+
* Keys the carrier reads but truncates, with the sub-keys that actually travel.
|
|
53
|
+
*
|
|
54
|
+
* The sub-key list is IMPORTED from `INDEXED_TRIGGER_KEYS` rather than restated.
|
|
55
|
+
* Restating it was the first version of this file and it is a silent-lie shape:
|
|
56
|
+
* add a third indexed key and a hand-copied literal keeps reporting the old two
|
|
57
|
+
* while the carrier transports three, so the census `travels` column would be
|
|
58
|
+
* wrong with every test still green.
|
|
59
|
+
*/
|
|
60
|
+
export const PARTIAL_KEYS: Readonly<Record<string, readonly string[]>> = {
|
|
61
|
+
triggers: INDEXED_TRIGGER_KEYS,
|
|
62
|
+
};
|
|
63
|
+
|
|
64
|
+
/** Why a skill is not body-portable. Empty when it is. */
|
|
65
|
+
export interface PortabilityReasons {
|
|
66
|
+
/** Declared keys the carrier transports only in part. */
|
|
67
|
+
readonly partial: readonly string[];
|
|
68
|
+
/** Declared keys the carrier does not read, including keys the schema does not declare. */
|
|
69
|
+
readonly dropped: readonly string[];
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
export interface PortabilityVerdict extends PortabilityReasons {
|
|
73
|
+
readonly portable: boolean;
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
/**
|
|
77
|
+
* Classify one skill's declared frontmatter keys.
|
|
78
|
+
*
|
|
79
|
+
* Pure: it reads nothing and depends only on its argument and the two constants
|
|
80
|
+
* above.
|
|
81
|
+
*/
|
|
82
|
+
export function classifyPortability(frontmatter: Readonly<Record<string, unknown>>): PortabilityVerdict {
|
|
83
|
+
const partial: string[] = [];
|
|
84
|
+
const dropped: string[] = [];
|
|
85
|
+
|
|
86
|
+
for (const key of Object.keys(frontmatter).sort()) {
|
|
87
|
+
if (CARRIED_KEYS.includes(key)) continue;
|
|
88
|
+
if (Object.hasOwn(PARTIAL_KEYS, key)) {
|
|
89
|
+
partial.push(key);
|
|
90
|
+
continue;
|
|
91
|
+
}
|
|
92
|
+
// Dropped, and an unknown key lands here too — failing closed.
|
|
93
|
+
dropped.push(key);
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
return { portable: partial.length === 0 && dropped.length === 0, partial, dropped };
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
/** The predicate itself. `true` only when nothing the skill declares is lost. */
|
|
100
|
+
export function isBodyPortable(frontmatter: Readonly<Record<string, unknown>>): boolean {
|
|
101
|
+
return classifyPortability(frontmatter).portable;
|
|
102
|
+
}
|
|
@@ -0,0 +1,94 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The cascade base — `road-to-adversarial-verification-and-long-runs` 5.1.
|
|
3
|
+
*
|
|
4
|
+
* `check_branch_freshness` asks whether a branch is behind THE BASE ITS PR
|
|
5
|
+
* TARGETS. That is one hop, and it is the right question for the common case
|
|
6
|
+
* where the base is the trunk. On a stacked PR it is only half of it: a branch
|
|
7
|
+
* targeting `feat/parent` can be perfectly current with `feat/parent` while
|
|
8
|
+
* `feat/parent` itself is fifty commits behind `main`, so the branch is stale
|
|
9
|
+
* against the tree it will eventually land in and every gate reports green.
|
|
10
|
+
*
|
|
11
|
+
* The cascade is the ordered list of refs a branch must be current with, nearest
|
|
12
|
+
* first. Merging in that order is not arbitrary: taking the trunk first would
|
|
13
|
+
* pull trunk commits past the parent and make the stack's own diff unreadable,
|
|
14
|
+
* which is the thing a stack exists to avoid.
|
|
15
|
+
*
|
|
16
|
+
* **Depth is bounded and the bound is deliberate.** A cascade is a chain of
|
|
17
|
+
* bases, and a chain can cycle — a branch whose PR targets a branch that targets
|
|
18
|
+
* it. `MAX_HOPS` stops the walk and the result says it was truncated, because
|
|
19
|
+
* a hop list silently cut short is indistinguishable from a short one.
|
|
20
|
+
*
|
|
21
|
+
* Pure: the caller supplies the base-of lookup, so the whole thing is testable
|
|
22
|
+
* without a forge.
|
|
23
|
+
*/
|
|
24
|
+
|
|
25
|
+
/** How many bases a cascade may chain through before the walk stops. */
|
|
26
|
+
export const MAX_HOPS = 8;
|
|
27
|
+
|
|
28
|
+
export interface Cascade {
|
|
29
|
+
/** Refs to merge in, nearest base first, trunk last. Never includes `branch`. */
|
|
30
|
+
readonly hops: readonly string[];
|
|
31
|
+
/** True when the walk stopped on a cycle rather than on the trunk. */
|
|
32
|
+
readonly cycle: boolean;
|
|
33
|
+
/** True when the walk stopped on {@link MAX_HOPS}. */
|
|
34
|
+
readonly truncated: boolean;
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
/** `null` when the ref has no PR base — i.e. it is a trunk or is unknown. */
|
|
38
|
+
export type BaseOf = (ref: string) => string | null;
|
|
39
|
+
|
|
40
|
+
/**
|
|
41
|
+
* Walk from `branch` to `trunk`, collecting every base on the way.
|
|
42
|
+
*
|
|
43
|
+
* A branch whose base IS the trunk yields one hop — identical to the single-hop
|
|
44
|
+
* behaviour that existed before, which is the compatibility property that
|
|
45
|
+
* matters: this widens the check, it does not change the common case.
|
|
46
|
+
*/
|
|
47
|
+
export function cascadeBase(branch: string, trunk: string, baseOf: BaseOf): Cascade {
|
|
48
|
+
const hops: string[] = [];
|
|
49
|
+
const seen = new Set<string>([branch]);
|
|
50
|
+
let current = branch;
|
|
51
|
+
|
|
52
|
+
for (let i = 0; i < MAX_HOPS; i += 1) {
|
|
53
|
+
const base = baseOf(current);
|
|
54
|
+
if (base === null || base === '') {
|
|
55
|
+
// No PR base. The trunk is still owed unless we are already on it.
|
|
56
|
+
if (current !== trunk && !hops.includes(trunk)) hops.push(trunk);
|
|
57
|
+
return { hops, cycle: false, truncated: false };
|
|
58
|
+
}
|
|
59
|
+
if (seen.has(base)) {
|
|
60
|
+
return { hops, cycle: true, truncated: false };
|
|
61
|
+
}
|
|
62
|
+
seen.add(base);
|
|
63
|
+
hops.push(base);
|
|
64
|
+
if (base === trunk) {
|
|
65
|
+
return { hops, cycle: false, truncated: false };
|
|
66
|
+
}
|
|
67
|
+
current = base;
|
|
68
|
+
}
|
|
69
|
+
return { hops, cycle: false, truncated: true };
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
/** One ref's freshness, as the caller measured it. */
|
|
73
|
+
export interface HopReading {
|
|
74
|
+
readonly ref: string;
|
|
75
|
+
/** `true` behind · `false` current · `null` not measured. */
|
|
76
|
+
readonly behind: boolean | null;
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
/**
|
|
80
|
+
* Which hops still need merging, in order.
|
|
81
|
+
*
|
|
82
|
+
* An UNMEASURED hop is returned alongside the behind ones rather than skipped:
|
|
83
|
+
* "we could not tell" and "it is current" are different states, and treating the
|
|
84
|
+
* first as the second is how a single-hop check reported green on a stale stack
|
|
85
|
+
* in the first place.
|
|
86
|
+
*/
|
|
87
|
+
export function hopsNeedingMerge(readings: readonly HopReading[]): HopReading[] {
|
|
88
|
+
return readings.filter((r) => r.behind !== false);
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
/** True only when every hop was measured and every hop is current. */
|
|
92
|
+
export function cascadeCurrent(readings: readonly HopReading[]): boolean {
|
|
93
|
+
return readings.length > 0 && readings.every((r) => r.behind === false);
|
|
94
|
+
}
|
|
@@ -82,6 +82,16 @@ export interface TranscriptRecord {
|
|
|
82
82
|
/** Raw `timestamp` field from the record, or null when absent. */
|
|
83
83
|
timestamp: string | null;
|
|
84
84
|
usage: TokenCounts;
|
|
85
|
+
/**
|
|
86
|
+
* UTF-8 byte length of this record's own `message.content`, or `null` when
|
|
87
|
+
* the scan did not ask for it ({@link ScanOptions.measureBytes}) or the
|
|
88
|
+
* record carries no serialisable content.
|
|
89
|
+
*
|
|
90
|
+
* `null` is NOT zero. A record whose content was never measured and a
|
|
91
|
+
* record whose content is empty are different facts, and a ratio built
|
|
92
|
+
* over the first would divide by a denominator it never observed.
|
|
93
|
+
*/
|
|
94
|
+
content_bytes: number | null;
|
|
85
95
|
}
|
|
86
96
|
|
|
87
97
|
export interface ScanOptions {
|
|
@@ -98,6 +108,17 @@ export interface ScanOptions {
|
|
|
98
108
|
maxAgeDays?: number;
|
|
99
109
|
/** Clock used to evaluate `maxAgeDays` — injectable for tests. */
|
|
100
110
|
now?: Date;
|
|
111
|
+
/**
|
|
112
|
+
* Also measure each record's `message.content` byte length
|
|
113
|
+
* ({@link TranscriptRecord.content_bytes}).
|
|
114
|
+
*
|
|
115
|
+
* OPT-IN, because it costs a second serialisation of content the scan
|
|
116
|
+
* otherwise never touches, and the existing callers — `cost_summary`,
|
|
117
|
+
* `preamble_byte_census`, the bench harness — want token sums only. A
|
|
118
|
+
* caller that does not ask gets `content_bytes: null` on every record and
|
|
119
|
+
* pays nothing.
|
|
120
|
+
*/
|
|
121
|
+
measureBytes?: boolean;
|
|
101
122
|
}
|
|
102
123
|
|
|
103
124
|
export interface ScanResult {
|
|
@@ -161,7 +182,7 @@ interface RawUsage {
|
|
|
161
182
|
|
|
162
183
|
interface RawAssistantRecord {
|
|
163
184
|
type?: unknown;
|
|
164
|
-
message?: { id?: unknown; model?: unknown; usage?: RawUsage };
|
|
185
|
+
message?: { id?: unknown; model?: unknown; usage?: RawUsage; content?: unknown };
|
|
165
186
|
requestId?: unknown;
|
|
166
187
|
agentId?: unknown;
|
|
167
188
|
isSidechain?: unknown;
|
|
@@ -180,6 +201,29 @@ function toTokenCounts(raw: RawUsage | undefined): TokenCounts {
|
|
|
180
201
|
};
|
|
181
202
|
}
|
|
182
203
|
|
|
204
|
+
/**
|
|
205
|
+
* UTF-8 byte length of one record's `message.content`, or `null` when there is
|
|
206
|
+
* nothing serialisable to measure.
|
|
207
|
+
*
|
|
208
|
+
* Bytes, never characters: a multibyte block costs what it costs, and
|
|
209
|
+
* `String.length` would under-report it. Content is what this reader can
|
|
210
|
+
* actually SEE — the request's system prompt, tool definitions and prior turns
|
|
211
|
+
* are not in the transcript at all (`preamble_byte_census` records the same
|
|
212
|
+
* limitation), so a ratio built from this is a ratio over assistant output and
|
|
213
|
+
* is labelled as such wherever it surfaces.
|
|
214
|
+
*/
|
|
215
|
+
export function measureContentBytes(raw: RawAssistantRecord): number | null {
|
|
216
|
+
const content = raw.message?.content;
|
|
217
|
+
if (content === undefined || content === null) return null;
|
|
218
|
+
if (typeof content === 'string') return Buffer.byteLength(content, 'utf8');
|
|
219
|
+
try {
|
|
220
|
+
return Buffer.byteLength(JSON.stringify(content), 'utf8');
|
|
221
|
+
} catch {
|
|
222
|
+
// Circular or otherwise unserialisable — unmeasurable, not zero.
|
|
223
|
+
return null;
|
|
224
|
+
}
|
|
225
|
+
}
|
|
226
|
+
|
|
183
227
|
/** Subagent iff a non-empty top-level `agentId` OR `isSidechain === true` — see module doc comment. */
|
|
184
228
|
function classifyBucket(raw: RawAssistantRecord): TranscriptBucket {
|
|
185
229
|
const hasAgentId = typeof raw.agentId === 'string' && raw.agentId.length > 0;
|
|
@@ -285,6 +329,7 @@ export function scanTranscripts(opts: ScanOptions = {}): ScanResult {
|
|
|
285
329
|
model: typeof raw.message.model === 'string' ? raw.message.model : 'unknown',
|
|
286
330
|
timestamp: typeof raw.timestamp === 'string' ? raw.timestamp : null,
|
|
287
331
|
usage: toTokenCounts(raw.message.usage),
|
|
332
|
+
content_bytes: opts.measureBytes === true ? measureContentBytes(raw) : null,
|
|
288
333
|
});
|
|
289
334
|
}
|
|
290
335
|
}
|
|
@@ -298,6 +343,92 @@ export function scanTranscripts(opts: ScanOptions = {}): ScanResult {
|
|
|
298
343
|
};
|
|
299
344
|
}
|
|
300
345
|
|
|
346
|
+
/* -------------------------------------------------------------------------- */
|
|
347
|
+
/* Bytes — measured on the corpus, never assumed */
|
|
348
|
+
/* -------------------------------------------------------------------------- */
|
|
349
|
+
|
|
350
|
+
/**
|
|
351
|
+
* A bytes-per-token ratio, together with the two sums it was divided from and
|
|
352
|
+
* the population it was measured over.
|
|
353
|
+
*
|
|
354
|
+
* The sums travel WITH the ratio on purpose. A bare ratio is the shape that
|
|
355
|
+
* becomes a constant: somebody copies the number, it outlives the corpus that
|
|
356
|
+
* produced it, and the next reader cannot tell what it measured. Carrying
|
|
357
|
+
* `content_bytes` and `output_tokens` means the division is re-derivable from
|
|
358
|
+
* the record itself.
|
|
359
|
+
*/
|
|
360
|
+
export interface BytesPerToken {
|
|
361
|
+
/** Always `'measured'` — this value is a division of two counted sums. */
|
|
362
|
+
basis: 'measured';
|
|
363
|
+
/** Summed `content_bytes` over the records that carried one. */
|
|
364
|
+
content_bytes: number;
|
|
365
|
+
/** Summed `output_tokens` over those same records. */
|
|
366
|
+
output_tokens: number;
|
|
367
|
+
/** `content_bytes / output_tokens`. */
|
|
368
|
+
bytes_per_token: number;
|
|
369
|
+
/** Records that contributed — `content_bytes !== null` and `output_tokens > 0`. */
|
|
370
|
+
records: number;
|
|
371
|
+
}
|
|
372
|
+
|
|
373
|
+
/**
|
|
374
|
+
* Measure a bytes-per-token ratio ON THE GIVEN RECORDS. Returns `null` when
|
|
375
|
+
* nothing in the corpus can supply one.
|
|
376
|
+
*
|
|
377
|
+
* `null` is the honest outcome and callers must render it as such rather than
|
|
378
|
+
* substituting a default: a ratio invented where none was measured is exactly
|
|
379
|
+
* the constant this function exists to make unnecessary. There is deliberately
|
|
380
|
+
* no fallback value anywhere in this module — the factor is a property of the
|
|
381
|
+
* corpus, and a corpus that cannot supply one has not supplied one.
|
|
382
|
+
*/
|
|
383
|
+
export function measureBytesPerToken(records: readonly TranscriptRecord[]): BytesPerToken | null {
|
|
384
|
+
let bytes = 0;
|
|
385
|
+
let tokens = 0;
|
|
386
|
+
let n = 0;
|
|
387
|
+
for (const rec of records) {
|
|
388
|
+
if (rec.content_bytes === null || rec.usage.output_tokens <= 0) continue;
|
|
389
|
+
bytes += rec.content_bytes;
|
|
390
|
+
tokens += rec.usage.output_tokens;
|
|
391
|
+
n += 1;
|
|
392
|
+
}
|
|
393
|
+
if (n === 0 || tokens <= 0) return null;
|
|
394
|
+
return {
|
|
395
|
+
basis: 'measured',
|
|
396
|
+
content_bytes: bytes,
|
|
397
|
+
output_tokens: tokens,
|
|
398
|
+
bytes_per_token: bytes / tokens,
|
|
399
|
+
records: n,
|
|
400
|
+
};
|
|
401
|
+
}
|
|
402
|
+
|
|
403
|
+
/** A byte figure obtained by multiplying a token count by a measured ratio. */
|
|
404
|
+
export interface DerivedBytes {
|
|
405
|
+
/** Always `'derived'`. Never `'measured'` — nothing counted these bytes. */
|
|
406
|
+
basis: 'derived';
|
|
407
|
+
/** The token count that was multiplied. */
|
|
408
|
+
tokens: number;
|
|
409
|
+
/** The ratio used, carried so the arithmetic sits beside its result. */
|
|
410
|
+
factor: BytesPerToken;
|
|
411
|
+
/** `tokens * factor.bytes_per_token`, rounded to a whole byte. */
|
|
412
|
+
bytes: number;
|
|
413
|
+
}
|
|
414
|
+
|
|
415
|
+
/**
|
|
416
|
+
* Multiply a token count by a ratio measured on the same corpus.
|
|
417
|
+
*
|
|
418
|
+
* The result carries `basis: 'derived'` and the factor that produced it, so a
|
|
419
|
+
* derived figure and a counted one can never be read as the same number — the
|
|
420
|
+
* failure this whole path is shaped to prevent. `null` factor in, `null` out.
|
|
421
|
+
*/
|
|
422
|
+
export function derivedBytes(tokens: number, factor: BytesPerToken | null): DerivedBytes | null {
|
|
423
|
+
if (factor === null) return null;
|
|
424
|
+
return {
|
|
425
|
+
basis: 'derived',
|
|
426
|
+
tokens,
|
|
427
|
+
factor,
|
|
428
|
+
bytes: Math.round(tokens * factor.bytes_per_token),
|
|
429
|
+
};
|
|
430
|
+
}
|
|
431
|
+
|
|
301
432
|
function emptyAggregate(bucket: TranscriptBucket): BucketAggregate {
|
|
302
433
|
return {
|
|
303
434
|
bucket,
|
|
@@ -231,8 +231,13 @@ export function archive_file_header(boundary: string): string {
|
|
|
231
231
|
'> `tests/test_changelog_eras.py`.\n' +
|
|
232
232
|
'>\n' +
|
|
233
233
|
'> **Read-only.** New entries land in `CHANGELOG.md`. Entries\n' +
|
|
234
|
-
'> here are not
|
|
235
|
-
'> for what shipped
|
|
234
|
+
'> here are not rewritten — git tags remain the canonical source\n' +
|
|
235
|
+
'> for what shipped. The one amendment an archive permits is a\n' +
|
|
236
|
+
'> **correction that adds**: where a published entry says something\n' +
|
|
237
|
+
'> the shipped tree contradicts, the correction is appended beside\n' +
|
|
238
|
+
'> it and names the commit that decided the matter. Nothing is\n' +
|
|
239
|
+
'> deleted, so both the original claim and its correction stay\n' +
|
|
240
|
+
'> readable.\n' +
|
|
236
241
|
'>\n' +
|
|
237
242
|
'> Entry shape follows\n' +
|
|
238
243
|
'> [`../contracts/CHANGELOG-conventions.md`](../contracts/CHANGELOG-conventions.md).\n' +
|
|
@@ -0,0 +1,200 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Per-concern SLA measurement and the warn-only window's reporting.
|
|
3
|
+
*
|
|
4
|
+
* Extracted from `bench_hook_latency.ts` on 2026-10-01, not for tidiness: the
|
|
5
|
+
* additions that made `one-question-per-ask` measurable and gave the warn-only
|
|
6
|
+
* window a reader pushed that file from 1,361 lines to 1,583, i.e. 83 lines
|
|
7
|
+
* past the 1,500-line source ceiling, and `check_source_size_budget`'s ratchet
|
|
8
|
+
* asked for exactly this by name. Raising the baseline was the other option
|
|
9
|
+
* and is the one the ratchet exists to make visible rather than easy — a MOVE
|
|
10
|
+
* gives the budget back instead of spending it, which is the doctrine every
|
|
11
|
+
* entry in `gate-violation-baselines.json` records.
|
|
12
|
+
*
|
|
13
|
+
* It is also the better boundary on its own terms. Everything here answers one
|
|
14
|
+
* question — which blocking concern is bounded, by how much, and did this run
|
|
15
|
+
* cross it — and none of it is about measuring a hook SLOT, which is what the
|
|
16
|
+
* bench itself does.
|
|
17
|
+
*
|
|
18
|
+
* NOTHING HERE GATES. The bound these functions read is not armed anywhere:
|
|
19
|
+
* step 3.3 of `road-to-a-kernel-that-guards-its-plumbing` is its only consumer
|
|
20
|
+
* and is blocked on the window these functions report. A measurement that can
|
|
21
|
+
* red a build before its bar is validated is Risk 1 of that roadmap.
|
|
22
|
+
*/
|
|
23
|
+
import * as fs from 'node:fs';
|
|
24
|
+
|
|
25
|
+
/**
|
|
26
|
+
* The subset of a per-concern row the window needs: a measurement and a bound.
|
|
27
|
+
*
|
|
28
|
+
* Declared here rather than imported so the dependency runs one way only —
|
|
29
|
+
* `bench_hook_latency.ts` imports this module and this module imports nothing
|
|
30
|
+
* back. `ConcernLatencyRow` satisfies it structurally, so the caller passes its
|
|
31
|
+
* own rows unchanged and no adapter exists to drift.
|
|
32
|
+
*/
|
|
33
|
+
export interface BoundedConcern {
|
|
34
|
+
readonly concern: string;
|
|
35
|
+
/** p95 of the concern's own durations in microseconds, or null when untimed. */
|
|
36
|
+
readonly p95_us: number | null;
|
|
37
|
+
/** The registered SLA in milliseconds, or null when none is registered. */
|
|
38
|
+
readonly sla_ms: number | null;
|
|
39
|
+
/** The budget carried a value for this concern that is not a number. */
|
|
40
|
+
readonly sla_malformed: boolean;
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
/**
|
|
44
|
+
* An alternate tool shape for the synthetic payload.
|
|
45
|
+
*
|
|
46
|
+
* The default payload names `Read`, which is the right default for every gated
|
|
47
|
+
* number: it is the common non-matching case consumers pay on every tool call.
|
|
48
|
+
* But a concern filtered by the manifest's per-concern `tools:` key is SKIPPED
|
|
49
|
+
* in-process on a payload naming a tool it does not claim, so it never produces
|
|
50
|
+
* a sample and the per-concern report prints `not_measured` for it — correctly,
|
|
51
|
+
* and uselessly for a step that needs a bound.
|
|
52
|
+
*
|
|
53
|
+
* `one-question-per-ask` is the live instance: `severity: blocking`, filtered to
|
|
54
|
+
* `AskUserQuestion` and five aliases. It is the only blocking concern the
|
|
55
|
+
* default payload cannot reach, and the owning roadmap's Phase-3 blocker names
|
|
56
|
+
* the choice explicitly — "either a payload shaped to trigger it or an explicit
|
|
57
|
+
* decision that an unmeasured blocking concern keeps the current 30 s timeout".
|
|
58
|
+
* This is the first branch: measure it, so the decision is not forced by the
|
|
59
|
+
* harness.
|
|
60
|
+
*
|
|
61
|
+
* Measurement-only, like `--bundle` and `--payload-bytes`. It is reachable only
|
|
62
|
+
* from `concernSlaPass`, which gates nothing and writes no budget row — a probe
|
|
63
|
+
* payload must never produce a gated slot number, because the slot caps were
|
|
64
|
+
* derived against the `Read` shape and a different shape runs a different
|
|
65
|
+
* concern chain.
|
|
66
|
+
*/
|
|
67
|
+
export interface ToolShape {
|
|
68
|
+
readonly tool_name: string;
|
|
69
|
+
readonly tool_input: Record<string, unknown>;
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
/**
|
|
73
|
+
* The probe that reaches `one-question-per-ask`.
|
|
74
|
+
*
|
|
75
|
+
* ONE question, not two: a single question is the ALLOW path, and the allow
|
|
76
|
+
* path is what an ordinary call costs. Two questions would measure the deny
|
|
77
|
+
* path — the rarer branch, and the one that builds a message — so a bound taken
|
|
78
|
+
* from it would describe the exception rather than the rule. Both run
|
|
79
|
+
* `countStructuredAskQuestions`, which is the concern's actual work.
|
|
80
|
+
*/
|
|
81
|
+
export const ASK_PROBE: ToolShape = {
|
|
82
|
+
tool_name: 'AskUserQuestion',
|
|
83
|
+
tool_input: { questions: [{ question: 'bench probe' }] },
|
|
84
|
+
};
|
|
85
|
+
|
|
86
|
+
/**
|
|
87
|
+
* The one concern the ask probe exists to measure.
|
|
88
|
+
*
|
|
89
|
+
* Named rather than inferred from the `tools:` filter: a future concern added
|
|
90
|
+
* with an overlapping filter would be measured under a payload chosen for THIS
|
|
91
|
+
* one, and silently adopting it is the contamination above wearing a different
|
|
92
|
+
* shape.
|
|
93
|
+
*/
|
|
94
|
+
export const PROBE_TARGET = 'one-question-per-ask';
|
|
95
|
+
|
|
96
|
+
/**
|
|
97
|
+
* Append only `target`'s samples from the probe sink onto the main sink.
|
|
98
|
+
*
|
|
99
|
+
* The probe sink is read and then left in place; it is a temp-dir file the
|
|
100
|
+
* caller owns. A read failure is not fatal — the probe is an observation, and
|
|
101
|
+
* a bench that dies because one auxiliary sink was unreadable converts a
|
|
102
|
+
* measurement into an outage, which is the posture `readConcernTimings`
|
|
103
|
+
* already takes one level down.
|
|
104
|
+
*/
|
|
105
|
+
export function mergeProbeSamples(probeSink: string, sinkPath: string, target: string): void {
|
|
106
|
+
let text: string;
|
|
107
|
+
try {
|
|
108
|
+
text = fs.readFileSync(probeSink, 'utf-8');
|
|
109
|
+
} catch {
|
|
110
|
+
return;
|
|
111
|
+
}
|
|
112
|
+
const keep: string[] = [];
|
|
113
|
+
for (const line of text.split('\n')) {
|
|
114
|
+
const trimmed = line.trim();
|
|
115
|
+
if (trimmed === '') continue;
|
|
116
|
+
let row: { concern?: unknown };
|
|
117
|
+
try {
|
|
118
|
+
row = JSON.parse(trimmed) as typeof row;
|
|
119
|
+
} catch {
|
|
120
|
+
continue;
|
|
121
|
+
}
|
|
122
|
+
if (row.concern === target) keep.push(trimmed);
|
|
123
|
+
}
|
|
124
|
+
if (keep.length === 0) return;
|
|
125
|
+
try {
|
|
126
|
+
fs.appendFileSync(sinkPath, `${keep.join('\n')}\n`, 'utf-8');
|
|
127
|
+
} catch {
|
|
128
|
+
/* the sink is an observation aid; losing it must not fail the bench */
|
|
129
|
+
}
|
|
130
|
+
}
|
|
131
|
+
|
|
132
|
+
/**
|
|
133
|
+
* The warn-only window, made legible — observe-only, gating nothing.
|
|
134
|
+
*
|
|
135
|
+
* Step 3.3 derives a per-concern timeout of `sla_ms x 3` and its own text says
|
|
136
|
+
* "warn-only for the first measured window, then deny". A window nothing
|
|
137
|
+
* observes is not a window: with `sla_ms` registered and no reader, the flip
|
|
138
|
+
* would be taken on the absence of a complaint rather than on a reading.
|
|
139
|
+
*
|
|
140
|
+
* So this is the instrument. Every bench run — local or CI, gated or not —
|
|
141
|
+
* prints each blocking concern's measured p95 beside its registered bound and
|
|
142
|
+
* names any concern that crossed it. The window is then a span of runs a human
|
|
143
|
+
* can read, and the flip has an evidence base that exists before it is taken.
|
|
144
|
+
*
|
|
145
|
+
* It lives in the measurement harness and NOT in the dispatcher deliberately.
|
|
146
|
+
* A runtime warn would be a dispatcher change shipped as part of a step that is
|
|
147
|
+
* blocked on the very readings it would produce, and Risk 1 of the owning
|
|
148
|
+
* roadmap is precisely about acting on a bound before it is validated.
|
|
149
|
+
*
|
|
150
|
+
* A concern with no registered SLA, or no measurement, is NOT an overrun — it
|
|
151
|
+
* is an unknown, and the two must not collapse. Returning it as "fine" would
|
|
152
|
+
* let an unregistered concern read as a clean window.
|
|
153
|
+
*/
|
|
154
|
+
export interface SlaOverrun {
|
|
155
|
+
concern: string;
|
|
156
|
+
p95_us: number;
|
|
157
|
+
/** `sla_ms x 3`, in microseconds — the bound step 3.3 would apply. */
|
|
158
|
+
bound_us: number;
|
|
159
|
+
}
|
|
160
|
+
|
|
161
|
+
/**
|
|
162
|
+
* What a run can actually say about the window — three counts, never one.
|
|
163
|
+
*
|
|
164
|
+
* `registered` is how many concerns carry a bound. `usable` is how many of
|
|
165
|
+
* those this run could compare against it. The gap is what an earlier version
|
|
166
|
+
* of the summary line silently dropped: it printed "none over" off `registered`,
|
|
167
|
+
* so a run that measured none of its bounded concerns read exactly like a run
|
|
168
|
+
* that measured all of them and found nothing. That is the same
|
|
169
|
+
* unknown-collapsed-into-fine failure `slaOverruns` refuses per row, one level
|
|
170
|
+
* up at the summary.
|
|
171
|
+
*/
|
|
172
|
+
export interface WindowState {
|
|
173
|
+
registered: number;
|
|
174
|
+
usable: number;
|
|
175
|
+
/** Registered but not comparable this run — unmeasured or malformed. */
|
|
176
|
+
unusable: number;
|
|
177
|
+
}
|
|
178
|
+
|
|
179
|
+
export function windowState(rows: readonly BoundedConcern[]): WindowState {
|
|
180
|
+
let registered = 0;
|
|
181
|
+
let usable = 0;
|
|
182
|
+
for (const row of rows) {
|
|
183
|
+
if (row.sla_ms === null && !row.sla_malformed) continue;
|
|
184
|
+
registered += 1;
|
|
185
|
+
if (!row.sla_malformed && row.sla_ms !== null && row.p95_us !== null) usable += 1;
|
|
186
|
+
}
|
|
187
|
+
return { registered, usable, unusable: registered - usable };
|
|
188
|
+
}
|
|
189
|
+
|
|
190
|
+
export function slaOverruns(rows: readonly BoundedConcern[]): SlaOverrun[] {
|
|
191
|
+
const out: SlaOverrun[] = [];
|
|
192
|
+
for (const row of rows) {
|
|
193
|
+
if (row.sla_ms === null || row.sla_malformed || row.p95_us === null) continue;
|
|
194
|
+
const bound_us = row.sla_ms * 3 * 1000;
|
|
195
|
+
if (row.p95_us > bound_us) {
|
|
196
|
+
out.push({ concern: row.concern, p95_us: row.p95_us, bound_us });
|
|
197
|
+
}
|
|
198
|
+
}
|
|
199
|
+
return out;
|
|
200
|
+
}
|