@event4u/agent-config 14.12.0 → 14.14.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 +118 -155
- package/CONTRIBUTING.md +38 -0
- package/CREDITS.md +135 -0
- package/NOTICE +13 -0
- package/README.md +8 -6
- package/dist/agent-src/commands/analyze/inbox.md +145 -2
- package/dist/agent-src/commands/feature/dev.md +7 -2
- package/dist/agent-src/commands/ghostwriter/write.md +5 -1
- package/dist/agent-src/commands/implement-ticket.md +1 -1
- package/dist/agent-src/commands/mission/upgrade.md +1 -1
- package/dist/agent-src/commands/orchestrate.md +2 -2
- package/dist/agent-src/commands/post-as/me.md +5 -1
- package/dist/agent-src/commands/pr/merge.md +19 -6
- package/dist/agent-src/commands/roadmap/process-full.md +19 -5
- package/dist/agent-src/commands/tdd/red.md +12 -2
- package/dist/agent-src/commands/work.md +1 -1
- package/dist/agent-src/contexts/communication/rules-auto/token-efficiency-mechanics.md +40 -0
- package/dist/agent-src/contexts/execution/auto-dispatch-classification.md +5 -2
- package/dist/agent-src/contexts/execution/autonomy-mechanics.md +21 -0
- package/dist/agent-src/contexts/execution/orchestration-telemetry.md +1 -0
- package/dist/agent-src/contexts/execution/subagent-response-contract.md +40 -0
- package/dist/agent-src/contexts/execution/subagent-steering.md +13 -1
- package/dist/agent-src/contexts/execution/terminal-states.md +96 -0
- package/dist/agent-src/rules/artifact-drafting-protocol.md +1 -1
- package/dist/agent-src/rules/broken-access-control.md +4 -0
- package/dist/agent-src/rules/code-comment-discipline.md +6 -0
- package/dist/agent-src/rules/code-provenance.md +4 -0
- package/dist/agent-src/rules/design-fidelity.md +1 -1
- package/dist/agent-src/rules/domain-safety-pii.md +4 -0
- package/dist/agent-src/rules/domain-safety-retention.md +4 -0
- package/dist/agent-src/rules/engineering-safety-floor.md +2 -0
- package/dist/agent-src/rules/external-code-graph-interop.md +14 -12
- package/dist/agent-src/rules/improve-before-implement.md +6 -8
- package/dist/agent-src/rules/laravel-routing.md +1 -1
- package/dist/agent-src/rules/lethal-trifecta-guard.md +4 -0
- package/dist/agent-src/rules/secret-vcs-guard.md +2 -0
- package/dist/agent-src/rules/security-sensitive-stop.md +2 -0
- package/dist/agent-src/rules/source-confidentiality.md +12 -34
- package/dist/agent-src/rules/symfony-routing.md +1 -1
- package/dist/agent-src/rules/token-efficiency.md +14 -0
- package/dist/agent-src/rules/tool-safety.md +4 -0
- package/dist/agent-src/rules/untrusted-input-defense.md +4 -0
- package/dist/agent-src/scripts/archive_completed_roadmaps.ts +252 -8
- package/dist/agent-src/scripts/guarded_baseline.ts +343 -0
- package/dist/agent-src/scripts/roadmap_set_step.ts +253 -0
- package/dist/agent-src/scripts/stub_queue.ts +82 -0
- package/dist/agent-src/scripts/stubs_due.ts +516 -0
- package/dist/agent-src/scripts/ticket_bundles.ts +84 -0
- package/dist/agent-src/scripts/update_roadmap_progress.ts +16 -12
- package/dist/agent-src/skills/brand-asset-generation/SKILL.md +17 -4
- package/dist/agent-src/skills/brand-audit/SKILL.md +33 -0
- package/dist/agent-src/skills/code-intelligence/SKILL.md +57 -0
- package/dist/agent-src/skills/database/SKILL.md +8 -0
- package/dist/agent-src/skills/dependency-upgrade/SKILL.md +27 -0
- package/dist/agent-src/skills/design-review/SKILL.md +1 -1
- package/dist/agent-src/skills/doc-coauthoring/SKILL.md +1 -1
- package/dist/agent-src/skills/existing-ui-audit/SKILL.md +17 -1
- package/dist/agent-src/skills/feature-planning/SKILL.md +9 -2
- package/dist/agent-src/skills/frontend-render-security/SKILL.md +44 -0
- package/dist/agent-src/skills/git-workflow/SKILL.md +1 -1
- package/dist/agent-src/skills/js-library-packaging/scripts/check_package_surface.ts +38 -4
- package/dist/agent-src/skills/judge-artifact-completeness/rubrics/ticket-quality-score.json +2 -2
- package/dist/agent-src/skills/laravel/SKILL.md +35 -0
- package/dist/agent-src/skills/laravel-migration/SKILL.md +57 -3
- package/dist/agent-src/skills/monorepo-workspace/SKILL.md +26 -0
- package/dist/agent-src/skills/playbook-authoring/SKILL.md +28 -1
- package/dist/agent-src/skills/react-shadcn-ui/SKILL.md +1 -1
- package/dist/agent-src/skills/skill-writing/references/procedure.md +5 -5
- package/dist/agent-src/skills/sql-writing/SKILL.md +7 -1
- package/dist/agent-src/skills/standards-from-config/SKILL.md +27 -4
- package/dist/agent-src/skills/supply-chain-intake/SKILL.md +11 -4
- package/dist/agent-src/skills/symfony-workflow/SKILL.md +35 -0
- package/dist/agent-src/skills/test-case-discovery/SKILL.md +85 -0
- package/dist/agent-src/skills/test-driven-development/SKILL.md +33 -9
- package/dist/agent-src/skills/testing-anti-patterns/SKILL.md +1 -1
- package/dist/agent-src/skills/testing-anti-patterns/process-anti-patterns.md +37 -0
- package/dist/agent-src/skills/ui-component-architect/SKILL.md +45 -2
- package/dist/agent-src/skills/upstream-contribute/SKILL.md +38 -1
- package/dist/agent-src/skills/verify-repair-loop/SKILL.md +9 -4
- package/dist/agent-src/templates/agents/agent-project-settings.example.yml +1 -1
- package/dist/agent-src/templates/roadmaps.md +97 -3
- package/dist/agent-src/templates/scripts/check_memory.ts +258 -17
- package/dist/agent-src/templates/scripts/check_memory_proposal.ts +9 -3
- package/dist/agent-src/templates/scripts/memory_lookup.ts +86 -0
- package/dist/agent-src/templates/scripts/memory_signal.ts +44 -0
- package/dist/agent-src/templates/scripts/work_engine/directives/backend/implement.ts +90 -0
- package/dist/agent-src/templates/scripts/work_engine/directives/backend/index.ts +11 -0
- package/dist/agent-src/templates/scripts/work_engine/directives/backend/test.ts +23 -1
- package/dist/agent-src/templates/scripts/work_engine/directives/backend/verify.ts +10 -1
- package/dist/agent-src/templates/scripts/work_engine/directives/mixed/stitch.ts +5 -1
- package/dist/agent-src/templates/scripts/work_engine/stack/runner.ts +102 -14
- package/dist/cli/commands/uiAudit.js +124 -5
- package/dist/cli/registry.js +2 -0
- package/dist/cli-delegate/{chunk-HYWSRNUX.js → chunk-2DK2VJWG.js} +3 -3
- package/dist/cli-delegate/{chunk-JCW7ZLHK.js → chunk-4FS4WRXP.js} +5 -0
- package/dist/cli-delegate/{chunk-OMIVVCG2.js → chunk-A4QJ6ENT.js} +24 -0
- package/dist/cli-delegate/{chunk-JJA2QEWA.js → chunk-AKJGUQNO.js} +466 -285
- package/dist/cli-delegate/{chunk-E46KPN6K.js → chunk-BTWUYDGN.js} +424 -363
- package/dist/cli-delegate/{chunk-K4FFODTS.js → chunk-ILMUPLGW.js} +1227 -316
- package/dist/cli-delegate/{chunk-FF7WAFMR.js → chunk-KPBWFOPZ.js} +5 -1
- package/dist/cli-delegate/cmd_adr_effective.js +102 -0
- package/dist/cli-delegate/cmd_conformance.js +80 -11
- package/dist/cli-delegate/cmd_doctor.js +3 -3
- package/dist/cli-delegate/cmd_export.js +3 -2
- package/dist/cli-delegate/cmd_fleet.js +2 -2
- package/dist/cli-delegate/cmd_handoff.js +1 -1
- package/dist/cli-delegate/cmd_preflight.js +2 -2
- package/dist/cli-delegate/cmd_prune.js +3 -2
- package/dist/cli-delegate/cmd_settings_get.js +1 -1
- package/dist/cli-delegate/cmd_settings_migrate.js +3 -2
- package/dist/cli-delegate/cmd_settings_set.js +2 -2
- package/dist/cli-delegate/cmd_sync.js +3 -2
- package/dist/cli-delegate/cmd_uninstall.js +3 -2
- package/dist/cli-delegate/cmd_validate.js +3 -2
- package/dist/discovery/deprecation-report.md +1 -1
- package/dist/discovery/discovery-manifest.json +58 -58
- package/dist/discovery/discovery-manifest.json.sha256 +1 -1
- package/dist/discovery/discovery-manifest.summary.md +1 -1
- package/dist/discovery/dormancy-report.md +1 -1
- package/dist/discovery/orphan-report.md +1 -1
- package/dist/discovery/packs.json +2 -2
- package/dist/discovery/trust-report.md +1 -1
- package/dist/discovery/workspaces.json +2 -2
- package/dist/hooks/dispatch.js +2900 -1049
- package/dist/install/claudeRuleRewrite.js +254 -0
- package/dist/install/detect.js +29 -16
- package/dist/install/detect_php_shape.js +159 -0
- package/dist/install/install.mjs +634 -428
- package/dist/mcp/awesome-mcp-servers.row.md +1 -1
- package/dist/mcp/mcp-cloudflare-catalogue.json +1 -1
- package/dist/mcp/registry-manifest.json +3 -3
- package/dist/mcp/server.json +3 -3
- package/dist/mcp/server.mjs +4895 -2627
- package/dist/server/schemas/settings.js +3 -0
- package/dist/ui/assets/index-D0jaCRPM.js +29 -0
- package/dist/ui/index.html +1 -1
- package/docs/MIGRATION.md +1 -1
- package/docs/THIRD-PARTY-NOTICES.md +25 -0
- package/docs/contracts/provider-lifecycle.md +1 -1
- package/docs/contracts/settings-classes.md +10 -4
- package/docs/guidelines/agent-infra/5w2h-analysis.md +3 -3
- package/docs/guidelines/agent-infra/agent-interaction-and-decision-quality.md +1 -0
- package/docs/guidelines/agent-infra/artifact-drafting-protocol-mechanics.md +58 -3
- package/docs/guidelines/agent-infra/ask-when-uncertain-demos.md +2 -2
- package/docs/guidelines/agent-infra/asking-and-brevity-examples.md +3 -3
- package/docs/guidelines/agent-infra/break-glass-usage.md +7 -7
- package/docs/guidelines/agent-infra/carve-out-predicates.md +3 -3
- package/docs/guidelines/agent-infra/comparison-matrix.md +1 -1
- package/docs/guidelines/agent-infra/critical-thinking.md +4 -4
- package/docs/guidelines/agent-infra/direct-answers-demos.md +2 -2
- package/docs/guidelines/agent-infra/domain-eval-anti-pattern.md +1 -1
- package/docs/guidelines/agent-infra/engineering-memory-data-format.md +3 -3
- package/docs/guidelines/agent-infra/existence-question-verdicts.md +80 -0
- package/docs/guidelines/agent-infra/first-principles.md +2 -2
- package/docs/guidelines/agent-infra/framework-neutrality-patterns.md +2 -2
- package/docs/guidelines/agent-infra/guarded-baseline.md +39 -0
- package/docs/guidelines/agent-infra/inversion-thinking.md +5 -5
- package/docs/guidelines/agent-infra/language-and-tone-examples.md +1 -1
- package/docs/guidelines/agent-infra/layered-settings.md +10 -10
- package/docs/guidelines/agent-infra/memory-access.md +1 -1
- package/docs/guidelines/agent-infra/mental-models.md +3 -3
- package/docs/guidelines/agent-infra/reuse-verdict-mechanics.md +71 -0
- package/docs/guidelines/agent-infra/review-routing-data-format.md +7 -7
- package/docs/guidelines/agent-infra/roadmap-progress-mechanics.md +13 -7
- package/docs/guidelines/agent-infra/role-contracts.md +2 -2
- package/docs/guidelines/agent-infra/role-mode-router.md +3 -3
- package/docs/guidelines/agent-infra/rule-type-governance.md +2 -2
- package/docs/guidelines/agent-infra/scqa-framework.md +5 -5
- package/docs/guidelines/agent-infra/self-improvement-pipeline.md +6 -6
- package/docs/guidelines/agent-infra/simplicity-and-goal-demos.md +2 -2
- package/docs/guidelines/agent-infra/six-hats.md +3 -3
- package/docs/guidelines/agent-infra/skill-quality-checklist.md +6 -6
- package/docs/guidelines/agent-infra/source-confidentiality-mechanics.md +109 -0
- package/docs/guidelines/agent-infra/systems-thinking.md +1 -1
- package/docs/guidelines/agent-infra/verify-before-complete-demos.md +2 -2
- package/docs/guidelines/augment-portability-patterns.md +6 -4
- package/docs/guidelines/code-clarity.md +18 -0
- package/docs/guidelines/component-oriented-and-oop-development.md +3 -2
- package/docs/guidelines/cross-role-handoff.md +2 -2
- package/docs/guidelines/design-fidelity-mechanics.md +1 -1
- package/docs/guidelines/docs/readme-size-and-splitting.md +3 -3
- package/docs/guidelines/php/database.md +90 -7
- package/docs/guidelines/prompt-templates.md +7 -7
- package/llms.txt +2 -2
- package/package.json +5 -2
- package/src/agent-src/scripts/archive_completed_roadmaps.ts +252 -8
- package/src/agent-src/scripts/guarded_baseline.ts +343 -0
- package/src/agent-src/scripts/roadmap_set_step.ts +253 -0
- package/src/agent-src/scripts/stub_queue.ts +82 -0
- package/src/agent-src/scripts/stubs_due.ts +516 -0
- package/src/agent-src/scripts/ticket_bundles.ts +84 -0
- package/src/agent-src/scripts/update_roadmap_progress.ts +16 -12
- package/src/agent-src/templates/scripts/check_memory.ts +258 -17
- package/src/agent-src/templates/scripts/check_memory_proposal.ts +9 -3
- package/src/agent-src/templates/scripts/memory_lookup.ts +86 -0
- package/src/agent-src/templates/scripts/memory_signal.ts +44 -0
- package/src/agent-src/templates/scripts/work_engine/directives/backend/implement.ts +90 -0
- package/src/agent-src/templates/scripts/work_engine/directives/backend/index.ts +11 -0
- package/src/agent-src/templates/scripts/work_engine/directives/backend/test.ts +23 -1
- package/src/agent-src/templates/scripts/work_engine/directives/backend/verify.ts +10 -1
- package/src/agent-src/templates/scripts/work_engine/directives/mixed/stitch.ts +5 -1
- package/src/agent-src/templates/scripts/work_engine/stack/runner.ts +102 -14
- package/src/config/agent-settings.template.yml +32 -3
- package/src/config/assurance-capability-registry.json +21 -9
- package/src/config/canonical-terms.yml +119 -0
- package/src/config/capability-native-outcome-bars.json +81 -0
- package/src/config/ci-local-parity.yml +80 -0
- package/src/config/drafting-channels.yml +43 -0
- package/src/config/estate-count-budget.json +57 -36
- package/src/config/evaluator-budgets.json +14 -14
- package/src/config/gate-coverage.yml +729 -63
- package/src/config/gate-reachability-exemptions.json +21 -0
- package/src/config/gate-violation-baselines.json +42 -19
- package/src/config/harness-evolution-budget.json +25 -0
- package/src/config/lapsed-beta-baseline.json +96 -0
- package/src/config/memory-twin-verdicts.yml +129 -0
- package/src/config/metric-registry.yml +124 -0
- package/src/config/pack-size-budget.json +16 -15
- package/src/config/preamble-payload-budget.json +12 -4
- package/src/config/profiles/balanced.ini +27 -0
- package/src/config/profiles/full.ini +27 -0
- package/src/config/profiles/minimal.ini +27 -0
- package/src/config/publish-surface.json +9 -2
- package/src/config/routing-coverage-seed.json +16 -2
- package/src/config/rule-stub-ceilings.json +133 -112
- package/src/config/turnaround-budget.json +66 -0
- package/src/install/claudeRuleRewrite.ts +288 -0
- package/src/install/detect.ts +60 -17
- package/src/install/detect_php_shape.ts +191 -0
- package/src/scripts/_cli/cmd_doctor.ts +27 -112
- package/src/scripts/_dispatch.bash +23 -0
- package/src/scripts/_lib/activation_ladder.ts +255 -0
- package/src/scripts/_lib/activation_receipt_producer.ts +341 -0
- package/src/scripts/_lib/activation_states.ts +88 -0
- package/src/scripts/_lib/adherence_detectors.ts +143 -0
- package/src/scripts/_lib/adr_frontmatter.ts +26 -0
- package/src/scripts/_lib/arith_claims.ts +181 -0
- package/src/scripts/_lib/arm_ranking.ts +62 -0
- package/src/scripts/_lib/asset_delivery_ledger.ts +303 -0
- package/src/scripts/_lib/audit_field_provenance.ts +54 -0
- package/src/scripts/_lib/auto_dispatch.ts +32 -3
- package/src/scripts/_lib/bench_ab_size_claim.ts +98 -4
- package/src/scripts/_lib/benchmark_cache_fields.ts +158 -0
- package/src/scripts/_lib/candidate_pair_delta.ts +349 -0
- package/src/scripts/_lib/candidate_proposer.ts +402 -0
- package/src/scripts/_lib/candidate_record.ts +601 -0
- package/src/scripts/_lib/capture_rate.ts +319 -0
- package/src/scripts/_lib/carrier_status.ts +118 -0
- package/src/scripts/_lib/cascade_stage_enumeration.ts +92 -0
- package/src/scripts/_lib/collector_denominator.ts +771 -0
- package/src/scripts/_lib/collector_record.ts +340 -0
- package/src/scripts/_lib/collector_store.ts +664 -0
- package/src/scripts/_lib/collector_supervision.ts +827 -0
- package/src/scripts/_lib/concern_estate.ts +77 -0
- package/src/scripts/_lib/config_chain.ts +477 -0
- package/src/scripts/_lib/config_cost.ts +209 -0
- package/src/scripts/_lib/corpus_manifest.ts +726 -0
- package/src/scripts/_lib/council_settings_block.ts +6 -0
- package/src/scripts/_lib/curator_ops.ts +172 -0
- package/src/scripts/_lib/delivery_arm_experiment.ts +282 -0
- package/src/scripts/_lib/delivery_criticality.ts +126 -0
- package/src/scripts/_lib/doctor_runtime_checks.ts +70 -0
- package/src/scripts/_lib/empty_cycles.ts +88 -0
- package/src/scripts/_lib/estate_carrier_cases.ts +72 -0
- package/src/scripts/_lib/eval_publication.ts +354 -0
- package/src/scripts/_lib/evaluation_cascade.ts +452 -0
- package/src/scripts/_lib/evaluation_vector.ts +283 -0
- package/src/scripts/_lib/evaluator_promotion.ts +225 -0
- package/src/scripts/_lib/evidence_basis.ts +118 -0
- package/src/scripts/_lib/evolution_roi.ts +562 -0
- package/src/scripts/_lib/exec_evidence.ts +8 -0
- package/src/scripts/_lib/experience_card.ts +272 -0
- package/src/scripts/_lib/experience_report.ts +151 -0
- package/src/scripts/_lib/experiment_freeze.ts +112 -0
- package/src/scripts/_lib/generated_by.ts +66 -0
- package/src/scripts/_lib/harness_evolution_guards.ts +239 -0
- package/src/scripts/_lib/hook_effect_probe.ts +141 -0
- package/src/scripts/_lib/host_denominator.ts +687 -0
- package/src/scripts/_lib/host_projection_reach.ts +157 -0
- package/src/scripts/_lib/ignored_blocker.ts +148 -0
- package/src/scripts/_lib/install_reach_checks.ts +495 -0
- package/src/scripts/_lib/json_duplicate_keys.ts +150 -0
- package/src/scripts/_lib/judge_hygiene.ts +228 -0
- package/src/scripts/_lib/legacy_boundary_map.ts +228 -0
- package/src/scripts/_lib/lexical_shortlist.ts +215 -0
- package/src/scripts/_lib/llm_candidate_proposer.ts +461 -0
- package/src/scripts/_lib/llm_proposer_transport.ts +153 -0
- package/src/scripts/_lib/loop_guards.ts +299 -0
- package/src/scripts/_lib/loss_class.ts +129 -0
- package/src/scripts/_lib/machine_wake.ts +51 -0
- package/src/scripts/_lib/md_prose_lines.ts +177 -0
- package/src/scripts/_lib/minimality_tiebreak.ts +168 -0
- package/src/scripts/_lib/one_resolver_invariant.ts +449 -0
- package/src/scripts/_lib/orchestration_record.ts +253 -5
- package/src/scripts/_lib/outcome_envelope.ts +227 -0
- package/src/scripts/_lib/outcome_vocabularies.ts +125 -0
- package/src/scripts/_lib/paired_stats.ts +245 -0
- package/src/scripts/_lib/paired_verdict.ts +226 -0
- package/src/scripts/_lib/pathology_archive.ts +393 -0
- package/src/scripts/_lib/prefix_stable_surfaces.ts +123 -0
- package/src/scripts/_lib/privacy_class.ts +47 -0
- package/src/scripts/_lib/promotion_capability.ts +315 -0
- package/src/scripts/_lib/promotion_evidence.ts +414 -0
- package/src/scripts/_lib/promotion_review.ts +294 -0
- package/src/scripts/_lib/prompt_shape.ts +92 -0
- package/src/scripts/_lib/regression_neighbourhood.ts +259 -0
- package/src/scripts/_lib/release_highlights.ts +70 -1
- package/src/scripts/_lib/release_material.ts +125 -2
- package/src/scripts/_lib/repeated_failure.ts +105 -0
- package/src/scripts/_lib/repo_root.ts +111 -0
- package/src/scripts/_lib/review_skipped_record.ts +37 -0
- package/src/scripts/_lib/role_split.ts +338 -0
- package/src/scripts/_lib/routing_corpus.ts +307 -0
- package/src/scripts/_lib/routing_index_input.ts +104 -0
- package/src/scripts/_lib/rule_injection.ts +22 -1
- package/src/scripts/_lib/runtime_journal.ts +1499 -0
- package/src/scripts/_lib/runtime_wiring_checks.ts +430 -0
- package/src/scripts/_lib/semantic_noop.ts +167 -0
- package/src/scripts/_lib/skill_catalogue.ts +11 -1
- package/src/scripts/_lib/source_digest.ts +200 -0
- package/src/scripts/_lib/source_redact.ts +260 -0
- package/src/scripts/_lib/source_shape.ts +502 -0
- package/src/scripts/_lib/source_snapshot_dedup.ts +199 -0
- package/src/scripts/_lib/strip_comments.d.mts +8 -0
- package/src/scripts/_lib/strip_comments.mjs +65 -0
- package/src/scripts/_lib/test_red_state.ts +154 -0
- package/src/scripts/_lib/trigger_shift.ts +144 -0
- package/src/scripts/_lib/untrusted_content.ts +82 -7
- package/src/scripts/activation_receipt.ts +258 -0
- package/src/scripts/adoption_report.ts +1 -1
- package/src/scripts/adr/evidence_census.ts +1 -1
- package/src/scripts/adr/regenerate_index.ts +26 -2
- package/src/scripts/adr_cite_check.ts +164 -1
- package/src/scripts/ai_council/blind_review.ts +11 -1
- package/src/scripts/ai_council/clients.ts +4 -0
- package/src/scripts/ai_council/config.ts +3 -0
- package/src/scripts/ai_council/consensus.ts +28 -8
- package/src/scripts/ai_council/consensus_round.ts +226 -0
- package/src/scripts/ai_council/cross_exam.ts +128 -0
- package/src/scripts/ai_council/disagreement_signal.ts +338 -0
- package/src/scripts/ai_council/explain_route.ts +157 -0
- package/src/scripts/ai_council/information_gain.ts +293 -0
- package/src/scripts/ai_council/inline_findings.ts +340 -0
- package/src/scripts/ai_council/judge_position_bias.ts +197 -0
- package/src/scripts/ai_council/leakage_corpus.ts +385 -0
- package/src/scripts/ai_council/leakage_patterns.ts +453 -0
- package/src/scripts/ai_council/minority_retention.ts +171 -0
- package/src/scripts/ai_council/orchestrator.ts +16 -203
- package/src/scripts/ai_council/orchestrator_results.ts +23 -3
- package/src/scripts/ai_council/pricing.ts +106 -0
- package/src/scripts/ai_council/prompts.ts +74 -7
- package/src/scripts/ai_council/provider_leakage_bench.ts +327 -0
- package/src/scripts/ai_council/py_format.ts +14 -3
- package/src/scripts/ai_council/recouncil_guard.ts +293 -0
- package/src/scripts/ai_council/recouncil_savings.ts +299 -0
- package/src/scripts/ai_council/replay_route.ts +223 -0
- package/src/scripts/ai_council/response_record.ts +50 -0
- package/src/scripts/ai_council/reviewer_assignment.ts +279 -0
- package/src/scripts/ai_council/routing_training_row.ts +197 -0
- package/src/scripts/ai_council/synthesis_strategy.ts +159 -0
- package/src/scripts/ai_council/topology_bench_manifest.ts +860 -0
- package/src/scripts/ai_council/topology_vocabulary.ts +175 -0
- package/src/scripts/asset_delivery_ledger.ts +157 -0
- package/src/scripts/attest_artifact.ts +193 -0
- package/src/scripts/audit_auto_rules.ts +2 -1
- package/src/scripts/audit_command_surface.ts +1 -1
- package/src/scripts/audit_mcp_tools.ts +2 -2
- package/src/scripts/audit_skill_overlap_allowlist.json +6 -1
- package/src/scripts/audit_user_type_axis.ts +2 -1
- package/src/scripts/bench_ab_clone.ts +215 -10
- package/src/scripts/bench_ab_integrity.ts +119 -5
- package/src/scripts/bench_ab_v2_stats.ts +40 -213
- package/src/scripts/build_archive_index.ts +1 -1
- package/src/scripts/build_linear_digest.ts +1 -1
- package/src/scripts/build_proof.ts +11 -4
- package/src/scripts/build_rule_trigger_matrix.ts +1 -1
- package/src/scripts/build_source_digests.ts +157 -0
- package/src/scripts/cache_realization_report.ts +103 -0
- package/src/scripts/check_adr_frontmatter.ts +131 -10
- package/src/scripts/check_benchmark_report_fields.ts +207 -0
- package/src/scripts/check_beta_review_markers.ts +235 -6
- package/src/scripts/check_bundle_path_leakage.ts +34 -1
- package/src/scripts/check_claims.ts +576 -6
- package/src/scripts/check_composite_arming.ts +35 -0
- package/src/scripts/check_concern_admissions.ts +365 -0
- package/src/scripts/check_enforcement_coverage.ts +28 -0
- package/src/scripts/check_estate_count.ts +347 -25
- package/src/scripts/check_gate_reachability.ts +383 -0
- package/src/scripts/check_host_loadability.ts +104 -0
- package/src/scripts/check_loss_class_declared.ts +308 -0
- package/src/scripts/check_md_language.ts +15 -98
- package/src/scripts/check_memory_proposal.ts +18 -11
- package/src/scripts/check_no_currency_in_cost_surfaces.ts +225 -0
- package/src/scripts/check_no_external_sources.ts +281 -6
- package/src/scripts/check_pack_conformance_fixture.ts +11 -1
- package/src/scripts/check_pack_size.ts +59 -2
- package/src/scripts/check_preamble_payload_budget.ts +114 -3
- package/src/scripts/check_prefix_stable_mutation.ts +372 -0
- package/src/scripts/check_publish_surface.ts +10 -1
- package/src/scripts/check_references.ts +24 -1
- package/src/scripts/check_release_highlights.ts +96 -13
- package/src/scripts/check_roadmap_trackable.ts +17 -2
- package/src/scripts/check_rule_evidence_declaration.ts +316 -0
- package/src/scripts/check_rule_projection_integrity.ts +14 -4
- package/src/scripts/check_score_contract.ts +26 -3
- package/src/scripts/check_static_parity.ts +285 -0
- package/src/scripts/check_supervision_claim_atomicity.ts +498 -0
- package/src/scripts/check_suppression_hygiene.ts +15 -0
- package/src/scripts/ci_settle.ts +39 -2
- package/src/scripts/code_graph/build.ts +60 -3
- package/src/scripts/code_graph/extract.ts +44 -0
- package/src/scripts/code_graph/types.ts +24 -0
- package/src/scripts/collector_daemon.ts +714 -0
- package/src/scripts/config_cost_report.ts +302 -0
- package/src/scripts/consumer_matrix.ts +74 -1
- package/src/scripts/corpus_manifest.ts +235 -0
- package/src/scripts/cost/budget.mjs +10 -3
- package/src/scripts/cost/track.mjs +10 -3
- package/src/scripts/council_cli.ts +37 -54
- package/src/scripts/dispatch_r2_reviewer.ts +46 -5
- package/src/scripts/evolution_lab.ts +1153 -0
- package/src/scripts/external_sources_denylist.json +5 -18
- package/src/scripts/extract_audit_patterns.ts +21 -0
- package/src/scripts/fold_intake.ts +12 -1
- package/src/scripts/gen_discovery_baseline.ts +1 -1
- package/src/scripts/generate_capabilities_index.ts +12 -5
- package/src/scripts/generate_capability_matrix.ts +3 -3
- package/src/scripts/generate_command_flows.ts +2 -2
- package/src/scripts/generate_cookbook.ts +2 -2
- package/src/scripts/generate_index.ts +8 -1
- package/src/scripts/generate_knowledge_index.ts +2 -1
- package/src/scripts/generate_ownership_matrix.ts +2 -1
- package/src/scripts/generate_pack_manifests.ts +10 -2
- package/src/scripts/generate_role_experiences_catalog.ts +2 -2
- package/src/scripts/generate_settings_reference.ts +1 -1
- package/src/scripts/git_authorization_hook.ts +22 -1
- package/src/scripts/grade_target_readiness.ts +48 -5
- package/src/scripts/hook_effect_doctor.ts +261 -0
- package/src/scripts/hook_manifest.json +1 -1
- package/src/scripts/hook_manifest.yaml +125 -16
- package/src/scripts/hooks/block_speaking_inbox_dir.ts +271 -0
- package/src/scripts/hooks/block_unauthorized_git.ts +18 -0
- package/src/scripts/hooks/comment_discipline_hook.ts +146 -0
- package/src/scripts/hooks/concern_registry.ts +6 -0
- package/src/scripts/hooks/dispatch_hook.ts +32 -0
- package/src/scripts/hooks/journal_record_hook.ts +345 -0
- package/src/scripts/hooks/prepush_metadata_sources.sh +110 -0
- package/src/scripts/hooks/run_continuation_hook.ts +44 -9
- package/src/scripts/hooks/shims/install.sh +61 -0
- package/src/scripts/hooks/shims/php +101 -0
- package/src/scripts/hooks/suggestion_capture_hook.ts +10 -0
- package/src/scripts/hooks/turn_end_gate_hook.ts +9 -1
- package/src/scripts/hot_context_hook.ts +16 -0
- package/src/scripts/install-hooks.sh +20 -5
- package/src/scripts/install.ts +17 -35
- package/src/scripts/inventory_frontmatter.ts +2 -1
- package/src/scripts/language_mirror_hook.ts +14 -2
- package/src/scripts/lint_adr_sweep_routing.ts +12 -0
- package/src/scripts/lint_canonical_terms.ts +604 -0
- package/src/scripts/lint_carrier_integrity.ts +820 -0
- package/src/scripts/lint_code_comments.ts +643 -0
- package/src/scripts/lint_composition_review.ts +619 -0
- package/src/scripts/lint_consolidation_lineage.ts +666 -0
- package/src/scripts/lint_eval_specs.ts +593 -0
- package/src/scripts/lint_hook_manifest.ts +19 -0
- package/src/scripts/lint_host_portability.ts +373 -0
- package/src/scripts/lint_judge_prompt_expectation.ts +27 -1
- package/src/scripts/lint_memory_twin_parity.ts +335 -0
- package/src/scripts/lint_metric_consumers.ts +355 -0
- package/src/scripts/lint_plan_risk_register.ts +39 -19
- package/src/scripts/lint_promotion_paths.ts +779 -0
- package/src/scripts/lint_skill_descriptions.ts +18 -1
- package/src/scripts/lint_skill_frontmatter_safety.ts +58 -0
- package/src/scripts/lint_skill_link_reach.ts +252 -0
- package/src/scripts/lint_skill_trigger_corpus.ts +426 -23
- package/src/scripts/lint_ticket_buildable.ts +17 -1
- package/src/scripts/llm_propose.ts +256 -0
- package/src/scripts/mcp_server/consumer_tool_catalog.json +1 -1
- package/src/scripts/mcp_server/tool_catalog_source.ts +1 -1
- package/src/scripts/measure_delivery_sets.ts +361 -0
- package/src/scripts/measure_docs_dead_links.ts +348 -0
- package/src/scripts/measure_host_capture.ts +461 -0
- package/src/scripts/measure_routing_signal.ts +307 -0
- package/src/scripts/migration_status.ts +3 -3
- package/src/scripts/model_rule_injection.ts +76 -0
- package/src/scripts/orchestration_record.ts +14 -0
- package/src/scripts/preamble_byte_census.ts +139 -10
- package/src/scripts/prepack-check.mjs +4 -1
- package/src/scripts/probe_council_retention.ts +495 -0
- package/src/scripts/probe_turnaround.ts +384 -0
- package/src/scripts/probe_unblocked_ask.ts +279 -0
- package/src/scripts/release.ts +21 -37
- package/src/scripts/release_drill.ts +143 -0
- package/src/scripts/release_env.ts +35 -0
- package/src/scripts/release_publication.ts +84 -0
- package/src/scripts/render_benchmark_md.ts +1 -1
- package/src/scripts/rule_activation_census.ts +70 -9
- package/src/scripts/rule_backlinks.ts +1 -1
- package/src/scripts/run_lifecycle_suite.ts +208 -0
- package/src/scripts/run_skill_evals.ts +40 -0
- package/src/scripts/schemas/delivery-manifest.schema.json +43 -0
- package/src/scripts/schemas/drafting-channels.schema.json +43 -0
- package/src/scripts/schemas/evals.schema.json +24 -1
- package/src/scripts/schemas/rule.schema.json +52 -1
- package/src/scripts/schemas/skill.schema.json +17 -2
- package/src/scripts/skill_overlap.ts +7 -1
- package/src/scripts/skill_tools/run_block_d_eval.ts +8 -1
- package/src/scripts/skill_tools/score_skill_relevance.ts +84 -3
- package/src/scripts/skill_tools/suggest_skill_for_task.ts +8 -1
- package/src/scripts/skill_usage_report.ts +2 -1
- package/src/scripts/stubs_due.ts +211 -0
- package/src/scripts/sweep_source_surfaces.ts +678 -0
- package/src/scripts/sync_pr_branch.ts +3 -3
- package/src/scripts/trigger_eval_grandfather.json +202 -222
- package/src/scripts/utilization_report.ts +22 -2
- package/src/server/schemas/settings.ts +5 -0
- package/src/templates/marketing-copy.yml +1 -1
- package/dist/ui/assets/index-Cxgy63kz.js +0 -26
|
@@ -0,0 +1,384 @@
|
|
|
1
|
+
#!/usr/bin/env tsx
|
|
2
|
+
/**
|
|
3
|
+
* Turnaround instrument — round-trips, batch size, blocking waits, context floor.
|
|
4
|
+
*
|
|
5
|
+
* WHY IT EXISTS. `agents/evidence/analysis/agent-turnaround-2026-08-30.md`
|
|
6
|
+
* measured that a user request in this package costs 42.6 API round-trips and a
|
|
7
|
+
* file change costs 58 tool calls, and its fifth finding is that NONE of it was
|
|
8
|
+
* measurable before: no instrument in the tree reported round-trips per request,
|
|
9
|
+
* tool-call batch size, or the blocking-wait tail, so no gate could have caught
|
|
10
|
+
* any of them growing. That analysis was a throwaway script. This is the
|
|
11
|
+
* committed form of it — `road-to-agent-turnaround` Phase 1.1.
|
|
12
|
+
*
|
|
13
|
+
* THE UNIT IS `requestId`, NOT THE ROW. The host writes `thinking`, `text` and
|
|
14
|
+
* `tool_use` as SEPARATE JSONL rows that share one `requestId`, so counting rows
|
|
15
|
+
* inflates every per-call figure. That was the first wrong answer the original
|
|
16
|
+
* measurement produced, and it is why this file groups before it counts.
|
|
17
|
+
*
|
|
18
|
+
* WHAT IT REPORTS, per corpus:
|
|
19
|
+
* - API calls per real user request (F1)
|
|
20
|
+
* - mean tool-call batch size (F1 — measured 1.00, i.e. fully serial)
|
|
21
|
+
* - the >60 s blocking tail and its share of tool time (F2)
|
|
22
|
+
* - the first-call context floor (F3 cross-check)
|
|
23
|
+
*
|
|
24
|
+
* A MEASUREMENT, NOT A GATE — by default. `--against-baseline` compares against
|
|
25
|
+
* `src/config/turnaround-budget.json` and is the only mode that can exit
|
|
26
|
+
* non-zero. Whether that mode may run in CI is answered in the config's own
|
|
27
|
+
* `_comment`, because a transcript store is machine-local and a fresh clone has
|
|
28
|
+
* none: a CI gate over it would be green-because-empty, the exact
|
|
29
|
+
* gate-that-scans-nothing-exits-green failure this repository names elsewhere.
|
|
30
|
+
*
|
|
31
|
+
* Usage:
|
|
32
|
+
* ./scripts-run src/scripts/probe_turnaround [--store PATH] [--limit N]
|
|
33
|
+
* ./scripts-run src/scripts/probe_turnaround --json
|
|
34
|
+
* ./scripts-run src/scripts/probe_turnaround --against-baseline
|
|
35
|
+
*
|
|
36
|
+
* The MEASURING session is excluded by default — see `recentSessions`.
|
|
37
|
+
*
|
|
38
|
+
* Exit codes: 0 = reported (or within baseline) · 1 = baseline regression, or an
|
|
39
|
+
* empty corpus under `--against-baseline` · 2 = usage error.
|
|
40
|
+
*/
|
|
41
|
+
import * as fs from 'node:fs';
|
|
42
|
+
import * as path from 'node:path';
|
|
43
|
+
import { fileURLToPath } from 'node:url';
|
|
44
|
+
|
|
45
|
+
import { defaultStore, isInjectedBody, userText } from './conformance_scan.js';
|
|
46
|
+
|
|
47
|
+
const _FILE = fileURLToPath(import.meta.url);
|
|
48
|
+
const REPO_ROOT = path.resolve(path.dirname(_FILE), '..', '..');
|
|
49
|
+
export const BUDGET_REL = 'src/config/turnaround-budget.json';
|
|
50
|
+
|
|
51
|
+
/** The blocking-tail threshold, in seconds. Fixed by the source measurement. */
|
|
52
|
+
export const BLOCKING_SECONDS = 60;
|
|
53
|
+
|
|
54
|
+
type Entry = Record<string, unknown>;
|
|
55
|
+
|
|
56
|
+
function isObj(v: unknown): v is Record<string, unknown> {
|
|
57
|
+
return typeof v === 'object' && v !== null && !Array.isArray(v);
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
export interface Turnaround {
|
|
61
|
+
sessions: number;
|
|
62
|
+
userRequests: number;
|
|
63
|
+
apiCalls: number;
|
|
64
|
+
callsPerRequest: number;
|
|
65
|
+
toolCalls: number;
|
|
66
|
+
/** Requests that carried at least one tool_use block. */
|
|
67
|
+
toolUsingRequests: number;
|
|
68
|
+
meanBatchSize: number;
|
|
69
|
+
toolSeconds: number;
|
|
70
|
+
blockingCalls: number;
|
|
71
|
+
blockingSeconds: number;
|
|
72
|
+
blockingShare: number;
|
|
73
|
+
contextFloorMin: number;
|
|
74
|
+
contextFloorMax: number;
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
const ZERO: Turnaround = {
|
|
78
|
+
sessions: 0,
|
|
79
|
+
userRequests: 0,
|
|
80
|
+
apiCalls: 0,
|
|
81
|
+
callsPerRequest: 0,
|
|
82
|
+
toolCalls: 0,
|
|
83
|
+
toolUsingRequests: 0,
|
|
84
|
+
meanBatchSize: 0,
|
|
85
|
+
toolSeconds: 0,
|
|
86
|
+
blockingCalls: 0,
|
|
87
|
+
blockingSeconds: 0,
|
|
88
|
+
blockingShare: 0,
|
|
89
|
+
contextFloorMin: 0,
|
|
90
|
+
contextFloorMax: 0,
|
|
91
|
+
};
|
|
92
|
+
|
|
93
|
+
function ms(iso: unknown): number | null {
|
|
94
|
+
if (typeof iso !== 'string') return null;
|
|
95
|
+
const t = Date.parse(iso);
|
|
96
|
+
return Number.isNaN(t) ? null : t;
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
/**
|
|
100
|
+
* The N most recently modified transcript files under `store`, excluding the
|
|
101
|
+
* MEASURING session.
|
|
102
|
+
*
|
|
103
|
+
* The exclusion is not hygiene, it is correctness, and it was found by the
|
|
104
|
+
* instrument failing its own baseline. Two consecutive runs minutes apart
|
|
105
|
+
* reported `calls_per_request` 81.42 then 81.61: the measuring session was
|
|
106
|
+
* adding API calls to the newest transcript while its user-request denominator
|
|
107
|
+
* stayed at one, so the probe was reading its own execution as a regression.
|
|
108
|
+
* The source analysis excluded the measuring session for the same reason and
|
|
109
|
+
* said so; this reproduces that rather than rediscovering it a third time.
|
|
110
|
+
*
|
|
111
|
+
* Identified by `CLAUDE_CODE_SESSION_ID` where the host exports it — the file
|
|
112
|
+
* is `<sessionId>.jsonl` — and otherwise by dropping the single
|
|
113
|
+
* most-recently-modified file, which is the same session by construction on any
|
|
114
|
+
* machine actually running this. `--include-current` opts back in for an
|
|
115
|
+
* offline corpus where nothing is being written.
|
|
116
|
+
*/
|
|
117
|
+
export function recentSessions(store: string, limit: number, includeCurrent = false): string[] {
|
|
118
|
+
if (!fs.existsSync(store)) return [];
|
|
119
|
+
const all = fs
|
|
120
|
+
.readdirSync(store)
|
|
121
|
+
.filter((f) => f.endsWith('.jsonl'))
|
|
122
|
+
.map((f) => ({ f, m: fs.statSync(path.join(store, f)).mtimeMs }))
|
|
123
|
+
.sort((a, b) => b.m - a.m);
|
|
124
|
+
if (!includeCurrent && all.length > 0) {
|
|
125
|
+
const sid = process.env['CLAUDE_CODE_SESSION_ID'];
|
|
126
|
+
const idx =
|
|
127
|
+
sid !== undefined && sid !== ''
|
|
128
|
+
? all.findIndex((x) => x.f === `${sid}.jsonl`)
|
|
129
|
+
: 0;
|
|
130
|
+
if (idx >= 0) all.splice(idx, 1);
|
|
131
|
+
}
|
|
132
|
+
return all.slice(0, limit).map(({ f }) => path.join(store, f));
|
|
133
|
+
}
|
|
134
|
+
|
|
135
|
+
export function measure(files: readonly string[]): Turnaround {
|
|
136
|
+
const requestIds = new Set<string>();
|
|
137
|
+
/** requestId → tool_use blocks in it. */
|
|
138
|
+
const batch = new Map<string, number>();
|
|
139
|
+
/** tool_use id → the timestamp the assistant emitted it. */
|
|
140
|
+
const emittedAt = new Map<string, number>();
|
|
141
|
+
const durations: number[] = [];
|
|
142
|
+
const contextFloors: number[] = [];
|
|
143
|
+
let userRequests = 0;
|
|
144
|
+
let sessions = 0;
|
|
145
|
+
|
|
146
|
+
for (const file of files) {
|
|
147
|
+
let sawFloor = false;
|
|
148
|
+
let sawAny = false;
|
|
149
|
+
for (const line of fs.readFileSync(file, 'utf8').split('\n')) {
|
|
150
|
+
if (!line.trim()) continue;
|
|
151
|
+
let e: Entry;
|
|
152
|
+
try {
|
|
153
|
+
e = JSON.parse(line) as Entry;
|
|
154
|
+
} catch {
|
|
155
|
+
continue;
|
|
156
|
+
}
|
|
157
|
+
if (e['isSidechain'] === true) continue;
|
|
158
|
+
sawAny = true;
|
|
159
|
+
const msg = e['message'];
|
|
160
|
+
|
|
161
|
+
if (e['type'] === 'user') {
|
|
162
|
+
// The SAME predicate the conformance scan uses, imported rather
|
|
163
|
+
// than re-typed: a skill body and a slash-command header arrive
|
|
164
|
+
// in the user role and are not requests. Counting them would
|
|
165
|
+
// deflate calls-per-request by inventing a denominator.
|
|
166
|
+
const text = userText(e);
|
|
167
|
+
if (text !== null && !isInjectedBody(text)) userRequests += 1;
|
|
168
|
+
if (isObj(msg) && Array.isArray(msg['content'])) {
|
|
169
|
+
const at = ms(e['timestamp']);
|
|
170
|
+
for (const b of msg['content']) {
|
|
171
|
+
if (!isObj(b) || b['type'] !== 'tool_result') continue;
|
|
172
|
+
const id = String(b['tool_use_id'] ?? '');
|
|
173
|
+
const started = emittedAt.get(id);
|
|
174
|
+
if (started === undefined || at === null) continue;
|
|
175
|
+
durations.push((at - started) / 1000);
|
|
176
|
+
emittedAt.delete(id);
|
|
177
|
+
}
|
|
178
|
+
}
|
|
179
|
+
continue;
|
|
180
|
+
}
|
|
181
|
+
|
|
182
|
+
if (e['type'] !== 'assistant') continue;
|
|
183
|
+
const rid = e['requestId'];
|
|
184
|
+
if (typeof rid === 'string' && rid !== '') requestIds.add(rid);
|
|
185
|
+
|
|
186
|
+
if (!sawFloor && isObj(msg) && isObj(msg['usage'])) {
|
|
187
|
+
const u = msg['usage'] as Record<string, unknown>;
|
|
188
|
+
const floor =
|
|
189
|
+
Number(u['input_tokens'] ?? 0) +
|
|
190
|
+
Number(u['cache_read_input_tokens'] ?? 0) +
|
|
191
|
+
Number(u['cache_creation_input_tokens'] ?? 0);
|
|
192
|
+
if (floor > 0) {
|
|
193
|
+
contextFloors.push(floor);
|
|
194
|
+
sawFloor = true;
|
|
195
|
+
}
|
|
196
|
+
}
|
|
197
|
+
|
|
198
|
+
if (!isObj(msg) || !Array.isArray(msg['content'])) continue;
|
|
199
|
+
const at = ms(e['timestamp']);
|
|
200
|
+
let n = 0;
|
|
201
|
+
for (const b of msg['content']) {
|
|
202
|
+
if (!isObj(b) || b['type'] !== 'tool_use') continue;
|
|
203
|
+
n += 1;
|
|
204
|
+
const id = String(b['id'] ?? '');
|
|
205
|
+
if (id !== '' && at !== null) emittedAt.set(id, at);
|
|
206
|
+
}
|
|
207
|
+
if (n > 0 && typeof rid === 'string' && rid !== '') {
|
|
208
|
+
batch.set(rid, (batch.get(rid) ?? 0) + n);
|
|
209
|
+
}
|
|
210
|
+
}
|
|
211
|
+
if (sawAny) sessions += 1;
|
|
212
|
+
}
|
|
213
|
+
|
|
214
|
+
const toolCalls = [...batch.values()].reduce((a, b) => a + b, 0);
|
|
215
|
+
const toolSeconds = durations.reduce((a, b) => a + b, 0);
|
|
216
|
+
const blocking = durations.filter((d) => d > BLOCKING_SECONDS);
|
|
217
|
+
const blockingSeconds = blocking.reduce((a, b) => a + b, 0);
|
|
218
|
+
const round = (x: number, p = 2): number => Math.round(x * 10 ** p) / 10 ** p;
|
|
219
|
+
|
|
220
|
+
return {
|
|
221
|
+
sessions,
|
|
222
|
+
userRequests,
|
|
223
|
+
apiCalls: requestIds.size,
|
|
224
|
+
callsPerRequest: userRequests === 0 ? 0 : round(requestIds.size / userRequests),
|
|
225
|
+
toolCalls,
|
|
226
|
+
toolUsingRequests: batch.size,
|
|
227
|
+
meanBatchSize: batch.size === 0 ? 0 : round(toolCalls / batch.size),
|
|
228
|
+
toolSeconds: round(toolSeconds, 1),
|
|
229
|
+
blockingCalls: blocking.length,
|
|
230
|
+
blockingSeconds: round(blockingSeconds, 1),
|
|
231
|
+
blockingShare: toolSeconds === 0 ? 0 : round(blockingSeconds / toolSeconds, 4),
|
|
232
|
+
contextFloorMin: contextFloors.length === 0 ? 0 : Math.min(...contextFloors),
|
|
233
|
+
contextFloorMax: contextFloors.length === 0 ? 0 : Math.max(...contextFloors),
|
|
234
|
+
};
|
|
235
|
+
}
|
|
236
|
+
|
|
237
|
+
export interface Budget {
|
|
238
|
+
baseline: {
|
|
239
|
+
calls_per_request: number;
|
|
240
|
+
mean_batch_size: number;
|
|
241
|
+
blocking_share: number;
|
|
242
|
+
context_floor_max: number;
|
|
243
|
+
};
|
|
244
|
+
empty_corpus: 'fail' | 'report';
|
|
245
|
+
}
|
|
246
|
+
|
|
247
|
+
export function readBudget(root = REPO_ROOT): Budget | null {
|
|
248
|
+
const p = path.join(root, BUDGET_REL);
|
|
249
|
+
if (!fs.existsSync(p)) return null;
|
|
250
|
+
return JSON.parse(fs.readFileSync(p, 'utf8')) as Budget;
|
|
251
|
+
}
|
|
252
|
+
|
|
253
|
+
/**
|
|
254
|
+
* Baseline comparison.
|
|
255
|
+
*
|
|
256
|
+
* `calls_per_request` is deliberately ABSENT — see the comment in the body.
|
|
257
|
+
* Direction is DOWN for `blocking_share`, and UP for
|
|
258
|
+
* `mean_batch_size` — a batch of 1.00 is the floor being fixed, so a higher
|
|
259
|
+
* number is the improvement and the ratchet must not read it as a regression.
|
|
260
|
+
* `context_floor_max` is DOWN. Getting one of these directions backwards would
|
|
261
|
+
* make the gate reward the defect, which is why they are listed rather than
|
|
262
|
+
* inferred from a shared helper.
|
|
263
|
+
*/
|
|
264
|
+
export function compare(t: Turnaround, b: Budget): string[] {
|
|
265
|
+
const out: string[] = [];
|
|
266
|
+
// `calls_per_request` is REPORTED, never compared. Twice measured unstable
|
|
267
|
+
// for reasons that are not regressions: it fell 81.42 -> 72.67 when the
|
|
268
|
+
// measuring session was excluded, then drifted 72.67 -> 73.73 within the
|
|
269
|
+
// same afternoon because the corpus is an MTIME WINDOW and other sessions
|
|
270
|
+
// in the same project store keep sliding into it. A ratchet on a number
|
|
271
|
+
// that moves when nothing changed teaches the reader to ignore the gate,
|
|
272
|
+
// which is the failure this repository records for gates that can only
|
|
273
|
+
// block. The three metrics below are stable across the same runs.
|
|
274
|
+
if (t.meanBatchSize < b.baseline.mean_batch_size) {
|
|
275
|
+
out.push(
|
|
276
|
+
`mean batch size ${String(t.meanBatchSize)} < baseline ${String(b.baseline.mean_batch_size)} (direction: UP — 1.00 is fully serial)`,
|
|
277
|
+
);
|
|
278
|
+
}
|
|
279
|
+
if (t.blockingShare > b.baseline.blocking_share) {
|
|
280
|
+
out.push(
|
|
281
|
+
`blocking share ${String(t.blockingShare)} > baseline ${String(b.baseline.blocking_share)} (direction: DOWN)`,
|
|
282
|
+
);
|
|
283
|
+
}
|
|
284
|
+
if (t.contextFloorMax > b.baseline.context_floor_max) {
|
|
285
|
+
out.push(
|
|
286
|
+
`context floor max ${String(t.contextFloorMax)} > baseline ${String(b.baseline.context_floor_max)} (direction: DOWN)`,
|
|
287
|
+
);
|
|
288
|
+
}
|
|
289
|
+
return out;
|
|
290
|
+
}
|
|
291
|
+
|
|
292
|
+
function render(t: Turnaround, store: string): string {
|
|
293
|
+
const pct = (x: number): string => `${String(Math.round(x * 1000) / 10)} %`;
|
|
294
|
+
return [
|
|
295
|
+
`probe:turnaround · ${String(t.sessions)} session(s) · ${store}`,
|
|
296
|
+
` API calls per user request ${String(t.callsPerRequest)} (${String(t.apiCalls)} calls / ${String(t.userRequests)} requests)`,
|
|
297
|
+
` mean tool-call batch size ${String(t.meanBatchSize)} (${String(t.toolCalls)} tool calls / ${String(t.toolUsingRequests)} tool-using requests)`,
|
|
298
|
+
` blocking tail (>${String(BLOCKING_SECONDS)}s) ${String(t.blockingCalls)} call(s), ${String(Math.round(t.blockingSeconds / 60))} min = ${pct(t.blockingShare)} of ${String(Math.round(t.toolSeconds / 60))} min tool time`,
|
|
299
|
+
` first-call context floor ${String(t.contextFloorMin)}–${String(t.contextFloorMax)} tokens`,
|
|
300
|
+
'',
|
|
301
|
+
].join('\n');
|
|
302
|
+
}
|
|
303
|
+
|
|
304
|
+
export function main(argv: string[] = process.argv.slice(2)): number {
|
|
305
|
+
const arg = (flag: string, dflt: string): string => {
|
|
306
|
+
const i = argv.indexOf(flag);
|
|
307
|
+
return i >= 0 && argv[i + 1] !== undefined ? (argv[i + 1] as string) : dflt;
|
|
308
|
+
};
|
|
309
|
+
for (const a of argv) {
|
|
310
|
+
if (a.startsWith('-') && !['--store', '--limit', '--json', '--against-baseline', '--include-current', '-h', '--help'].includes(a)) {
|
|
311
|
+
if (!argv.includes('--store') && !argv.includes('--limit')) {
|
|
312
|
+
process.stderr.write(`probe_turnaround: unrecognized argument: ${a}\n`);
|
|
313
|
+
return 2;
|
|
314
|
+
}
|
|
315
|
+
}
|
|
316
|
+
}
|
|
317
|
+
if (argv.includes('-h') || argv.includes('--help')) {
|
|
318
|
+
process.stdout.write(
|
|
319
|
+
'usage: probe_turnaround [--store PATH] [--limit N] [--json] [--against-baseline] [--include-current]\n',
|
|
320
|
+
);
|
|
321
|
+
return 0;
|
|
322
|
+
}
|
|
323
|
+
const store = arg('--store', defaultStore(process.cwd()));
|
|
324
|
+
const limit = Number(arg('--limit', '10'));
|
|
325
|
+
if (!Number.isFinite(limit) || limit < 1) {
|
|
326
|
+
process.stderr.write('probe_turnaround: --limit needs a positive integer\n');
|
|
327
|
+
return 2;
|
|
328
|
+
}
|
|
329
|
+
const files = recentSessions(store, limit, argv.includes('--include-current'));
|
|
330
|
+
const t = files.length === 0 ? ZERO : measure(files);
|
|
331
|
+
const gating = argv.includes('--against-baseline');
|
|
332
|
+
|
|
333
|
+
if (argv.includes('--json')) {
|
|
334
|
+
process.stdout.write(`${JSON.stringify({ store, ...t }, null, 2)}\n`);
|
|
335
|
+
} else {
|
|
336
|
+
process.stdout.write(render(t, store));
|
|
337
|
+
}
|
|
338
|
+
|
|
339
|
+
if (!gating) {
|
|
340
|
+
if (files.length === 0) {
|
|
341
|
+
// A REPORT over an empty store is honest and exits 0; only the
|
|
342
|
+
// gating mode treats emptiness as a failure. The distinction is the
|
|
343
|
+
// whole of step 1.3.
|
|
344
|
+
process.stdout.write(
|
|
345
|
+
`probe:turnaround · no transcript store at ${store} — nothing measured (report mode; ` +
|
|
346
|
+
'use --against-baseline for the fail-closed form)\n',
|
|
347
|
+
);
|
|
348
|
+
}
|
|
349
|
+
return 0;
|
|
350
|
+
}
|
|
351
|
+
|
|
352
|
+
const budget = readBudget();
|
|
353
|
+
if (budget === null) {
|
|
354
|
+
process.stderr.write(`❌ probe_turnaround: ${BUDGET_REL} is missing — nothing to compare against.\n`);
|
|
355
|
+
return 1;
|
|
356
|
+
}
|
|
357
|
+
if (files.length === 0) {
|
|
358
|
+
if (budget.empty_corpus === 'fail') {
|
|
359
|
+
process.stderr.write(
|
|
360
|
+
`❌ probe_turnaround: empty corpus at ${store}. A baseline comparison over zero ` +
|
|
361
|
+
'sessions certifies nothing, and the config records `empty_corpus: "fail"` so it ' +
|
|
362
|
+
'refuses rather than reporting green.\n',
|
|
363
|
+
);
|
|
364
|
+
return 1;
|
|
365
|
+
}
|
|
366
|
+
process.stdout.write('probe:turnaround · empty corpus, `empty_corpus: "report"` — no verdict.\n');
|
|
367
|
+
return 0;
|
|
368
|
+
}
|
|
369
|
+
const regressions = compare(t, budget);
|
|
370
|
+
if (regressions.length > 0) {
|
|
371
|
+
process.stderr.write('❌ probe_turnaround: regression against the recorded baseline:\n');
|
|
372
|
+
for (const r of regressions) process.stderr.write(` ${r}\n`);
|
|
373
|
+
process.stderr.write(
|
|
374
|
+
` → raising a baseline in ${BUDGET_REL} needs the reason as a sentence in the same commit.\n`,
|
|
375
|
+
);
|
|
376
|
+
return 1;
|
|
377
|
+
}
|
|
378
|
+
process.stdout.write('✅ probe_turnaround: within the recorded baseline on all four metrics.\n');
|
|
379
|
+
return 0;
|
|
380
|
+
}
|
|
381
|
+
|
|
382
|
+
if (process.argv[1] !== undefined && process.argv[1].includes('probe_turnaround')) {
|
|
383
|
+
process.exit(main());
|
|
384
|
+
}
|
|
@@ -0,0 +1,279 @@
|
|
|
1
|
+
#!/usr/bin/env tsx
|
|
2
|
+
/**
|
|
3
|
+
* The unblocked-ask rate — the class `user-interaction` measured and then had
|
|
4
|
+
* no instrument for.
|
|
5
|
+
*
|
|
6
|
+
* ## Why this one, and not one of the other 55
|
|
7
|
+
*
|
|
8
|
+
* `road-to-undeclared-obligation-disposition` Phase 2.1 ranks the
|
|
9
|
+
* `cheaply-probeable` bucket by MEASURED failure rather than by feel, and the
|
|
10
|
+
* ranking is lopsided: of 56 rules in that bucket, exactly **one** carries a
|
|
11
|
+
* cited measurement of its own failure. `user-interaction.md:75` records a
|
|
12
|
+
* 30-session conformance audit in which **every** malformed ask was a one-line
|
|
13
|
+
* parenthetical or a trailing free-text offer, and states that **no gate ships**
|
|
14
|
+
* for that class because `check_reply_consistency` inspects numbered-option
|
|
15
|
+
* blocks — "exactly the surface that did not fail".
|
|
16
|
+
*
|
|
17
|
+
* That is the whole case for building this and not a fourth probe. Phase 2.3
|
|
18
|
+
* caps the roadmap at one.
|
|
19
|
+
*
|
|
20
|
+
* ## What is counted
|
|
21
|
+
*
|
|
22
|
+
* A **hand-back** assistant turn — one immediately followed by a genuine user
|
|
23
|
+
* turn, i.e. where the agent actually stopped — that HANDS A DECISION and
|
|
24
|
+
* carries **no** numbered-options block. The rule's own § "The trigger is a
|
|
25
|
+
* decision, not a format" says the obligation fires on handing a decision in any
|
|
26
|
+
* form, so a trailing offer is an ask and owes a recommendation line.
|
|
27
|
+
*
|
|
28
|
+
* Of those, an ask is **malformed** when no recommendation line follows it.
|
|
29
|
+
*
|
|
30
|
+
* ## Honest bounds, and they are wide
|
|
31
|
+
*
|
|
32
|
+
* 1. **"Hands a decision" is a heuristic.** It matches offer shapes — *"soll ich
|
|
33
|
+
* …?"*, *"shall I …?"*, *"sag Bescheid, wenn …"*, *"willst du …?"* — anchored
|
|
34
|
+
* in the closing lines. A genuine clarifying question is deliberately NOT
|
|
35
|
+
* excluded, because `user-interaction` treats a bare yes/no as an ask too.
|
|
36
|
+
* Some rhetorical questions will be counted. The rate is therefore a
|
|
37
|
+
* CEILING for malformed asks of this class, not a point estimate.
|
|
38
|
+
* 2. **Turns WITH a numbered block are excluded**, not judged. That surface
|
|
39
|
+
* already has `check_reply_consistency`; measuring it here would double-count
|
|
40
|
+
* a covered class and hide the uncovered one.
|
|
41
|
+
* 3. **A recommendation line is matched on the label**, `Recommendation:` or
|
|
42
|
+
* `Empfehlung:` (with or without emphasis). A recommendation phrased without
|
|
43
|
+
* the label reads as malformed here. The rule requires the label — a
|
|
44
|
+
* wrong-language label is explicitly "no recommendation" — so this is the
|
|
45
|
+
* rule's own bar, but it is a bar on FORM.
|
|
46
|
+
*
|
|
47
|
+
* Both edges are printed. A reader who cannot see the bracket cannot judge the
|
|
48
|
+
* number.
|
|
49
|
+
*
|
|
50
|
+
* Usage:
|
|
51
|
+
* ./scripts-run src/scripts/probe_unblocked_ask [--limit N] [--store PATH] [--json]
|
|
52
|
+
* ./scripts-run src/scripts/probe_unblocked_ask --self-test
|
|
53
|
+
*
|
|
54
|
+
* Exit codes: 0 always — a measurement, not a gate. `--self-test` exits 1 when
|
|
55
|
+
* the detector fails to fire on its fixtures.
|
|
56
|
+
*/
|
|
57
|
+
import fs from 'node:fs';
|
|
58
|
+
import path from 'node:path';
|
|
59
|
+
import { pathToFileURL } from 'node:url';
|
|
60
|
+
|
|
61
|
+
import { defaultStore, HARNESS_TEXT, isInjectedBody } from './conformance_scan.js';
|
|
62
|
+
import { isSyntheticPrompt } from './_lib/prompt_shape.js';
|
|
63
|
+
|
|
64
|
+
/** A numbered-options block — the surface `check_reply_consistency` already reads. */
|
|
65
|
+
export const NUMBERED_BLOCK = /^\s{0,3}(\d+)[.)]\s+\S/m;
|
|
66
|
+
|
|
67
|
+
/** The recommendation line, matched on its label. Either language. */
|
|
68
|
+
export const RECOMMENDATION = /^\s{0,3}\**\s*(Recommendation|Empfehlung)\s*:?\**/im;
|
|
69
|
+
|
|
70
|
+
/**
|
|
71
|
+
* Offer shapes that hand a decision without a block. Every one of these is a
|
|
72
|
+
* shape the 2026-08-06 audit found in a REAL malformed ask.
|
|
73
|
+
*/
|
|
74
|
+
export const OFFER_SHAPES: readonly RegExp[] = [
|
|
75
|
+
/\bsoll(en)? ich\b[^.!?]{0,120}\?/i,
|
|
76
|
+
/\bwillst du\b[^.!?]{0,120}\?/i,
|
|
77
|
+
/\bm(ö|oe)chtest du\b[^.!?]{0,120}\?/i,
|
|
78
|
+
/\bsag (mir )?bescheid,? (wenn|ob|falls)\b/i,
|
|
79
|
+
/\bwenn du (willst|m(ö|oe)chtest),? (dann )?(mache|kann|nehme|ziehe)\b/i,
|
|
80
|
+
/\bshall i\b[^.!?]{0,120}\?/i,
|
|
81
|
+
/\bshould i\b[^.!?]{0,120}\?/i,
|
|
82
|
+
/\bdo you want me to\b[^.!?]{0,120}\?/i,
|
|
83
|
+
/\bwould you like me to\b[^.!?]{0,120}\?/i,
|
|
84
|
+
/\blet me know (if|whether)\b/i,
|
|
85
|
+
];
|
|
86
|
+
|
|
87
|
+
interface Turn {
|
|
88
|
+
role: 'user' | 'assistant';
|
|
89
|
+
text: string;
|
|
90
|
+
at: string;
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
function turns(lines: string[]): Turn[] {
|
|
94
|
+
const out: Turn[] = [];
|
|
95
|
+
for (const line of lines) {
|
|
96
|
+
if (!line.trim()) continue;
|
|
97
|
+
let e: Record<string, unknown>;
|
|
98
|
+
try {
|
|
99
|
+
e = JSON.parse(line) as Record<string, unknown>;
|
|
100
|
+
} catch {
|
|
101
|
+
continue;
|
|
102
|
+
}
|
|
103
|
+
if (e['isSidechain'] === true) continue;
|
|
104
|
+
if (e['type'] === 'user' && e['toolUseResult'] !== undefined) continue;
|
|
105
|
+
if (e['isCompactSummary'] === true) continue;
|
|
106
|
+
const msg = e['message'] as { role?: string; content?: unknown } | undefined;
|
|
107
|
+
const role = msg?.role;
|
|
108
|
+
if (role !== 'user' && role !== 'assistant') continue;
|
|
109
|
+
const c = msg?.content;
|
|
110
|
+
const text =
|
|
111
|
+
typeof c === 'string'
|
|
112
|
+
? c
|
|
113
|
+
: Array.isArray(c)
|
|
114
|
+
? c
|
|
115
|
+
.filter((b: { type?: string }) => b?.type === 'text')
|
|
116
|
+
.map((b: { text?: string }) => b.text ?? '')
|
|
117
|
+
.join('\n')
|
|
118
|
+
: '';
|
|
119
|
+
if (!text.trim()) continue;
|
|
120
|
+
if (role === 'assistant' && HARNESS_TEXT.test(text.trim())) continue;
|
|
121
|
+
if (role === 'user' && (isSyntheticPrompt(text) || isInjectedBody(text))) continue;
|
|
122
|
+
out.push({ role, text, at: String(e['timestamp'] ?? '') });
|
|
123
|
+
}
|
|
124
|
+
return out;
|
|
125
|
+
}
|
|
126
|
+
|
|
127
|
+
export interface AskVerdict {
|
|
128
|
+
/** The turn hands a decision with no numbered block. */
|
|
129
|
+
unblockedAsk: boolean;
|
|
130
|
+
/** …and carries no recommendation label. */
|
|
131
|
+
malformed: boolean;
|
|
132
|
+
}
|
|
133
|
+
|
|
134
|
+
/** Classify one assistant hand-back's closing lines. */
|
|
135
|
+
export function classifyTail(tail: string): AskVerdict {
|
|
136
|
+
if (NUMBERED_BLOCK.test(tail)) return { unblockedAsk: false, malformed: false };
|
|
137
|
+
const offers = OFFER_SHAPES.some((re) => re.test(tail));
|
|
138
|
+
if (!offers) return { unblockedAsk: false, malformed: false };
|
|
139
|
+
return { unblockedAsk: true, malformed: !RECOMMENDATION.test(tail) };
|
|
140
|
+
}
|
|
141
|
+
|
|
142
|
+
export interface Result {
|
|
143
|
+
handbacks: number;
|
|
144
|
+
withBlock: number;
|
|
145
|
+
unblockedAsks: number;
|
|
146
|
+
malformed: number;
|
|
147
|
+
samples: { session: string; at: string; span: string }[];
|
|
148
|
+
}
|
|
149
|
+
|
|
150
|
+
export function measure(store: string, limit: number): Result {
|
|
151
|
+
const r: Result = { handbacks: 0, withBlock: 0, unblockedAsks: 0, malformed: 0, samples: [] };
|
|
152
|
+
let files: string[];
|
|
153
|
+
try {
|
|
154
|
+
files = fs.readdirSync(store).filter((f) => f.endsWith('.jsonl'));
|
|
155
|
+
} catch {
|
|
156
|
+
return r;
|
|
157
|
+
}
|
|
158
|
+
const ordered = files
|
|
159
|
+
.map((f) => ({ f, m: fs.statSync(path.join(store, f)).mtimeMs }))
|
|
160
|
+
.sort((a, b) => b.m - a.m)
|
|
161
|
+
.slice(0, limit)
|
|
162
|
+
.map((x) => x.f);
|
|
163
|
+
|
|
164
|
+
for (const f of ordered) {
|
|
165
|
+
const session = f.replace(/\.jsonl$/, '').slice(0, 8);
|
|
166
|
+
const ts = turns(fs.readFileSync(path.join(store, f), 'utf8').split('\n'));
|
|
167
|
+
for (let i = 0; i < ts.length; i += 1) {
|
|
168
|
+
const t = ts[i] as Turn;
|
|
169
|
+
if (t.role !== 'assistant') continue;
|
|
170
|
+
const next = ts[i + 1];
|
|
171
|
+
if (next !== undefined && next.role !== 'user') continue;
|
|
172
|
+
r.handbacks += 1;
|
|
173
|
+
// Closing lines only: an offer buried mid-reply is not the hand-back.
|
|
174
|
+
const tail = t.text.trimEnd().split('\n').slice(-8).join('\n');
|
|
175
|
+
if (NUMBERED_BLOCK.test(tail)) {
|
|
176
|
+
r.withBlock += 1;
|
|
177
|
+
continue;
|
|
178
|
+
}
|
|
179
|
+
const v = classifyTail(tail);
|
|
180
|
+
if (!v.unblockedAsk) continue;
|
|
181
|
+
r.unblockedAsks += 1;
|
|
182
|
+
if (v.malformed) {
|
|
183
|
+
r.malformed += 1;
|
|
184
|
+
if (r.samples.length < 5) {
|
|
185
|
+
const hit = OFFER_SHAPES.find((re) => re.test(tail));
|
|
186
|
+
r.samples.push({
|
|
187
|
+
session,
|
|
188
|
+
at: t.at,
|
|
189
|
+
span: (tail.match(hit as RegExp)?.[0] ?? '').slice(0, 110),
|
|
190
|
+
});
|
|
191
|
+
}
|
|
192
|
+
}
|
|
193
|
+
}
|
|
194
|
+
}
|
|
195
|
+
return r;
|
|
196
|
+
}
|
|
197
|
+
|
|
198
|
+
/**
|
|
199
|
+
* Prove the detector fires, and prove it stays silent where it must.
|
|
200
|
+
*
|
|
201
|
+
* An instrument never seen fire has unknown sensitivity — and one that fires on
|
|
202
|
+
* everything measures nothing, which is why the negative cases are here too.
|
|
203
|
+
*/
|
|
204
|
+
export function selfTest(): number {
|
|
205
|
+
const cases: Array<[string, string, boolean, boolean]> = [
|
|
206
|
+
['a trailing German offer with no recommendation', 'Fertig.\n\nSag Bescheid, wenn ich die drei Zeilen mitnehmen soll.', true, true],
|
|
207
|
+
['a bare yes/no offer', 'Der Branch ist grün.\n\nSoll ich das so umsetzen?', true, true],
|
|
208
|
+
['an English trailing offer', 'Done.\n\nLet me know if you want the second half too.', true, true],
|
|
209
|
+
['an offer WITH a recommendation label', 'Soll ich das so umsetzen?\n\n**Empfehlung:** ja, es ist reversibel.', true, false],
|
|
210
|
+
['a numbered block is excluded — another gate reads it', '1. do it\n2. skip it\n\nRecommendation: 1', false, false],
|
|
211
|
+
['an ordinary statement hands nothing', 'Fixed the schema lint; force-pushed b17c4ef8.', false, false],
|
|
212
|
+
['a question about the code is not an offer to act', 'Why does this function take a callback?', false, false],
|
|
213
|
+
];
|
|
214
|
+
let failed = 0;
|
|
215
|
+
for (const [name, tail, wantAsk, wantMalformed] of cases) {
|
|
216
|
+
const v = classifyTail(tail);
|
|
217
|
+
const ok = v.unblockedAsk === wantAsk && v.malformed === wantMalformed;
|
|
218
|
+
process.stdout.write(
|
|
219
|
+
`${ok ? '✅' : '❌'} ${name} — ask=${String(v.unblockedAsk)} malformed=${String(v.malformed)}` +
|
|
220
|
+
(ok ? '\n' : ` (wanted ask=${String(wantAsk)} malformed=${String(wantMalformed)})\n`),
|
|
221
|
+
);
|
|
222
|
+
if (!ok) failed += 1;
|
|
223
|
+
}
|
|
224
|
+
const positives = cases.filter((c) => c[3]).length;
|
|
225
|
+
if (positives < 3) {
|
|
226
|
+
process.stderr.write('❌ probe_unblocked_ask --self-test: fewer than 3 positive cases — a truncated suite must fail.\n');
|
|
227
|
+
return 1;
|
|
228
|
+
}
|
|
229
|
+
process.stdout.write(
|
|
230
|
+
`\nprobe_unblocked_ask --self-test: ${String(cases.length - failed)}/${String(cases.length)} case(s) behaved ` +
|
|
231
|
+
`(${String(positives)} positive, ${String(cases.length - positives)} negative)\n`,
|
|
232
|
+
);
|
|
233
|
+
return failed > 0 ? 1 : 0;
|
|
234
|
+
}
|
|
235
|
+
|
|
236
|
+
export function main(argv: string[]): number {
|
|
237
|
+
let limit = 60;
|
|
238
|
+
let storeArg: string | null = null;
|
|
239
|
+
let json = false;
|
|
240
|
+
for (let i = 0; i < argv.length; i += 1) {
|
|
241
|
+
const a = argv[i] as string;
|
|
242
|
+
if (a === '--limit') limit = Number(argv[++i]);
|
|
243
|
+
else if (a === '--store') storeArg = String(argv[++i]);
|
|
244
|
+
else if (a === '--json') json = true;
|
|
245
|
+
// --self-test runs BEFORE the store is resolved: the fixtures are
|
|
246
|
+
// in-process, and resolving a transcript store that may not exist here
|
|
247
|
+
// would make the sensitivity proof depend on the host having one.
|
|
248
|
+
else if (a === '--self-test') return selfTest();
|
|
249
|
+
else {
|
|
250
|
+
process.stderr.write(`probe_unblocked_ask: unrecognized argument: ${a}\n`);
|
|
251
|
+
return 0;
|
|
252
|
+
}
|
|
253
|
+
}
|
|
254
|
+
const store = storeArg ?? defaultStore(process.cwd());
|
|
255
|
+
const r = measure(store, limit);
|
|
256
|
+
if (json) {
|
|
257
|
+
process.stdout.write(`${JSON.stringify(r, null, 2)}\n`);
|
|
258
|
+
return 0;
|
|
259
|
+
}
|
|
260
|
+
const pct = (n: number, d: number): string => (d === 0 ? 'n/a' : `${((100 * n) / d).toFixed(1)}%`);
|
|
261
|
+
process.stdout.write(
|
|
262
|
+
`probe_unblocked_ask · ${String(limit)} most recent session(s) under ${store}\n` +
|
|
263
|
+
` hand-back turns ${String(r.handbacks)}\n` +
|
|
264
|
+
` …with a numbered block ${String(r.withBlock)} (excluded — check_reply_consistency reads that surface)\n` +
|
|
265
|
+
` …unblocked asks ${String(r.unblockedAsks)} ${pct(r.unblockedAsks, r.handbacks)} of hand-backs\n` +
|
|
266
|
+
` …of those, malformed ${String(r.malformed)} ${pct(r.malformed, r.unblockedAsks)} of unblocked asks\n` +
|
|
267
|
+
'\n CEILING, not a point estimate: "hands a decision" is a heuristic and a\n' +
|
|
268
|
+
' rhetorical question can match it. A recommendation is matched on its LABEL,\n' +
|
|
269
|
+
' which is the rule\'s own bar but a bar on form.\n',
|
|
270
|
+
);
|
|
271
|
+
for (const s of r.samples) {
|
|
272
|
+
process.stdout.write(` · ${s.session} ${s.at}: ${s.span}\n`);
|
|
273
|
+
}
|
|
274
|
+
return 0;
|
|
275
|
+
}
|
|
276
|
+
|
|
277
|
+
if (process.argv[1] && import.meta.url === pathToFileURL(process.argv[1]).href) {
|
|
278
|
+
process.exit(main(process.argv.slice(2)));
|
|
279
|
+
}
|