@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,619 @@
|
|
|
1
|
+
#!/usr/bin/env tsx
|
|
2
|
+
/**
|
|
3
|
+
* The authoring-search record — `composition_review` (road-to-composition-before-creation).
|
|
4
|
+
*
|
|
5
|
+
* The estate is 299 skills, 120 rules and 114 guidelines. Two gates measure its
|
|
6
|
+
* SIZE and three tools measure overlap AFTER the fact. None of them holds the
|
|
7
|
+
* authoring-time record: `artifact-drafting-protocol` Phase B mandates a
|
|
8
|
+
* four-surface overlap scan and ships `instruction-only`, so a skill authored
|
|
9
|
+
* without one and a skill authored after a thorough one are indiscernible in
|
|
10
|
+
* the tree.
|
|
11
|
+
*
|
|
12
|
+
* Measured 2026-08-27 over the twenty most recently added skills and rules
|
|
13
|
+
* (`agents/evidence/analysis/authoring-search-record-sample-2026-08-27.md`):
|
|
14
|
+
* **1 of 20** carried a machine-readable record, **9 of 20** carried a strict
|
|
15
|
+
* prose one, **18 of 20** carried something loose enough to count if you
|
|
16
|
+
* squinted. The kill criterion in that roadmap did not fire on either reading a
|
|
17
|
+
* two-seat council was willing to adopt.
|
|
18
|
+
*
|
|
19
|
+
* ## What this gate does, and the split that matters
|
|
20
|
+
*
|
|
21
|
+
* **HARD (exit 1) — a `composition_review` that is present and malformed.** A
|
|
22
|
+
* `candidate` that resolves to no artifact in the tree, a `none` candidate
|
|
23
|
+
* paired with a disposition other than `none_found` (or the reverse), or a
|
|
24
|
+
* duplicate candidate inside one block. A record pointing at an incumbent that
|
|
25
|
+
* does not exist is worse than no record: the reference is checkable and wrong.
|
|
26
|
+
*
|
|
27
|
+
* **What that certifies is REFERENTIAL CONSISTENCY, never that a search
|
|
28
|
+
* happened.** Narrowed after an independent review, where both seats made the
|
|
29
|
+
* point independently: an author can name any existing artifact, write
|
|
30
|
+
* `create_separate` and a sixteen-character rationale, and pass every check
|
|
31
|
+
* here. The earlier wording said such a record "reads as evidence of a search
|
|
32
|
+
* that cannot have happened", which is a claim about the search — and this gate
|
|
33
|
+
* has none to make. It buys that a record cannot point at nothing; whether it
|
|
34
|
+
* points at the RIGHT thing is a reviewer's judgement, and the roadmap's own
|
|
35
|
+
* Risk 1 is that the field becomes a pro-forma line.
|
|
36
|
+
*
|
|
37
|
+
* **ADVISORY (never fails) — an artifact ADDED relative to a base ref carrying
|
|
38
|
+
* no record at all.** Reported by count and by path, exit 0. Flipping this to a
|
|
39
|
+
* block needs a false-positive rate from at least one release of advisory
|
|
40
|
+
* operation, which this roadmap does not have and deliberately does not
|
|
41
|
+
* pre-authorise: a measurement is not a gate.
|
|
42
|
+
*
|
|
43
|
+
* ## What it cannot do, stated rather than implied
|
|
44
|
+
*
|
|
45
|
+
* It checks that a delta was WRITTEN. It cannot check that the search happened,
|
|
46
|
+
* that the rationale is true, or that the incumbent named is the nearest one.
|
|
47
|
+
* The council that scoped it said so in both seats, and the roadmap's own Risk 1
|
|
48
|
+
* is that the field becomes a pro-forma line. Nothing here refutes that risk;
|
|
49
|
+
* the advisory-first sequencing is what is supposed to measure it.
|
|
50
|
+
*
|
|
51
|
+
* ## Why the corpus is the scan scope and the diff is only the finding
|
|
52
|
+
*
|
|
53
|
+
* A diff-scoped gate legitimately scans 0 on most runs and can carry no honest
|
|
54
|
+
* `min_scanned` floor — `lint_evidence_artifacts` declines registration in
|
|
55
|
+
* `gate-coverage.yml` for exactly that reason. This gate walks the whole
|
|
56
|
+
* skill+rule corpus for its structural half, so its floor is real, and narrows
|
|
57
|
+
* to the diff only for the advisory half.
|
|
58
|
+
*
|
|
59
|
+
* The addition set unions `git ls-files` with `git ls-files --others
|
|
60
|
+
* --exclude-standard`: a create-only canary plants an UNTRACKED file, and a
|
|
61
|
+
* plain `ls-files` cannot see it, so the gate would report green on its own
|
|
62
|
+
* canary.
|
|
63
|
+
*
|
|
64
|
+
* Exit codes: 0 = pass, 1 = policy violation (a dead scan scope included — a
|
|
65
|
+
* gate that read nothing has not passed), 2 = internal error. `scanned:` is
|
|
66
|
+
* emitted on every path.
|
|
67
|
+
*/
|
|
68
|
+
|
|
69
|
+
import { execFileSync } from 'node:child_process';
|
|
70
|
+
import * as fs from 'node:fs';
|
|
71
|
+
import * as os from 'node:os';
|
|
72
|
+
import * as path from 'node:path';
|
|
73
|
+
import { fileURLToPath, pathToFileURL } from 'node:url';
|
|
74
|
+
|
|
75
|
+
import { GateLedger } from './_lib/gate_ledger.js';
|
|
76
|
+
import { runGateCli, runSelfTest } from './_lib/gate_self_test.js';
|
|
77
|
+
import { DeadScopeError, reportScanned } from './_lib/scan_scope.js';
|
|
78
|
+
|
|
79
|
+
const GATE = 'lint_composition_review';
|
|
80
|
+
|
|
81
|
+
/** The four values `composition_review[].disposition` may take. */
|
|
82
|
+
export const COMPOSITION_DISPOSITIONS = [
|
|
83
|
+
'extend_incumbent',
|
|
84
|
+
'compose_with_incumbent',
|
|
85
|
+
'create_separate',
|
|
86
|
+
'none_found',
|
|
87
|
+
] as const;
|
|
88
|
+
export type CompositionDisposition = (typeof COMPOSITION_DISPOSITIONS)[number];
|
|
89
|
+
|
|
90
|
+
/** The candidate value that records "searched, found no credible incumbent". */
|
|
91
|
+
export const NO_INCUMBENT = 'none';
|
|
92
|
+
|
|
93
|
+
export interface Violation {
|
|
94
|
+
kind: string;
|
|
95
|
+
file: string;
|
|
96
|
+
detail: string;
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
export interface Advisory {
|
|
100
|
+
file: string;
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
/** Thrown when a diff-scoped read was requested and git could not answer it. */
|
|
104
|
+
export class GitScopeError extends Error {
|
|
105
|
+
constructor(command: string, cause: unknown) {
|
|
106
|
+
super(
|
|
107
|
+
`git ${command} failed, so the added-artifact set is UNKNOWN rather than empty — a ` +
|
|
108
|
+
'diff-scoped reading that silently reports zero is indistinguishable from a clean ' +
|
|
109
|
+
`one. Underlying error: ${String(cause)}`,
|
|
110
|
+
);
|
|
111
|
+
this.name = 'GitScopeError';
|
|
112
|
+
}
|
|
113
|
+
}
|
|
114
|
+
|
|
115
|
+
/** Sentinel for a field whose value was a YAML block scalar this parser refuses. */
|
|
116
|
+
export const BLOCK_SCALAR = '\u0000block-scalar';
|
|
117
|
+
|
|
118
|
+
/** `skill:foo`, `rule:bar`, `command:baz`, `guideline:a/b`, or the literal `none`. */
|
|
119
|
+
// Path segments are non-empty, so `guideline:/foo`, `guideline:foo/` and
|
|
120
|
+
// `guideline:foo//bar` are rejected rather than accepted-then-unresolvable.
|
|
121
|
+
// Found by review: the previous class admitted all three.
|
|
122
|
+
const CANDIDATE_RE = /^(skill|rule|command|guideline):([A-Za-z0-9._-]+(?:\/[A-Za-z0-9._-]+)*)$/;
|
|
123
|
+
|
|
124
|
+
function _frontmatter(text: string): string | null {
|
|
125
|
+
if (!text.startsWith('---')) return null;
|
|
126
|
+
const end = text.indexOf('\n---', 3);
|
|
127
|
+
return end === -1 ? null : text.slice(text.indexOf('\n') + 1, end + 1);
|
|
128
|
+
}
|
|
129
|
+
|
|
130
|
+
export interface CompositionEntry {
|
|
131
|
+
candidate: string;
|
|
132
|
+
disposition: string;
|
|
133
|
+
rationale: string;
|
|
134
|
+
}
|
|
135
|
+
|
|
136
|
+
/**
|
|
137
|
+
* Read `composition_review` out of a frontmatter block.
|
|
138
|
+
*
|
|
139
|
+
* A deliberately small YAML reader rather than a dependency: the block shape is
|
|
140
|
+
* pinned by the JSON schema (`skill.schema.json` / `rule.schema.json`), which is
|
|
141
|
+
* what `validate_frontmatter` enforces. This parser exists to find the entries,
|
|
142
|
+
* not to re-validate their types — a malformed block that the schema rejects is
|
|
143
|
+
* that gate's finding, not this one's, and duplicating the check would produce
|
|
144
|
+
* two error messages for one defect.
|
|
145
|
+
*
|
|
146
|
+
* @returns `null` when the artifact carries no block at all.
|
|
147
|
+
*/
|
|
148
|
+
export function parseCompositionReview(text: string): CompositionEntry[] | null {
|
|
149
|
+
const fm = _frontmatter(text);
|
|
150
|
+
if (fm === null || !/^composition_review:/m.test(fm)) return null;
|
|
151
|
+
|
|
152
|
+
const lines = fm.split('\n');
|
|
153
|
+
const start = lines.findIndex((l) => /^composition_review:/.test(l));
|
|
154
|
+
const entries: CompositionEntry[] = [];
|
|
155
|
+
let cur: Partial<CompositionEntry> | null = null;
|
|
156
|
+
|
|
157
|
+
for (let i = start + 1; i < lines.length; i++) {
|
|
158
|
+
const line = lines[i] as string;
|
|
159
|
+
if (/^\S/.test(line)) break; // dedented back to a sibling key
|
|
160
|
+
const item = /^\s*-\s+(\w+):\s*(.*)$/.exec(line);
|
|
161
|
+
if (item) {
|
|
162
|
+
if (cur !== null) entries.push(cur as CompositionEntry);
|
|
163
|
+
cur = {};
|
|
164
|
+
cur[item[1] as keyof CompositionEntry] = _unquote(item[2] as string);
|
|
165
|
+
continue;
|
|
166
|
+
}
|
|
167
|
+
const field = /^\s+(\w+):\s*(.*)$/.exec(line);
|
|
168
|
+
if (field && cur !== null) {
|
|
169
|
+
const raw = (field[2] as string).trim();
|
|
170
|
+
// A YAML block scalar (`rationale: |`) would otherwise capture the
|
|
171
|
+
// `|` itself and yield a one-character value — valid YAML read
|
|
172
|
+
// wrongly, which is worse than a parse error because it looks like
|
|
173
|
+
// data. Found by review. Marked, so the caller reports it.
|
|
174
|
+
cur[field[1] as keyof CompositionEntry] = /^[|>][-+0-9]*$/.test(raw)
|
|
175
|
+
? BLOCK_SCALAR
|
|
176
|
+
: _unquote(field[2] as string);
|
|
177
|
+
}
|
|
178
|
+
}
|
|
179
|
+
if (cur !== null) entries.push(cur as CompositionEntry);
|
|
180
|
+
return entries;
|
|
181
|
+
}
|
|
182
|
+
|
|
183
|
+
function _unquote(s: string): string {
|
|
184
|
+
const t = s.trim();
|
|
185
|
+
if ((t.startsWith('"') && t.endsWith('"')) || (t.startsWith("'") && t.endsWith("'"))) {
|
|
186
|
+
return t.slice(1, -1);
|
|
187
|
+
}
|
|
188
|
+
return t;
|
|
189
|
+
}
|
|
190
|
+
|
|
191
|
+
/**
|
|
192
|
+
* Every artifact id the tree defines, across all four kinds.
|
|
193
|
+
*
|
|
194
|
+
* **All four, and that is a correction.** The first version resolved only
|
|
195
|
+
* `skill:` and `rule:`, and carried a comment saying a `command:` or
|
|
196
|
+
* `guideline:` id "lives in a tree this gate does not walk" — while this
|
|
197
|
+
* module's own contract and BOTH schema descriptions said a lint checks that
|
|
198
|
+
* `candidate` resolves. An independent cross-model review found the
|
|
199
|
+
* contradiction: `candidate: command:this-does-not-exist` passed every check.
|
|
200
|
+
* Two ways to close it — narrow the claim, or walk the trees. The trees are
|
|
201
|
+
* right here, so the claim stands and the code now earns it.
|
|
202
|
+
*/
|
|
203
|
+
export function artefactIds(repo: string): Set<string> {
|
|
204
|
+
const ids = new Set<string>();
|
|
205
|
+
const skills = path.join(repo, 'src', 'skills');
|
|
206
|
+
if (fs.existsSync(skills)) {
|
|
207
|
+
for (const e of fs.readdirSync(skills, { withFileTypes: true })) {
|
|
208
|
+
if (e.isDirectory() && fs.existsSync(path.join(skills, e.name, 'SKILL.md'))) {
|
|
209
|
+
ids.add(`skill:${e.name}`);
|
|
210
|
+
}
|
|
211
|
+
}
|
|
212
|
+
}
|
|
213
|
+
const rules = path.join(repo, 'src', 'rules');
|
|
214
|
+
if (fs.existsSync(rules)) {
|
|
215
|
+
for (const e of fs.readdirSync(rules, { withFileTypes: true })) {
|
|
216
|
+
if (e.isFile() && e.name.endsWith('.md')) ids.add(`rule:${e.name.slice(0, -3)}`);
|
|
217
|
+
}
|
|
218
|
+
}
|
|
219
|
+
|
|
220
|
+
// Commands are `src/domains/<pack>/<path...>/command.md`, addressed by the
|
|
221
|
+
// path BELOW the pack — `refine-ticket`, `roadmap/create`. The pack is a
|
|
222
|
+
// distribution detail, not part of the name a rule or skill cites.
|
|
223
|
+
const domains = path.join(repo, 'src', 'domains');
|
|
224
|
+
for (const pack of _dirs(domains)) {
|
|
225
|
+
const walk = (rel: string): void => {
|
|
226
|
+
for (const e of fs.readdirSync(path.join(domains, pack, rel), { withFileTypes: true })) {
|
|
227
|
+
if (e.isDirectory()) walk(path.join(rel, e.name));
|
|
228
|
+
else if (e.name === 'command.md' && rel !== '') {
|
|
229
|
+
ids.add(`command:${rel.split(path.sep).join('/')}`);
|
|
230
|
+
}
|
|
231
|
+
}
|
|
232
|
+
};
|
|
233
|
+
try {
|
|
234
|
+
walk('');
|
|
235
|
+
} catch {
|
|
236
|
+
// An unreadable pack resolves no command — the conservative
|
|
237
|
+
// direction, since the candidate then fails rather than passing.
|
|
238
|
+
}
|
|
239
|
+
}
|
|
240
|
+
|
|
241
|
+
// Guidelines are `docs/guidelines/<path>.md`, addressed without the
|
|
242
|
+
// extension — `code-clarity`, `agent-infra/rule-type-governance`.
|
|
243
|
+
const guidelines = path.join(repo, 'docs', 'guidelines');
|
|
244
|
+
const walkG = (rel: string): void => {
|
|
245
|
+
for (const e of fs.readdirSync(path.join(guidelines, rel), { withFileTypes: true })) {
|
|
246
|
+
if (e.isDirectory()) walkG(path.join(rel, e.name));
|
|
247
|
+
else if (e.name.endsWith('.md')) {
|
|
248
|
+
ids.add(`guideline:${path.join(rel, e.name.slice(0, -3)).split(path.sep).join('/')}`);
|
|
249
|
+
}
|
|
250
|
+
}
|
|
251
|
+
};
|
|
252
|
+
try {
|
|
253
|
+
walkG('');
|
|
254
|
+
} catch {
|
|
255
|
+
// Same direction: an unreadable tree resolves nothing, never everything.
|
|
256
|
+
}
|
|
257
|
+
|
|
258
|
+
return ids;
|
|
259
|
+
}
|
|
260
|
+
|
|
261
|
+
function _dirs(root: string): string[] {
|
|
262
|
+
try {
|
|
263
|
+
return fs.readdirSync(root, { withFileTypes: true }).filter((e) => e.isDirectory()).map((e) => e.name);
|
|
264
|
+
} catch {
|
|
265
|
+
return [];
|
|
266
|
+
}
|
|
267
|
+
}
|
|
268
|
+
|
|
269
|
+
/**
|
|
270
|
+
* Structural check on one artifact's record. Returns [] when it carries none —
|
|
271
|
+
* absence is the ADVISORY half and is never a violation here.
|
|
272
|
+
*/
|
|
273
|
+
export function checkArtefact(rel: string, text: string, known: ReadonlySet<string>): Violation[] {
|
|
274
|
+
const entries = parseCompositionReview(text);
|
|
275
|
+
if (entries === null) return [];
|
|
276
|
+
|
|
277
|
+
// `composition_review: []` is PRESENT and says nothing — neither of the two
|
|
278
|
+
// states this gate distinguishes. The schema's `minItems: 1` rejects it, but
|
|
279
|
+
// a gate whose comments claim "present and malformed → exit 1" has to deliver
|
|
280
|
+
// that itself rather than rely on another gate having run first. Found by
|
|
281
|
+
// review: the parser returned `[]` and this returned no violation.
|
|
282
|
+
if (entries.length === 0) {
|
|
283
|
+
return [
|
|
284
|
+
{
|
|
285
|
+
kind: 'empty-record',
|
|
286
|
+
file: rel,
|
|
287
|
+
detail:
|
|
288
|
+
'a `composition_review:` block is present but has no entries — record at least one ' +
|
|
289
|
+
'candidate, or omit the block entirely. Absence is advisory; an empty claim is not.',
|
|
290
|
+
},
|
|
291
|
+
];
|
|
292
|
+
}
|
|
293
|
+
|
|
294
|
+
const out: Violation[] = [];
|
|
295
|
+
const seen = new Set<string>();
|
|
296
|
+
for (const [i, e] of entries.entries()) {
|
|
297
|
+
const where = `entry ${String(i + 1)}`;
|
|
298
|
+
const cand = (e.candidate ?? '').trim();
|
|
299
|
+
const disp = (e.disposition ?? '').trim();
|
|
300
|
+
|
|
301
|
+
if (cand === '') {
|
|
302
|
+
out.push({ kind: 'missing-candidate', file: rel, detail: `${where}: no \`candidate\`.` });
|
|
303
|
+
continue;
|
|
304
|
+
}
|
|
305
|
+
const blockScalar = [e.candidate, e.disposition, e.rationale].filter((v) => v === BLOCK_SCALAR);
|
|
306
|
+
if (blockScalar.length > 0) {
|
|
307
|
+
out.push({
|
|
308
|
+
kind: 'block-scalar-field',
|
|
309
|
+
file: rel,
|
|
310
|
+
detail: `${where}: a field uses a YAML block scalar (\`|\` / \`>\`). This gate reads one line per field, so a block scalar would be read as its marker character rather than its text — write the value inline.`,
|
|
311
|
+
});
|
|
312
|
+
continue;
|
|
313
|
+
}
|
|
314
|
+
if (seen.has(cand)) {
|
|
315
|
+
out.push({
|
|
316
|
+
kind: 'duplicate-candidate',
|
|
317
|
+
file: rel,
|
|
318
|
+
detail: `${where}: \`${cand}\` is recorded twice — one candidate, one disposition.`,
|
|
319
|
+
});
|
|
320
|
+
}
|
|
321
|
+
seen.add(cand);
|
|
322
|
+
|
|
323
|
+
if (!(COMPOSITION_DISPOSITIONS as readonly string[]).includes(disp)) {
|
|
324
|
+
out.push({
|
|
325
|
+
kind: 'unknown-disposition',
|
|
326
|
+
file: rel,
|
|
327
|
+
detail: `${where}: disposition \`${disp}\` is not one of ${COMPOSITION_DISPOSITIONS.join(', ')}.`,
|
|
328
|
+
});
|
|
329
|
+
}
|
|
330
|
+
|
|
331
|
+
// `none` and `none_found` are the same fact stated twice; either one
|
|
332
|
+
// without the other is a record whose meaning cannot be recovered.
|
|
333
|
+
if (cand === NO_INCUMBENT && disp !== 'none_found') {
|
|
334
|
+
out.push({
|
|
335
|
+
kind: 'none-mismatch',
|
|
336
|
+
file: rel,
|
|
337
|
+
detail: `${where}: candidate \`none\` requires disposition \`none_found\`, got \`${disp}\`.`,
|
|
338
|
+
});
|
|
339
|
+
} else if (cand !== NO_INCUMBENT && disp === 'none_found') {
|
|
340
|
+
out.push({
|
|
341
|
+
kind: 'none-mismatch',
|
|
342
|
+
file: rel,
|
|
343
|
+
detail: `${where}: disposition \`none_found\` requires candidate \`none\`, got \`${cand}\`.`,
|
|
344
|
+
});
|
|
345
|
+
}
|
|
346
|
+
|
|
347
|
+
if (cand !== NO_INCUMBENT) {
|
|
348
|
+
const m = CANDIDATE_RE.exec(cand);
|
|
349
|
+
if (m === null) {
|
|
350
|
+
out.push({
|
|
351
|
+
kind: 'malformed-candidate',
|
|
352
|
+
file: rel,
|
|
353
|
+
detail: `${where}: \`${cand}\` is not \`skill:<name>\` / \`rule:<name>\` / \`command:<name>\` / \`guideline:<path>\` / \`none\`.`,
|
|
354
|
+
});
|
|
355
|
+
} else if (!known.has(cand)) {
|
|
356
|
+
// All four kinds, since `artefactIds` now walks all four. The
|
|
357
|
+
// carve-out that used to sit here let `command:` and
|
|
358
|
+
// `guideline:` ids through unchecked while the contract claimed
|
|
359
|
+
// otherwise — found by review, closed rather than documented.
|
|
360
|
+
out.push({
|
|
361
|
+
kind: 'unresolvable-candidate',
|
|
362
|
+
file: rel,
|
|
363
|
+
detail: `${where}: \`${cand}\` names no artifact in the tree. The reference is checkable and wrong — which is all this gate can tell you; it cannot tell you whether a search happened.`,
|
|
364
|
+
});
|
|
365
|
+
}
|
|
366
|
+
}
|
|
367
|
+
}
|
|
368
|
+
return out;
|
|
369
|
+
}
|
|
370
|
+
|
|
371
|
+
function _corpus(repo: string): string[] {
|
|
372
|
+
const out: string[] = [];
|
|
373
|
+
const skills = path.join(repo, 'src', 'skills');
|
|
374
|
+
if (fs.existsSync(skills)) {
|
|
375
|
+
for (const e of fs.readdirSync(skills, { withFileTypes: true })) {
|
|
376
|
+
const f = path.join('src', 'skills', e.name, 'SKILL.md');
|
|
377
|
+
if (e.isDirectory() && fs.existsSync(path.join(repo, f))) out.push(f);
|
|
378
|
+
}
|
|
379
|
+
}
|
|
380
|
+
const rules = path.join(repo, 'src', 'rules');
|
|
381
|
+
if (fs.existsSync(rules)) {
|
|
382
|
+
for (const e of fs.readdirSync(rules, { withFileTypes: true })) {
|
|
383
|
+
if (e.isFile() && e.name.endsWith('.md')) out.push(path.join('src', 'rules', e.name));
|
|
384
|
+
}
|
|
385
|
+
}
|
|
386
|
+
return out.sort();
|
|
387
|
+
}
|
|
388
|
+
|
|
389
|
+
/**
|
|
390
|
+
* Artifacts ADDED relative to `baseRef`, unioned with untracked files.
|
|
391
|
+
*
|
|
392
|
+
* The union is load-bearing: a create-only canary plants an untracked artifact,
|
|
393
|
+
* and `git diff --name-only` against a base ref cannot see it, so the gate would
|
|
394
|
+
* report green on its own canary and the advisory half would be blind exactly
|
|
395
|
+
* where it is being tested.
|
|
396
|
+
*/
|
|
397
|
+
export function addedArtefacts(repo: string, baseRef: string, corpus: readonly string[]): string[] {
|
|
398
|
+
const inCorpus = new Set(corpus);
|
|
399
|
+
const added = new Set<string>();
|
|
400
|
+
const run = (args: readonly string[]): string[] => {
|
|
401
|
+
try {
|
|
402
|
+
return execFileSync('git', args, { cwd: repo, encoding: 'utf-8' })
|
|
403
|
+
.split('\n')
|
|
404
|
+
.map((s) => s.trim())
|
|
405
|
+
.filter((s) => s !== '');
|
|
406
|
+
} catch (exc) {
|
|
407
|
+
throw new GitScopeError(args.join(' '), exc);
|
|
408
|
+
}
|
|
409
|
+
};
|
|
410
|
+
for (const f of run(['diff', '--name-only', '--diff-filter=A', `${baseRef}...HEAD`])) {
|
|
411
|
+
if (inCorpus.has(f)) added.add(f);
|
|
412
|
+
}
|
|
413
|
+
for (const f of run(['ls-files', '--others', '--exclude-standard'])) {
|
|
414
|
+
if (inCorpus.has(f)) added.add(f);
|
|
415
|
+
}
|
|
416
|
+
return [...added].sort();
|
|
417
|
+
}
|
|
418
|
+
|
|
419
|
+
export function main(argv?: readonly string[]): number {
|
|
420
|
+
const args = argv ?? process.argv.slice(2);
|
|
421
|
+
let repo = process.cwd();
|
|
422
|
+
let baseRef = '';
|
|
423
|
+
let quiet = false;
|
|
424
|
+
for (let i = 0; i < args.length; i++) {
|
|
425
|
+
const a = args[i] as string;
|
|
426
|
+
if (a === '--repo' || a === '--root') repo = args[++i] as string;
|
|
427
|
+
else if (a === '--base-ref') baseRef = args[++i] as string;
|
|
428
|
+
else if (a === '--quiet') quiet = true;
|
|
429
|
+
else if (a === '--self-test') return selfTest();
|
|
430
|
+
else if (a === '-h' || a === '--help') {
|
|
431
|
+
process.stdout.write(
|
|
432
|
+
'usage: lint_composition_review [--repo PATH] [--base-ref REF] [--quiet] [--self-test]\n',
|
|
433
|
+
);
|
|
434
|
+
process.stdout.write('scanned: 0\n');
|
|
435
|
+
return 0;
|
|
436
|
+
}
|
|
437
|
+
}
|
|
438
|
+
|
|
439
|
+
const corpus = _corpus(repo);
|
|
440
|
+
try {
|
|
441
|
+
reportScanned({
|
|
442
|
+
gate: GATE,
|
|
443
|
+
scanned: corpus.length,
|
|
444
|
+
units: 'skill/rule artifact(s)',
|
|
445
|
+
roots: ['src/skills', 'src/rules'],
|
|
446
|
+
});
|
|
447
|
+
} catch (exc) {
|
|
448
|
+
// A dead scope is a POLICY violation, never an internal error: exit 2 is
|
|
449
|
+
// warn-and-allow at every call site, so a moved corpus root would
|
|
450
|
+
// silently degrade this gate to advisory on its HARD half too.
|
|
451
|
+
process.stderr.write(`❌ ${exc instanceof DeadScopeError ? exc.message : String(exc)}\n`);
|
|
452
|
+
return 1;
|
|
453
|
+
}
|
|
454
|
+
|
|
455
|
+
const known = artefactIds(repo);
|
|
456
|
+
const ledger = new GateLedger(GATE);
|
|
457
|
+
ledger.plan(corpus);
|
|
458
|
+
const violations: Violation[] = [];
|
|
459
|
+
const advisories: Advisory[] = [];
|
|
460
|
+
const added = baseRef === '' ? [] : new Set(addedArtefacts(repo, baseRef, corpus));
|
|
461
|
+
|
|
462
|
+
let scanned = 0;
|
|
463
|
+
try {
|
|
464
|
+
for (const rel of corpus) {
|
|
465
|
+
const text = fs.readFileSync(path.join(repo, rel), 'utf-8');
|
|
466
|
+
scanned += 1;
|
|
467
|
+
const found = checkArtefact(rel, text, known);
|
|
468
|
+
if (found.length > 0) ledger.fail(rel, `${String(found.length)} record violation(s)`);
|
|
469
|
+
else ledger.complete(rel);
|
|
470
|
+
violations.push(...found);
|
|
471
|
+
if (added !== null && added instanceof Set && added.has(rel) && parseCompositionReview(text) === null) {
|
|
472
|
+
advisories.push({ file: rel });
|
|
473
|
+
}
|
|
474
|
+
}
|
|
475
|
+
} catch (exc) {
|
|
476
|
+
process.stderr.write(
|
|
477
|
+
`❌ ${GATE}: internal error after ${String(scanned)} artifact(s): ${String(exc)}\n`,
|
|
478
|
+
);
|
|
479
|
+
return 2;
|
|
480
|
+
}
|
|
481
|
+
ledger.report();
|
|
482
|
+
|
|
483
|
+
if (!quiet) {
|
|
484
|
+
for (const v of violations) process.stderr.write(`❌ ${v.file} — ${v.detail}\n`);
|
|
485
|
+
for (const a of advisories) {
|
|
486
|
+
process.stdout.write(
|
|
487
|
+
`⚠️ ${a.file}: added without a \`composition_review\` record — advisory, not a block.\n`,
|
|
488
|
+
);
|
|
489
|
+
}
|
|
490
|
+
}
|
|
491
|
+
|
|
492
|
+
if (violations.length > 0) {
|
|
493
|
+
process.stderr.write(
|
|
494
|
+
`❌ ${GATE}: ${String(violations.length)} malformed record(s) across ${String(corpus.length)} artifact(s).\n`,
|
|
495
|
+
);
|
|
496
|
+
return 1;
|
|
497
|
+
}
|
|
498
|
+
process.stdout.write(
|
|
499
|
+
`✅ ${GATE}: ${String(corpus.length)} artifact(s) — records well-formed; ` +
|
|
500
|
+
`${String(advisories.length)} addition(s) without one (advisory).\n`,
|
|
501
|
+
);
|
|
502
|
+
return 0;
|
|
503
|
+
}
|
|
504
|
+
|
|
505
|
+
// ── self-test ──────────────────────────────────────────────────────────────
|
|
506
|
+
|
|
507
|
+
function _fixture(files: Record<string, string>): string {
|
|
508
|
+
const d = fs.mkdtempSync(path.join(fs.realpathSync(os.tmpdir()), 'lcr-'));
|
|
509
|
+
for (const [rel, body] of Object.entries(files)) {
|
|
510
|
+
const f = path.join(d, rel);
|
|
511
|
+
fs.mkdirSync(path.dirname(f), { recursive: true });
|
|
512
|
+
fs.writeFileSync(f, body);
|
|
513
|
+
}
|
|
514
|
+
return d;
|
|
515
|
+
}
|
|
516
|
+
|
|
517
|
+
function _fm(block: string): string {
|
|
518
|
+
return `---\nname: probe\n${block}---\n\n# probe\n`;
|
|
519
|
+
}
|
|
520
|
+
|
|
521
|
+
function selfTest(): number {
|
|
522
|
+
const repoRoot = path.resolve(path.dirname(fileURLToPath(import.meta.url)), '..', '..');
|
|
523
|
+
const rel = 'src/scripts/lint_composition_review.ts';
|
|
524
|
+
const run = (files: Record<string, string>): number =>
|
|
525
|
+
runGateCli(repoRoot, rel, ['--quiet'], _fixture(files));
|
|
526
|
+
|
|
527
|
+
const incumbent = { 'src/rules/incumbent.md': _fm('') };
|
|
528
|
+
|
|
529
|
+
return runSelfTest({
|
|
530
|
+
gate: GATE,
|
|
531
|
+
minCases: 6,
|
|
532
|
+
minRejectCases: 4,
|
|
533
|
+
cases: [
|
|
534
|
+
{
|
|
535
|
+
name: 'a well-formed record naming a real incumbent is accepted',
|
|
536
|
+
expect: 'accept',
|
|
537
|
+
run: () =>
|
|
538
|
+
run({
|
|
539
|
+
...incumbent,
|
|
540
|
+
'src/rules/probe.md': _fm(
|
|
541
|
+
'composition_review:\n - candidate: rule:incumbent\n disposition: create_separate\n rationale: the incumbent cannot express this case at all\n',
|
|
542
|
+
),
|
|
543
|
+
}),
|
|
544
|
+
},
|
|
545
|
+
{
|
|
546
|
+
name: 'candidate `none` with `none_found` is accepted — a search that found nothing is a record',
|
|
547
|
+
expect: 'accept',
|
|
548
|
+
run: () =>
|
|
549
|
+
run({
|
|
550
|
+
'src/rules/probe.md': _fm(
|
|
551
|
+
'composition_review:\n - candidate: none\n disposition: none_found\n rationale: no artifact in the tree covers this surface\n',
|
|
552
|
+
),
|
|
553
|
+
}),
|
|
554
|
+
},
|
|
555
|
+
{
|
|
556
|
+
name: 'an artifact with NO record is accepted — absence is advisory, never a block',
|
|
557
|
+
expect: 'accept',
|
|
558
|
+
run: () => run({ 'src/rules/probe.md': _fm('') }),
|
|
559
|
+
},
|
|
560
|
+
{
|
|
561
|
+
name: 'a candidate naming no artifact in the tree is REJECTED',
|
|
562
|
+
expect: 'reject',
|
|
563
|
+
run: () =>
|
|
564
|
+
run({
|
|
565
|
+
'src/rules/probe.md': _fm(
|
|
566
|
+
'composition_review:\n - candidate: rule:does-not-exist\n disposition: create_separate\n rationale: this incumbent was never in the tree at all\n',
|
|
567
|
+
),
|
|
568
|
+
}),
|
|
569
|
+
},
|
|
570
|
+
{
|
|
571
|
+
name: '`none` paired with a non-`none_found` disposition is REJECTED',
|
|
572
|
+
expect: 'reject',
|
|
573
|
+
run: () =>
|
|
574
|
+
run({
|
|
575
|
+
'src/rules/probe.md': _fm(
|
|
576
|
+
'composition_review:\n - candidate: none\n disposition: extend_incumbent\n rationale: cannot extend an incumbent that was never found\n',
|
|
577
|
+
),
|
|
578
|
+
}),
|
|
579
|
+
},
|
|
580
|
+
{
|
|
581
|
+
name: '`none_found` paired with a real candidate is REJECTED — the reverse direction',
|
|
582
|
+
expect: 'reject',
|
|
583
|
+
run: () =>
|
|
584
|
+
run({
|
|
585
|
+
...incumbent,
|
|
586
|
+
'src/rules/probe.md': _fm(
|
|
587
|
+
'composition_review:\n - candidate: rule:incumbent\n disposition: none_found\n rationale: an incumbent was named and also declared absent\n',
|
|
588
|
+
),
|
|
589
|
+
}),
|
|
590
|
+
},
|
|
591
|
+
{
|
|
592
|
+
name: 'a disposition outside the enum is REJECTED',
|
|
593
|
+
expect: 'reject',
|
|
594
|
+
run: () =>
|
|
595
|
+
run({
|
|
596
|
+
...incumbent,
|
|
597
|
+
'src/rules/probe.md': _fm(
|
|
598
|
+
'composition_review:\n - candidate: rule:incumbent\n disposition: superseded\n rationale: borrowing a value from another vocabulary entirely\n',
|
|
599
|
+
),
|
|
600
|
+
}),
|
|
601
|
+
},
|
|
602
|
+
{
|
|
603
|
+
name: 'the same candidate recorded twice is REJECTED',
|
|
604
|
+
expect: 'reject',
|
|
605
|
+
run: () =>
|
|
606
|
+
run({
|
|
607
|
+
...incumbent,
|
|
608
|
+
'src/rules/probe.md': _fm(
|
|
609
|
+
'composition_review:\n - candidate: rule:incumbent\n disposition: create_separate\n rationale: the first of two contradictory dispositions\n - candidate: rule:incumbent\n disposition: extend_incumbent\n rationale: the second of two contradictory dispositions\n',
|
|
610
|
+
),
|
|
611
|
+
}),
|
|
612
|
+
},
|
|
613
|
+
],
|
|
614
|
+
});
|
|
615
|
+
}
|
|
616
|
+
|
|
617
|
+
if (process.argv[1] !== undefined && import.meta.url === pathToFileURL(process.argv[1]).href) {
|
|
618
|
+
process.exit(main());
|
|
619
|
+
}
|