@event4u/agent-config 16.0.0 → 16.2.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/.augment-plugin/marketplace.json +3 -3
- package/.augment-plugin/plugin.json +1 -1
- package/.claude-plugin/marketplace.json +1 -1
- package/CHANGELOG.md +289 -140
- package/CONTRIBUTING.md +9 -0
- package/README.md +2 -2
- package/dist/agent-src/commands/analyze/inbox.md +26 -0
- package/dist/agent-src/commands/analyze/roadmap-repos.md +27 -0
- package/dist/agent-src/commands/challenge-me/closure.md +178 -0
- package/dist/agent-src/commands/challenge-me.md +9 -2
- package/dist/agent-src/commands/council/default.md +25 -5
- package/dist/agent-src/commands/feature/plan.md +28 -1
- package/dist/agent-src/commands/feature/roadmap.md +28 -1
- package/dist/agent-src/commands/implement-ticket.md +27 -0
- package/dist/agent-src/commands/jira-ticket.md +27 -0
- package/dist/agent-src/commands/pr/create.md +29 -0
- package/dist/agent-src/commands/pr/merge.md +60 -9
- package/dist/agent-src/commands/roadmap/ai-council.md +7 -1
- package/dist/agent-src/commands/roadmap/create.md +74 -27
- package/dist/agent-src/commands/roadmap/materialize.md +27 -0
- package/dist/agent-src/commands/roadmap/process-full.md +123 -18
- package/dist/agent-src/commands/team.md +22 -0
- package/dist/agent-src/commands/work.md +4 -0
- package/dist/agent-src/contexts/authority/scope-growth-ownership.md +80 -0
- package/dist/agent-src/contexts/communication/rules-auto/user-interaction-mechanics.md +47 -0
- package/dist/agent-src/contexts/execution/auto-orchestration-activation.md +44 -3
- package/dist/agent-src/contexts/execution/autonomy-mechanics.md +19 -23
- package/dist/agent-src/contexts/execution/decision-closure-execution.md +93 -0
- package/dist/agent-src/contexts/execution/fix-loop-ladder.md +108 -0
- package/dist/agent-src/contexts/execution/plan-confidence-gate.md +4 -2
- package/dist/agent-src/contexts/execution/roadmap-process-loop.md +75 -6
- package/dist/agent-src/guidelines/agent-infra/active-remediation-mechanics.md +57 -0
- package/dist/agent-src/guidelines/agent-infra/evaluator-independence-mechanics.md +102 -0
- package/dist/agent-src/guidelines/design-fidelity-routing.md +29 -0
- package/dist/agent-src/rules/autonomous-execution.md +15 -8
- package/dist/agent-src/rules/delegation-policy.md +23 -45
- package/dist/agent-src/rules/design-fidelity.md +32 -37
- package/dist/agent-src/rules/evaluator-independence.md +43 -49
- package/dist/agent-src/rules/test-first.md +41 -0
- package/dist/agent-src/rules/ui-audit-gate.md +1 -2
- package/dist/agent-src/skills/accessibility-auditor/data/aria-patterns.csv +1 -1
- package/dist/agent-src/skills/accessibility-auditor/data/manifest.json +3 -3
- package/dist/agent-src/skills/ai-council/SKILL.md +11 -3
- package/dist/agent-src/skills/api-design/data/manifest.json +3 -3
- package/dist/agent-src/skills/database/data/manifest.json +4 -4
- package/dist/agent-src/skills/database/data/query-tuning.csv +3 -3
- package/dist/agent-src/skills/design-review/SKILL.md +80 -4
- package/dist/agent-src/skills/design-review/references/verification-automation.md +32 -0
- package/dist/agent-src/skills/existing-ui-audit/SKILL.md +30 -5
- package/dist/agent-src/skills/existing-ui-audit/references/component-taxonomy-detection.md +56 -0
- package/dist/agent-src/skills/experiment-loop/SKILL.md +31 -1
- package/dist/agent-src/skills/markitdown/SKILL.md +4 -0
- package/dist/agent-src/skills/playwright-testing/SKILL.md +30 -6
- package/dist/agent-src/skills/roadmap-writing/SKILL.md +1 -1
- package/dist/agent-src/skills/storybook-workshop/SKILL.md +42 -3
- package/dist/agent-src/skills/threat-modeling/SKILL.md +9 -4
- package/dist/agent-src/skills/threat-modeling/data/manifest.json +4 -4
- package/dist/agent-src/skills/ui-component-architect/SKILL.md +23 -0
- package/dist/agent-src/skills/verify-repair-loop/SKILL.md +37 -3
- package/dist/agent-src/templates/agent-settings.md +1 -1
- package/dist/agent-src/templates/agents/agent-project-settings.example.yml +1 -1
- package/dist/agent-src/templates/roadmaps.md +218 -0
- package/dist/agent-src/templates/scripts/work_engine/directives/backend/_self_fix.ts +62 -2
- package/dist/agent-src/templates/scripts/work_engine/directives/backend/test.ts +1 -0
- package/dist/agent-src/templates/scripts/work_engine/directives/backend/verify.ts +1 -0
- package/dist/agent-src/templates/scripts/work_engine/directives/ui/app_spec.ts +10 -4
- package/dist/agent-src/templates/scripts/work_engine/directives/ui/apply.ts +260 -21
- package/dist/agent-src/templates/scripts/work_engine/directives/ui/audit.ts +22 -3
- package/dist/agent-src/templates/scripts/work_engine/directives/ui/scaffold.ts +6 -1
- package/dist/agent-src/templates/scripts/work_engine/state.ts +57 -7
- package/dist/agent-src/templates/scripts/work_engine/taxonomy/detect.ts +500 -0
- package/dist/agent-src/templates/scripts/work_engine/taxonomy/place.ts +183 -0
- package/dist/cli/python/knowledge_ingest.js +1 -1
- package/dist/cli/python/workspace_drive.js +1 -1
- package/dist/cli-delegate/check_update_banner.js +40 -2
- package/dist/cli-delegate/chunk-4E2W63DA.js +335 -0
- package/dist/cli-delegate/{chunk-S3VFXMTM.js → chunk-4XFP2Y3E.js} +1 -1
- package/dist/cli-delegate/chunk-5R7HOLAF.js +99 -0
- package/dist/cli-delegate/{chunk-IVBPRCQO.js → chunk-6KFEC3G7.js} +1 -1
- package/dist/cli-delegate/{chunk-2TAQ2K4V.js → chunk-6KM6ORRY.js} +55 -386
- package/dist/cli-delegate/{chunk-AK6HODNQ.js → chunk-7XGBX6ZH.js} +973 -555
- package/dist/cli-delegate/{chunk-2XY7ILBT.js → chunk-FC75PGJE.js} +42 -2
- package/dist/cli-delegate/{chunk-55ACLDUE.js → chunk-FP36SWJD.js} +587 -141
- package/dist/cli-delegate/{chunk-3FKR64R5.js → chunk-IGD6ERX6.js} +62 -7
- package/dist/cli-delegate/{chunk-GYC3VRYN.js → chunk-JNMVLWLR.js} +99 -14
- package/dist/cli-delegate/{chunk-H32DGUJH.js → chunk-P6ZHP5W2.js} +1 -1
- package/dist/cli-delegate/{chunk-57PN4GYJ.js → chunk-RGNIWTEA.js} +1 -1
- package/dist/cli-delegate/chunk-ROYYTEZU.js +39 -0
- package/dist/cli-delegate/{chunk-2DK2VJWG.js → chunk-WGXTVKCQ.js} +20 -6
- package/dist/cli-delegate/chunk-YDPILCOL.js +152 -0
- package/dist/cli-delegate/{chunk-QJWAUMJW.js → chunk-YJ72UIAK.js} +730 -15
- package/dist/cli-delegate/cmd_analyze_session.js +1 -1
- package/dist/cli-delegate/cmd_brand_status.js +1 -1
- package/dist/cli-delegate/cmd_conformance.js +985 -831
- package/dist/cli-delegate/cmd_doctor.js +7 -5
- package/dist/cli-delegate/cmd_explain.js +2 -2
- package/dist/cli-delegate/cmd_export.js +9 -7
- package/dist/cli-delegate/cmd_fleet.js +4 -2
- package/dist/cli-delegate/cmd_handoff.js +7 -7
- package/dist/cli-delegate/cmd_mcp_available.js +1 -1
- package/dist/cli-delegate/cmd_memory_get.js +1 -1
- package/dist/cli-delegate/cmd_migrate.js +2 -2
- package/dist/cli-delegate/cmd_packs_active.js +2 -2
- package/dist/cli-delegate/cmd_preflight.js +4 -2
- package/dist/cli-delegate/cmd_prune.js +10 -8
- package/dist/cli-delegate/cmd_route_audit.js +3 -3
- package/dist/cli-delegate/cmd_route_explain.js +1 -1
- package/dist/cli-delegate/cmd_session_recycle.js +4 -4
- package/dist/cli-delegate/cmd_settings_get.js +28 -2
- package/dist/cli-delegate/cmd_settings_migrate.js +9 -7
- package/dist/cli-delegate/cmd_settings_set.js +1 -1
- package/dist/cli-delegate/cmd_sync.js +10 -8
- package/dist/cli-delegate/cmd_uninstall.js +16 -13
- package/dist/cli-delegate/cmd_update.js +1 -1
- package/dist/cli-delegate/cmd_upgrade.js +1 -1
- package/dist/cli-delegate/cmd_validate.js +12 -10
- package/dist/cli-delegate/cmd_versions.js +1 -1
- package/dist/discovery/deprecation-report.md +1 -1
- package/dist/discovery/discovery-manifest.json +90 -43
- package/dist/discovery/discovery-manifest.json.sha256 +1 -1
- package/dist/discovery/discovery-manifest.summary.md +4 -4
- package/dist/discovery/dormancy-report.md +1 -1
- package/dist/discovery/orphan-report.md +1 -1
- package/dist/discovery/packs.json +10 -8
- package/dist/discovery/trust-report.md +3 -3
- package/dist/discovery/workspaces.json +8 -6
- package/dist/hooks/dispatch.js +7682 -2772
- package/dist/hooks/dispatch.sha256 +1 -0
- package/dist/install/claudeRuleRewrite.js +14 -3
- package/dist/install/conflict.js +60 -10
- package/dist/install/conflictTracker.js +218 -0
- package/dist/install/fsPrimitives.js +54 -0
- package/dist/install/install.mjs +2200 -1559
- package/dist/install/manifestFiles.js +259 -0
- package/dist/install/preflight.js +14 -2
- package/dist/install/preserve.js +157 -0
- package/dist/install/recordedOwnership.js +132 -0
- package/dist/install/txlog.js +11 -2
- package/dist/mcp/registry-manifest.json +3 -3
- package/dist/mcp/server.json +2 -2
- package/dist/mcp/server.mjs +3066 -1470
- package/dist/router.json +1 -1
- package/dist/scripts/_lib/agent_settings.js +3 -2
- package/dist/scripts/_lib/fs_atomic.js +167 -0
- package/dist/scripts/_lib/installed_tools.js +460 -0
- package/dist/scripts/_lib/lean_projection_mode.js +3 -2
- package/dist/scripts/_lib/settings_renamed_keys.js +78 -0
- package/dist/server/routes/install.js +8 -0
- package/dist/server/routes/wizard.js +9 -3
- package/dist/server/schemas/settings.js +40 -5
- package/dist/shared/missionExecution.js +97 -0
- package/dist/ui/assets/index-Cwq4d0hX.js +23 -0
- package/dist/ui/assets/index-Cyjy8bWO.css +1 -0
- package/dist/ui/index.html +2 -2
- package/docs/MIGRATION.md +103 -0
- package/docs/contracts/settings-classes.md +28 -7
- package/docs/guidelines/agent-infra/active-remediation-mechanics.md +57 -0
- package/docs/guidelines/agent-infra/evaluator-independence-mechanics.md +102 -0
- package/docs/guidelines/design-fidelity-routing.md +29 -0
- package/package.json +4 -4
- package/src/agent-src/templates/scripts/work_engine/directives/backend/_self_fix.ts +62 -2
- package/src/agent-src/templates/scripts/work_engine/directives/backend/test.ts +1 -0
- package/src/agent-src/templates/scripts/work_engine/directives/backend/verify.ts +1 -0
- package/src/agent-src/templates/scripts/work_engine/directives/ui/app_spec.ts +10 -4
- package/src/agent-src/templates/scripts/work_engine/directives/ui/apply.ts +260 -21
- package/src/agent-src/templates/scripts/work_engine/directives/ui/audit.ts +22 -3
- package/src/agent-src/templates/scripts/work_engine/directives/ui/scaffold.ts +6 -1
- package/src/agent-src/templates/scripts/work_engine/state.ts +57 -7
- package/src/agent-src/templates/scripts/work_engine/taxonomy/detect.ts +500 -0
- package/src/agent-src/templates/scripts/work_engine/taxonomy/place.ts +183 -0
- package/src/config/agent-settings.template.yml +168 -27
- package/src/config/auto-rule-budget.json +16 -0
- package/src/config/ci-local-parity.yml +36 -5
- package/src/config/continuity-surface.json +23 -2
- package/src/config/gate-coverage.yml +377 -13
- package/src/config/gate-violation-baselines.json +10 -6
- package/src/config/hook-latency-budget.json +21 -0
- package/src/config/hook-token-budget.json +4 -2
- package/src/config/loop-surfaces.yaml +215 -0
- package/src/config/metric-registry.yml +114 -0
- package/src/config/placeholder-drift-budget.json +10 -0
- package/src/config/platform-anchor.json +3 -2
- package/src/config/release-gate-locality.yml +11 -0
- package/src/config/routing-coverage-seed.json +33 -5
- package/src/config/rule-enforcement-baseline.json +0 -1
- package/src/install/claudeRuleRewrite.ts +16 -2
- package/src/install/conflict.ts +65 -9
- package/src/install/conflictTracker.ts +299 -0
- package/src/install/fsPrimitives.ts +54 -0
- package/src/install/manifestFiles.ts +312 -0
- package/src/install/preflight.ts +16 -2
- package/src/install/preserve.ts +205 -0
- package/src/install/recordedOwnership.ts +149 -0
- package/src/install/txlog.ts +11 -2
- package/src/install/types.ts +19 -4
- package/src/scripts/_cli/cmd_conformance.ts +88 -15
- package/src/scripts/_cli/cmd_doctor.ts +7 -20
- package/src/scripts/_cli/cmd_route_explain.ts +9 -2
- package/src/scripts/_cli/cmd_settings_get.ts +44 -0
- package/src/scripts/_cli/doctor_execution.ts +173 -0
- package/src/scripts/_cli/doctor_network_posture.ts +226 -0
- package/src/scripts/_lib/activation_payload.ts +247 -0
- package/src/scripts/_lib/agent_settings.ts +4 -2
- package/src/scripts/_lib/authority_path.ts +102 -0
- package/src/scripts/_lib/body_portable.ts +102 -0
- package/src/scripts/_lib/cascade_base.ts +94 -0
- package/src/scripts/_lib/cc_transcript.ts +132 -1
- package/src/scripts/_lib/changelog_eras.ts +7 -2
- package/src/scripts/_lib/concern_sla_window.ts +200 -0
- package/src/scripts/_lib/continuation_ladder.ts +149 -2
- package/src/scripts/_lib/council_transport.ts +129 -0
- package/src/scripts/_lib/delivery_ready.ts +112 -0
- package/src/scripts/_lib/forge_protection.ts +381 -0
- package/src/scripts/_lib/forge_reader.ts +558 -0
- package/src/scripts/_lib/hook_platform_keys.ts +27 -0
- package/src/scripts/_lib/host_env_write.ts +111 -0
- package/src/scripts/_lib/json_python_parity.ts +127 -0
- package/src/scripts/_lib/lean_projection_mode.ts +14 -3
- package/src/scripts/_lib/llm_proposer_transport.ts +14 -4
- package/src/scripts/_lib/loop_surfaces.ts +382 -0
- package/src/scripts/_lib/mission_record.ts +190 -0
- package/src/scripts/_lib/obligation_frequency.ts +217 -0
- package/src/scripts/_lib/obligations.ts +513 -0
- package/src/scripts/_lib/orchestration_record.ts +11 -20
- package/src/scripts/_lib/probe_inputs.ts +271 -0
- package/src/scripts/_lib/read_surface_scan.ts +322 -0
- package/src/scripts/_lib/release_highlights.ts +201 -11
- package/src/scripts/_lib/release_holds.ts +493 -0
- package/src/scripts/_lib/release_migration_gate.ts +184 -0
- package/src/scripts/_lib/retrieval_sanitize.ts +77 -16
- package/src/scripts/_lib/rides_along.ts +102 -0
- package/src/scripts/_lib/settings_renamed_keys.ts +82 -0
- package/src/scripts/_lib/shell_write_shapes.ts +108 -0
- package/src/scripts/_lib/structural_hiding.ts +622 -0
- package/src/scripts/_lib/test_delta.ts +166 -0
- package/src/scripts/_lib/test_provenance.ts +171 -0
- package/src/scripts/_lib/turn_end_refusals.ts +525 -4
- package/src/scripts/_lib/turn_end_transcript.ts +216 -0
- package/src/scripts/_lib/typed_op_grant.ts +246 -0
- package/src/scripts/_lib/typed_op_watch.ts +185 -0
- package/src/scripts/_lib/verification_command.ts +324 -0
- package/src/scripts/_lib/verification_evidence.ts +647 -0
- package/src/scripts/_lib/verify_clause.ts +280 -0
- package/src/scripts/ai_council/config.ts +8 -11
- package/src/scripts/ai_council/fallback_config.ts +5 -3
- package/src/scripts/ai_council/ownership_classes.ts +118 -0
- package/src/scripts/ai_team/team_dispatch.ts +4 -0
- package/src/scripts/ask_block_census.ts +285 -15
- package/src/scripts/before_complete_hook.ts +340 -23
- package/src/scripts/bench_hook_latency.ts +412 -5
- package/src/scripts/cache_realization_report.ts +61 -1
- package/src/scripts/check_argument_hint.ts +287 -0
- package/src/scripts/check_claims.ts +125 -1
- package/src/scripts/check_enforcement_coverage.ts +38 -9
- package/src/scripts/check_enforcement_matrix.ts +866 -0
- package/src/scripts/check_finding_dispositions.ts +22 -3
- package/src/scripts/check_gate_reachability.ts +173 -10
- package/src/scripts/check_generator_sync.ts +1025 -0
- package/src/scripts/check_host_docs_digest.ts +786 -0
- package/src/scripts/check_host_format_column.ts +559 -0
- package/src/scripts/check_input_references.ts +228 -0
- package/src/scripts/check_inputs_parity.ts +235 -0
- package/src/scripts/check_kernel_edit_ratified.ts +47 -5
- package/src/scripts/check_kill_switch_table.ts +294 -0
- package/src/scripts/check_placeholder_drift.ts +221 -0
- package/src/scripts/check_preamble_payload_budget.ts +75 -2
- package/src/scripts/check_read_surface_coverage.ts +515 -0
- package/src/scripts/check_release_highlights.ts +33 -1
- package/src/scripts/check_release_holds.ts +568 -0
- package/src/scripts/check_routing_coverage.ts +378 -11
- package/src/scripts/check_rule_projection_integrity.ts +22 -12
- package/src/scripts/check_static_parity.ts +17 -0
- package/src/scripts/check_test_delta.ts +294 -0
- package/src/scripts/check_test_weakening.ts +299 -0
- package/src/scripts/ci_settle.ts +145 -19
- package/src/scripts/closure_scan.ts +447 -0
- package/src/scripts/compile_hook_manifest.ts +102 -18
- package/src/scripts/council_record_shape.ts +209 -0
- package/src/scripts/external_sources_denylist.json +29 -4
- package/src/scripts/hook_manifest.json +1 -1
- package/src/scripts/hook_manifest.yaml +127 -11
- package/src/scripts/hooks/block_config_weakening.ts +229 -4
- package/src/scripts/hooks/block_kernel_rule_writes.ts +9 -78
- package/src/scripts/hooks/block_plumbing_writes.ts +314 -0
- package/src/scripts/hooks/bundle_integrity.ts +291 -0
- package/src/scripts/hooks/chain_nudge_hook.ts +1 -2
- package/src/scripts/hooks/code_graph_context_hook.ts +1 -2
- package/src/scripts/hooks/comment_discipline_hook.ts +1 -3
- package/src/scripts/hooks/concern_registry.ts +6 -0
- package/src/scripts/hooks/concern_timings.ts +94 -0
- package/src/scripts/hooks/delegation_nudge_hook.ts +1 -2
- package/src/scripts/hooks/design_pass_hook.ts +221 -5
- package/src/scripts/hooks/design_slop_hook.ts +1 -2
- package/src/scripts/hooks/dispatch_hook.ts +25 -19
- package/src/scripts/hooks/edit_shape_hook.ts +1 -2
- package/src/scripts/hooks/end_review_nudge_hook.ts +1 -1
- package/src/scripts/hooks/evidence_independence.ts +97 -2
- package/src/scripts/hooks/exit_codes.ts +128 -0
- package/src/scripts/hooks/host_lowering.json +1 -0
- package/src/scripts/hooks/host_lowering.ts +128 -3
- package/src/scripts/hooks/host_lowering.yaml +262 -46
- package/src/scripts/hooks/host_semantics.ts +30 -2
- package/src/scripts/hooks/injection_budget.ts +9 -2
- package/src/scripts/hooks/interruption_ledger_hook.ts +1 -2
- package/src/scripts/hooks/journal_record_hook.ts +1 -2
- package/src/scripts/hooks/memory_recall_hook.ts +539 -0
- package/src/scripts/hooks/obligation_settle_hook.ts +328 -0
- package/src/scripts/hooks/one_question_per_ask_hook.ts +25 -4
- package/src/scripts/hooks/orchestration_record_hook.ts +1 -2
- package/src/scripts/hooks/permission_gate.ts +1 -2
- package/src/scripts/hooks/push_settle_hook.ts +1 -3
- package/src/scripts/hooks/replay_hook.ts +2 -1
- package/src/scripts/hooks/reread_guard_hook.ts +1 -2
- package/src/scripts/hooks/review_baseline_hook.ts +1 -2
- package/src/scripts/hooks/rtk_wrap_hook.ts +1 -2
- package/src/scripts/hooks/rule_inject_hook.ts +55 -4
- package/src/scripts/hooks/run_continuation_hook.ts +30 -35
- package/src/scripts/hooks/skill_route_hook.ts +1 -2
- package/src/scripts/hooks/source_first_gate_hook.ts +1 -2
- package/src/scripts/hooks/spawn_guard_shadow_hook.ts +1 -2
- package/src/scripts/hooks/subagent_ledger_hook.ts +1 -2
- package/src/scripts/hooks/suggestion_capture_hook.ts +1 -1
- package/src/scripts/hooks/table_fingerprint.ts +49 -0
- package/src/scripts/hooks/telemetry_flush_hook.ts +1 -2
- package/src/scripts/hooks/telemetry_usage_hook.ts +1 -2
- package/src/scripts/hooks/tool_result_bytes_hook.ts +93 -9
- package/src/scripts/hooks/turn_end_gate_hook.ts +337 -324
- package/src/scripts/hooks/ui_route_nudge_hook.ts +1 -2
- package/src/scripts/hooks_doctor.ts +85 -0
- package/src/scripts/hooks_status.ts +19 -0
- package/src/scripts/install.ts +109 -210
- package/src/scripts/janitor.ts +7 -0
- package/src/scripts/lint_decision_classes.ts +464 -0
- package/src/scripts/lint_deny_text.ts +419 -0
- package/src/scripts/lint_exit_codes.ts +262 -0
- package/src/scripts/lint_hook_manifest.ts +66 -0
- package/src/scripts/lint_major_migration_sections.ts +450 -0
- package/src/scripts/lint_plan_risk_register.ts +47 -3
- package/src/scripts/lint_roadmap_blockers.ts +139 -12
- package/src/scripts/lint_roadmap_producers.ts +202 -0
- package/src/scripts/lint_skill_descriptions.ts +1 -1
- package/src/scripts/lint_skill_scripts_readonly.ts +1 -1
- package/src/scripts/mcp_server/prompts.ts +142 -1
- package/src/scripts/measure_rule_budget.ts +101 -5
- package/src/scripts/measure_skill_ranker_baseline.ts +305 -51
- package/src/scripts/measure_turn_end_gate.ts +193 -0
- package/src/scripts/memory_lookup.ts +1 -1
- package/src/scripts/new_roadmap.ts +37 -0
- package/src/scripts/orchestration_record.ts +0 -5
- package/src/scripts/prepack-check.mjs +53 -0
- package/src/scripts/release.ts +14 -98
- package/src/scripts/release_drill.ts +267 -1
- package/src/scripts/release_publication.ts +64 -1
- package/src/scripts/report_enforcement_drift.ts +566 -0
- package/src/scripts/report_host_injection_effect.ts +3 -11
- package/src/scripts/report_invocation_surface.ts +383 -0
- package/src/scripts/report_obligation_carriers.ts +699 -0
- package/src/scripts/report_obligation_writer_split.ts +236 -0
- package/src/scripts/report_skill_menu_census.ts +208 -0
- package/src/scripts/report_standing_payload_by_host.ts +26 -152
- package/src/scripts/roadmap_verify_share.ts +213 -0
- package/src/scripts/schemas/agent-settings.schema.json +23 -0
- package/src/scripts/schemas/command.schema.json +48 -0
- package/src/scripts/schemas/review-findings.schema.json +4 -0
- package/src/scripts/schemas/skill.schema.json +44 -0
- package/src/scripts/scope_growth.ts +219 -0
- package/src/scripts/self_review_gate.ts +359 -10
- package/src/scripts/semantic_parity_census.ts +314 -0
- package/src/scripts/session_eol_report.ts +54 -1
- package/src/scripts/sweep_dead_scan_roots.ts +1 -1
- package/src/scripts/trigger_eval_grandfather.json +0 -3
- package/src/scripts/trigger_eval_rotation.ts +122 -27
- package/src/scripts/ui_conformance_probe.ts +774 -0
- package/src/scripts/update_prices.ts +85 -3
- package/src/scripts/write_bundle_digest.mjs +74 -0
- package/src/server/schemas/settings.ts +62 -7
- package/src/shared/missionExecution.ts +126 -0
- package/src/templates/consumer-settings/ONBOARDING.md +1 -0
- package/dist/cli-delegate/chunk-523HCFYO.js +0 -90
- package/dist/ui/assets/index-D0jaCRPM.js +0 -29
- package/dist/ui/assets/index-jSfMW8_t.css +0 -1
- /package/dist/cli-delegate/{chunk-YUM5KURG.js → chunk-7RQP4UDH.js} +0 -0
|
@@ -27,6 +27,7 @@
|
|
|
27
27
|
* `--strict` upgrades warnings to errors.
|
|
28
28
|
*/
|
|
29
29
|
import fs from "node:fs";
|
|
30
|
+
import { PLATFORM_METADATA_KEYS } from "./_lib/hook_platform_keys.js";
|
|
30
31
|
import path from "node:path";
|
|
31
32
|
import { fileURLToPath, pathToFileURL } from "node:url";
|
|
32
33
|
import { parse as parseYaml } from "yaml";
|
|
@@ -452,10 +453,20 @@ function _check_platforms(
|
|
|
452
453
|
errors.push(`platforms.${plat}: must be mapping or null`);
|
|
453
454
|
continue;
|
|
454
455
|
}
|
|
456
|
+
const askShape = block["ask"];
|
|
457
|
+
if (askShape !== undefined && askShape !== "native" && askShape !== "text") {
|
|
458
|
+
errors.push(
|
|
459
|
+
`platforms.${plat}.ask: must be 'native' or 'text' ` +
|
|
460
|
+
`(got ${JSON.stringify(askShape)})`,
|
|
461
|
+
);
|
|
462
|
+
}
|
|
455
463
|
if (block["fallback_only"]) {
|
|
456
464
|
continue; // Copilot — intentional, no event surface
|
|
457
465
|
}
|
|
458
466
|
for (const [event, names] of Object.entries(block)) {
|
|
467
|
+
if (PLATFORM_METADATA_KEYS.has(event)) {
|
|
468
|
+
continue;
|
|
469
|
+
}
|
|
459
470
|
if (!EVENT_VOCABULARY.has(event)) {
|
|
460
471
|
errors.push(
|
|
461
472
|
`platforms.${plat}.${event}: unknown event ` +
|
|
@@ -705,6 +716,61 @@ export function _check_host_lowering(
|
|
|
705
716
|
}
|
|
706
717
|
}
|
|
707
718
|
}
|
|
719
|
+
_check_slot_answers(table, errors);
|
|
720
|
+
}
|
|
721
|
+
|
|
722
|
+
/**
|
|
723
|
+
* Every (host, slot) pair carries a date, and a citation to go with it.
|
|
724
|
+
*
|
|
725
|
+
* WHY AN ERROR AND NOT A WARNING. `block_exit: null` is the value the table
|
|
726
|
+
* carries on 29 of its 32 pairs, and until this check it meant two different
|
|
727
|
+
* things at once: "read the host's contract, it honours no refusal here" and
|
|
728
|
+
* "nobody has ever looked at this pair". Those are opposite epistemic states
|
|
729
|
+
* printed as the same byte, and the file's own header promises the first one
|
|
730
|
+
* ("`verified: null` means nobody established anything") without giving the
|
|
731
|
+
* per-slot layer any way to say it. `answered_at` is that way, and a soft
|
|
732
|
+
* warning would let the ambiguity back in one undated slot at a time — the
|
|
733
|
+
* exact drift the row-level `verified` gate was added to stop one level up.
|
|
734
|
+
*
|
|
735
|
+
* THE CITATION MAY BE INHERITED. A host publishes ONE hooks page, so requiring
|
|
736
|
+
* a per-slot `docs_url` would mean five copies of the same URL per host and
|
|
737
|
+
* five places for it to rot. The row's `verified.docs_url` is the citation
|
|
738
|
+
* unless the slot overrides it; what the check refuses is a dated answer with
|
|
739
|
+
* no reachable citation at all.
|
|
740
|
+
*
|
|
741
|
+
* A slot on a row with NO `verified` block is exempt from the citation half
|
|
742
|
+
* and not from the date: an undated row is already an error wherever it carries
|
|
743
|
+
* a blocking binding, and demanding a URL from a row that has none would report
|
|
744
|
+
* the same missing provenance twice in different words.
|
|
745
|
+
*/
|
|
746
|
+
export function _check_slot_answers(table: HostLowering, errors: string[]): void {
|
|
747
|
+
for (const [host, surfaces] of table) {
|
|
748
|
+
for (const [surface, row] of surfaces) {
|
|
749
|
+
for (const [slot, s] of row.slots) {
|
|
750
|
+
const where = `host_lowering ${host}/${surface}/${slot}`;
|
|
751
|
+
if (s.answered_at === null) {
|
|
752
|
+
errors.push(
|
|
753
|
+
`${where}: no \`answered_at\`. Every host-slot pair carries the date it was ` +
|
|
754
|
+
"last answered, so that a `block_exit: null` is a finding with a date on it " +
|
|
755
|
+
"rather than a slot nobody looked at.",
|
|
756
|
+
);
|
|
757
|
+
continue;
|
|
758
|
+
}
|
|
759
|
+
if (!/^\d{4}-\d{2}-\d{2}$/.test(s.answered_at)) {
|
|
760
|
+
errors.push(`${where}: \`answered_at: ${s.answered_at}\` is not an ISO \`YYYY-MM-DD\` date.`);
|
|
761
|
+
}
|
|
762
|
+
// The row-level exemption the block comment above states: a row with no
|
|
763
|
+
// `verified` block already reports its missing provenance once, at row level.
|
|
764
|
+
if (row.verified !== null && s.docs_url === null && row.verified.docs_url === null) {
|
|
765
|
+
errors.push(
|
|
766
|
+
`${where}: dated \`${s.answered_at}\` with no citation — the slot carries no ` +
|
|
767
|
+
"`docs_url` and the row's `verified.docs_url` is absent. Cite the page the " +
|
|
768
|
+
"answer was read off, on the slot or on the row.",
|
|
769
|
+
);
|
|
770
|
+
}
|
|
771
|
+
}
|
|
772
|
+
}
|
|
773
|
+
}
|
|
708
774
|
}
|
|
709
775
|
|
|
710
776
|
export function lint(
|
|
@@ -0,0 +1,450 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
/**
|
|
3
|
+
* Gate: a major that ships BREAKING CHANGES owes `docs/MIGRATION.md` a section.
|
|
4
|
+
*
|
|
5
|
+
* WHY THIS EXISTS. `lint_scheduled_deprecations` is the FORWARD half of the
|
|
6
|
+
* release pre-flight's MIGRATION.md obligation — commitments due at a future
|
|
7
|
+
* major. The backward half had nothing at all behind it, and it failed exactly
|
|
8
|
+
* the way an unchecked obligation does: 15.0.0 and 16.0.0 both shipped
|
|
9
|
+
* BREAKING CHANGES, neither had a migration section, and the gap survived
|
|
10
|
+
* seventeen separate reports before anything in the tree could see it.
|
|
11
|
+
*
|
|
12
|
+
* WHAT IT REQUIRES, AND WHAT IT DELIBERATELY DOES NOT. A major section in a
|
|
13
|
+
* changelog carrying at least one `### BREAKING CHANGES` entry must have a
|
|
14
|
+
* `## ` heading in `docs/MIGRATION.md` naming that version. That is the whole
|
|
15
|
+
* requirement: a STATED ANSWER, never a non-empty procedure. Not every
|
|
16
|
+
* breaking change asks something of a consumer, and a gate that could not
|
|
17
|
+
* express "this asks nothing of you" would turn into a ritual heading with no
|
|
18
|
+
* content — which is worse than no gate, because the heading would be
|
|
19
|
+
* believed. The 16.0.0 section this gate ships beside says exactly that, and
|
|
20
|
+
* passes.
|
|
21
|
+
*
|
|
22
|
+
* THE FLOOR, AND WHY IT IS A CONSTANT RATHER THAN `package.json`. Majors below
|
|
23
|
+
* {@link FLOOR_MAJOR} are out of scope. The obvious alternative — grandfather
|
|
24
|
+
* everything at or below the shipped version, the cutoff `lint_changelog_rollback`
|
|
25
|
+
* uses — would leave this gate comparing ZERO majors on the day it lands, which
|
|
26
|
+
* is the "a gate that scans nothing exits green" shape. A constant floor set at
|
|
27
|
+
* the oldest major this repository actually carries a section for means the gate
|
|
28
|
+
* does real arithmetic from its first run (two majors today, both passing) and
|
|
29
|
+
* a reader can see which historical majors were forgiven and why.
|
|
30
|
+
*
|
|
31
|
+
* EXIT CONTRACT
|
|
32
|
+
* 0 every in-scope major has a section, or none is in scope for a reason
|
|
33
|
+
* the output names
|
|
34
|
+
* 1 an in-scope major carries BREAKING CHANGES and has no section, or the
|
|
35
|
+
* scan root is dead
|
|
36
|
+
* 2 usage error
|
|
37
|
+
*
|
|
38
|
+
* `--root <dir>` drives the whole check against a fixture tree, which is the
|
|
39
|
+
* seam `--self-test` and the unit suite use. `--self-test` proves the rule
|
|
40
|
+
* against the real CLI on inline fixtures.
|
|
41
|
+
*/
|
|
42
|
+
import * as fs from 'node:fs';
|
|
43
|
+
import * as os from 'node:os';
|
|
44
|
+
import * as path from 'node:path';
|
|
45
|
+
import { fileURLToPath, pathToFileURL } from 'node:url';
|
|
46
|
+
|
|
47
|
+
import { GateLedger } from './_lib/gate_ledger.js';
|
|
48
|
+
import { runGateCli, runSelfTest, type SelfTestCase } from './_lib/gate_self_test.js';
|
|
49
|
+
import { DeadScopeError, reportScanned } from './_lib/scan_scope.js';
|
|
50
|
+
|
|
51
|
+
const _HERE = fileURLToPath(import.meta.url);
|
|
52
|
+
const REPO_ROOT = path.resolve(path.dirname(_HERE), '..', '..');
|
|
53
|
+
|
|
54
|
+
export const GATE = 'lint_major_migration_sections';
|
|
55
|
+
export const MIGRATION_PATH = path.join('docs', 'MIGRATION.md');
|
|
56
|
+
const CHANGELOG_PATH = 'CHANGELOG.md';
|
|
57
|
+
const ARCHIVE_DIR = path.join('docs', 'archive');
|
|
58
|
+
|
|
59
|
+
/**
|
|
60
|
+
* Oldest major this gate holds to the requirement.
|
|
61
|
+
*
|
|
62
|
+
* 15 because 15.0.0 is the oldest major `docs/MIGRATION.md` carries a section
|
|
63
|
+
* for. Everything below it shipped before the obligation existed and is
|
|
64
|
+
* forgiven on the record rather than silently: retro-failing eight majors
|
|
65
|
+
* nobody can now write an honest section for is the gate-lands-as-N-blockers
|
|
66
|
+
* failure this repository has refused before. Raise it only by deleting a
|
|
67
|
+
* section, which is not a move this gate should make cheap.
|
|
68
|
+
*/
|
|
69
|
+
export const FLOOR_MAJOR = 15;
|
|
70
|
+
|
|
71
|
+
const MAJOR_HEADING_RE = /^## \[(\d+)\.(\d+)\.(\d+)\]/;
|
|
72
|
+
const ANY_SECTION_RE = /^#{1,2} /;
|
|
73
|
+
const BREAKING_HEADING_RE = /^### BREAKING CHANGES\s*$/;
|
|
74
|
+
const SUB_HEADING_RE = /^### /;
|
|
75
|
+
const BULLET_RE = /^\*\s+\S/;
|
|
76
|
+
|
|
77
|
+
/** One major section found in a changelog file. */
|
|
78
|
+
export interface MajorSection {
|
|
79
|
+
/** Bare `X.0.0`. */
|
|
80
|
+
readonly version: string;
|
|
81
|
+
readonly major: number;
|
|
82
|
+
/** Repo-relative file the heading was read from. */
|
|
83
|
+
readonly source: string;
|
|
84
|
+
/** 1-based heading line. */
|
|
85
|
+
readonly line: number;
|
|
86
|
+
/** BREAKING CHANGES bullets under the heading. */
|
|
87
|
+
readonly breaking: number;
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
/**
|
|
91
|
+
* Collect every `## [X.0.0](…)` section and how many BREAKING bullets it has.
|
|
92
|
+
*
|
|
93
|
+
* Minor and patch sections are not returned at all: the obligation is stated
|
|
94
|
+
* per MAJOR, because that is the boundary a consumer plans an upgrade around
|
|
95
|
+
* and the boundary the release runbook already stops at.
|
|
96
|
+
*/
|
|
97
|
+
export function findMajorSections(text: string, source: string): MajorSection[] {
|
|
98
|
+
const lines = text.split('\n');
|
|
99
|
+
const out: MajorSection[] = [];
|
|
100
|
+
for (let i = 0; i < lines.length; i++) {
|
|
101
|
+
const m = MAJOR_HEADING_RE.exec(lines[i] as string);
|
|
102
|
+
if (m === null) continue;
|
|
103
|
+
if (m[2] !== '0' || m[3] !== '0') continue;
|
|
104
|
+
let breaking = 0;
|
|
105
|
+
let inBreaking = false;
|
|
106
|
+
for (let j = i + 1; j < lines.length; j++) {
|
|
107
|
+
const line = lines[j] as string;
|
|
108
|
+
if (ANY_SECTION_RE.test(line)) break;
|
|
109
|
+
if (BREAKING_HEADING_RE.test(line)) {
|
|
110
|
+
inBreaking = true;
|
|
111
|
+
continue;
|
|
112
|
+
}
|
|
113
|
+
if (inBreaking && SUB_HEADING_RE.test(line)) {
|
|
114
|
+
inBreaking = false;
|
|
115
|
+
continue;
|
|
116
|
+
}
|
|
117
|
+
if (inBreaking && BULLET_RE.test(line)) breaking += 1;
|
|
118
|
+
}
|
|
119
|
+
out.push({
|
|
120
|
+
version: `${m[1] as string}.0.0`,
|
|
121
|
+
major: Number(m[1]),
|
|
122
|
+
source,
|
|
123
|
+
line: i + 1,
|
|
124
|
+
breaking,
|
|
125
|
+
});
|
|
126
|
+
}
|
|
127
|
+
return out;
|
|
128
|
+
}
|
|
129
|
+
|
|
130
|
+
/**
|
|
131
|
+
* Versions named by a `## ` heading in `docs/MIGRATION.md`.
|
|
132
|
+
*
|
|
133
|
+
* The heading text is matched on a whole version token, so both shapes the
|
|
134
|
+
* file already uses are accepted — `## 16.0.0 — …` and `## 8.x → 9.0.0 — …` —
|
|
135
|
+
* without this gate dictating a heading grammar the file did not choose.
|
|
136
|
+
*/
|
|
137
|
+
export function migrationVersions(text: string): Set<string> {
|
|
138
|
+
const out = new Set<string>();
|
|
139
|
+
for (const line of text.split('\n')) {
|
|
140
|
+
if (!line.startsWith('## ')) continue;
|
|
141
|
+
for (const m of line.matchAll(/(?<![\d.])(\d+)\.0\.0(?![\d.])/g)) {
|
|
142
|
+
out.add(`${m[1] as string}.0.0`);
|
|
143
|
+
}
|
|
144
|
+
}
|
|
145
|
+
return out;
|
|
146
|
+
}
|
|
147
|
+
|
|
148
|
+
export interface Finding {
|
|
149
|
+
readonly section: MajorSection;
|
|
150
|
+
}
|
|
151
|
+
|
|
152
|
+
/** Read every changelog file under `root`, newest-era file first. */
|
|
153
|
+
export function changelogSources(root: string): string[] {
|
|
154
|
+
const out: string[] = [];
|
|
155
|
+
if (fs.existsSync(path.join(root, CHANGELOG_PATH))) out.push(CHANGELOG_PATH);
|
|
156
|
+
const archive = path.join(root, ARCHIVE_DIR);
|
|
157
|
+
if (fs.existsSync(archive)) {
|
|
158
|
+
for (const name of fs.readdirSync(archive).sort()) {
|
|
159
|
+
if (name.startsWith('CHANGELOG-') && name.endsWith('.md')) {
|
|
160
|
+
out.push(path.join(ARCHIVE_DIR, name));
|
|
161
|
+
}
|
|
162
|
+
}
|
|
163
|
+
}
|
|
164
|
+
return out;
|
|
165
|
+
}
|
|
166
|
+
|
|
167
|
+
export interface EvaluateResult {
|
|
168
|
+
readonly findings: Finding[];
|
|
169
|
+
readonly sections: MajorSection[];
|
|
170
|
+
readonly inScope: number;
|
|
171
|
+
}
|
|
172
|
+
|
|
173
|
+
/**
|
|
174
|
+
* Compare the majors found against the sections MIGRATION.md carries.
|
|
175
|
+
*
|
|
176
|
+
* Every section reaches exactly one ledger outcome: out of scope below the
|
|
177
|
+
* floor, out of scope with no BREAKING entries, completed when a heading
|
|
178
|
+
* exists, failed when it does not.
|
|
179
|
+
*/
|
|
180
|
+
export function evaluate(
|
|
181
|
+
sections: readonly MajorSection[],
|
|
182
|
+
migration: ReadonlySet<string>,
|
|
183
|
+
ledger: GateLedger,
|
|
184
|
+
): EvaluateResult {
|
|
185
|
+
const findings: Finding[] = [];
|
|
186
|
+
let inScope = 0;
|
|
187
|
+
for (const s of sections) {
|
|
188
|
+
const target = `${s.source}:${s.version}`;
|
|
189
|
+
ledger.plan(target);
|
|
190
|
+
if (s.major < FLOOR_MAJOR) {
|
|
191
|
+
ledger.outOfScope(target, 'declared_exemption');
|
|
192
|
+
continue;
|
|
193
|
+
}
|
|
194
|
+
if (s.breaking === 0) {
|
|
195
|
+
ledger.outOfScope(target, 'not_applicable_kind');
|
|
196
|
+
continue;
|
|
197
|
+
}
|
|
198
|
+
inScope += 1;
|
|
199
|
+
if (migration.has(s.version)) {
|
|
200
|
+
ledger.complete(target);
|
|
201
|
+
continue;
|
|
202
|
+
}
|
|
203
|
+
ledger.fail(target, `no docs/MIGRATION.md section for ${s.version}`);
|
|
204
|
+
findings.push({ section: s });
|
|
205
|
+
}
|
|
206
|
+
return { findings, sections: [...sections], inScope };
|
|
207
|
+
}
|
|
208
|
+
|
|
209
|
+
/**
|
|
210
|
+
* The cut-time question, answered against an entry that is not on disk yet.
|
|
211
|
+
*
|
|
212
|
+
* `release.ts` renders the changelog entry before prepending it, so at the
|
|
213
|
+
* moment the cut can still be refused the section exists only in memory. This
|
|
214
|
+
* takes that text directly rather than re-reading `CHANGELOG.md`, which at that
|
|
215
|
+
* point still describes the PREVIOUS release.
|
|
216
|
+
*
|
|
217
|
+
* @returns a refusal reason naming the version, or `null` when the cut is clear.
|
|
218
|
+
*/
|
|
219
|
+
export function pendingMajorFinding(
|
|
220
|
+
target: string,
|
|
221
|
+
changelogEntry: string,
|
|
222
|
+
migrationText: string,
|
|
223
|
+
): string | null {
|
|
224
|
+
const m = /^(\d+)\.0\.0$/.exec(target.trim());
|
|
225
|
+
if (m === null) return null;
|
|
226
|
+
const sections = findMajorSections(changelogEntry, '(pending entry)');
|
|
227
|
+
const breaking = sections.reduce((n, s) => n + s.breaking, 0);
|
|
228
|
+
if (breaking === 0) return null;
|
|
229
|
+
if (Number(m[1]) < FLOOR_MAJOR) return null;
|
|
230
|
+
if (migrationVersions(migrationText).has(target)) return null;
|
|
231
|
+
return (
|
|
232
|
+
`the ${target} entry carries ${String(breaking)} BREAKING CHANGES entr(y/ies) and ` +
|
|
233
|
+
`docs/MIGRATION.md has no "## " heading naming ${target}. Add one before the cut — ` +
|
|
234
|
+
'a section stating that the change asks nothing of a consumer satisfies this, and is ' +
|
|
235
|
+
'the right answer when it is true. What is not acceptable is silence.'
|
|
236
|
+
);
|
|
237
|
+
}
|
|
238
|
+
|
|
239
|
+
function repoRoot(): string {
|
|
240
|
+
return REPO_ROOT;
|
|
241
|
+
}
|
|
242
|
+
|
|
243
|
+
const SELF_TEST_MIN_CASES = 6;
|
|
244
|
+
const SELF_TEST_MIN_REJECT = 3;
|
|
245
|
+
|
|
246
|
+
/** Prove the rejections still fire against the real CLI, not against imports. */
|
|
247
|
+
function selfTest(): number {
|
|
248
|
+
const root = fs.mkdtempSync(path.join(os.tmpdir(), 'lmms-selftest-'));
|
|
249
|
+
const repo = repoRoot();
|
|
250
|
+
const script = path.join('src', 'scripts', 'lint_major_migration_sections.ts');
|
|
251
|
+
|
|
252
|
+
const changelog = (version: string, breakingBullets: readonly string[]): string =>
|
|
253
|
+
[
|
|
254
|
+
'# Changelog',
|
|
255
|
+
'',
|
|
256
|
+
`## [${version}](https://example.invalid/compare) (2026-01-01)`,
|
|
257
|
+
'',
|
|
258
|
+
...(breakingBullets.length > 0
|
|
259
|
+
? ['### BREAKING CHANGES', '', ...breakingBullets.map((b) => `* ${b}`), '']
|
|
260
|
+
: []),
|
|
261
|
+
'### Features',
|
|
262
|
+
'',
|
|
263
|
+
'* something additive',
|
|
264
|
+
'',
|
|
265
|
+
].join('\n');
|
|
266
|
+
|
|
267
|
+
const fixture = (
|
|
268
|
+
name: string,
|
|
269
|
+
changelogText: string,
|
|
270
|
+
migrationHeadings: readonly string[],
|
|
271
|
+
): number => {
|
|
272
|
+
const dir = path.join(root, name);
|
|
273
|
+
fs.mkdirSync(path.join(dir, 'docs'), { recursive: true });
|
|
274
|
+
fs.writeFileSync(path.join(dir, 'CHANGELOG.md'), changelogText, 'utf8');
|
|
275
|
+
fs.writeFileSync(
|
|
276
|
+
path.join(dir, 'docs', 'MIGRATION.md'),
|
|
277
|
+
['# Migration Guide', '', ...migrationHeadings.flatMap((h) => [h, '', 'body', ''])].join(
|
|
278
|
+
'\n',
|
|
279
|
+
),
|
|
280
|
+
'utf8',
|
|
281
|
+
);
|
|
282
|
+
return runGateCli(repo, script, ['--root', dir, '--quiet'], repo);
|
|
283
|
+
};
|
|
284
|
+
|
|
285
|
+
const cases: SelfTestCase[] = [
|
|
286
|
+
{
|
|
287
|
+
name: 'a major with BREAKING and no MIGRATION heading is refused',
|
|
288
|
+
expect: 'reject',
|
|
289
|
+
run: () => fixture('missing', changelog('17.0.0', ['**x:** drop y']), []),
|
|
290
|
+
},
|
|
291
|
+
{
|
|
292
|
+
// The same fixture with the heading added. This pair IS the gate:
|
|
293
|
+
// one half proves the refusal fires, the other that the heading is
|
|
294
|
+
// what clears it, so neither can be satisfied by a gate that always
|
|
295
|
+
// reds or always passes.
|
|
296
|
+
name: 'the same major passes once the heading exists',
|
|
297
|
+
expect: 'accept',
|
|
298
|
+
run: () =>
|
|
299
|
+
fixture('present', changelog('17.0.0', ['**x:** drop y']), [
|
|
300
|
+
'## 17.0.0 — what to do',
|
|
301
|
+
]),
|
|
302
|
+
},
|
|
303
|
+
{
|
|
304
|
+
name: 'a heading naming a DIFFERENT major does not satisfy the requirement',
|
|
305
|
+
expect: 'reject',
|
|
306
|
+
run: () =>
|
|
307
|
+
fixture('wrong-version', changelog('17.0.0', ['**x:** drop y']), [
|
|
308
|
+
'## 16.0.0 — what to do',
|
|
309
|
+
]),
|
|
310
|
+
},
|
|
311
|
+
{
|
|
312
|
+
name: 'an arrow-form heading naming the version satisfies it',
|
|
313
|
+
expect: 'accept',
|
|
314
|
+
run: () =>
|
|
315
|
+
fixture('arrow', changelog('17.0.0', ['**x:** drop y']), [
|
|
316
|
+
'## 16.x → 17.0.0 — what to do',
|
|
317
|
+
]),
|
|
318
|
+
},
|
|
319
|
+
{
|
|
320
|
+
name: 'a major BELOW the floor is forgiven',
|
|
321
|
+
expect: 'accept',
|
|
322
|
+
run: () => fixture('below-floor', changelog('9.0.0', ['**x:** drop y']), []),
|
|
323
|
+
},
|
|
324
|
+
{
|
|
325
|
+
name: 'a major with no BREAKING entries owes nothing',
|
|
326
|
+
expect: 'accept',
|
|
327
|
+
run: () => fixture('no-breaking', changelog('17.0.0', []), []),
|
|
328
|
+
},
|
|
329
|
+
{
|
|
330
|
+
// The anti-vacuity half. A tree with no changelog at all must not
|
|
331
|
+
// read as "every major has its section"; a gate that greens over an
|
|
332
|
+
// empty corpus is the failure `_lib/scan_scope.ts` exists for.
|
|
333
|
+
name: 'a tree with no changelog is a dead scan root, not a pass',
|
|
334
|
+
expect: 'reject',
|
|
335
|
+
run: () => {
|
|
336
|
+
const dir = path.join(root, 'no-changelog');
|
|
337
|
+
fs.mkdirSync(path.join(dir, 'docs'), { recursive: true });
|
|
338
|
+
fs.writeFileSync(path.join(dir, 'docs', 'MIGRATION.md'), '# Migration Guide\n', 'utf8');
|
|
339
|
+
return runGateCli(repo, script, ['--root', dir, '--quiet'], repo);
|
|
340
|
+
},
|
|
341
|
+
},
|
|
342
|
+
];
|
|
343
|
+
|
|
344
|
+
try {
|
|
345
|
+
return runSelfTest({
|
|
346
|
+
gate: GATE,
|
|
347
|
+
cases,
|
|
348
|
+
minCases: SELF_TEST_MIN_CASES,
|
|
349
|
+
minRejectCases: SELF_TEST_MIN_REJECT,
|
|
350
|
+
});
|
|
351
|
+
} finally {
|
|
352
|
+
fs.rmSync(root, { recursive: true, force: true });
|
|
353
|
+
}
|
|
354
|
+
}
|
|
355
|
+
|
|
356
|
+
export function main(argv: readonly string[]): number {
|
|
357
|
+
let root = REPO_ROOT;
|
|
358
|
+
let quiet = false;
|
|
359
|
+
for (let i = 0; i < argv.length; i++) {
|
|
360
|
+
const a = argv[i];
|
|
361
|
+
if (a === '--quiet') quiet = true;
|
|
362
|
+
else if (a === '--self-test') return selfTest();
|
|
363
|
+
else if (a === '--root') {
|
|
364
|
+
const next = argv[++i];
|
|
365
|
+
if (next === undefined || next.startsWith('--')) {
|
|
366
|
+
process.stderr.write('usage: --root expects a directory\n');
|
|
367
|
+
return 2;
|
|
368
|
+
}
|
|
369
|
+
root = path.resolve(next);
|
|
370
|
+
} else {
|
|
371
|
+
process.stderr.write(
|
|
372
|
+
`usage: ${GATE} [--root <dir>] [--quiet] [--self-test]\nunknown argument: ${String(a)}\n`,
|
|
373
|
+
);
|
|
374
|
+
return 2;
|
|
375
|
+
}
|
|
376
|
+
}
|
|
377
|
+
|
|
378
|
+
const migrationFile = path.join(root, MIGRATION_PATH);
|
|
379
|
+
if (!fs.existsSync(migrationFile)) {
|
|
380
|
+
process.stderr.write(`❌ ${GATE}: ${MIGRATION_PATH} not found under ${root}\n`);
|
|
381
|
+
return 1;
|
|
382
|
+
}
|
|
383
|
+
const migration = migrationVersions(fs.readFileSync(migrationFile, 'utf-8'));
|
|
384
|
+
|
|
385
|
+
const sources = changelogSources(root);
|
|
386
|
+
const sections: MajorSection[] = [];
|
|
387
|
+
for (const rel of sources) {
|
|
388
|
+
sections.push(...findMajorSections(fs.readFileSync(path.join(root, rel), 'utf-8'), rel));
|
|
389
|
+
}
|
|
390
|
+
|
|
391
|
+
const ledger = new GateLedger(GATE);
|
|
392
|
+
const result = evaluate(sections, migration, ledger);
|
|
393
|
+
const tally = ledger.finalize();
|
|
394
|
+
|
|
395
|
+
try {
|
|
396
|
+
// No `allowEmpty`. Zero major sections means the heading shape drifted
|
|
397
|
+
// or the changelog moved — blindness, not cleanliness, and this
|
|
398
|
+
// repository has shipped majors continuously since 1.x.
|
|
399
|
+
reportScanned({
|
|
400
|
+
gate: GATE,
|
|
401
|
+
scanned: tally.planned,
|
|
402
|
+
units: 'major changelog section(s)',
|
|
403
|
+
roots: [CHANGELOG_PATH, ARCHIVE_DIR],
|
|
404
|
+
});
|
|
405
|
+
} catch (exc) {
|
|
406
|
+
if (exc instanceof DeadScopeError) {
|
|
407
|
+
process.stderr.write(`❌ ${exc.message}\n`);
|
|
408
|
+
return 1;
|
|
409
|
+
}
|
|
410
|
+
throw exc;
|
|
411
|
+
}
|
|
412
|
+
|
|
413
|
+
if (result.findings.length > 0) {
|
|
414
|
+
for (const f of result.findings) {
|
|
415
|
+
process.stderr.write(
|
|
416
|
+
`❌ ${GATE}: ${f.section.version} ships ${String(f.section.breaking)} BREAKING ` +
|
|
417
|
+
`CHANGES entr(y/ies) (${f.section.source}:${String(f.section.line)}) and ` +
|
|
418
|
+
`${MIGRATION_PATH} carries no "## " heading naming it.\n` +
|
|
419
|
+
` Add a section for ${f.section.version}. A section stating that the change\n` +
|
|
420
|
+
' asks nothing of a consumer satisfies this and is the right answer when it\n' +
|
|
421
|
+
' is true — the requirement is a stated answer, never a procedure.\n',
|
|
422
|
+
);
|
|
423
|
+
}
|
|
424
|
+
return 1;
|
|
425
|
+
}
|
|
426
|
+
|
|
427
|
+
if (!quiet) {
|
|
428
|
+
ledger.report();
|
|
429
|
+
process.stdout.write(
|
|
430
|
+
`✅ ${GATE}: ${String(tally.planned)} major section(s) read, ` +
|
|
431
|
+
`${String(result.inScope)} in scope (major >= ${String(FLOOR_MAJOR)} with BREAKING ` +
|
|
432
|
+
'CHANGES), each with a migration section' +
|
|
433
|
+
(result.inScope === 0
|
|
434
|
+
? ' — NONE in scope this run, so no comparison ran. Green here means\n' +
|
|
435
|
+
' "nothing to compare", not "the comparison passed".\n'
|
|
436
|
+
: '.\n'),
|
|
437
|
+
);
|
|
438
|
+
}
|
|
439
|
+
return 0;
|
|
440
|
+
}
|
|
441
|
+
|
|
442
|
+
function _isCliEntry(): boolean {
|
|
443
|
+
const entry = process.argv[1];
|
|
444
|
+
if (entry === undefined) return false;
|
|
445
|
+
return import.meta.url === pathToFileURL(path.resolve(entry)).href;
|
|
446
|
+
}
|
|
447
|
+
|
|
448
|
+
if (_isCliEntry()) {
|
|
449
|
+
process.exit(main(process.argv.slice(2)));
|
|
450
|
+
}
|
|
@@ -588,6 +588,46 @@ function _blobAt(absPath: string, sha: string): string | null {
|
|
|
588
588
|
return _git(root, ['show', `${sha}:${rel}`]);
|
|
589
589
|
}
|
|
590
590
|
|
|
591
|
+
/** The `reviewed:` value carried by `text`, or null when it has no valid marker. */
|
|
592
|
+
function _reviewedIn(text: string): string | null {
|
|
593
|
+
for (const line of text.split('\n')) {
|
|
594
|
+
const m = parseMarker(line);
|
|
595
|
+
if (m !== null) return m.reviewed;
|
|
596
|
+
}
|
|
597
|
+
return null;
|
|
598
|
+
}
|
|
599
|
+
|
|
600
|
+
/**
|
|
601
|
+
* The commit that INTRODUCED the current `reviewed:` value — the oldest of the
|
|
602
|
+
* unbroken run of commits, counting back from the newest, that all carry it.
|
|
603
|
+
*
|
|
604
|
+
* Merge-date lag is why this exists. A review stamped on the day the work was
|
|
605
|
+
* authored but committed (or squash-merged) the next day puts the stamp and the
|
|
606
|
+
* content it reviewed in ONE commit whose date is later than the stamp itself.
|
|
607
|
+
* A baseline of "newest commit dated <= reviewed" then skips that very commit,
|
|
608
|
+
* falls back to the pre-work state, and reports stale against a plan that was
|
|
609
|
+
* reviewed exactly as the contract asks — every author-today/merge-tomorrow PR
|
|
610
|
+
* reproduces it.
|
|
611
|
+
*
|
|
612
|
+
* The oldest commit of the run is the honest baseline, and the age matters:
|
|
613
|
+
* comparing against the NEWEST commit carrying the stamp would compare the
|
|
614
|
+
* current content with itself, making every committed file trivially fresh and
|
|
615
|
+
* emptying the gate.
|
|
616
|
+
*/
|
|
617
|
+
function _stampIntroducedAt(
|
|
618
|
+
absPath: string,
|
|
619
|
+
history: HistoryEntry[],
|
|
620
|
+
reviewed: string,
|
|
621
|
+
): HistoryEntry | undefined {
|
|
622
|
+
let introduced: HistoryEntry | undefined;
|
|
623
|
+
for (const entry of history) {
|
|
624
|
+
const blob = _blobAt(absPath, entry.sha);
|
|
625
|
+
if (blob === null || _reviewedIn(blob) !== reviewed) break;
|
|
626
|
+
introduced = entry;
|
|
627
|
+
}
|
|
628
|
+
return introduced;
|
|
629
|
+
}
|
|
630
|
+
|
|
591
631
|
export type FileStatus = 'ok' | 'draft-exempt' | 'grandfathered' | 'fail';
|
|
592
632
|
|
|
593
633
|
export interface FileResult {
|
|
@@ -653,10 +693,14 @@ export function checkFile(absPath: string): FileResult {
|
|
|
653
693
|
// current content, so staleness is not judgeable — see
|
|
654
694
|
// hasUncommittedChanges. The committed-history case below is unchanged.
|
|
655
695
|
if (content.reviewed !== null && !hasUncommittedChanges(absPath)) {
|
|
656
|
-
// Staleness (contract §§ 1.1 + 3): compare features of the
|
|
657
|
-
//
|
|
696
|
+
// Staleness (contract §§ 1.1 + 3): compare the features of the version
|
|
697
|
+
// the review was stamped on against the current content. That version
|
|
698
|
+
// is the commit which INTRODUCED the current `reviewed:` value; the
|
|
699
|
+
// date lookup is the fallback for a stamp no commit carries yet.
|
|
658
700
|
const history = fileHistory(absPath);
|
|
659
|
-
const base =
|
|
701
|
+
const base =
|
|
702
|
+
_stampIntroducedAt(absPath, history, content.reviewed) ??
|
|
703
|
+
history.find((h) => h.date <= (content.reviewed as string));
|
|
660
704
|
if (base !== undefined) {
|
|
661
705
|
const blob = _blobAt(absPath, base.sha);
|
|
662
706
|
if (blob !== null && !featuresEqual(extractFeatures(blob), extractFeatures(text))) {
|