@event4u/agent-config 14.12.0 → 14.13.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/.augment-plugin/marketplace.json +3 -3
- package/.augment-plugin/plugin.json +1 -1
- package/.claude-plugin/marketplace.json +1 -1
- package/CHANGELOG.md +234 -0
- 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 +109 -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-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 +247 -3
- package/dist/agent-src/scripts/guarded_baseline.ts +328 -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 +400 -0
- package/dist/agent-src/scripts/ticket_bundles.ts +84 -0
- package/dist/agent-src/scripts/update_roadmap_progress.ts +8 -4
- 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 +42 -10
- 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 +57 -57
- 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 +1836 -712
- 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 +4818 -2645
- 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/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 +247 -3
- package/src/agent-src/scripts/guarded_baseline.ts +328 -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 +400 -0
- package/src/agent-src/scripts/ticket_bundles.ts +84 -0
- package/src/agent-src/scripts/update_roadmap_progress.ts +8 -4
- 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 +38 -36
- package/src/config/evaluator-budgets.json +14 -14
- package/src/config/gate-coverage.yml +579 -63
- package/src/config/gate-reachability-exemptions.json +21 -0
- package/src/config/gate-violation-baselines.json +37 -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 +8 -8
- 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 +126 -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 +27 -0
- package/src/scripts/_lib/activation_ladder.ts +211 -0
- package/src/scripts/_lib/activation_states.ts +88 -0
- package/src/scripts/_lib/adherence_detectors.ts +143 -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_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/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/council_settings_block.ts +6 -0
- package/src/scripts/_lib/curator_ops.ts +172 -0
- package/src/scripts/_lib/delivery_arm_experiment.ts +266 -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/eval_publication.ts +354 -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/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 +229 -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/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/prefix_stable_surfaces.ts +123 -0
- package/src/scripts/_lib/privacy_class.ts +47 -0
- package/src/scripts/_lib/regression_neighbourhood.ts +259 -0
- 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/runtime_journal.ts +1499 -0
- package/src/scripts/_lib/runtime_wiring_checks.ts +430 -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/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/disagreement_signal.ts +338 -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/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/response_record.ts +50 -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 +126 -0
- 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 +142 -1
- 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_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/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 +980 -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 +98 -11
- 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/concern_registry.ts +4 -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/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/lint_adr_sweep_routing.ts +12 -0
- package/src/scripts/lint_canonical_terms.ts +604 -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_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/mcp_server/consumer_tool_catalog.json +1 -1
- package/src/scripts/mcp_server/tool_catalog_source.ts +1 -1
- package/src/scripts/measure_docs_dead_links.ts +348 -0
- package/src/scripts/measure_host_capture.ts +461 -0
- package/src/scripts/migration_status.ts +3 -3
- package/src/scripts/model_rule_injection.ts +60 -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_turnaround.ts +384 -0
- package/src/scripts/probe_unblocked_ask.ts +279 -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,143 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Deterministic adherence detection, and a hard refusal to guess.
|
|
3
|
+
*
|
|
4
|
+
* Step 5.1 splits a per-asset outcome into five states. The hardest of its
|
|
5
|
+
* rungs to observe is `followed`: whether the work actually conformed to the
|
|
6
|
+
* rule that was loaded. It is tempting to have a model read the transcript and
|
|
7
|
+
* say. That is precisely what this module refuses to do.
|
|
8
|
+
*
|
|
9
|
+
* The rule, from step 5.2: where a rule has an OBSERVABLE FOOTPRINT, adherence
|
|
10
|
+
* is computed from that footprint. Where it has none, adherence is `unknown` —
|
|
11
|
+
* never inferred, never a model's guess. An inferred adherence figure is worse
|
|
12
|
+
* than a missing one, because a missing one is visible in the `unknown` share
|
|
13
|
+
* and an inferred one is not.
|
|
14
|
+
*
|
|
15
|
+
* A DETECTOR ANSWERS FOR ONE CLAUSE, NOT FOR A RULE. Rules in this tree carry
|
|
16
|
+
* many clauses; `think-before-action` alone spans a read set, a verification
|
|
17
|
+
* matrix, and a test-first preference. A detector that observed one clause and
|
|
18
|
+
* reported "rule followed" would be overclaiming by construction, so every
|
|
19
|
+
* registration below names the clause it decides and the registry key is the
|
|
20
|
+
* pair, not the rule id.
|
|
21
|
+
*/
|
|
22
|
+
|
|
23
|
+
import type { ActivationState } from './activation_states.js';
|
|
24
|
+
|
|
25
|
+
/** The subset of {@link ActivationState} a detector may return. */
|
|
26
|
+
export type Adherence = Extract<
|
|
27
|
+
ActivationState,
|
|
28
|
+
'activated-followed' | 'activated-not-followed' | 'unknown'
|
|
29
|
+
>;
|
|
30
|
+
|
|
31
|
+
/**
|
|
32
|
+
* One observed write, reduced to the two things a detector may see: where it
|
|
33
|
+
* went, and in what order relative to the others.
|
|
34
|
+
*
|
|
35
|
+
* Deliberately carries NO content field — a detector that could read a diff
|
|
36
|
+
* body would be a second place for file contents to travel, and the audit
|
|
37
|
+
* privacy floor exists to stop exactly that.
|
|
38
|
+
*/
|
|
39
|
+
export interface WriteObservation {
|
|
40
|
+
/** Repository-relative path. */
|
|
41
|
+
path: string;
|
|
42
|
+
/** Monotonic ordering key within one task. Absolute time is not required. */
|
|
43
|
+
seq: number;
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
export interface DetectorInput {
|
|
47
|
+
writes: readonly WriteObservation[];
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
export interface ClauseDetector {
|
|
51
|
+
/** The rule whose clause this decides. */
|
|
52
|
+
rule_id: string;
|
|
53
|
+
/** Which clause — a rule has many, and a detector answers for one. */
|
|
54
|
+
clause: string;
|
|
55
|
+
/** Where that clause is written, so a reader can check the detector against it. */
|
|
56
|
+
source: string;
|
|
57
|
+
detect(input: DetectorInput): Adherence;
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
/** A path is a test path when it sits under a `tests/` segment or is named `*.test.*` / `*.spec.*`. */
|
|
61
|
+
export function isTestPath(p: string): boolean {
|
|
62
|
+
const normalized = p.replace(/\\/g, '/');
|
|
63
|
+
if (/(^|\/)tests?(\/|$)/.test(normalized)) return true;
|
|
64
|
+
return /\.(test|spec)\.[cm]?[jt]sx?$/.test(normalized);
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
/**
|
|
68
|
+
* `think-before-action` § "When behavior can be defined → prefer test-first /
|
|
69
|
+
* TDD" (`dist/agent-src/rules/think-before-action.md:43`).
|
|
70
|
+
*
|
|
71
|
+
* The footprint is write ORDER: a test file written before the production file
|
|
72
|
+
* it covers is test-first; the reverse is not.
|
|
73
|
+
*
|
|
74
|
+
* The `unknown` branch is the load-bearing one and is wider than it first
|
|
75
|
+
* looks. If only test writes are observed, no production change happened and
|
|
76
|
+
* the clause did not apply. If only production writes are observed, it is
|
|
77
|
+
* tempting to answer `activated-not-followed` — and that would be a GUESS: the
|
|
78
|
+
* test may have been written in an earlier task, or the change may be one the
|
|
79
|
+
* clause's own "when behavior can be defined" qualifier excludes. Both
|
|
80
|
+
* one-sided cases are therefore `unknown`, which is the honest answer and the
|
|
81
|
+
* conservative one in the direction that matters — it never manufactures a
|
|
82
|
+
* violation.
|
|
83
|
+
*/
|
|
84
|
+
export const testFirstDetector: ClauseDetector = {
|
|
85
|
+
rule_id: 'think-before-action',
|
|
86
|
+
clause: 'prefer test-first / TDD when behavior can be defined',
|
|
87
|
+
source: 'dist/agent-src/rules/think-before-action.md:43',
|
|
88
|
+
detect({ writes }) {
|
|
89
|
+
let firstTest: number | null = null;
|
|
90
|
+
let firstProd: number | null = null;
|
|
91
|
+
for (const w of writes) {
|
|
92
|
+
if (isTestPath(w.path)) {
|
|
93
|
+
if (firstTest === null || w.seq < firstTest) firstTest = w.seq;
|
|
94
|
+
} else if (firstProd === null || w.seq < firstProd) {
|
|
95
|
+
firstProd = w.seq;
|
|
96
|
+
}
|
|
97
|
+
}
|
|
98
|
+
// Either side missing means the order was never observed, not that it
|
|
99
|
+
// was violated. See the note above on why the production-only case is
|
|
100
|
+
// deliberately NOT a violation.
|
|
101
|
+
if (firstTest === null || firstProd === null) return 'unknown';
|
|
102
|
+
return firstTest < firstProd ? 'activated-followed' : 'activated-not-followed';
|
|
103
|
+
},
|
|
104
|
+
};
|
|
105
|
+
|
|
106
|
+
/**
|
|
107
|
+
* The registry. Keyed `${rule_id}#${clause}` so one rule may gain a second
|
|
108
|
+
* detector later without either silently replacing the other.
|
|
109
|
+
*/
|
|
110
|
+
const DETECTORS: readonly ClauseDetector[] = [testFirstDetector];
|
|
111
|
+
|
|
112
|
+
export function detectorsFor(ruleId: string): readonly ClauseDetector[] {
|
|
113
|
+
return DETECTORS.filter((d) => d.rule_id === ruleId);
|
|
114
|
+
}
|
|
115
|
+
|
|
116
|
+
/**
|
|
117
|
+
* Adherence for one rule.
|
|
118
|
+
*
|
|
119
|
+
* A rule with NO registered detector returns `unknown`. That is the whole
|
|
120
|
+
* point: 118 of this tree's 119 rules have no observable footprint today, and
|
|
121
|
+
* the honest report says so rather than filling the column with inference.
|
|
122
|
+
*
|
|
123
|
+
* With several detectors registered for one rule, a single observed violation
|
|
124
|
+
* wins over a `followed` — a rule is not adhered to if any decided clause was
|
|
125
|
+
* broken — and `unknown` only survives when nothing was decided at all.
|
|
126
|
+
*/
|
|
127
|
+
export function detectAdherence(ruleId: string, input: DetectorInput): Adherence {
|
|
128
|
+
const detectors = detectorsFor(ruleId);
|
|
129
|
+
if (detectors.length === 0) return 'unknown';
|
|
130
|
+
|
|
131
|
+
let sawFollowed = false;
|
|
132
|
+
for (const d of detectors) {
|
|
133
|
+
const verdict = d.detect(input);
|
|
134
|
+
if (verdict === 'activated-not-followed') return 'activated-not-followed';
|
|
135
|
+
if (verdict === 'activated-followed') sawFollowed = true;
|
|
136
|
+
}
|
|
137
|
+
return sawFollowed ? 'activated-followed' : 'unknown';
|
|
138
|
+
}
|
|
139
|
+
|
|
140
|
+
/** Rule ids that have at least one detector. Everything else reports `unknown`. */
|
|
141
|
+
export function rulesWithDetectors(): readonly string[] {
|
|
142
|
+
return [...new Set(DETECTORS.map((d) => d.rule_id))];
|
|
143
|
+
}
|
|
@@ -0,0 +1,181 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Check an arithmetic claim written in prose against itself.
|
|
3
|
+
*
|
|
4
|
+
* The defect this exists for, observed rather than imagined: a fixture states
|
|
5
|
+
* the same number twice — once as a declared `expected` and once inside the
|
|
6
|
+
* rationale that derives it — and the two disagree. A judge then prefers the
|
|
7
|
+
* declared value over the recomputed one and scores a correct response as a
|
|
8
|
+
* loss. Both numbers are in the file, so nothing has to be inferred; they
|
|
9
|
+
* simply were never compared.
|
|
10
|
+
*
|
|
11
|
+
* The scanner reads CHAINS (`a x b / c = d / c = e`), because that is how the
|
|
12
|
+
* derivations in this tree are actually written. Comparing only the outermost
|
|
13
|
+
* pair would mis-read every intermediate step as a claim of its own — the
|
|
14
|
+
* first version of this check did exactly that and produced three false
|
|
15
|
+
* findings on a corpus that was correct.
|
|
16
|
+
*
|
|
17
|
+
* Prose rounding is legitimate and is tolerated to the PRECISION THE PROSE
|
|
18
|
+
* ITSELF USES: `1.333` agrees with 4/3, `1.33` does not have to. A fixed
|
|
19
|
+
* epsilon would either reject honest rounding or accept a real disagreement,
|
|
20
|
+
* depending on the magnitude of the numbers, which is the wrong axis.
|
|
21
|
+
*/
|
|
22
|
+
|
|
23
|
+
/** One prose arithmetic chain and what it evaluates to. */
|
|
24
|
+
export interface ArithChain {
|
|
25
|
+
/** The chain as written. */
|
|
26
|
+
text: string;
|
|
27
|
+
/** Every `=`-separated segment that evaluated to a number. */
|
|
28
|
+
values: readonly { segment: string; value: number }[];
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
/** A chain whose segments disagree beyond the precision the prose claims. */
|
|
32
|
+
export interface ArithDisagreement {
|
|
33
|
+
text: string;
|
|
34
|
+
leftSegment: string;
|
|
35
|
+
left: number;
|
|
36
|
+
rightSegment: string;
|
|
37
|
+
right: number;
|
|
38
|
+
/** The tolerance the right-hand segment's own precision earns it. */
|
|
39
|
+
tolerance: number;
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
const CHAIN = /(?:[-+]?[\d,]*\.?\d+[\s\d,.+\-*/x×^()%]*=\s*)+[-+]?[\d,]*\.?\d+/g;
|
|
43
|
+
|
|
44
|
+
/**
|
|
45
|
+
* Evaluate one prose segment, or `null` when it is not pure arithmetic.
|
|
46
|
+
*
|
|
47
|
+
* Deliberately narrow: digits, the four operators, `^`, parentheses and the
|
|
48
|
+
* separators prose uses. Anything else returns `null` rather than being
|
|
49
|
+
* coerced — a segment carrying a word is a sentence, not a claim, and guessing
|
|
50
|
+
* at it is how a checker starts inventing findings.
|
|
51
|
+
*/
|
|
52
|
+
export function evaluateSegment(segment: string): number | null {
|
|
53
|
+
const t = segment
|
|
54
|
+
.trim()
|
|
55
|
+
.replace(/,/g, '')
|
|
56
|
+
.replace(/[×x]/g, '*')
|
|
57
|
+
.replace(/%/g, '')
|
|
58
|
+
.replace(/\$/g, '');
|
|
59
|
+
if (!/\d/.test(t)) return null;
|
|
60
|
+
if (!/^[\d\s.+\-*/^()]+$/.test(t)) return null;
|
|
61
|
+
// `^` is exponentiation in prose and XOR in JS — translate before eval.
|
|
62
|
+
const js = t.replace(/\^/g, '**');
|
|
63
|
+
try {
|
|
64
|
+
// Input is whitelisted above to digits, operators and parentheses — a
|
|
65
|
+
// segment carrying anything else returned null before reaching here.
|
|
66
|
+
const fn = new Function(`"use strict"; return (${js});`) as () => unknown;
|
|
67
|
+
const v = fn();
|
|
68
|
+
return typeof v === 'number' && Number.isFinite(v) ? v : null;
|
|
69
|
+
} catch {
|
|
70
|
+
return null;
|
|
71
|
+
}
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
/** Decimal places written in a literal, used as the rounding tolerance. */
|
|
75
|
+
function precisionTolerance(segment: string, magnitude: number): number {
|
|
76
|
+
const m = /\.(\d+)\s*$/.exec(segment.trim());
|
|
77
|
+
const decimals = m?.[1];
|
|
78
|
+
if (decimals !== undefined) return 0.5 * 10 ** -decimals.length;
|
|
79
|
+
// An integer-looking claim is tolerated to half a unit, plus float noise
|
|
80
|
+
// proportional to the magnitude — `20,000` may legitimately stand for
|
|
81
|
+
// 20000.000000001 produced by a chain of divisions.
|
|
82
|
+
return Math.max(0.5, Math.abs(magnitude) * 1e-9);
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
/** Every arithmetic chain in a piece of prose. */
|
|
86
|
+
export function findArithChains(prose: string): ArithChain[] {
|
|
87
|
+
const out: ArithChain[] = [];
|
|
88
|
+
for (const m of prose.matchAll(CHAIN)) {
|
|
89
|
+
const values: { segment: string; value: number }[] = [];
|
|
90
|
+
for (const segment of m[0].split('=')) {
|
|
91
|
+
const value = evaluateSegment(segment);
|
|
92
|
+
if (value !== null) values.push({ segment, value });
|
|
93
|
+
}
|
|
94
|
+
if (values.length >= 2) out.push({ text: m[0], values });
|
|
95
|
+
}
|
|
96
|
+
return out;
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
/**
|
|
100
|
+
* Every chain whose own segments disagree.
|
|
101
|
+
*
|
|
102
|
+
* Each segment is compared against the FIRST evaluable one rather than against
|
|
103
|
+
* its neighbour: a chain is a claim that all of its forms are the same number,
|
|
104
|
+
* and comparing neighbours lets an error accumulate quietly across three steps.
|
|
105
|
+
*/
|
|
106
|
+
export function findArithDisagreements(prose: string): ArithDisagreement[] {
|
|
107
|
+
const out: ArithDisagreement[] = [];
|
|
108
|
+
for (const chain of findArithChains(prose)) {
|
|
109
|
+
const first = chain.values[0];
|
|
110
|
+
if (first === undefined) continue;
|
|
111
|
+
for (const later of chain.values.slice(1)) {
|
|
112
|
+
const tolerance = precisionTolerance(later.segment, first.value);
|
|
113
|
+
if (Math.abs(later.value - first.value) > tolerance) {
|
|
114
|
+
out.push({
|
|
115
|
+
text: chain.text,
|
|
116
|
+
leftSegment: first.segment.trim(),
|
|
117
|
+
left: first.value,
|
|
118
|
+
rightSegment: later.segment.trim(),
|
|
119
|
+
right: later.value,
|
|
120
|
+
tolerance,
|
|
121
|
+
});
|
|
122
|
+
break;
|
|
123
|
+
}
|
|
124
|
+
}
|
|
125
|
+
}
|
|
126
|
+
return out;
|
|
127
|
+
}
|
|
128
|
+
|
|
129
|
+
/**
|
|
130
|
+
* Does a prose derivation ever state the declared value?
|
|
131
|
+
*
|
|
132
|
+
* Returns `null` when the prose carries no evaluable chain at all — an absent
|
|
133
|
+
* derivation is not a disagreement, and treating it as one would make the
|
|
134
|
+
* check fire on every fixture that simply cites a source instead.
|
|
135
|
+
*/
|
|
136
|
+
export function chainSupportsExpected(
|
|
137
|
+
prose: string,
|
|
138
|
+
expected: number,
|
|
139
|
+
tolerance: number,
|
|
140
|
+
): boolean | null {
|
|
141
|
+
const chains = findArithChains(prose);
|
|
142
|
+
if (chains.length === 0) return null;
|
|
143
|
+
for (const chain of chains) {
|
|
144
|
+
for (const v of chain.values) {
|
|
145
|
+
const tol = Math.max(tolerance, precisionTolerance(v.segment, expected));
|
|
146
|
+
if (Math.abs(v.value - expected) <= tol) return true;
|
|
147
|
+
if (sameMantissa(v.value, expected, tol)) return true;
|
|
148
|
+
}
|
|
149
|
+
}
|
|
150
|
+
return false;
|
|
151
|
+
}
|
|
152
|
+
|
|
153
|
+
/**
|
|
154
|
+
* Do two numbers agree once a UNIT SCALE is allowed?
|
|
155
|
+
*
|
|
156
|
+
* A measured false positive is what this exists for, not a hypothetical: a
|
|
157
|
+
* fixture derives `4,200,000,000 x 0.006 = 25,200,000 = $25.2M` and declares
|
|
158
|
+
* `expected: 25.2`, because the scenario asks for the figure "in $M". The
|
|
159
|
+
* derivation works in dollars and the expectation in millions. Nothing in the
|
|
160
|
+
* file states the unit in a machine-readable way, so demanding exact agreement
|
|
161
|
+
* reports a correct fixture as broken.
|
|
162
|
+
*
|
|
163
|
+
* **The weakening this accepts, stated rather than buried.** A real defect that
|
|
164
|
+
* happens to be a pure power of ten away — `0.85` written where `8.5` is meant —
|
|
165
|
+
* is no longer caught by THIS check. It is a narrow hole: a transcription or
|
|
166
|
+
* arithmetic error almost always changes the digit sequence, and the
|
|
167
|
+
* self-consistency check above still reads the derivation on its own terms,
|
|
168
|
+
* where no scale tolerance applies at all. The alternative was worse: a check
|
|
169
|
+
* that fires on every unit-scaled fixture is a check that gets suppressed.
|
|
170
|
+
*
|
|
171
|
+
* Bounded to 10^±12 so an unrelated number cannot be dragged into agreement by
|
|
172
|
+
* an arbitrarily large exponent.
|
|
173
|
+
*/
|
|
174
|
+
function sameMantissa(a: number, b: number, tolerance: number): boolean {
|
|
175
|
+
if (a === 0 || b === 0) return false;
|
|
176
|
+
const ratio = Math.abs(a / b);
|
|
177
|
+
const exp = Math.round(Math.log10(ratio));
|
|
178
|
+
if (!Number.isFinite(exp) || Math.abs(exp) > 12 || exp === 0) return false;
|
|
179
|
+
const scaled = a / 10 ** exp;
|
|
180
|
+
return Math.abs(scaled - b) <= Math.max(tolerance, Math.abs(b) * 1e-9);
|
|
181
|
+
}
|
|
@@ -0,0 +1,62 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Arm ranking metrics (`road-to-runtime-context-floors` step 4.2).
|
|
3
|
+
*
|
|
4
|
+
* `tokens` stays the default and `cost-per-solved` becomes AVAILABLE — not a
|
|
5
|
+
* replacement. The reason it has to exist at all is the same one behind the
|
|
6
|
+
* mandatory cache fields: an arm can reduce token volume and cost more, because
|
|
7
|
+
* rewriting a prefix pays the cache-write rate rather than the cache-read rate.
|
|
8
|
+
* A comparison that can only rank on tokens cannot see that.
|
|
9
|
+
*
|
|
10
|
+
* The reason it must NOT become the default is the sibling risk: a single
|
|
11
|
+
* mandatory score invites tuning the score. Keeping both available, and keeping
|
|
12
|
+
* the cache ratio diagnostic rather than scored, is what stops this from being
|
|
13
|
+
* the Goodhart shape that `road-to-cache-economy`'s falsified C-5 already
|
|
14
|
+
* demonstrated for token count.
|
|
15
|
+
*/
|
|
16
|
+
|
|
17
|
+
export const RANKING_METRICS = ['tokens', 'cost-per-solved'] as const;
|
|
18
|
+
export type RankingMetric = (typeof RANKING_METRICS)[number];
|
|
19
|
+
export const DEFAULT_RANKING_METRIC: RankingMetric = 'tokens';
|
|
20
|
+
|
|
21
|
+
export interface ArmResult {
|
|
22
|
+
arm: string;
|
|
23
|
+
/** Total tokens consumed by the arm across the run. */
|
|
24
|
+
tokens: number;
|
|
25
|
+
/** Total cost in USD. */
|
|
26
|
+
cost_usd: number;
|
|
27
|
+
/** Tasks the arm actually solved — the denominator of cost-per-solved. */
|
|
28
|
+
solved: number;
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
export interface RankedArm extends ArmResult {
|
|
32
|
+
/** The value ranked on. `null` when the metric is undefined for this arm. */
|
|
33
|
+
score: number | null;
|
|
34
|
+
/** Present only when `score` is null. */
|
|
35
|
+
reason?: string;
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
export function isRankingMetric(v: unknown): v is RankingMetric {
|
|
39
|
+
return typeof v === 'string' && (RANKING_METRICS as readonly string[]).includes(v);
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
function score(arm: ArmResult, metric: RankingMetric): { score: number | null; reason?: string } {
|
|
43
|
+
if (metric === 'tokens') return { score: arm.tokens };
|
|
44
|
+
if (arm.solved === 0) {
|
|
45
|
+
// Not Infinity and not 0: an arm that solved nothing has no
|
|
46
|
+
// cost-per-solved, and ranking it as "infinitely expensive" would be a
|
|
47
|
+
// number nobody measured.
|
|
48
|
+
return { score: null, reason: 'the arm solved nothing, so cost-per-solved is undefined' };
|
|
49
|
+
}
|
|
50
|
+
return { score: arm.cost_usd / arm.solved };
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
/**
|
|
54
|
+
* Rank ascending — lower is better under both metrics. Arms with an undefined
|
|
55
|
+
* score sort last, in input order, and carry their reason.
|
|
56
|
+
*/
|
|
57
|
+
export function rankArms(arms: readonly ArmResult[], metric: RankingMetric = DEFAULT_RANKING_METRIC): RankedArm[] {
|
|
58
|
+
const scored: RankedArm[] = arms.map((a) => ({ ...a, ...score(a, metric) }));
|
|
59
|
+
const defined = scored.filter((a) => a.score !== null).sort((a, b) => (a.score as number) - (b.score as number) || a.arm.localeCompare(b.arm));
|
|
60
|
+
const undef = scored.filter((a) => a.score === null);
|
|
61
|
+
return [...defined, ...undef];
|
|
62
|
+
}
|
|
@@ -0,0 +1,303 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Per-asset delivery ledger — which assets produce the standing payload
|
|
3
|
+
* (`road-to-delivered-cost-truth` step 2.1).
|
|
4
|
+
*
|
|
5
|
+
* `check_preamble_payload_budget` measures three BUCKET totals and refuses
|
|
6
|
+
* growth. It cannot say which asset inside a bucket is producing the cost, so a
|
|
7
|
+
* reduction effort has a number and no target. This module ranks the assets.
|
|
8
|
+
*
|
|
9
|
+
* WHAT "DELIVERY" MEANS PER BUCKET, because the two are not the same shape:
|
|
10
|
+
*
|
|
11
|
+
* - a RULE in the standing set delivers its whole body on every spawn;
|
|
12
|
+
* - a SKILL delivers only its catalogue line — `- <name>: <description>` — on
|
|
13
|
+
* every spawn. Its body is loaded on activation, which is not standing cost
|
|
14
|
+
* and is deliberately not counted here.
|
|
15
|
+
*
|
|
16
|
+
* Ranking the two together without that distinction would put every skill body
|
|
17
|
+
* above every rule and send a reduction effort at the wrong half of the tree.
|
|
18
|
+
*
|
|
19
|
+
* MEASUREMENT HONESTY. Each row states whether it was measured with the exact
|
|
20
|
+
* BPE tokenizer or the `chars/4` proxy, and a proxy reading within its own error
|
|
21
|
+
* margin of a threshold is reported `unresolved` rather than classified — the
|
|
22
|
+
* discipline `lint_token_budget_discipline` already applies to the rich band,
|
|
23
|
+
* reused here rather than reinvented.
|
|
24
|
+
*/
|
|
25
|
+
import * as fs from 'node:fs';
|
|
26
|
+
import * as path from 'node:path';
|
|
27
|
+
|
|
28
|
+
import { parseFrontmatter } from '../preamble_byte_census.js';
|
|
29
|
+
import { gpt_tokens, gpt_tokens_proxy } from './token_count.js';
|
|
30
|
+
|
|
31
|
+
/** Worst-case per-file error of the character proxy, as measured for the rich band. */
|
|
32
|
+
export const PROXY_ERROR_MARGIN = 0.06;
|
|
33
|
+
|
|
34
|
+
export type AssetKind = 'rule' | 'skill-catalogue-line';
|
|
35
|
+
|
|
36
|
+
export interface AssetRow {
|
|
37
|
+
/** Repo-relative path of the asset. */
|
|
38
|
+
file: string;
|
|
39
|
+
/** Asset name as the preamble names it. */
|
|
40
|
+
name: string;
|
|
41
|
+
kind: AssetKind;
|
|
42
|
+
/** Tokens this asset contributes to EVERY spawn. */
|
|
43
|
+
tokens: number;
|
|
44
|
+
/**
|
|
45
|
+
* The same asset under the `chars/4` proxy.
|
|
46
|
+
*
|
|
47
|
+
* Carried alongside the BPE figure, not instead of it, because the payload
|
|
48
|
+
* gate counts chars/4 and reconciling a BPE total against a chars/4 total
|
|
49
|
+
* measures the METHODS, not the ledger. See {@link reconcile}.
|
|
50
|
+
*/
|
|
51
|
+
proxy_tokens: number;
|
|
52
|
+
/** `exact` when the BPE tokenizer resolved, `proxy` otherwise. */
|
|
53
|
+
method: 'exact' | 'proxy';
|
|
54
|
+
/** Share of the ledger total. */
|
|
55
|
+
share: number;
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
export interface LedgerVerdict {
|
|
59
|
+
rows: AssetRow[];
|
|
60
|
+
total_tokens: number;
|
|
61
|
+
/** True only when EVERY row came from the exact tokenizer. */
|
|
62
|
+
exact_throughout: boolean;
|
|
63
|
+
by_kind: Record<AssetKind, { count: number; tokens: number; proxy_tokens: number }>;
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
/**
|
|
67
|
+
* Is a proxy reading too close to `threshold` to be classified?
|
|
68
|
+
*
|
|
69
|
+
* Symmetric, deliberately: the measured proxy error runs in BOTH directions
|
|
70
|
+
* (−5.3 % on the largest rich artifact), so a band that straddles the threshold
|
|
71
|
+
* yields no verdict rather than a confident wrong one.
|
|
72
|
+
*/
|
|
73
|
+
export function unresolvedAgainst(tokens: number, method: 'exact' | 'proxy', threshold: number): boolean {
|
|
74
|
+
if (method === 'exact') return false;
|
|
75
|
+
const margin = Math.ceil(tokens * PROXY_ERROR_MARGIN);
|
|
76
|
+
return tokens - margin <= threshold && tokens + margin > threshold;
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
/** Walk the standing rule directory. Each rule delivers its whole body. */
|
|
80
|
+
export function measureRules(rulesDir: string, repoRoot: string): AssetRow[] {
|
|
81
|
+
if (!fs.existsSync(rulesDir)) return [];
|
|
82
|
+
const rows: AssetRow[] = [];
|
|
83
|
+
for (const entry of fs.readdirSync(rulesDir, { withFileTypes: true })) {
|
|
84
|
+
if (!entry.isFile() || !entry.name.endsWith('.md')) continue;
|
|
85
|
+
const abs = path.join(rulesDir, entry.name);
|
|
86
|
+
const text = fs.readFileSync(abs, 'utf-8');
|
|
87
|
+
const count = gpt_tokens(text);
|
|
88
|
+
rows.push({
|
|
89
|
+
file: path.relative(repoRoot, abs),
|
|
90
|
+
name: entry.name.replace(/\.md$/, ''),
|
|
91
|
+
kind: 'rule',
|
|
92
|
+
tokens: count.tokens,
|
|
93
|
+
proxy_tokens: gpt_tokens_proxy(text).tokens,
|
|
94
|
+
method: count.exact ? 'exact' : 'proxy',
|
|
95
|
+
share: 0,
|
|
96
|
+
});
|
|
97
|
+
}
|
|
98
|
+
return rows;
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
/**
|
|
102
|
+
* Walk the skill catalogue. Each skill delivers ONE line, not its body.
|
|
103
|
+
*
|
|
104
|
+
* The line is built the same way `censusSkillsCatalog` builds it — `- <name>:
|
|
105
|
+
* <description>\n` — because a ledger that measured the body would rank a skill
|
|
106
|
+
* that is never activated above a rule that is always delivered.
|
|
107
|
+
*/
|
|
108
|
+
export function measureSkillCatalogue(skillsDir: string, repoRoot: string): AssetRow[] {
|
|
109
|
+
if (!fs.existsSync(skillsDir)) return [];
|
|
110
|
+
const rows: AssetRow[] = [];
|
|
111
|
+
for (const entry of fs.readdirSync(skillsDir, { withFileTypes: true })) {
|
|
112
|
+
if (!entry.isDirectory()) continue;
|
|
113
|
+
const skillMd = path.join(skillsDir, entry.name, 'SKILL.md');
|
|
114
|
+
let text: string;
|
|
115
|
+
try {
|
|
116
|
+
text = fs.readFileSync(skillMd, 'utf-8');
|
|
117
|
+
} catch {
|
|
118
|
+
continue;
|
|
119
|
+
}
|
|
120
|
+
// The SAME parser the payload census uses, not a second reader of the
|
|
121
|
+
// same bytes. A regex version reconciled 17.1 % low because a folded
|
|
122
|
+
// multi-line `description:` truncates at the first newline.
|
|
123
|
+
const fm = parseFrontmatter(text);
|
|
124
|
+
const name = typeof fm.name === 'string' && fm.name.length > 0 ? fm.name : entry.name;
|
|
125
|
+
const description = typeof fm.description === 'string' ? fm.description : '';
|
|
126
|
+
const line = `- ${name}: ${description}\n`;
|
|
127
|
+
const count = gpt_tokens(line);
|
|
128
|
+
rows.push({
|
|
129
|
+
file: path.relative(repoRoot, skillMd),
|
|
130
|
+
name,
|
|
131
|
+
kind: 'skill-catalogue-line',
|
|
132
|
+
tokens: count.tokens,
|
|
133
|
+
proxy_tokens: gpt_tokens_proxy(line).tokens,
|
|
134
|
+
method: count.exact ? 'exact' : 'proxy',
|
|
135
|
+
share: 0,
|
|
136
|
+
});
|
|
137
|
+
}
|
|
138
|
+
return rows;
|
|
139
|
+
}
|
|
140
|
+
|
|
141
|
+
export function buildLedger(rulesDir: string, skillsDir: string, repoRoot: string): LedgerVerdict {
|
|
142
|
+
const rows = [...measureRules(rulesDir, repoRoot), ...measureSkillCatalogue(skillsDir, repoRoot)];
|
|
143
|
+
const total = rows.reduce((n, r) => n + r.tokens, 0);
|
|
144
|
+
for (const r of rows) r.share = total > 0 ? r.tokens / total : 0;
|
|
145
|
+
rows.sort((a, b) => b.tokens - a.tokens || a.file.localeCompare(b.file));
|
|
146
|
+
|
|
147
|
+
const byKind: Record<AssetKind, { count: number; tokens: number; proxy_tokens: number }> = {
|
|
148
|
+
rule: { count: 0, tokens: 0, proxy_tokens: 0 },
|
|
149
|
+
'skill-catalogue-line': { count: 0, tokens: 0, proxy_tokens: 0 },
|
|
150
|
+
};
|
|
151
|
+
for (const r of rows) {
|
|
152
|
+
byKind[r.kind].count += 1;
|
|
153
|
+
byKind[r.kind].tokens += r.tokens;
|
|
154
|
+
byKind[r.kind].proxy_tokens += r.proxy_tokens;
|
|
155
|
+
}
|
|
156
|
+
return { rows, total_tokens: total, exact_throughout: rows.every((r) => r.method === 'exact'), by_kind: byKind };
|
|
157
|
+
}
|
|
158
|
+
|
|
159
|
+
export interface Reconciliation {
|
|
160
|
+
kind: AssetKind;
|
|
161
|
+
bucket: string;
|
|
162
|
+
/** The ledger's chars/4 total — the SAME method the bucket uses. */
|
|
163
|
+
ledger_proxy_tokens: number;
|
|
164
|
+
/** The ledger's BPE total, reported for contrast, never reconciled against. */
|
|
165
|
+
ledger_exact_tokens: number;
|
|
166
|
+
bucket_tokens: number;
|
|
167
|
+
/** Absolute relative difference, like-for-like. */
|
|
168
|
+
drift: number;
|
|
169
|
+
within_margin: boolean;
|
|
170
|
+
}
|
|
171
|
+
|
|
172
|
+
/**
|
|
173
|
+
* Reconcile the ledger against the payload gate's own bucket totals.
|
|
174
|
+
*
|
|
175
|
+
* LIKE-FOR-LIKE, and this took a correction. The first cut compared the
|
|
176
|
+
* ledger's BPE total against the gate's `chars/4` bucket and set a 15 % margin
|
|
177
|
+
* to absorb the difference. That margin was GUESSED, which is the exact defect
|
|
178
|
+
* this roadmap's own budget file is careful about — and it did not even work:
|
|
179
|
+
* rules reconciled at 1.6 % while skill catalogue lines drifted 28.1 %, because
|
|
180
|
+
* `chars/4` overestimates short structured lines far more than it does prose.
|
|
181
|
+
* The 15 % would have hidden the rule drift and failed the skill drift, and
|
|
182
|
+
* neither verdict would have been about the ledger.
|
|
183
|
+
*
|
|
184
|
+
* So the ledger carries BOTH figures and reconciles the proxy against the
|
|
185
|
+
* proxy. What remains is real disagreement — a different corpus, a different
|
|
186
|
+
* line format, a missed file — which is what a reconciliation is for. The
|
|
187
|
+
* margin can then be tight, because two implementations of one arithmetic
|
|
188
|
+
* should agree closely or one of them is wrong.
|
|
189
|
+
*/
|
|
190
|
+
export const RECONCILE_MARGIN = 0.02;
|
|
191
|
+
|
|
192
|
+
export function reconcile(
|
|
193
|
+
ledger: LedgerVerdict,
|
|
194
|
+
buckets: ReadonlyArray<{ name: string; tokens: number }>,
|
|
195
|
+
): Reconciliation[] {
|
|
196
|
+
const map: Array<{ kind: AssetKind; bucket: string }> = [
|
|
197
|
+
{ kind: 'rule', bucket: 'project-scope rules' },
|
|
198
|
+
{ kind: 'skill-catalogue-line', bucket: 'preloaded skills catalog' },
|
|
199
|
+
];
|
|
200
|
+
const out: Reconciliation[] = [];
|
|
201
|
+
for (const m of map) {
|
|
202
|
+
const bucket = buckets.find((b) => b.name === m.bucket);
|
|
203
|
+
if (bucket === undefined) continue;
|
|
204
|
+
const proxyTokens = ledger.by_kind[m.kind].proxy_tokens;
|
|
205
|
+
const drift =
|
|
206
|
+
bucket.tokens === 0 ? (proxyTokens === 0 ? 0 : 1) : Math.abs(proxyTokens - bucket.tokens) / bucket.tokens;
|
|
207
|
+
out.push({
|
|
208
|
+
kind: m.kind,
|
|
209
|
+
bucket: m.bucket,
|
|
210
|
+
ledger_proxy_tokens: proxyTokens,
|
|
211
|
+
ledger_exact_tokens: ledger.by_kind[m.kind].tokens,
|
|
212
|
+
bucket_tokens: bucket.tokens,
|
|
213
|
+
drift,
|
|
214
|
+
within_margin: drift <= RECONCILE_MARGIN,
|
|
215
|
+
});
|
|
216
|
+
}
|
|
217
|
+
return out;
|
|
218
|
+
}
|
|
219
|
+
|
|
220
|
+
// ─────────────────────────────────────────── growth attribution (step 2.2)
|
|
221
|
+
|
|
222
|
+
export interface GrowthEntry {
|
|
223
|
+
name: string;
|
|
224
|
+
kind: AssetKind;
|
|
225
|
+
/** Positive = this asset grew or arrived. */
|
|
226
|
+
delta: number;
|
|
227
|
+
status: 'added' | 'grew' | 'shrank' | 'removed';
|
|
228
|
+
}
|
|
229
|
+
|
|
230
|
+
export interface GrowthAttribution {
|
|
231
|
+
/** Assets that grew or arrived, largest first. */
|
|
232
|
+
increases: GrowthEntry[];
|
|
233
|
+
/** Assets that shrank or left, largest saving first. */
|
|
234
|
+
decreases: GrowthEntry[];
|
|
235
|
+
net_delta: number;
|
|
236
|
+
}
|
|
237
|
+
|
|
238
|
+
/**
|
|
239
|
+
* Attribute a payload change to the assets that caused it (step 2.2).
|
|
240
|
+
*
|
|
241
|
+
* A ratchet that says "the payload grew past the ceiling" states a fact and
|
|
242
|
+
* leaves the reader to find the cause; the same refusal naming the asset and its
|
|
243
|
+
* token delta reads as a quantified saving instead of an obstacle. That is the
|
|
244
|
+
* whole difference this function buys, and it is a presentation difference with
|
|
245
|
+
* a real consequence: a refusal nobody can act on gets suppressed rather than
|
|
246
|
+
* fixed.
|
|
247
|
+
*
|
|
248
|
+
* Pure and order-independent — both sides are keyed on `kind:name`, so a
|
|
249
|
+
* reordered ledger produces the same attribution.
|
|
250
|
+
*/
|
|
251
|
+
export function attributeGrowth(before: readonly AssetRow[], after: readonly AssetRow[]): GrowthAttribution {
|
|
252
|
+
const key = (r: AssetRow): string => `${r.kind}:${r.name}`;
|
|
253
|
+
const b = new Map(before.map((r) => [key(r), r.tokens]));
|
|
254
|
+
const a = new Map(after.map((r) => [key(r), r.tokens]));
|
|
255
|
+
|
|
256
|
+
const entries: GrowthEntry[] = [];
|
|
257
|
+
for (const [k, tokens] of a) {
|
|
258
|
+
const prev = b.get(k);
|
|
259
|
+
const [kind, ...rest] = k.split(':');
|
|
260
|
+
const name = rest.join(':');
|
|
261
|
+
if (prev === undefined) {
|
|
262
|
+
entries.push({ name, kind: kind as AssetKind, delta: tokens, status: 'added' });
|
|
263
|
+
} else if (tokens !== prev) {
|
|
264
|
+
entries.push({ name, kind: kind as AssetKind, delta: tokens - prev, status: tokens > prev ? 'grew' : 'shrank' });
|
|
265
|
+
}
|
|
266
|
+
}
|
|
267
|
+
for (const [k, tokens] of b) {
|
|
268
|
+
if (a.has(k)) continue;
|
|
269
|
+
const [kind, ...rest] = k.split(':');
|
|
270
|
+
entries.push({ name: rest.join(':'), kind: kind as AssetKind, delta: -tokens, status: 'removed' });
|
|
271
|
+
}
|
|
272
|
+
|
|
273
|
+
const increases = entries.filter((e) => e.delta > 0).sort((x, y) => y.delta - x.delta || x.name.localeCompare(y.name));
|
|
274
|
+
const decreases = entries.filter((e) => e.delta < 0).sort((x, y) => x.delta - y.delta || x.name.localeCompare(y.name));
|
|
275
|
+
return { increases, decreases, net_delta: entries.reduce((n, e) => n + e.delta, 0) };
|
|
276
|
+
}
|
|
277
|
+
|
|
278
|
+
/**
|
|
279
|
+
* Render an attribution as the lines a refusing gate appends to its message.
|
|
280
|
+
*
|
|
281
|
+
* Capped, because a refusal listing 400 assets is as unactionable as one listing
|
|
282
|
+
* none — and the cap is stated rather than silent, so a reader knows the list is
|
|
283
|
+
* a head and not the whole story.
|
|
284
|
+
*/
|
|
285
|
+
export function renderAttribution(g: GrowthAttribution, limit = 5): string[] {
|
|
286
|
+
if (g.increases.length === 0 && g.decreases.length === 0) return [];
|
|
287
|
+
const out: string[] = [];
|
|
288
|
+
const sign = (n: number): string => (n >= 0 ? `+${String(n)}` : String(n));
|
|
289
|
+
out.push(` net delivered-token change: ${sign(g.net_delta)} tok`);
|
|
290
|
+
if (g.increases.length > 0) {
|
|
291
|
+
out.push(` grew or arrived (top ${String(Math.min(limit, g.increases.length))} of ${String(g.increases.length)}):`);
|
|
292
|
+
for (const e of g.increases.slice(0, limit)) {
|
|
293
|
+
out.push(` ${sign(e.delta).padStart(7)} tok ${e.name} (${e.kind}, ${e.status})`);
|
|
294
|
+
}
|
|
295
|
+
}
|
|
296
|
+
if (g.decreases.length > 0) {
|
|
297
|
+
out.push(` shrank or left (top ${String(Math.min(limit, g.decreases.length))} of ${String(g.decreases.length)}):`);
|
|
298
|
+
for (const e of g.decreases.slice(0, limit)) {
|
|
299
|
+
out.push(` ${sign(e.delta).padStart(7)} tok ${e.name} (${e.kind}, ${e.status})`);
|
|
300
|
+
}
|
|
301
|
+
}
|
|
302
|
+
return out;
|
|
303
|
+
}
|