@event4u/agent-config 7.5.0 → 8.1.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/.claude-plugin/marketplace.json +8 -1
- package/AGENTS.md +9 -9
- package/CHANGELOG.md +317 -260
- package/README.md +35 -5
- package/dist/agent-src/commands/analytics/prune.md +1 -1
- package/dist/agent-src/commands/analytics/show.md +1 -1
- package/dist/agent-src/commands/analyze/decision.md +2 -2
- package/dist/agent-src/commands/analyze/incident.md +2 -2
- package/dist/agent-src/commands/analyze/near-miss.md +2 -2
- package/dist/agent-src/commands/analyze/postmortem.md +2 -2
- package/dist/agent-src/commands/analyze/premortem.md +2 -2
- package/dist/agent-src/commands/cost-report.md +25 -0
- package/dist/agent-src/commands/evals/analyze.json +30 -0
- package/dist/agent-src/commands/evals/mission-upgrade.json +30 -0
- package/dist/agent-src/commands/implement-ticket.md +1 -1
- package/dist/agent-src/commands/knowledge/forget.md +1 -1
- package/dist/agent-src/commands/knowledge/ingest.md +1 -1
- package/dist/agent-src/commands/knowledge/list.md +1 -1
- package/dist/agent-src/commands/memory/load.md +12 -11
- package/dist/agent-src/commands/memory/promote.md +30 -0
- package/dist/agent-src/commands/memory/propose.md +16 -2
- package/dist/agent-src/commands/mission/upgrade.md +1 -1
- package/dist/agent-src/commands/module/explore.md +11 -7
- package/dist/agent-src/commands/optimize-project.md +243 -0
- package/dist/agent-src/commands/optimize.md +5 -0
- package/dist/agent-src/commands/pr/create.md +37 -8
- package/dist/agent-src/commands/prediction-pool.md +2 -2
- package/dist/agent-src/commands/profile/activate.md +3 -3
- package/dist/agent-src/commands/profile/deactivate.md +2 -2
- package/dist/agent-src/commands/profile/show.md +3 -3
- package/dist/agent-src/commands/research/deep.md +5 -0
- package/dist/agent-src/commands/research/report.md +2 -1
- package/dist/agent-src/commands/roadmap/create.md +88 -10
- package/dist/agent-src/commands/roadmap/materialize.md +1 -1
- package/dist/agent-src/commands/roadmap/process-full.md +15 -2
- package/dist/agent-src/commands/roadmap/process-phase.md +18 -6
- package/dist/agent-src/commands/roadmap/process-step.md +4 -0
- package/dist/agent-src/commands/roadmap.md +1 -1
- package/dist/agent-src/commands/security-audit-config.md +1 -1
- package/dist/agent-src/commands/sync-agent-settings.md +1 -1
- package/dist/agent-src/commands/sync-gitignore/fix.md +42 -6
- package/dist/agent-src/commands/team-knowledge/bootstrap.md +106 -0
- package/dist/agent-src/commands/team-knowledge/consolidate.md +105 -0
- package/dist/agent-src/commands/team-knowledge.md +64 -0
- package/dist/agent-src/contexts/authority/commit-mechanics.md +8 -0
- package/dist/agent-src/contexts/authority/destructive-mechanics.md +11 -0
- package/dist/agent-src/contexts/authority/scope-mechanics.md +25 -0
- package/dist/agent-src/contexts/communication/rules-auto/reply-close-mechanics.md +43 -0
- package/dist/agent-src/contexts/contracts/agents-md-anatomy.md +1 -1
- package/dist/agent-src/contexts/contracts/artifact-engagement-flow.md +2 -2
- package/dist/agent-src/contexts/contracts/frugality-charter.md +1 -0
- package/dist/agent-src/contexts/execution/auto-dispatch-classification.md +14 -4
- package/dist/agent-src/contexts/execution/autonomy-mechanics.md +11 -0
- package/dist/agent-src/contexts/execution/orchestration-telemetry.md +32 -8
- package/dist/agent-src/contexts/execution/roadmap-execution-contract.md +107 -0
- package/dist/agent-src/contexts/execution/roadmap-process-loop.md +71 -15
- package/dist/agent-src/contexts/execution/subagent-steering.md +41 -1
- package/dist/agent-src/contexts/override-system.md +1 -1
- package/dist/agent-src/personas/brand-strategist.md +0 -1
- package/dist/agent-src/personas/data-integrity.md +66 -0
- package/dist/agent-src/personas/design-director.md +0 -1
- package/dist/agent-src/personas/performance-engineer.md +66 -0
- package/dist/agent-src/personas/production-validator.md +73 -0
- package/dist/agent-src/rules/agent-authority.md +3 -4
- package/dist/agent-src/rules/analysis-skill-routing.md +3 -5
- package/dist/agent-src/rules/architecture.md +2 -4
- package/dist/agent-src/rules/artifact-drafting-protocol.md +35 -6
- package/dist/agent-src/rules/artifact-engagement-recording.md +2 -4
- package/dist/agent-src/rules/ask-when-uncertain.md +10 -14
- package/dist/agent-src/rules/augment-edit-discipline.md +3 -5
- package/dist/agent-src/rules/autonomous-execution.md +3 -5
- package/dist/agent-src/rules/brand-consistency.md +3 -5
- package/dist/agent-src/rules/brand-source-of-truth.md +3 -5
- package/dist/agent-src/rules/cli-output-handling.md +4 -6
- package/dist/agent-src/rules/command-suggestion-policy.md +3 -5
- package/dist/agent-src/rules/commit-conventions.md +3 -5
- package/dist/agent-src/rules/commit-policy.md +18 -10
- package/dist/agent-src/rules/content-quoting-floor.md +88 -0
- package/dist/agent-src/rules/context-hygiene.md +3 -5
- package/dist/agent-src/rules/copilot-routing.md +4 -6
- package/dist/agent-src/rules/decision-revisit-gate.md +120 -0
- package/dist/agent-src/rules/delegation-policy.md +10 -6
- package/dist/agent-src/rules/design-fidelity.md +3 -5
- package/dist/agent-src/rules/devcontainer-routing.md +4 -6
- package/dist/agent-src/rules/direct-answers.md +13 -17
- package/dist/agent-src/rules/docker-commands.md +3 -5
- package/dist/agent-src/rules/domain-adoption-policy.md +3 -5
- package/dist/agent-src/rules/domain-safety-disclaimer.md +3 -5
- package/dist/agent-src/rules/domain-safety-pii.md +3 -5
- package/dist/agent-src/rules/domain-safety-retention.md +3 -5
- package/dist/agent-src/rules/downstream-changes.md +3 -4
- package/dist/agent-src/rules/engineering-safety-floor.md +3 -5
- package/dist/agent-src/rules/external-reference-deep-dive.md +3 -5
- package/dist/agent-src/rules/fast-path-marker-visibility.md +3 -5
- package/dist/agent-src/rules/finance-safety-floor.md +3 -6
- package/dist/agent-src/rules/framework-neutrality-in-generic-skills.md +3 -5
- package/dist/agent-src/rules/git-history-discipline.md +3 -5
- package/dist/agent-src/rules/guidelines.md +2 -4
- package/dist/agent-src/rules/icon-consistency.md +3 -5
- package/dist/agent-src/rules/image-likeness-and-rights.md +3 -5
- package/dist/agent-src/rules/improve-before-implement.md +5 -5
- package/dist/agent-src/rules/invite-challenge.md +3 -5
- package/dist/agent-src/rules/language-and-tone.md +13 -15
- package/dist/agent-src/rules/laravel-routing.md +3 -5
- package/dist/agent-src/rules/laravel-translations.md +3 -5
- package/dist/agent-src/rules/legal-safety-floor.md +3 -5
- package/dist/agent-src/rules/lethal-trifecta-guard.md +3 -5
- package/dist/agent-src/rules/linked-projects-onboarding-gate.md +4 -7
- package/dist/agent-src/rules/low-impact-corpus-privacy-floor.md +3 -5
- package/dist/agent-src/rules/markdown-safe-codeblocks.md +2 -4
- package/dist/agent-src/rules/media-governance-routing.md +3 -5
- package/dist/agent-src/rules/media-sync-ground-truth.md +3 -5
- package/dist/agent-src/rules/minimal-safe-diff.md +5 -5
- package/dist/agent-src/rules/missing-tool-handling.md +3 -5
- package/dist/agent-src/rules/model-recommendation.md +4 -6
- package/dist/agent-src/rules/no-attribution-footers.md +3 -5
- package/dist/agent-src/rules/no-cheap-questions.md +5 -8
- package/dist/agent-src/rules/no-decorative-emojis-in-git-surfaces.md +3 -6
- package/dist/agent-src/rules/no-pr-progress-comments.md +3 -6
- package/dist/agent-src/rules/no-roadmap-references.md +3 -5
- package/dist/agent-src/rules/non-destructive-by-default.md +11 -13
- package/dist/agent-src/rules/notes-first-reasoning.md +3 -5
- package/dist/agent-src/rules/onboarding-gate.md +4 -6
- package/dist/agent-src/rules/output-discipline.md +3 -5
- package/dist/agent-src/rules/package-ci-checks.md +3 -5
- package/dist/agent-src/rules/persona-governance.md +2 -4
- package/dist/agent-src/rules/php-coding.md +3 -5
- package/dist/agent-src/rules/preservation-guard.md +2 -4
- package/dist/agent-src/rules/provider-lifecycle-discipline.md +3 -5
- package/dist/agent-src/rules/reviewer-awareness.md +5 -6
- package/dist/agent-src/rules/roadmap-ci-steps-policy.md +3 -5
- package/dist/agent-src/rules/roadmap-progress-sync.md +5 -5
- package/dist/agent-src/rules/role-mode-adherence.md +2 -4
- package/dist/agent-src/rules/rule-type-governance.md +3 -5
- package/dist/agent-src/rules/runtime-safety.md +2 -4
- package/dist/agent-src/rules/scope-control.md +10 -11
- package/dist/agent-src/rules/security-sensitive-stop.md +4 -5
- package/dist/agent-src/rules/size-enforcement.md +2 -4
- package/dist/agent-src/rules/skill-improvement-trigger.md +3 -5
- package/dist/agent-src/rules/skill-quality.md +3 -5
- package/dist/agent-src/rules/slash-command-routing-policy.md +3 -5
- package/dist/agent-src/rules/source-confidentiality.md +3 -5
- package/dist/agent-src/rules/source-discovery-gate.md +3 -5
- package/dist/agent-src/rules/source-of-truth.md +3 -5
- package/dist/agent-src/rules/strategy-safety-floor.md +3 -5
- package/dist/agent-src/rules/symfony-routing.md +4 -6
- package/dist/agent-src/rules/telegraph-speak.md +4 -5
- package/dist/agent-src/rules/think-before-action.md +7 -4
- package/dist/agent-src/rules/token-budget-discipline.md +29 -5
- package/dist/agent-src/rules/token-efficiency.md +3 -5
- package/dist/agent-src/rules/token-optimizer-maintenance.md +3 -5
- package/dist/agent-src/rules/tool-safety.md +2 -4
- package/dist/agent-src/rules/ui-audit-gate.md +3 -6
- package/dist/agent-src/rules/untrusted-input-defense.md +3 -5
- package/dist/agent-src/rules/upstream-proposal.md +4 -6
- package/dist/agent-src/rules/user-interaction.md +7 -5
- package/dist/agent-src/rules/user-interrupt-priority.md +17 -8
- package/dist/agent-src/rules/verify-before-complete.md +12 -22
- package/dist/agent-src/scripts/archive_completed_roadmaps.ts +3 -3
- package/dist/agent-src/scripts/update_roadmap_progress.ts +197 -7
- package/dist/agent-src/skills/adr-create/SKILL.md +48 -42
- package/dist/agent-src/skills/agent-docs-writing/SKILL.md +18 -0
- package/dist/agent-src/skills/agent-security-review/SKILL.md +1 -1
- package/dist/agent-src/skills/ai-council/SKILL.md +14 -1
- package/dist/agent-src/skills/blameless-post-mortem/SKILL.md +0 -3
- package/dist/agent-src/skills/brand/SKILL.md +1 -1
- package/dist/agent-src/skills/brand-identity/SKILL.md +3 -3
- package/dist/agent-src/skills/brand-strategy/SKILL.md +1 -1
- package/dist/agent-src/skills/brand-to-tokens/SKILL.md +3 -3
- package/dist/agent-src/skills/canvas-design/SKILL.md +6 -0
- package/dist/agent-src/skills/check-refs/SKILL.md +7 -4
- package/dist/agent-src/skills/code-review/SKILL.md +1 -0
- package/dist/agent-src/skills/complexity-first-planning/SKILL.md +6 -5
- package/dist/agent-src/skills/condense-memory/SKILL.md +11 -11
- package/dist/agent-src/skills/context-document/SKILL.md +17 -0
- package/dist/agent-src/skills/corpus-grounding/SKILL.md +3 -3
- package/dist/agent-src/skills/decision-record/SKILL.md +6 -0
- package/dist/agent-src/skills/decision-review/SKILL.md +5 -3
- package/dist/agent-src/skills/deep-reading-analyst/SKILL.md +6 -1
- package/dist/agent-src/skills/design-intelligence/SKILL.md +5 -2
- package/dist/agent-src/skills/design-intelligence/references/design-languages.md +1 -1
- package/dist/agent-src/skills/design-tokens/SKILL.md +10 -4
- package/dist/agent-src/skills/design-variations/SKILL.md +127 -0
- package/dist/agent-src/skills/design-variations/evals/triggers.json +17 -0
- package/dist/agent-src/skills/emit-tickets/SKILL.md +4 -4
- package/dist/agent-src/skills/fe-design/SKILL.md +20 -0
- package/dist/agent-src/skills/html-deck/SKILL.md +123 -0
- package/dist/agent-src/skills/html-deck/evals/triggers.json +17 -0
- package/dist/agent-src/skills/html-deck/templates/deck-shell.html +77 -0
- package/dist/agent-src/skills/judge-code-quality/SKILL.md +1 -0
- package/dist/agent-src/skills/laravel/SKILL.md +0 -1
- package/dist/agent-src/skills/learning-to-rule-or-skill/SKILL.md +46 -0
- package/dist/agent-src/skills/lint-skills/SKILL.md +6 -6
- package/dist/agent-src/skills/md-language-check/SKILL.md +3 -3
- package/dist/agent-src/skills/memory-consolidation/SKILL.md +66 -1
- package/dist/agent-src/skills/migration-architect/SKILL.md +1 -0
- package/dist/agent-src/skills/module-detect-on-the-fly/SKILL.md +2 -2
- package/dist/agent-src/skills/performance-analysis/SKILL.md +2 -0
- package/dist/agent-src/skills/prediction-pool-optimizer/SKILL.md +4 -4
- package/dist/agent-src/skills/premortem/SKILL.md +0 -3
- package/dist/agent-src/skills/project-analysis-core/SKILL.md +16 -0
- package/dist/agent-src/skills/reasoning-orchestrator/SKILL.md +3 -1
- package/dist/agent-src/skills/release-comms/SKILL.md +46 -45
- package/dist/agent-src/skills/roadmap-management/SKILL.md +21 -0
- package/dist/agent-src/skills/roadmap-writing/SKILL.md +32 -2
- package/dist/agent-src/skills/root-cause-frameworks/SKILL.md +0 -3
- package/dist/agent-src/skills/security-audit/SKILL.md +1 -0
- package/dist/agent-src/skills/skill-improvement-pipeline/SKILL.md +4 -0
- package/dist/agent-src/skills/source-discovery/SKILL.md +22 -3
- package/dist/agent-src/skills/subagent-orchestration/SKILL.md +33 -14
- package/dist/agent-src/skills/subagent-orchestration/prompts/README.md +11 -0
- package/dist/agent-src/skills/subagent-orchestration/prompts/do-in-steps.md +8 -0
- package/dist/agent-src/skills/systematic-debugging/SKILL.md +29 -0
- package/dist/agent-src/skills/threat-modeling/SKILL.md +2 -2
- package/dist/agent-src/skills/typography-system/SKILL.md +2 -2
- package/dist/agent-src/skills/verify-completion-evidence/SKILL.md +2 -0
- package/dist/agent-src/skills/wireframe/SKILL.md +118 -0
- package/dist/agent-src/skills/wireframe/evals/triggers.json +17 -0
- package/dist/agent-src/templates/agent-settings.md +22 -1
- package/dist/agent-src/templates/agents/agent-project-settings.example.yml +1 -1
- package/dist/agent-src/templates/command.md +2 -2
- package/dist/agent-src/templates/contexts/knowledge-pages.md +116 -0
- package/dist/agent-src/templates/hooks/pre-commit-frontmatter +1 -1
- package/dist/agent-src/templates/roadmaps.md +70 -2
- package/dist/agent-src/templates/rule.md +1 -1
- package/dist/agent-src/templates/scripts/README.md +3 -3
- package/dist/agent-src/templates/scripts/work_engine/_lib/agent_settings.ts +109 -0
- package/dist/agent-src/templates/tickets.md +8 -8
- package/dist/cli/commands/recordTriggerEval.test.js +1 -1
- package/dist/cli/commands/recordTriggerEval.test.js.map +1 -1
- package/dist/cli/initRouting.js +4 -2
- package/dist/cli/initRouting.js.map +1 -1
- package/dist/cli/registry.js +1 -0
- package/dist/cli/registry.js.map +1 -1
- package/dist/discovery/deprecation-report.md +1 -1
- package/dist/discovery/discovery-manifest.json +718 -240
- package/dist/discovery/discovery-manifest.json.sha256 +1 -1
- package/dist/discovery/discovery-manifest.summary.md +8 -8
- package/dist/discovery/orphan-report.md +1 -1
- package/dist/discovery/packs.json +43 -28
- package/dist/discovery/trust-report.md +12 -11
- package/dist/discovery/workspaces.json +29 -18
- package/dist/install/preflight.js +173 -0
- package/dist/install/preflight.js.map +1 -0
- package/dist/mcp/registry-manifest.json +4 -4
- package/dist/router.json +1 -1
- package/dist/server/routes/settings.js +4 -0
- package/dist/server/routes/settings.js.map +1 -1
- package/dist/server/schemas/settings.js +3 -1
- package/dist/server/schemas/settings.js.map +1 -1
- package/docs/CLAIMS.md +108 -0
- package/docs/MIGRATION.md +3 -3
- package/docs/architecture.md +5 -5
- package/docs/archive/CHANGELOG-pre-8.0.0.md +301 -0
- package/docs/benchmark.md +133 -2
- package/docs/benchmark.pinned.yml +31 -0
- package/docs/capability-matrix.md +1 -0
- package/docs/catalog.md +70 -60
- package/docs/command-flows.md +15 -3
- package/docs/comparison.yaml +35 -0
- package/docs/contracts/agents-layout.md +148 -0
- package/docs/contracts/brand-token-consumption.md +5 -0
- package/docs/contracts/capability-boundary.md +5 -0
- package/docs/contracts/command-category-governance.md +5 -0
- package/docs/contracts/command-clusters.md +1 -0
- package/docs/contracts/command-surface-tiers.md +1 -1
- package/docs/contracts/conformance.md +84 -0
- package/docs/contracts/creative-pack-boundary.md +5 -0
- package/docs/contracts/discovery-manifest.schema.json +5 -4
- package/docs/contracts/file-ownership-matrix.json +4605 -985
- package/docs/contracts/governance-enforcement-projection.md +5 -0
- package/docs/contracts/gui-wizard.md +1 -1
- package/docs/contracts/hook-architecture-v1.md +2 -2
- package/docs/contracts/implement-ticket-flow.md +1 -1
- package/docs/contracts/kernel-membership.md +13 -0
- package/docs/contracts/mcp-tool-inventory.md +23 -23
- package/docs/contracts/no-runtime-boundary.md +6 -1
- package/docs/contracts/review-lens-schema.md +5 -0
- package/docs/contracts/rule-router.md +102 -8
- package/docs/contracts/skill-bundled-assets.md +1 -1
- package/docs/contracts/skill-dry-run.md +1 -1
- package/docs/contracts/subagent-boundary.md +5 -0
- package/docs/contracts/ticket-bundle-format.md +9 -4
- package/docs/contracts/tier-3-contrib-plugin.md +2 -2
- package/docs/contracts/write-engine.md +3 -0
- package/docs/customization.md +87 -15
- package/docs/decisions/ADR-107-legal-domain-pack-adoption.md +1 -1
- package/docs/decisions/ADR-109-subagent-v1-contract.md +246 -0
- package/docs/decisions/ADR-110-discipline-profile-resolution-locus.md +105 -0
- package/docs/decisions/INDEX.md +2 -0
- package/docs/distribution/awesome-list-submission.md +46 -0
- package/docs/distribution/launch-story-draft.md +58 -0
- package/docs/end-to-end-walkthroughs.md +2 -2
- package/docs/getting-started.md +2 -2
- package/docs/guidelines/agent-infra/asking-and-brevity-examples.md +24 -0
- package/docs/guidelines/agent-infra/corpus-grounding-authoring.md +1 -1
- package/docs/guidelines/agent-infra/linked-projects-onboarding-gate.md +2 -2
- package/docs/guidelines/agent-infra/missing-tool-handling.md +3 -3
- package/docs/guidelines/agent-infra/verify-before-complete-demos.md +2 -2
- package/docs/guidelines/augment-portability-patterns.md +1 -1
- package/docs/guidelines/code-clarity.md +19 -0
- package/docs/guidelines/design-antipatterns-triggers.json +2 -1
- package/docs/guidelines/design-antipatterns.md +10 -1
- package/docs/guidelines/design-modes.md +6 -0
- package/docs/guidelines/php/general.md +9 -1
- package/docs/guidelines/php/php-coding-patterns.md +7 -0
- package/docs/guides/cross-repo-retrieval.md +1 -1
- package/docs/guides/frontend-design-corpus-refresh.md +2 -2
- package/docs/guides/local-analytics.md +4 -4
- package/docs/guides/skill-discovery.md +1 -1
- package/docs/guides/skill-preview.md +1 -1
- package/docs/hook-payload-capture.md +6 -6
- package/docs/install-friction-report.md +66 -0
- package/docs/installation.md +1 -1
- package/docs/maintainers/optional-media-deps.md +50 -0
- package/docs/mcp-server.md +11 -12
- package/docs/media/proof-demo.cast +14 -0
- package/docs/media/proof-demo.gif +0 -0
- package/docs/migration/consumer-template-consumption-model.md +1 -1
- package/docs/migration/divergences/bench-stats-float-precision.md +1 -1
- package/docs/migration/node-floor.md +1 -1
- package/docs/migration/yaml-roundtrip-spike.md +1 -1
- package/docs/personas.md +16 -5
- package/docs/positioning-evidence.md +86 -0
- package/docs/proof.md +86 -0
- package/docs/skills-catalog.md +1 -1
- package/docs/troubleshooting.md +8 -8
- package/docs/wedge/production-validator/README.md +57 -0
- package/docs/wedge/production-validator/production-validator.md +56 -0
- package/docs/wizard.md +2 -2
- package/package.json +1 -1
- package/src/config/agent-settings.template.yml +59 -4
- package/src/config/agents-paths.yml +254 -0
- package/src/config/budgets.yml +16 -0
- package/src/config/discovery/packs.yml +1 -0
- package/src/config/discovery/unassigned-artefacts.yml +2 -0
- package/src/config/gitignore-block.txt +25 -0
- package/src/config/host-capabilities.yml +31 -0
- package/src/config/profiles/balanced.ini +4 -0
- package/src/config/profiles/full.ini +4 -0
- package/src/config/profiles/minimal.ini +4 -0
- package/src/scripts/_cli/cmd_conformance.ts +716 -0
- package/src/scripts/_cli/cmd_doctor.ts +23 -2
- package/src/scripts/_cli/cmd_fleet.ts +401 -0
- package/src/scripts/_cli/cmd_preflight.ts +159 -0
- package/src/scripts/_dispatch.bash +10 -0
- package/src/scripts/_lib/knowledge_events.ts +193 -0
- package/src/scripts/_lib/subagent_steering.ts +115 -3
- package/src/scripts/_lib/text_similarity.ts +75 -0
- package/src/scripts/ai-video/test-pipeline.sh +1 -1
- package/src/scripts/ai_council/clients.ts +19 -3
- package/src/scripts/append_contested.ts +112 -0
- package/src/scripts/audit_auto_rules.ts +11 -1
- package/src/scripts/audit_mcp_tools.ts +25 -11
- package/src/scripts/bench_ab_v2_run.ts +527 -40
- package/src/scripts/bench_ab_v2_stats.ts +6 -0
- package/src/scripts/bench_matrix.ts +266 -0
- package/src/scripts/bootstrap_knowledge.ts +267 -0
- package/src/scripts/build_discovery_manifest.ts +42 -2
- package/src/scripts/build_proof.ts +214 -0
- package/src/scripts/check_always_budget.ts +3 -0
- package/src/scripts/check_augment_description_cap.ts +2 -2
- package/src/scripts/check_claims.ts +270 -0
- package/src/scripts/check_comparison.ts +79 -0
- package/src/scripts/check_generated_artefact_headers.ts +109 -0
- package/src/scripts/check_generator_output_coverage.ts +142 -0
- package/src/scripts/check_gitignore_freshness.ts +127 -0
- package/src/scripts/check_knowledge_pages.ts +210 -0
- package/src/scripts/check_knowledge_sharing.ts +162 -0
- package/src/scripts/check_media_deps.ts +91 -0
- package/src/scripts/check_memory_contradiction.ts +280 -0
- package/src/scripts/check_memory_similarity.ts +134 -0
- package/src/scripts/check_quality_regression.ts +56 -10
- package/src/scripts/check_references.ts +3 -1
- package/src/scripts/check_release_pr_shape.ts +4 -0
- package/src/scripts/check_site_links.ts +135 -0
- package/src/scripts/check_skill_gaps.ts +99 -0
- package/src/scripts/check_token_quality_golden.ts +197 -11
- package/src/scripts/check_tracked_but_ignored.ts +67 -0
- package/src/scripts/cluster_near_miss_patterns.ts +0 -0
- package/src/scripts/compile_router.ts +30 -3
- package/src/scripts/condense.ts +350 -14
- package/src/scripts/consolidate_knowledge_events.ts +192 -0
- package/src/scripts/degrade_to_pointer.ts +136 -0
- package/src/scripts/design_slop_rules.test.ts +15 -0
- package/src/scripts/design_slop_rules.ts +28 -0
- package/src/scripts/emit_knowledge_event.ts +158 -0
- package/src/scripts/export_replay_corpus.ts +187 -0
- package/src/scripts/external_sources_denylist.json +7 -2
- package/src/scripts/fold_intake.ts +281 -0
- package/src/scripts/generate_capability_matrix.ts +9 -1
- package/src/scripts/generate_knowledge_index.ts +222 -0
- package/src/scripts/hook_manifest.yaml +26 -14
- package/src/scripts/hooks/dispatch_hook.ts +18 -0
- package/src/scripts/hot_context_hook.ts +372 -0
- package/src/scripts/install +47 -0
- package/src/scripts/install-hooks.sh +17 -0
- package/src/scripts/inventory_meta_layers.ts +11 -0
- package/src/scripts/janitor.ts +258 -0
- package/src/scripts/lint_agents_layout.ts +74 -15
- package/src/scripts/lint_bench_ab.ts +4 -1
- package/src/scripts/lint_featured_skills.ts +0 -0
- package/src/scripts/lint_framework_leakage_allowlist.json +26 -3
- package/src/scripts/lint_knowledge_scale.ts +250 -0
- package/src/scripts/lint_legal_pack.ts +67 -0
- package/src/scripts/lint_persona_governance.ts +8 -3
- package/src/scripts/lint_regression.ts +15 -0
- package/src/scripts/lint_roadmap_blockers.ts +202 -0
- package/src/scripts/lint_roadmap_complexity.ts +81 -2
- package/src/scripts/lint_subagent_determinism.ts +125 -0
- package/src/scripts/measure_augment_budget.ts +46 -6
- package/src/scripts/measure_patterns.ts +4 -3
- package/src/scripts/measure_skill_reduction.ts +6 -6
- package/src/scripts/migration_status.ts +6 -2
- package/src/scripts/mine_missing_guardrails.ts +189 -0
- package/src/scripts/parity/phase_gate.ts +1 -1
- package/src/scripts/parity/replay.ts +21 -0
- package/src/scripts/project_thin_rules.ts +120 -16
- package/src/scripts/release.ts +39 -1
- package/src/scripts/render_benchmark_composite.ts +378 -0
- package/src/scripts/roadmap_progress_hook.ts +101 -43
- package/src/scripts/schemas/skill.schema.json +27 -0
- package/src/scripts/schemas/subagent.schema.json +106 -0
- package/src/scripts/skill_linter.ts +1 -0
- package/src/scripts/smoke/kernel.sh +5 -4
- package/src/scripts/surface-tiers.yml +1 -0
- package/src/scripts/sync_gitignore.ts +4 -0
- package/src/scripts/update_skill_candidates.ts +172 -0
- package/src/scripts/validate_frontmatter.ts +1 -0
|
@@ -68,6 +68,70 @@ export function kernel_ids(): Set<string> {
|
|
|
68
68
|
return new Set();
|
|
69
69
|
}
|
|
70
70
|
|
|
71
|
+
/**
|
|
72
|
+
* id → workspaces for every non-kernel router entry (router.json schema v2,
|
|
73
|
+
* road-to-request-scoped-rule-load Phase 1). Rules absent from the map (or
|
|
74
|
+
* with an empty list) fail safe: they stay in scope.
|
|
75
|
+
*/
|
|
76
|
+
export function rule_workspaces_map(): Map<string, string[]> {
|
|
77
|
+
const data = JSON.parse(fs.readFileSync(ROUTER, 'utf-8')) as Record<string, unknown>;
|
|
78
|
+
const map = new Map<string, string[]>();
|
|
79
|
+
for (const tier of ['tier_1', 'tier_2']) {
|
|
80
|
+
const entries = data[tier];
|
|
81
|
+
if (!Array.isArray(entries)) continue;
|
|
82
|
+
for (const e of entries) {
|
|
83
|
+
const obj = e as Record<string, unknown>;
|
|
84
|
+
const ws = Array.isArray(obj.workspaces) ? obj.workspaces.map((w) => String(w)) : [];
|
|
85
|
+
map.set(String(obj.id), ws);
|
|
86
|
+
}
|
|
87
|
+
}
|
|
88
|
+
return map;
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
/**
|
|
92
|
+
* Whether a rule id survives a workspace scope. Kernel always survives;
|
|
93
|
+
* `scope === null` = legacy-all. An out-of-scope non-kernel rule is dropped
|
|
94
|
+
* entirely — neither body nor pointer line (the pointer floor shrinks with
|
|
95
|
+
* consumer scoping).
|
|
96
|
+
*/
|
|
97
|
+
export function id_in_scope(
|
|
98
|
+
rule_id: string,
|
|
99
|
+
scope: readonly string[] | null,
|
|
100
|
+
kernel: ReadonlySet<string>,
|
|
101
|
+
wsMap: ReadonlyMap<string, string[]>,
|
|
102
|
+
fallback_ws: readonly string[] = [],
|
|
103
|
+
): boolean {
|
|
104
|
+
if (scope === null || kernel.has(rule_id)) {
|
|
105
|
+
return true;
|
|
106
|
+
}
|
|
107
|
+
// Router map first (schema v2); `type: manual` rules are reference-only
|
|
108
|
+
// and never emitted to the router — their frontmatter is the fallback.
|
|
109
|
+
const ws = wsMap.get(rule_id) ?? fallback_ws;
|
|
110
|
+
if (ws.length === 0) {
|
|
111
|
+
return true; // untagged / unknown → fail safe
|
|
112
|
+
}
|
|
113
|
+
return ws.some((w) => scope.includes(w));
|
|
114
|
+
}
|
|
115
|
+
|
|
116
|
+
/** `workspaces:` list parsed straight from a rule file's frontmatter. */
|
|
117
|
+
export function fm_workspaces(text: string): string[] {
|
|
118
|
+
const [fm] = split_frontmatter(text);
|
|
119
|
+
// Flow style: `workspaces: [a, b, c]`
|
|
120
|
+
const flow = /^workspaces:[ \t]*\[([^\]]*)\]/m.exec(fm);
|
|
121
|
+
if (flow) {
|
|
122
|
+
return (flow[1] as string)
|
|
123
|
+
.split(',')
|
|
124
|
+
.map((s) => s.trim().replace(/^["']|["']$/g, ''))
|
|
125
|
+
.filter((s) => s.length > 0);
|
|
126
|
+
}
|
|
127
|
+
// Block style; tolerate the list being the LAST frontmatter key.
|
|
128
|
+
const m = /^workspaces:[ \t]*\n((?:[ \t]+-[ \t]+.*(?:\n|$))+)/m.exec(fm);
|
|
129
|
+
if (!m) {
|
|
130
|
+
return [];
|
|
131
|
+
}
|
|
132
|
+
return [...(m[1] as string).matchAll(/-[ \t]+(\S+)/g)].map((x) => x[1] as string);
|
|
133
|
+
}
|
|
134
|
+
|
|
71
135
|
/** Return [frontmatter_including_fences, body]. Empty fm if none. */
|
|
72
136
|
export function split_frontmatter(text: string): [string, string] {
|
|
73
137
|
if (text.startsWith('---\n')) {
|
|
@@ -123,12 +187,19 @@ export function thin_entry(rule_id: string, text: string): string {
|
|
|
123
187
|
}
|
|
124
188
|
|
|
125
189
|
/** Map {filename: thin_or_full_text} for every rule. Kernel stays full. */
|
|
126
|
-
export function build_thin(
|
|
190
|
+
export function build_thin(
|
|
191
|
+
rules_dir: string = RULES_SOURCE,
|
|
192
|
+
scope: readonly string[] | null = null,
|
|
193
|
+
): Map<string, string> {
|
|
127
194
|
const kernel = kernel_ids();
|
|
195
|
+
const wsMap = scope !== null ? rule_workspaces_map() : new Map<string, string[]>();
|
|
128
196
|
const out = new Map<string, string>();
|
|
129
197
|
for (const p of _globSortedMd(rules_dir)) {
|
|
130
198
|
const text = fs.readFileSync(p, 'utf-8');
|
|
131
199
|
const stem = path.basename(p).replace(/\.md$/, '');
|
|
200
|
+
if (!id_in_scope(stem, scope, kernel, wsMap, fm_workspaces(text))) {
|
|
201
|
+
continue; // out of workspace scope — no body, no pointer line
|
|
202
|
+
}
|
|
132
203
|
out.set(path.basename(p), kernel.has(stem) ? text : thin_entry(stem, text));
|
|
133
204
|
}
|
|
134
205
|
return out;
|
|
@@ -163,12 +234,24 @@ function _python_round1(x: number): number {
|
|
|
163
234
|
return r / 10;
|
|
164
235
|
}
|
|
165
236
|
|
|
166
|
-
/** Eager vs thin token footprint for the rule layer. */
|
|
167
|
-
export function measure(
|
|
237
|
+
/** Eager vs thin token footprint for the rule layer (scope applies to both arms). */
|
|
238
|
+
export function measure(
|
|
239
|
+
rules_dir: string = RULES_SOURCE,
|
|
240
|
+
scope: readonly string[] | null = null,
|
|
241
|
+
): ThinMeasure {
|
|
168
242
|
const kernel = kernel_ids();
|
|
169
|
-
const
|
|
243
|
+
const wsMap = scope !== null ? rule_workspaces_map() : new Map<string, string[]>();
|
|
244
|
+
const mdPaths = _globSortedMd(rules_dir).filter((p) =>
|
|
245
|
+
id_in_scope(
|
|
246
|
+
path.basename(p).replace(/\.md$/, ''),
|
|
247
|
+
scope,
|
|
248
|
+
kernel,
|
|
249
|
+
wsMap,
|
|
250
|
+
fm_workspaces(fs.readFileSync(p, 'utf-8')),
|
|
251
|
+
),
|
|
252
|
+
);
|
|
170
253
|
const eager_blob = mdPaths.map((p) => fs.readFileSync(p, 'utf-8')).join('');
|
|
171
|
-
const thin_blob = [...build_thin(rules_dir).values()].join('');
|
|
254
|
+
const thin_blob = [...build_thin(rules_dir, scope).values()].join('');
|
|
172
255
|
const eager = token_count.measure(eager_blob);
|
|
173
256
|
const thin = token_count.measure(thin_blob);
|
|
174
257
|
const n = mdPaths.length;
|
|
@@ -198,9 +281,13 @@ export function measure(rules_dir: string = RULES_SOURCE): ThinMeasure {
|
|
|
198
281
|
}
|
|
199
282
|
|
|
200
283
|
/** Write thin rule files to `out_dir`; return the count written. */
|
|
201
|
-
export function write_thin(
|
|
284
|
+
export function write_thin(
|
|
285
|
+
out_dir: string,
|
|
286
|
+
rules_dir: string = RULES_SOURCE,
|
|
287
|
+
scope: readonly string[] | null = null,
|
|
288
|
+
): number {
|
|
202
289
|
fs.mkdirSync(out_dir, { recursive: true });
|
|
203
|
-
const files = build_thin(rules_dir);
|
|
290
|
+
const files = build_thin(rules_dir, scope);
|
|
204
291
|
for (const [name, text] of files) {
|
|
205
292
|
fs.writeFileSync(path.join(out_dir, name), text, 'utf-8');
|
|
206
293
|
}
|
|
@@ -293,22 +380,26 @@ interface ParsedArgs {
|
|
|
293
380
|
measure: boolean;
|
|
294
381
|
out: string | null;
|
|
295
382
|
json: boolean;
|
|
383
|
+
workspaces: string[] | null;
|
|
296
384
|
}
|
|
297
385
|
|
|
298
386
|
// argparse `prog` defaults to basename(sys.argv[0]) → `project_thin_rules.py`.
|
|
299
|
-
const _USAGE =
|
|
387
|
+
const _USAGE =
|
|
388
|
+
'usage: project_thin_rules.py [-h] [--measure] [--out OUT] [--json] [--workspaces WS]\n';
|
|
300
389
|
|
|
301
|
-
// Full `-h` help block
|
|
390
|
+
// Full `-h` help block.
|
|
302
391
|
const _HELP =
|
|
303
392
|
_USAGE +
|
|
304
393
|
'\n' +
|
|
305
394
|
'Thin-projection of the rule layer (lean-initial-context build-out, Phase 3.1).\n' +
|
|
306
395
|
'\n' +
|
|
307
396
|
'optional arguments:\n' +
|
|
308
|
-
' -h, --help
|
|
309
|
-
' --measure
|
|
310
|
-
' --out OUT
|
|
311
|
-
' --json\n'
|
|
397
|
+
' -h, --help show this help message and exit\n' +
|
|
398
|
+
' --measure print the eager-vs-thin token delta\n' +
|
|
399
|
+
' --out OUT write thin rule files to this dir\n' +
|
|
400
|
+
' --json\n' +
|
|
401
|
+
' --workspaces WS comma-separated workspace scope (router.json v2 fields);\n' +
|
|
402
|
+
' kernel always survives, out-of-scope rules drop entirely\n';
|
|
312
403
|
|
|
313
404
|
function _argError(msg: string): never {
|
|
314
405
|
process.stderr.write(_USAGE);
|
|
@@ -317,7 +408,7 @@ function _argError(msg: string): never {
|
|
|
317
408
|
}
|
|
318
409
|
|
|
319
410
|
function parse_args(argv: string[]): ParsedArgs {
|
|
320
|
-
const out: ParsedArgs = { measure: false, out: null, json: false };
|
|
411
|
+
const out: ParsedArgs = { measure: false, out: null, json: false, workspaces: null };
|
|
321
412
|
for (let i = 0; i < argv.length; i += 1) {
|
|
322
413
|
const a = argv[i] as string;
|
|
323
414
|
if (a === '-h' || a === '--help') {
|
|
@@ -336,6 +427,19 @@ function parse_args(argv: string[]): ParsedArgs {
|
|
|
336
427
|
i += 1;
|
|
337
428
|
} else if (a.startsWith('--out=')) {
|
|
338
429
|
out.out = a.slice('--out='.length);
|
|
430
|
+
} else if (a === '--workspaces') {
|
|
431
|
+
const next = argv[i + 1];
|
|
432
|
+
if (next === undefined) {
|
|
433
|
+
_argError('argument --workspaces: expected one argument');
|
|
434
|
+
}
|
|
435
|
+
out.workspaces = next.split(',').map((s) => s.trim()).filter((s) => s.length > 0);
|
|
436
|
+
i += 1;
|
|
437
|
+
} else if (a.startsWith('--workspaces=')) {
|
|
438
|
+
out.workspaces = a
|
|
439
|
+
.slice('--workspaces='.length)
|
|
440
|
+
.split(',')
|
|
441
|
+
.map((s) => s.trim())
|
|
442
|
+
.filter((s) => s.length > 0);
|
|
339
443
|
} else {
|
|
340
444
|
_argError(`unrecognized arguments: ${a}`);
|
|
341
445
|
}
|
|
@@ -357,12 +461,12 @@ export function main(argv: string[] | null = null): number {
|
|
|
357
461
|
const args = parse_args(argv ?? process.argv.slice(2));
|
|
358
462
|
|
|
359
463
|
if (args.out !== null) {
|
|
360
|
-
const n = write_thin(args.out);
|
|
464
|
+
const n = write_thin(args.out, RULES_SOURCE, args.workspaces);
|
|
361
465
|
process.stdout.write(`wrote ${n} thin rule files → ${args.out}\n`);
|
|
362
466
|
return 0;
|
|
363
467
|
}
|
|
364
468
|
|
|
365
|
-
const m = measure();
|
|
469
|
+
const m = measure(RULES_SOURCE, args.workspaces);
|
|
366
470
|
if (args.json) {
|
|
367
471
|
// `saved_pct` is a Python float (round(..., 1)); wrap it so an
|
|
368
472
|
// integer-valued result still renders as `N.0` (json.dumps parity).
|
package/src/scripts/release.ts
CHANGED
|
@@ -151,6 +151,18 @@ class CalledProcessError extends Error {
|
|
|
151
151
|
const REPO_ROOT = path.resolve(path.dirname(_HERE), '..', '..');
|
|
152
152
|
const PACKAGE_JSON = path.join(REPO_ROOT, 'package.json');
|
|
153
153
|
const MARKETPLACE_JSON = path.join(REPO_ROOT, '.claude-plugin', 'marketplace.json');
|
|
154
|
+
// Source-of-truth project-settings template. Carries an `agent_config_version`
|
|
155
|
+
// pin that check_template_pin_drift requires to equal package.json.version. We
|
|
156
|
+
// bump the src twin here; `task release-prepare` (run right after the bump)
|
|
157
|
+
// regenerates the dist/agent-src/ twin from it.
|
|
158
|
+
const PROJECT_TEMPLATE = path.join(
|
|
159
|
+
REPO_ROOT,
|
|
160
|
+
'src',
|
|
161
|
+
'agent-src',
|
|
162
|
+
'templates',
|
|
163
|
+
'agents',
|
|
164
|
+
'agent-project-settings.example.yml',
|
|
165
|
+
);
|
|
154
166
|
const CHANGELOG = path.join(REPO_ROOT, 'CHANGELOG.md');
|
|
155
167
|
const MAIN_BRANCH = 'main';
|
|
156
168
|
const REMOTE = 'origin';
|
|
@@ -879,6 +891,29 @@ function set_marketplace_version(p: string, version: string): void {
|
|
|
879
891
|
fs.writeFileSync(p, jsonDumpsIndent(data, 2) + '\n', 'utf-8');
|
|
880
892
|
}
|
|
881
893
|
|
|
894
|
+
function set_template_pin(p: string, version: string): void {
|
|
895
|
+
// Rewrite the single `agent_config_version:` line in place, preserving the
|
|
896
|
+
// rest of the YAML byte-for-byte. Quoted value to match the existing pin
|
|
897
|
+
// style and check_template_pin_drift's tolerant parse.
|
|
898
|
+
const text = fs.readFileSync(p, 'utf-8');
|
|
899
|
+
const lines = text.split('\n');
|
|
900
|
+
let found = false;
|
|
901
|
+
for (let i = 0; i < lines.length; i += 1) {
|
|
902
|
+
if (/^\s*agent_config_version\s*:/.test(lines[i]!)) {
|
|
903
|
+
lines[i] = lines[i]!.replace(
|
|
904
|
+
/^(\s*agent_config_version\s*:\s*).*/,
|
|
905
|
+
`$1"${version}"`,
|
|
906
|
+
);
|
|
907
|
+
found = true;
|
|
908
|
+
break;
|
|
909
|
+
}
|
|
910
|
+
}
|
|
911
|
+
if (!found) {
|
|
912
|
+
die(`set_template_pin: no \`agent_config_version:\` line in ${p}`);
|
|
913
|
+
}
|
|
914
|
+
fs.writeFileSync(p, lines.join('\n'), 'utf-8');
|
|
915
|
+
}
|
|
916
|
+
|
|
882
917
|
// ─── preflight ────────────────────────────────────────────────────────────────
|
|
883
918
|
|
|
884
919
|
/**
|
|
@@ -982,6 +1017,7 @@ function print_preview(plan: Plan): void {
|
|
|
982
1017
|
process.stdout.write('Files to change:\n');
|
|
983
1018
|
process.stdout.write(` · ${path.relative(REPO_ROOT, PACKAGE_JSON)}\n`);
|
|
984
1019
|
process.stdout.write(` · ${path.relative(REPO_ROOT, MARKETPLACE_JSON)}\n`);
|
|
1020
|
+
process.stdout.write(` · ${path.relative(REPO_ROOT, PROJECT_TEMPLATE)}\n`);
|
|
985
1021
|
process.stdout.write(` · ${path.relative(REPO_ROOT, CHANGELOG)}\n`);
|
|
986
1022
|
process.stdout.write(' · regenerated derived files via `task release-prepare`\n');
|
|
987
1023
|
process.stdout.write(
|
|
@@ -1204,9 +1240,10 @@ function execute(
|
|
|
1204
1240
|
if (resume && current_pkg === plan.target) {
|
|
1205
1241
|
_step(2, total, `Files already at ${plan.target} — skip bump`);
|
|
1206
1242
|
} else {
|
|
1207
|
-
_step(2, total, 'Bump package.json + marketplace.json, prepend CHANGELOG');
|
|
1243
|
+
_step(2, total, 'Bump package.json + marketplace.json + template pin, prepend CHANGELOG');
|
|
1208
1244
|
set_package_version(PACKAGE_JSON, plan.target);
|
|
1209
1245
|
set_marketplace_version(MARKETPLACE_JSON, plan.target);
|
|
1246
|
+
set_template_pin(PROJECT_TEMPLATE, plan.target);
|
|
1210
1247
|
prepend_changelog(CHANGELOG, plan.changelog_entry);
|
|
1211
1248
|
}
|
|
1212
1249
|
|
|
@@ -1727,6 +1764,7 @@ export {
|
|
|
1727
1764
|
prepend_changelog,
|
|
1728
1765
|
set_package_version,
|
|
1729
1766
|
set_marketplace_version,
|
|
1767
|
+
set_template_pin,
|
|
1730
1768
|
resolve_bump,
|
|
1731
1769
|
_detect_in_flight_target,
|
|
1732
1770
|
print_preview,
|
|
@@ -0,0 +1,378 @@
|
|
|
1
|
+
#!/usr/bin/env tsx
|
|
2
|
+
/**
|
|
3
|
+
* Per-pinned-section renderer for the curated composite `docs/benchmark.md`
|
|
4
|
+
* (road-to-flow-learnings Phase 3).
|
|
5
|
+
*
|
|
6
|
+
* The composite is DELIBERATELY not a single-latest-report render (a
|
|
7
|
+
* single-report render would bury one host's finding — the in-file rationale
|
|
8
|
+
* stands). This script keeps that decision and removes only the manual
|
|
9
|
+
* table-editing step: each `<!-- pinned:<id> -->` … `<!-- /pinned:<id> -->`
|
|
10
|
+
* marker region in the doc is regenerated INDEPENDENTLY from ITS pinned
|
|
11
|
+
* report (`docs/benchmark.pinned.yml`); every line outside the markers —
|
|
12
|
+
* curated headings, verdict framing, findings prose, the history section —
|
|
13
|
+
* is preserved byte-for-byte.
|
|
14
|
+
*
|
|
15
|
+
* Section modes:
|
|
16
|
+
* - `honesty-labels` — the generated "Honesty labels" block.
|
|
17
|
+
* - `stats-body` — the full generated stats body (verdict bullets +
|
|
18
|
+
* interpretation + tables + methodology).
|
|
19
|
+
* - `compact-table` — one 3-row capability/discipline/tokens table for
|
|
20
|
+
* `package` vs `vanilla`.
|
|
21
|
+
* - `arm-cost-table` — the per-arm cost-factor table (lift per arm vs
|
|
22
|
+
* vanilla; labels curated in the manifest).
|
|
23
|
+
*
|
|
24
|
+
* Deterministic: same pinned inputs → byte-identical output. Pinned reports
|
|
25
|
+
* are operator-local (untracked); when a report is missing the script exits
|
|
26
|
+
* 0 with a skip notice so CI stays green — it can only verify where the
|
|
27
|
+
* pinned inputs exist.
|
|
28
|
+
*
|
|
29
|
+
* Exit: 0 ok/skip · 1 `--check` drift · 2 config error.
|
|
30
|
+
*/
|
|
31
|
+
|
|
32
|
+
import * as fs from 'node:fs';
|
|
33
|
+
import * as path from 'node:path';
|
|
34
|
+
import process from 'node:process';
|
|
35
|
+
import { fileURLToPath, pathToFileURL } from 'node:url';
|
|
36
|
+
|
|
37
|
+
import { parse as parseYaml } from 'yaml';
|
|
38
|
+
|
|
39
|
+
import { analyse, compare, gate_verdict, mean_tokens_by_arm, to_markdown } from './bench_ab_v2_stats.js';
|
|
40
|
+
|
|
41
|
+
const _HERE = fileURLToPath(import.meta.url);
|
|
42
|
+
export const REPO_ROOT = path.resolve(path.dirname(_HERE), '..', '..');
|
|
43
|
+
const DOC_PATH = path.join(REPO_ROOT, 'docs', 'benchmark.md');
|
|
44
|
+
const MANIFEST_PATH = path.join(REPO_ROOT, 'docs', 'benchmark.pinned.yml');
|
|
45
|
+
|
|
46
|
+
type Dict = Record<string, unknown>;
|
|
47
|
+
|
|
48
|
+
export class CompositeConfigError extends Error {}
|
|
49
|
+
|
|
50
|
+
export interface PinnedSection {
|
|
51
|
+
readonly id: string;
|
|
52
|
+
readonly report: string;
|
|
53
|
+
readonly mode: 'honesty-labels' | 'stats-body' | 'compact-table' | 'arm-cost-table';
|
|
54
|
+
/** arm-cost-table only: ordered arm list with curated labels. */
|
|
55
|
+
readonly arms?: ReadonlyArray<{ id: string; label: string }>;
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
export function parseManifest(text: string): PinnedSection[] {
|
|
59
|
+
const raw = parseYaml(text) as Dict | null;
|
|
60
|
+
const sections = raw?.['sections'];
|
|
61
|
+
if (!Array.isArray(sections) || sections.length === 0) {
|
|
62
|
+
throw new CompositeConfigError('`sections:` must be a non-empty list');
|
|
63
|
+
}
|
|
64
|
+
const modes = new Set(['honesty-labels', 'stats-body', 'compact-table', 'arm-cost-table']);
|
|
65
|
+
return sections.map((s, i) => {
|
|
66
|
+
const e = s as Dict;
|
|
67
|
+
const id = String(e['id'] ?? '');
|
|
68
|
+
const report = String(e['report'] ?? '');
|
|
69
|
+
const mode = String(e['mode'] ?? '');
|
|
70
|
+
if (!id || !report || !modes.has(mode)) {
|
|
71
|
+
throw new CompositeConfigError(`sections[${i}] needs id, report, and a valid mode`);
|
|
72
|
+
}
|
|
73
|
+
const armsRaw = e['arms'];
|
|
74
|
+
let arms: Array<{ id: string; label: string }> | undefined;
|
|
75
|
+
if (armsRaw !== undefined) {
|
|
76
|
+
if (!Array.isArray(armsRaw)) {
|
|
77
|
+
throw new CompositeConfigError(`sections[${i}].arms must be a list`);
|
|
78
|
+
}
|
|
79
|
+
arms = armsRaw.map((a) => {
|
|
80
|
+
const ar = a as Dict;
|
|
81
|
+
return { id: String(ar['id'] ?? ''), label: String(ar['label'] ?? '') };
|
|
82
|
+
});
|
|
83
|
+
}
|
|
84
|
+
return { id, report, mode: mode as PinnedSection['mode'], ...(arms ? { arms } : {}) };
|
|
85
|
+
});
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
// ---------------------------------------------------------------------------
|
|
89
|
+
// Section renderers — every value comes from the section's OWN pinned report.
|
|
90
|
+
// ---------------------------------------------------------------------------
|
|
91
|
+
|
|
92
|
+
function loadReport(reportPath: string): { analysis: Dict; payload: Dict } {
|
|
93
|
+
const payload = JSON.parse(fs.readFileSync(reportPath, 'utf-8')) as Dict;
|
|
94
|
+
const analysis = analyse(payload);
|
|
95
|
+
// Mirror the stats CLI: the gate verdict is merged before rendering.
|
|
96
|
+
analysis['gate'] = gate_verdict(analysis);
|
|
97
|
+
return { analysis, payload };
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
/** Unwrap the stats module's PyFloat wrapper (duck-typed — the class is private). */
|
|
101
|
+
function num(v: unknown): number {
|
|
102
|
+
if (v !== null && typeof v === 'object' && 'value' in (v as Dict)) {
|
|
103
|
+
return Number((v as { value: unknown }).value);
|
|
104
|
+
}
|
|
105
|
+
return Number(v);
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
/** Python float repr for p-values / effect sizes: `1` → `1.0`, `0.81` → `0.81`. */
|
|
109
|
+
function pyf(v: unknown): string {
|
|
110
|
+
const n = num(v);
|
|
111
|
+
if (!Number.isFinite(n)) return String(n);
|
|
112
|
+
return Number.isInteger(n) ? `${n}.0` : String(n);
|
|
113
|
+
}
|
|
114
|
+
|
|
115
|
+
/** Slice a generated full render between two headings (end exclusive of next `## `). */
|
|
116
|
+
function sliceFrom(md: string, startHeadingPrefix: string, includeHeading: boolean): string {
|
|
117
|
+
const lines = md.split('\n');
|
|
118
|
+
const start = lines.findIndex((l) => l.startsWith(startHeadingPrefix));
|
|
119
|
+
if (start < 0) {
|
|
120
|
+
throw new CompositeConfigError(`generated render lacks heading ${startHeadingPrefix}`);
|
|
121
|
+
}
|
|
122
|
+
return lines.slice(includeHeading ? start : start + 1).join('\n').trimEnd();
|
|
123
|
+
}
|
|
124
|
+
|
|
125
|
+
export function renderHonestyLabels(reportPath: string): string {
|
|
126
|
+
const { analysis, payload } = loadReport(reportPath);
|
|
127
|
+
const md = to_markdown(analysis, payload);
|
|
128
|
+
const lines = md.split('\n');
|
|
129
|
+
const start = lines.findIndex((l) => l.startsWith('## Honesty labels'));
|
|
130
|
+
const end = lines.findIndex((l, i) => i > start && l.startsWith('## '));
|
|
131
|
+
if (start < 0 || end < 0) {
|
|
132
|
+
throw new CompositeConfigError('generated render lacks the honesty-labels block');
|
|
133
|
+
}
|
|
134
|
+
// Body only — the curated doc owns the section heading.
|
|
135
|
+
return lines.slice(start + 1, end).join('\n').trim();
|
|
136
|
+
}
|
|
137
|
+
|
|
138
|
+
export function renderStatsBody(reportPath: string): string {
|
|
139
|
+
const { analysis, payload } = loadReport(reportPath);
|
|
140
|
+
const md = to_markdown(analysis, payload);
|
|
141
|
+
// Everything from the verdict bullets (line after `## Gate verdict`) to EOF:
|
|
142
|
+
// verdict bullets + interpretation + per-arm tables + status buckets +
|
|
143
|
+
// methodology. The curated doc supplies its own retitled section heading.
|
|
144
|
+
return sliceFrom(md, '## Gate verdict:', false).trim();
|
|
145
|
+
}
|
|
146
|
+
|
|
147
|
+
const fmtInt = (n: number): string => Math.round(n).toLocaleString('en-US');
|
|
148
|
+
|
|
149
|
+
function armRuns(rec: Dict, arm: string): Dict[] {
|
|
150
|
+
const runs = (rec['arms'] as Dict | undefined)?.[arm];
|
|
151
|
+
return Array.isArray(runs) ? (runs as Dict[]) : [];
|
|
152
|
+
}
|
|
153
|
+
|
|
154
|
+
function disciplineMean(records: Dict[], arm: string): number {
|
|
155
|
+
let sum = 0;
|
|
156
|
+
let n = 0;
|
|
157
|
+
for (const rec of records) {
|
|
158
|
+
for (const r of armRuns(rec, arm)) {
|
|
159
|
+
if (r['errored']) continue;
|
|
160
|
+
const d = Number(r['discipline_score'] ?? NaN);
|
|
161
|
+
if (Number.isFinite(d)) {
|
|
162
|
+
sum += d;
|
|
163
|
+
n += 1;
|
|
164
|
+
}
|
|
165
|
+
}
|
|
166
|
+
}
|
|
167
|
+
return n > 0 ? sum / n : 0;
|
|
168
|
+
}
|
|
169
|
+
|
|
170
|
+
function meanTokens(records: Dict[], arm: string): number {
|
|
171
|
+
const per = mean_tokens_by_arm(records, [arm]) as Dict;
|
|
172
|
+
return Number((per[arm] as Dict | undefined)?.['mean_tokens'] ?? 0);
|
|
173
|
+
}
|
|
174
|
+
|
|
175
|
+
export function renderCompactTable(reportPath: string): string {
|
|
176
|
+
const { payload } = loadReport(reportPath);
|
|
177
|
+
const records = (payload['records'] as Dict[] | undefined) ?? [];
|
|
178
|
+
const host = String(payload['model'] ?? 'unknown');
|
|
179
|
+
const cmp = compare(records, 'package', 'vanilla') as Dict;
|
|
180
|
+
const nPairs = Number(cmp['n_pairs'] ?? 0);
|
|
181
|
+
const cap = cmp['capability'] as Dict;
|
|
182
|
+
const disc = cmp['discipline'] as Dict;
|
|
183
|
+
const tv = meanTokens(records, 'vanilla');
|
|
184
|
+
const tp = meanTokens(records, 'package');
|
|
185
|
+
const capB = num(cap['rate_baseline']);
|
|
186
|
+
const capT = num(cap['rate_treatment']);
|
|
187
|
+
const discB = num(disc['mean_baseline']);
|
|
188
|
+
const discT = num(disc['mean_treatment']);
|
|
189
|
+
const dPP = Math.round((capT - capB) * 100);
|
|
190
|
+
const ratio = tv > 0 ? ` (~${Math.round(tp / tv)}×)` : '';
|
|
191
|
+
const sign = (v: number, f: (x: number) => string): string =>
|
|
192
|
+
v < 0 ? `−${f(Math.abs(v))}` : `+${f(v)}`;
|
|
193
|
+
return [
|
|
194
|
+
`| axis | vanilla | package | Δ | test |`,
|
|
195
|
+
`|---|---|---|---|---|`,
|
|
196
|
+
`| capability (pass-rate) | ${Math.round(capB * 100)}% | ${Math.round(capT * 100)}% | ` +
|
|
197
|
+
`${sign(dPP, (x) => String(x))}pp | McNemar p=${pyf(cap['mcnemar_p'])}, h=${pyf(cap['cohens_h'])} |`,
|
|
198
|
+
`| discipline (0–1) | ${discB.toFixed(3)} | ${discT.toFixed(3)} | ` +
|
|
199
|
+
`${sign(discT - discB, (x) => x.toFixed(3))} | Wilcoxon p=${pyf(disc['wilcoxon_p'])}, ` +
|
|
200
|
+
`rb=${pyf(disc['rank_biserial'])} (n≠0=${disc['n_nonzero']}) |`,
|
|
201
|
+
`| mean tokens/run | ${fmtInt(tv)} | ${fmtInt(tp)} | ${sign(tp - tv, fmtInt)}${ratio} | — |`,
|
|
202
|
+
``,
|
|
203
|
+
`(host \`${host}\`, n=${nPairs} pairs — generated from the pinned report)`,
|
|
204
|
+
].join('\n');
|
|
205
|
+
}
|
|
206
|
+
|
|
207
|
+
export function renderArmCostTable(
|
|
208
|
+
reportPath: string,
|
|
209
|
+
arms: ReadonlyArray<{ id: string; label: string }>,
|
|
210
|
+
): string {
|
|
211
|
+
const { payload } = loadReport(reportPath);
|
|
212
|
+
const records = (payload['records'] as Dict[] | undefined) ?? [];
|
|
213
|
+
const baseTokens = meanTokens(records, 'vanilla');
|
|
214
|
+
const rows: string[] = [
|
|
215
|
+
`| arm | loaded content | injected chars | mean tokens/run | cost factor | mean discipline | lift vs vanilla |`,
|
|
216
|
+
`|---|---|---|---|---|---|---|`,
|
|
217
|
+
];
|
|
218
|
+
for (const arm of arms) {
|
|
219
|
+
const t = meanTokens(records, arm.id);
|
|
220
|
+
const factor = baseTokens > 0 ? (t / baseTokens).toFixed(1) : '?';
|
|
221
|
+
const disc = disciplineMean(records, arm.id);
|
|
222
|
+
let injected = 0;
|
|
223
|
+
for (const rec of records) {
|
|
224
|
+
for (const r of armRuns(rec, arm.id)) {
|
|
225
|
+
injected = Math.max(injected, Number(r['injected_chars'] ?? 0));
|
|
226
|
+
}
|
|
227
|
+
}
|
|
228
|
+
if (arm.id === 'vanilla') {
|
|
229
|
+
rows.push(
|
|
230
|
+
`| \`vanilla\` | ${arm.label} | 0 | ${fmtInt(t)} | 1.0× | ${disc.toFixed(3)} | — |`,
|
|
231
|
+
);
|
|
232
|
+
continue;
|
|
233
|
+
}
|
|
234
|
+
const cmp = compare(records, arm.id, 'vanilla') as Dict;
|
|
235
|
+
const d = cmp['discipline'] as Dict;
|
|
236
|
+
const delta = num(d['mean_delta']);
|
|
237
|
+
const p = num(d['wilcoxon_p']);
|
|
238
|
+
const deltaStr = `${delta < 0 ? '−' : '+'}${Math.abs(delta).toFixed(3)}`;
|
|
239
|
+
const lift =
|
|
240
|
+
p < 0.05
|
|
241
|
+
? `**${deltaStr} (p=${pyf(d['wilcoxon_p'])}, significant)**`
|
|
242
|
+
: `${deltaStr} (p=${pyf(d['wilcoxon_p'])}, **NULL**)`;
|
|
243
|
+
rows.push(
|
|
244
|
+
`| \`${arm.id}\` | ${arm.label} | ${fmtInt(injected)} | ${fmtInt(t)} | ` +
|
|
245
|
+
`**${factor}×** | ${disc.toFixed(3)} | ${lift} |`,
|
|
246
|
+
);
|
|
247
|
+
}
|
|
248
|
+
rows.push('');
|
|
249
|
+
rows.push('(generated from the pinned report — curated labels from `docs/benchmark.pinned.yml`)');
|
|
250
|
+
return rows.join('\n');
|
|
251
|
+
}
|
|
252
|
+
|
|
253
|
+
export function renderSection(section: PinnedSection, repoRoot: string = REPO_ROOT): string {
|
|
254
|
+
const reportPath = path.isAbsolute(section.report)
|
|
255
|
+
? section.report
|
|
256
|
+
: path.join(repoRoot, section.report);
|
|
257
|
+
switch (section.mode) {
|
|
258
|
+
case 'honesty-labels':
|
|
259
|
+
return renderHonestyLabels(reportPath);
|
|
260
|
+
case 'stats-body':
|
|
261
|
+
return renderStatsBody(reportPath);
|
|
262
|
+
case 'compact-table':
|
|
263
|
+
return renderCompactTable(reportPath);
|
|
264
|
+
case 'arm-cost-table':
|
|
265
|
+
if (!section.arms || section.arms.length === 0) {
|
|
266
|
+
throw new CompositeConfigError(`section '${section.id}' (arm-cost-table) needs arms`);
|
|
267
|
+
}
|
|
268
|
+
return renderArmCostTable(reportPath, section.arms);
|
|
269
|
+
}
|
|
270
|
+
}
|
|
271
|
+
|
|
272
|
+
// ---------------------------------------------------------------------------
|
|
273
|
+
// Marker splice — pure, unit-tested.
|
|
274
|
+
// ---------------------------------------------------------------------------
|
|
275
|
+
|
|
276
|
+
export function markerBounds(id: string): [string, string] {
|
|
277
|
+
return [`<!-- pinned:${id} -->`, `<!-- /pinned:${id} -->`];
|
|
278
|
+
}
|
|
279
|
+
|
|
280
|
+
/**
|
|
281
|
+
* Replace every marker region with its rendered content; everything outside
|
|
282
|
+
* the markers is preserved byte-for-byte. Throws on a missing or unbalanced
|
|
283
|
+
* marker so drift never passes silently.
|
|
284
|
+
*/
|
|
285
|
+
export function spliceMarkers(doc: string, contentById: ReadonlyMap<string, string>): string {
|
|
286
|
+
let out = doc;
|
|
287
|
+
for (const [id, content] of contentById) {
|
|
288
|
+
const [begin, end] = markerBounds(id);
|
|
289
|
+
const b = out.indexOf(begin);
|
|
290
|
+
const e = out.indexOf(end);
|
|
291
|
+
if (b < 0 || e < 0 || e < b) {
|
|
292
|
+
throw new CompositeConfigError(`marker pair for '${id}' missing or unbalanced in the doc`);
|
|
293
|
+
}
|
|
294
|
+
out = `${out.slice(0, b + begin.length)}\n${content}\n${out.slice(e)}`;
|
|
295
|
+
}
|
|
296
|
+
return out;
|
|
297
|
+
}
|
|
298
|
+
|
|
299
|
+
// ---------------------------------------------------------------------------
|
|
300
|
+
// CLI.
|
|
301
|
+
// ---------------------------------------------------------------------------
|
|
302
|
+
|
|
303
|
+
const USAGE = 'usage: render_benchmark_composite [-h] [--check]\n';
|
|
304
|
+
|
|
305
|
+
export function main(argv: string[] | null = null): number {
|
|
306
|
+
const args = argv !== null ? Array.from(argv) : process.argv.slice(2);
|
|
307
|
+
let check = false;
|
|
308
|
+
for (const a of args) {
|
|
309
|
+
if (a === '-h' || a === '--help') {
|
|
310
|
+
process.stdout.write(USAGE);
|
|
311
|
+
return 0;
|
|
312
|
+
}
|
|
313
|
+
if (a === '--check') {
|
|
314
|
+
check = true;
|
|
315
|
+
} else {
|
|
316
|
+
process.stderr.write(USAGE);
|
|
317
|
+
return 2;
|
|
318
|
+
}
|
|
319
|
+
}
|
|
320
|
+
let sections: PinnedSection[];
|
|
321
|
+
try {
|
|
322
|
+
sections = parseManifest(fs.readFileSync(MANIFEST_PATH, 'utf-8'));
|
|
323
|
+
} catch (exc) {
|
|
324
|
+
process.stderr.write(`render_benchmark_composite: error: ${(exc as Error).message}\n`);
|
|
325
|
+
return 2;
|
|
326
|
+
}
|
|
327
|
+
const missing = sections.filter(
|
|
328
|
+
(s) => !fs.existsSync(path.isAbsolute(s.report) ? s.report : path.join(REPO_ROOT, s.report)),
|
|
329
|
+
);
|
|
330
|
+
if (missing.length > 0) {
|
|
331
|
+
process.stdout.write(
|
|
332
|
+
`render_benchmark_composite: SKIP — pinned report(s) not present locally ` +
|
|
333
|
+
`(${missing.map((m) => m.id).join(', ')}); nothing rendered.\n`,
|
|
334
|
+
);
|
|
335
|
+
return 0;
|
|
336
|
+
}
|
|
337
|
+
const contentById = new Map<string, string>();
|
|
338
|
+
try {
|
|
339
|
+
for (const s of sections) {
|
|
340
|
+
contentById.set(s.id, renderSection(s));
|
|
341
|
+
}
|
|
342
|
+
const doc = fs.readFileSync(DOC_PATH, 'utf-8');
|
|
343
|
+
const next = spliceMarkers(doc, contentById);
|
|
344
|
+
if (check) {
|
|
345
|
+
if (next !== doc) {
|
|
346
|
+
process.stderr.write(
|
|
347
|
+
'render_benchmark_composite: DRIFT — docs/benchmark.md pinned sections ' +
|
|
348
|
+
'do not match their pinned reports; re-run without --check.\n',
|
|
349
|
+
);
|
|
350
|
+
return 1;
|
|
351
|
+
}
|
|
352
|
+
process.stdout.write('render_benchmark_composite: clean — pinned sections match.\n');
|
|
353
|
+
return 0;
|
|
354
|
+
}
|
|
355
|
+
if (next !== doc) {
|
|
356
|
+
fs.writeFileSync(DOC_PATH, next);
|
|
357
|
+
process.stdout.write(`render_benchmark_composite: wrote docs/benchmark.md (${contentById.size} section(s)).\n`);
|
|
358
|
+
} else {
|
|
359
|
+
process.stdout.write('render_benchmark_composite: no changes.\n');
|
|
360
|
+
}
|
|
361
|
+
return 0;
|
|
362
|
+
} catch (exc) {
|
|
363
|
+
if (exc instanceof CompositeConfigError) {
|
|
364
|
+
process.stderr.write(`render_benchmark_composite: error: ${exc.message}\n`);
|
|
365
|
+
return 2;
|
|
366
|
+
}
|
|
367
|
+
throw exc;
|
|
368
|
+
}
|
|
369
|
+
}
|
|
370
|
+
|
|
371
|
+
// --- CLI entry ---
|
|
372
|
+
|
|
373
|
+
const _isCliEntry =
|
|
374
|
+
process.argv[1] !== undefined &&
|
|
375
|
+
import.meta.url === pathToFileURL(path.resolve(process.argv[1])).href;
|
|
376
|
+
if (_isCliEntry || process.argv[1] === _HERE) {
|
|
377
|
+
process.exitCode = main(process.argv.slice(2));
|
|
378
|
+
}
|