@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
package/src/scripts/ci_settle.ts
CHANGED
|
@@ -237,9 +237,30 @@ export function classifyPoll(stdout: string, stderr: string, status: number | nu
|
|
|
237
237
|
return { kind: 'pending', total: rows.length, done };
|
|
238
238
|
}
|
|
239
239
|
const bad = new Set(['FAILURE', 'TIMED_OUT', 'CANCELLED', 'ACTION_REQUIRED', 'STARTUP_FAILURE']);
|
|
240
|
-
|
|
241
|
-
|
|
242
|
-
|
|
240
|
+
// A concurrency group that supersedes its own earlier run leaves TWO rows
|
|
241
|
+
// under one name: the superseded one CANCELLED, the one that counts
|
|
242
|
+
// SUCCESS. The forge reports that check green; this classifier reported the
|
|
243
|
+
// whole PR red, because CANCELLED sat in `bad` and nothing looked at the
|
|
244
|
+
// twin. Measured 2026-10-01 on PR #2135 (`lint commit subjects`), where the
|
|
245
|
+
// false red cost a run a diagnosis it did not owe.
|
|
246
|
+
//
|
|
247
|
+
// The reprieve is deliberately the narrowest one that fixes it: CANCELLED
|
|
248
|
+
// only, and only when a row of the SAME NAME concluded SUCCESS. A genuine
|
|
249
|
+
// cancellation — a human stopping a run, a timeout killing one — has no
|
|
250
|
+
// successful twin and stays failing. Widening this to every bad conclusion,
|
|
251
|
+
// or to CANCELLED unconditionally, would trade a false red for a false
|
|
252
|
+
// green, which is the worse error in a tool whose output is acted on.
|
|
253
|
+
const nameOf = (r: (typeof rows)[number]): string => String(r.name ?? '?');
|
|
254
|
+
const concl = (r: (typeof rows)[number]): string => String(r.conclusion).toUpperCase();
|
|
255
|
+
const succeeded = new Set(rows.filter((r) => concl(r) === 'SUCCESS').map(nameOf));
|
|
256
|
+
const failing = [
|
|
257
|
+
...new Set(
|
|
258
|
+
rows
|
|
259
|
+
.filter((r) => bad.has(concl(r)))
|
|
260
|
+
.filter((r) => !(concl(r) === 'CANCELLED' && succeeded.has(nameOf(r))))
|
|
261
|
+
.map(nameOf),
|
|
262
|
+
),
|
|
263
|
+
];
|
|
243
264
|
return { kind: 'settled', failing, total: rows.length };
|
|
244
265
|
}
|
|
245
266
|
|
|
@@ -293,25 +314,131 @@ function sleepSync(seconds: number): void {
|
|
|
293
314
|
});
|
|
294
315
|
}
|
|
295
316
|
|
|
296
|
-
|
|
297
|
-
|
|
298
|
-
|
|
299
|
-
|
|
300
|
-
|
|
301
|
-
|
|
302
|
-
|
|
303
|
-
|
|
317
|
+
/** The only flags this waiter honours. Anything else is a caller mistake. */
|
|
318
|
+
const KNOWN_FLAGS = ['--timeout-min', '--interval-sec'] as const;
|
|
319
|
+
|
|
320
|
+
const USAGE =
|
|
321
|
+
'usage: ci_settle <pr> [--timeout-min N] [--interval-sec N]\n' +
|
|
322
|
+
` default --timeout-min is ${String(FOREGROUND_CEILING_MIN)}, which fits inside one foreground Bash call (600 s cap).\n` +
|
|
323
|
+
' a longer wait is a BACKGROUND job, not a bigger number — a foreground call past the cap is killed and reports nothing.\n';
|
|
324
|
+
|
|
325
|
+
export type ParsedArgs =
|
|
326
|
+
| { kind: 'ok'; pr: string; timeoutMin: number; intervalSec: number }
|
|
327
|
+
| { kind: 'usage'; message: string };
|
|
328
|
+
|
|
329
|
+
/**
|
|
330
|
+
* Parse argv, REFUSING anything this waiter would not honour.
|
|
331
|
+
*
|
|
332
|
+
* Refusal rather than a default is the whole point. The previous parser read
|
|
333
|
+
* its two knobs with `argv.indexOf(flag)` and filtered positionals on a leading
|
|
334
|
+
* `--`, so a caller who wrote the near-universal `--timeout 1700` got two
|
|
335
|
+
* silent substitutions at once: the unknown flag was ignored, and its value —
|
|
336
|
+
* carrying no dashes — survived as a second positional and was dropped. The
|
|
337
|
+
* wait then ran for the 9-minute default while the caller believed they had
|
|
338
|
+
* asked for 28, and the resulting `DID NOT SETTLE` read as a slow CI rather
|
|
339
|
+
* than as their own typo.
|
|
340
|
+
*
|
|
341
|
+
* That is the one failure a waiter must not have. Its entire product is a
|
|
342
|
+
* verdict somebody will act on, and a verdict produced under arguments the
|
|
343
|
+
* caller did not actually give is worse than no verdict, because it looks like
|
|
344
|
+
* one. So every unrecognised flag, every unusable value and every extra
|
|
345
|
+
* positional is a usage refusal, exit 2 — the code this tool already reserves
|
|
346
|
+
* for "no verdict is claimed".
|
|
347
|
+
*
|
|
348
|
+
* `--flag=value` is accepted beside `--flag value` because it is the other
|
|
349
|
+
* spelling a caller reaches for, and refusing it would trade one silent
|
|
350
|
+
* surprise for a noisy one with no safety gained.
|
|
351
|
+
*/
|
|
352
|
+
export function parseArgs(argv: readonly string[]): ParsedArgs {
|
|
353
|
+
const refuse = (why: string): ParsedArgs => ({ kind: 'usage', message: `ci_settle: ${why}\n${USAGE}` });
|
|
354
|
+
|
|
355
|
+
const positional: string[] = [];
|
|
356
|
+
const given = new Map<string, string>();
|
|
357
|
+
|
|
358
|
+
for (let i = 0; i < argv.length; i++) {
|
|
359
|
+
const a = argv[i] as string;
|
|
360
|
+
if (!a.startsWith('--')) {
|
|
361
|
+
positional.push(a);
|
|
362
|
+
continue;
|
|
363
|
+
}
|
|
364
|
+
const eq = a.indexOf('=');
|
|
365
|
+
const name = eq === -1 ? a : a.slice(0, eq);
|
|
366
|
+
if (!(KNOWN_FLAGS as readonly string[]).includes(name)) {
|
|
367
|
+
return refuse(
|
|
368
|
+
`unknown flag ${name}. This waiter spells its deadline --timeout-min (in MINUTES), not --timeout`,
|
|
369
|
+
);
|
|
370
|
+
}
|
|
371
|
+
if (given.has(name)) return refuse(`${name} given twice`);
|
|
372
|
+
if (eq !== -1) {
|
|
373
|
+
given.set(name, a.slice(eq + 1));
|
|
374
|
+
continue;
|
|
375
|
+
}
|
|
376
|
+
const v = argv[i + 1];
|
|
377
|
+
if (v === undefined || v.startsWith('--')) return refuse(`${name} needs a value`);
|
|
378
|
+
given.set(name, v);
|
|
379
|
+
i++;
|
|
380
|
+
}
|
|
381
|
+
|
|
382
|
+
const raw = positional[0];
|
|
383
|
+
if (raw === undefined) return refuse('no PR number given');
|
|
384
|
+
if (positional.length > 1) {
|
|
385
|
+
return refuse(
|
|
386
|
+
`unexpected argument ${JSON.stringify(positional[1])} — this tool takes exactly one positional, the PR number; ` +
|
|
387
|
+
'a flag value must follow its own flag',
|
|
304
388
|
);
|
|
305
|
-
return 2;
|
|
306
389
|
}
|
|
307
|
-
|
|
308
|
-
|
|
309
|
-
|
|
310
|
-
|
|
311
|
-
|
|
312
|
-
|
|
390
|
+
|
|
391
|
+
// Finding 1 of the 2026-10-01 R2 review: the first version of this guard
|
|
392
|
+
// validated every OPTIONAL argument and waved the mandatory one through, so
|
|
393
|
+
// the failure it exists to remove stayed reachable through the front door.
|
|
394
|
+
// `ci_settle PR-2130` parsed as ok; `gh pr view PR-2130` then exits non-zero
|
|
395
|
+
// on every poll, each one classifies as unreadable, no streak caps the loop,
|
|
396
|
+
// and the run spends its entire budget to arrive at `DID NOT SETTLE` — which
|
|
397
|
+
// is read as a slow CI rather than as the typo it is. A guard that refuses
|
|
398
|
+
// the arguments a caller may omit and accepts the one they cannot is not a
|
|
399
|
+
// guard. `#123` and a PR URL are admitted because both are what a caller
|
|
400
|
+
// copies out of the forge; a branch name is NOT, because this tool's whole
|
|
401
|
+
// verdict is bound to one head and a branch makes that binding ambiguous.
|
|
402
|
+
const pr = /^#?[0-9]+$/.test(raw)
|
|
403
|
+
? raw.replace(/^#/, '')
|
|
404
|
+
: (/^https?:\/\/[^\s]*\/pull\/([0-9]+)(?:[/?#].*)?$/.exec(raw)?.[1] ?? null);
|
|
405
|
+
if (pr === null || pr === '0' || /^0+$/.test(pr)) {
|
|
406
|
+
return refuse(
|
|
407
|
+
`${JSON.stringify(raw)} is not a PR number — give the number itself (2130), #2130, or the pull-request URL`,
|
|
408
|
+
);
|
|
409
|
+
}
|
|
410
|
+
|
|
411
|
+
const num = (flag: string, dflt: number): number | string => {
|
|
412
|
+
const v = given.get(flag);
|
|
413
|
+
if (v === undefined) return dflt;
|
|
414
|
+
const unit = flag === '--timeout-min' ? 'minutes' : 'seconds';
|
|
415
|
+
if (!/^[0-9]+$/.test(v)) return `${flag} wants a whole number of ${unit}, got ${JSON.stringify(v)}`;
|
|
416
|
+
const n = parseInt(v, 10);
|
|
417
|
+
// Finding 2: the parser this replaced guarded with `Number.isFinite`, and
|
|
418
|
+
// the digits-only test that replaced it does not. A long enough run of
|
|
419
|
+
// digits parses to Infinity, clears `n > 0`, and makes `deadline`
|
|
420
|
+
// unreachable — an unbounded wait in a tool whose contract is that it
|
|
421
|
+
// always returns a stated verdict or a stated non-verdict.
|
|
422
|
+
if (!Number.isFinite(n)) return `${flag} is too large to be a number of ${unit}`;
|
|
423
|
+
if (n <= 0) return `${flag} wants a positive number, got ${v}`;
|
|
424
|
+
return n;
|
|
313
425
|
};
|
|
426
|
+
|
|
314
427
|
const timeoutMin = num('--timeout-min', FOREGROUND_CEILING_MIN);
|
|
428
|
+
if (typeof timeoutMin === 'string') return refuse(timeoutMin);
|
|
429
|
+
const intervalSec = num('--interval-sec', 60);
|
|
430
|
+
if (typeof intervalSec === 'string') return refuse(intervalSec);
|
|
431
|
+
|
|
432
|
+
return { kind: 'ok', pr, timeoutMin, intervalSec };
|
|
433
|
+
}
|
|
434
|
+
|
|
435
|
+
export function main(argv: readonly string[]): number {
|
|
436
|
+
const parsed = parseArgs(argv);
|
|
437
|
+
if (parsed.kind === 'usage') {
|
|
438
|
+
process.stderr.write(parsed.message);
|
|
439
|
+
return 2;
|
|
440
|
+
}
|
|
441
|
+
const { pr, timeoutMin, intervalSec } = parsed;
|
|
315
442
|
if (timeoutMin > FOREGROUND_CEILING_MIN) {
|
|
316
443
|
// Said once, up front, rather than discovered when the call is killed:
|
|
317
444
|
// a truncated wait produces no line at all, so the warning has to come
|
|
@@ -322,7 +449,6 @@ export function main(argv: readonly string[]): number {
|
|
|
322
449
|
'the deadline and will report nothing — run it as a background job for a wait this long.\n',
|
|
323
450
|
);
|
|
324
451
|
}
|
|
325
|
-
const intervalSec = num('--interval-sec', 60);
|
|
326
452
|
|
|
327
453
|
// Up front, because waiting nine minutes to learn the PR was merged before
|
|
328
454
|
// the wait began is the expensive way to find out.
|
|
@@ -0,0 +1,447 @@
|
|
|
1
|
+
#!/usr/bin/env tsx
|
|
2
|
+
/**
|
|
3
|
+
* The closure detector — finds the open decisions in a plan, and classifies
|
|
4
|
+
* each one by OWNERSHIP. It never resolves one.
|
|
5
|
+
*
|
|
6
|
+
* That boundary is the whole design. A decision dispatcher was killed on
|
|
7
|
+
* purpose: no TypeScript path routes a decision, and this script adds none.
|
|
8
|
+
* What it adds is the half that can be mechanical — *which questions are open,
|
|
9
|
+
* and whose they are* — so the claim "this plan reached execution with twelve
|
|
10
|
+
* open technical decisions and asked the owner none of them" is falsifiable
|
|
11
|
+
* rather than a feeling.
|
|
12
|
+
*
|
|
13
|
+
* Detector families, each a phrase an author writes when they KNOW something
|
|
14
|
+
* is open. Deliberately narrow: a detector that fires on ordinary plan prose
|
|
15
|
+
* turns every producer red at once, and the cheapest repair is then to weaken
|
|
16
|
+
* it until it finds nothing.
|
|
17
|
+
*
|
|
18
|
+
* `tbd` TBD / to be decided / decide later
|
|
19
|
+
* `unpicked-alternative` "either X or Y", "option A … option B"
|
|
20
|
+
* `unchecked-assumption` "assuming", "presumably", "probably fine"
|
|
21
|
+
* `missing-verify` an open step with no `verify:` line
|
|
22
|
+
* `unfalsifiable-verify` a step WITH a `verify:` line whose oracle
|
|
23
|
+
* cannot say no
|
|
24
|
+
* `ambiguous-acceptance` a vague acceptance criterion (improve / clean up
|
|
25
|
+
* / best practices / handle errors properly)
|
|
26
|
+
* `contradictory` "contradicts", "conflicts with", "but also"
|
|
27
|
+
* `product-semantics` two valid user-visible outcomes
|
|
28
|
+
* `typed-op` an operation from the typed vocabulary
|
|
29
|
+
* `spend` a ceiling or quota question
|
|
30
|
+
*
|
|
31
|
+
* Ownership classification is the output that matters. Six families classify
|
|
32
|
+
* to a technical class the agent, an independent session, a council or a team
|
|
33
|
+
* resolves; exactly two reach the owner (`product-semantics` → `product-owned`,
|
|
34
|
+
* `typed-op` → `destructive-owned`); `spend` reaches nobody — it pauses and
|
|
35
|
+
* reports (ADR-268 § 8).
|
|
36
|
+
*
|
|
37
|
+
* Exit codes: 0 no open decisions · 1 open decisions found · 2 bad argv.
|
|
38
|
+
* Finding open decisions is the POINT, so `--strict` is what makes exit 1 a
|
|
39
|
+
* failure for a caller that wants one; the default is a report.
|
|
40
|
+
*
|
|
41
|
+
* ONE FAMILY IS A LISTING AND NEVER A GATE. `unfalsifiable-verify` exits 0
|
|
42
|
+
* unconditionally — under `--strict` too — and that is a property of the
|
|
43
|
+
* family, not a flag a caller can flip. The reason is the one this script's
|
|
44
|
+
* own header already records about a detector that fires on ordinary prose,
|
|
45
|
+
* sharpened by a measurement: on 2026-09-29 the active roadmap tree carried
|
|
46
|
+
* 230 clauses and 6 machine-decidable expectations — 97.4% of them state no
|
|
47
|
+
* oracle — so a family promoted to a gate would red all but a handful of
|
|
48
|
+
* roadmaps at once, and the cheapest repair for a gate that reds on
|
|
49
|
+
* everything is to weaken it until it finds nothing. The exemption is
|
|
50
|
+
* narrow: a `tbd` in the same corpus still goes red under `--strict`.
|
|
51
|
+
*
|
|
52
|
+
* The figure is the MEASURED one, not the commissioning roadmap's. That
|
|
53
|
+
* roadmap asserted zero expectations tree-wide; a reading refuted it — six
|
|
54
|
+
* exist, all six written before the grammar was legal, and three of them are
|
|
55
|
+
* that roadmap's own verify lines. The producing
|
|
56
|
+
* command is `./scripts-run src/scripts/roadmap_verify_share`, so the number
|
|
57
|
+
* above is re-derivable rather than remembered.
|
|
58
|
+
*/
|
|
59
|
+
|
|
60
|
+
import * as fs from 'node:fs';
|
|
61
|
+
import * as path from 'node:path';
|
|
62
|
+
import process from 'node:process';
|
|
63
|
+
import { fileURLToPath } from 'node:url';
|
|
64
|
+
|
|
65
|
+
import { parseVerifyClause, type VerifyClause } from './_lib/verify_clause.js';
|
|
66
|
+
|
|
67
|
+
const _HERE = fileURLToPath(import.meta.url);
|
|
68
|
+
const _PROG = 'closure_scan';
|
|
69
|
+
|
|
70
|
+
export type Kind =
|
|
71
|
+
| 'tbd'
|
|
72
|
+
| 'unpicked-alternative'
|
|
73
|
+
| 'unchecked-assumption'
|
|
74
|
+
| 'missing-verify'
|
|
75
|
+
| 'unfalsifiable-verify'
|
|
76
|
+
| 'ambiguous-acceptance'
|
|
77
|
+
| 'contradictory'
|
|
78
|
+
| 'product-semantics'
|
|
79
|
+
| 'typed-op'
|
|
80
|
+
| 'spend';
|
|
81
|
+
|
|
82
|
+
export interface Finding {
|
|
83
|
+
readonly id: string;
|
|
84
|
+
readonly line: number;
|
|
85
|
+
readonly kind: Kind;
|
|
86
|
+
readonly ownership: string;
|
|
87
|
+
readonly excerpt: string;
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
/**
|
|
91
|
+
* Kind → ownership class. The two owner-routed rows are the whole cost of
|
|
92
|
+
* closure: everything else is resolved without reaching a person, which is
|
|
93
|
+
* what "zero owner questions for twelve technical ambiguities" means.
|
|
94
|
+
*/
|
|
95
|
+
export const OWNERSHIP_BY_KIND: Readonly<Record<Kind, string>> = {
|
|
96
|
+
tbd: 'contested-technical',
|
|
97
|
+
'unpicked-alternative': 'contested-technical',
|
|
98
|
+
'unchecked-assumption': 'reversible-technical',
|
|
99
|
+
'missing-verify': 'deterministic',
|
|
100
|
+
'unfalsifiable-verify': 'deterministic',
|
|
101
|
+
'ambiguous-acceptance': 'reversible-technical',
|
|
102
|
+
contradictory: 'contested-technical',
|
|
103
|
+
'product-semantics': 'product-owned',
|
|
104
|
+
'typed-op': 'destructive-owned',
|
|
105
|
+
spend: 'spend-exhaustion',
|
|
106
|
+
};
|
|
107
|
+
|
|
108
|
+
/** Classes that reach a person. Everything else closes without an ask. */
|
|
109
|
+
export const OWNER_ROUTED: ReadonlySet<string> = new Set([
|
|
110
|
+
'product-owned',
|
|
111
|
+
'business-owned',
|
|
112
|
+
'destructive-owned',
|
|
113
|
+
]);
|
|
114
|
+
|
|
115
|
+
/**
|
|
116
|
+
* Families that report and never gate — `--strict` cannot make these exit 1.
|
|
117
|
+
*
|
|
118
|
+
* A set rather than a flag, so the exemption is a property of the family that a
|
|
119
|
+
* caller cannot accidentally promote. See the header for the measurement that
|
|
120
|
+
* makes promotion a bad trade today.
|
|
121
|
+
*/
|
|
122
|
+
export const NON_BLOCKING_KINDS: ReadonlySet<Kind> = new Set<Kind>(['unfalsifiable-verify']);
|
|
123
|
+
|
|
124
|
+
/**
|
|
125
|
+
* Command heads whose exit status is decided by something other than the
|
|
126
|
+
* property a step is claiming.
|
|
127
|
+
*
|
|
128
|
+
* Deliberately short. `echo`, `true` and `printf` cannot fail at all; `cat`,
|
|
129
|
+
* `ls`, `pwd` and `date` fail only on a missing path or a broken clock, never
|
|
130
|
+
* on the CONTENT a step asserts. Anything whose exit code already tracks the
|
|
131
|
+
* claim — `grep` (1 on no match), a linter, a test runner — is absent on
|
|
132
|
+
* purpose: including it would turn this listing into a census of every verify
|
|
133
|
+
* line in the tree, which is step 2.2's instrument, not this one.
|
|
134
|
+
*/
|
|
135
|
+
const FIXED_OUTPUT_HEADS: ReadonlySet<string> = new Set(['echo', 'true', 'printf', 'cat', 'ls', 'pwd', 'date']);
|
|
136
|
+
|
|
137
|
+
/**
|
|
138
|
+
* Why this clause's oracle cannot say no — or `null` when it can.
|
|
139
|
+
*
|
|
140
|
+
* Three shapes, each one a way of carrying a `verify:` line that reads as
|
|
141
|
+
* verification and decides nothing.
|
|
142
|
+
*
|
|
143
|
+
* THE COUNT IS A LOWER BOUND, and saying so is the point. `scan()` assigns at
|
|
144
|
+
* most one kind per unit and the line patterns run first, so a step that also
|
|
145
|
+
* says "assuming", "merge" or "deploy" is claimed by the broader family and
|
|
146
|
+
* never reaches this one. Measured on 2026-09-29: 12 of 50 candidates were
|
|
147
|
+
* shadowed that way, so the published listing under-reports by roughly a
|
|
148
|
+
* quarter. A reader told a number and not told it is a floor would read the
|
|
149
|
+
* remainder as clean, which is the same mistake as reporting a share off a
|
|
150
|
+
* corpus nobody scanned.
|
|
151
|
+
*/
|
|
152
|
+
export function unfalsifiableReason(clause: VerifyClause): string | null {
|
|
153
|
+
if (clause.command === null) {
|
|
154
|
+
// A MANUAL clause asserting a QUANTITY nothing produced. Narrowed to a
|
|
155
|
+
// multi-digit number or an explicit `N of`/`N%` shape: a lone digit is
|
|
156
|
+
// usually a step id or an exit code being discussed, and firing on
|
|
157
|
+
// those would bury the real ones.
|
|
158
|
+
const quantity = /\b\d{2,}\b/.test(clause.raw) || /\b\d+\s*(?:%|of\b)/.test(clause.raw);
|
|
159
|
+
return quantity ? 'a manual clause asserts a quantity nothing here produces' : null;
|
|
160
|
+
}
|
|
161
|
+
|
|
162
|
+
const head = (clause.command.trim().split(/\s+/)[0] ?? '').split('/').pop() ?? '';
|
|
163
|
+
if (FIXED_OUTPUT_HEADS.has(head) && clause.expect?.kind !== 'regex') {
|
|
164
|
+
// A regex expectation rescues a fixed-output head: `cat f -> /x/` does
|
|
165
|
+
// decide something. An exit expectation does not — naming exit 0 on a
|
|
166
|
+
// command that cannot exit anything else adds a symbol, not an oracle.
|
|
167
|
+
return `the command head \`${head}\` produces a fixed result, so the clause cannot fail`;
|
|
168
|
+
}
|
|
169
|
+
|
|
170
|
+
if (clause.expect?.kind === 'regex') {
|
|
171
|
+
try {
|
|
172
|
+
if (new RegExp(clause.expect.source).test(clause.command)) {
|
|
173
|
+
return 'the expectation matches the command text, so an echo of the command satisfies it';
|
|
174
|
+
}
|
|
175
|
+
} catch {
|
|
176
|
+
// An expectation that cannot compile never became one upstream.
|
|
177
|
+
}
|
|
178
|
+
}
|
|
179
|
+
return null;
|
|
180
|
+
}
|
|
181
|
+
|
|
182
|
+
interface Pattern {
|
|
183
|
+
readonly kind: Kind;
|
|
184
|
+
readonly re: RegExp;
|
|
185
|
+
}
|
|
186
|
+
|
|
187
|
+
const LINE_PATTERNS: readonly Pattern[] = [
|
|
188
|
+
{ kind: 'tbd', re: /\bTBD\b/ },
|
|
189
|
+
{ kind: 'tbd', re: /\bto be decided\b/i },
|
|
190
|
+
{ kind: 'tbd', re: /\bdecide later\b/i },
|
|
191
|
+
{ kind: 'unpicked-alternative', re: /\beither\b[^.]{3,80}\bor\b/i },
|
|
192
|
+
{ kind: 'unpicked-alternative', re: /\boption A\b[^.]{0,120}\boption B\b/i },
|
|
193
|
+
{ kind: 'unpicked-alternative', re: /\bwe could (?:either )?\w+[^.]{0,80}\bor\b/i },
|
|
194
|
+
// "two equal technical strategies" is the canonical unpicked alternative,
|
|
195
|
+
// and the phrasing an author reaches for when the choice is genuinely open
|
|
196
|
+
// — it carries no `either`, no `or` and no `option A`.
|
|
197
|
+
{ kind: 'unpicked-alternative', re: /\btwo (?:equal|competing)\b[^.]{0,60}\b(?:strateg|approach|design|option)/i },
|
|
198
|
+
{ kind: 'unchecked-assumption', re: /\bassuming\b/i },
|
|
199
|
+
{ kind: 'unchecked-assumption', re: /\bpresumably\b/i },
|
|
200
|
+
{ kind: 'unchecked-assumption', re: /\bprobably (?:fine|works|ok)\b/i },
|
|
201
|
+
{ kind: 'ambiguous-acceptance', re: /\b(?:improve|optimi[sz]e)\b(?![^.]*\bby\b)/i },
|
|
202
|
+
{ kind: 'ambiguous-acceptance', re: /\bmake it (?:better|cleaner|nicer)\b/i },
|
|
203
|
+
{ kind: 'ambiguous-acceptance', re: /\buse best practices\b/i },
|
|
204
|
+
{ kind: 'ambiguous-acceptance', re: /\bhandle errors properly\b/i },
|
|
205
|
+
{ kind: 'contradictory', re: /\bcontradicts\b/i },
|
|
206
|
+
{ kind: 'contradictory', re: /\bconflicts with\b/i },
|
|
207
|
+
{ kind: 'product-semantics', re: /\buser-visible\b[^.]{0,80}\b(?:or|versus|vs\.?)\b/i },
|
|
208
|
+
{ kind: 'product-semantics', re: /\bwhat the user sees\b/i },
|
|
209
|
+
{ kind: 'product-semantics', re: /\btwo valid (?:user|product) semantics\b/i },
|
|
210
|
+
{ kind: 'typed-op', re: /\b(?:merge|force-push|deploy|publish|purchase|rotate the secret)\b/i },
|
|
211
|
+
{ kind: 'spend', re: /\b(?:over|above|crosses?) the (?:configured )?(?:spend )?ceiling\b/i },
|
|
212
|
+
{ kind: 'spend', re: /\bquota (?:is )?(?:exhausted|gone)\b/i },
|
|
213
|
+
];
|
|
214
|
+
|
|
215
|
+
const STEP_RE = /^\s*-\s\[[ x~-]\]\s/;
|
|
216
|
+
const OPEN_STEP_RE = /^\s*-\s\[ \]\s/;
|
|
217
|
+
const H2_RE = /^##\s+/;
|
|
218
|
+
const DISCHARGE_H2_RE = /^##\s+(Decisions|Blockers|Kill register|Risk Register)\s*$/;
|
|
219
|
+
const ACCEPTANCE_H2_RE = /^##\s+Acceptance Criteria\s*$/;
|
|
220
|
+
|
|
221
|
+
function _isFence(line: string): boolean {
|
|
222
|
+
return /^\s*(```|~~~)/.test(line);
|
|
223
|
+
}
|
|
224
|
+
|
|
225
|
+
/**
|
|
226
|
+
* A scan unit: one logical statement, which is NOT one physical line.
|
|
227
|
+
*
|
|
228
|
+
* A roadmap step wraps over several lines, and the ambiguity it carries
|
|
229
|
+
* routinely straddles the wrap ("either newline-delimited JSON / or a single
|
|
230
|
+
* array"). Matching per line missed exactly those, which are the ones an
|
|
231
|
+
* author is least likely to notice. A step block is therefore ONE unit, joined
|
|
232
|
+
* and matched once — which also means one finding per step at most, so a step
|
|
233
|
+
* carrying two hedges is one open decision rather than two.
|
|
234
|
+
*/
|
|
235
|
+
export interface Unit {
|
|
236
|
+
/** 1-based line of the unit's first line. */
|
|
237
|
+
readonly line: number;
|
|
238
|
+
readonly text: string;
|
|
239
|
+
/**
|
|
240
|
+
* The same block with its NEWLINES intact.
|
|
241
|
+
*
|
|
242
|
+
* `text` is joined with spaces so a wrapped pattern still matches across
|
|
243
|
+
* the wrap, and that is right for the line patterns. It is wrong for the
|
|
244
|
+
* `verify:` clause: with no newline left, "to end of line" means "to end of
|
|
245
|
+
* the step", so a manual clause inherited every digit in the evidence block
|
|
246
|
+
* written under it. Measured before this field existed: 46 of 50
|
|
247
|
+
* `unfalsifiable-verify` hits fired on a quantity the clause never wrote.
|
|
248
|
+
*/
|
|
249
|
+
readonly blockText: string;
|
|
250
|
+
readonly inAcceptance: boolean;
|
|
251
|
+
readonly isOpenStep: boolean;
|
|
252
|
+
readonly hasVerify: boolean;
|
|
253
|
+
}
|
|
254
|
+
|
|
255
|
+
export function units(lines: readonly string[]): Unit[] {
|
|
256
|
+
const out: Unit[] = [];
|
|
257
|
+
let inFence = false;
|
|
258
|
+
let inDischarge = false;
|
|
259
|
+
let inAcceptance = false;
|
|
260
|
+
let i = 0;
|
|
261
|
+
while (i < lines.length) {
|
|
262
|
+
const line = lines[i] as string;
|
|
263
|
+
if (_isFence(line)) {
|
|
264
|
+
inFence = !inFence;
|
|
265
|
+
i += 1;
|
|
266
|
+
continue;
|
|
267
|
+
}
|
|
268
|
+
if (inFence) {
|
|
269
|
+
i += 1;
|
|
270
|
+
continue;
|
|
271
|
+
}
|
|
272
|
+
if (H2_RE.test(line)) {
|
|
273
|
+
inDischarge = DISCHARGE_H2_RE.test(line);
|
|
274
|
+
inAcceptance = ACCEPTANCE_H2_RE.test(line);
|
|
275
|
+
}
|
|
276
|
+
if (inDischarge) {
|
|
277
|
+
i += 1;
|
|
278
|
+
continue;
|
|
279
|
+
}
|
|
280
|
+
if (STEP_RE.test(line)) {
|
|
281
|
+
let j = i + 1;
|
|
282
|
+
while (j < lines.length && !STEP_RE.test(lines[j] as string) && !H2_RE.test(lines[j] as string)) {
|
|
283
|
+
j += 1;
|
|
284
|
+
}
|
|
285
|
+
const block = lines.slice(i, j);
|
|
286
|
+
out.push({
|
|
287
|
+
line: i + 1,
|
|
288
|
+
// Whitespace-normalised: a wrapped step joined with a single
|
|
289
|
+
// space still carries the next line's indent, so a pattern
|
|
290
|
+
// written with single spaces stops matching across the wrap —
|
|
291
|
+
// which is the case this unit exists to catch.
|
|
292
|
+
text: block.join(' ').replace(/\s+/g, ' '),
|
|
293
|
+
blockText: block.join('\n'),
|
|
294
|
+
inAcceptance,
|
|
295
|
+
isOpenStep: OPEN_STEP_RE.test(line),
|
|
296
|
+
hasVerify: /(?:^|\s)verify:/m.test(block.join('\n')),
|
|
297
|
+
});
|
|
298
|
+
i = j;
|
|
299
|
+
continue;
|
|
300
|
+
}
|
|
301
|
+
out.push({
|
|
302
|
+
line: i + 1,
|
|
303
|
+
text: line,
|
|
304
|
+
blockText: line,
|
|
305
|
+
inAcceptance,
|
|
306
|
+
isOpenStep: false,
|
|
307
|
+
hasVerify: false,
|
|
308
|
+
});
|
|
309
|
+
i += 1;
|
|
310
|
+
}
|
|
311
|
+
return out;
|
|
312
|
+
}
|
|
313
|
+
|
|
314
|
+
export function scan(text: string): Finding[] {
|
|
315
|
+
const lines = text.split('\n');
|
|
316
|
+
const findings: Finding[] = [];
|
|
317
|
+
|
|
318
|
+
for (const u of units(lines)) {
|
|
319
|
+
if (/<!--\s*closure:\s*ignore\s*-->/.test(u.text)) continue;
|
|
320
|
+
let matched: Kind | null = null;
|
|
321
|
+
for (const { kind, re } of LINE_PATTERNS) {
|
|
322
|
+
if (re.test(u.text)) {
|
|
323
|
+
matched = kind;
|
|
324
|
+
break;
|
|
325
|
+
}
|
|
326
|
+
}
|
|
327
|
+
// An open step carries a `verify:` line — a step whose success cannot
|
|
328
|
+
// be checked is a trigger for a question, not for work. Acceptance
|
|
329
|
+
// criteria are criteria, not steps: they have their own contract and
|
|
330
|
+
// carry no verify line by design.
|
|
331
|
+
if (matched === null && u.isOpenStep && !u.hasVerify && !u.inAcceptance) {
|
|
332
|
+
matched = 'missing-verify';
|
|
333
|
+
}
|
|
334
|
+
// A step that HAS a verify line, whose oracle still cannot say no.
|
|
335
|
+
// Checked on closed steps too — a `[x]` flipped on a command that
|
|
336
|
+
// could not fail is the defect in its completed form, and restricting
|
|
337
|
+
// this to open steps would hide every instance that already landed.
|
|
338
|
+
if (matched === null && u.hasVerify && !u.inAcceptance) {
|
|
339
|
+
const clause = parseVerifyClause(u.blockText);
|
|
340
|
+
if (clause !== null && unfalsifiableReason(clause) !== null) matched = 'unfalsifiable-verify';
|
|
341
|
+
}
|
|
342
|
+
if (matched === null) continue;
|
|
343
|
+
findings.push({
|
|
344
|
+
id: '',
|
|
345
|
+
line: u.line,
|
|
346
|
+
kind: matched,
|
|
347
|
+
ownership: OWNERSHIP_BY_KIND[matched],
|
|
348
|
+
excerpt: u.text.trim().replace(/\s+/g, ' ').slice(0, 120),
|
|
349
|
+
});
|
|
350
|
+
}
|
|
351
|
+
|
|
352
|
+
findings.sort((a, b) => a.line - b.line);
|
|
353
|
+
return findings.map((f, idx) => ({ ...f, id: `C${String(idx + 1)}` }));
|
|
354
|
+
}
|
|
355
|
+
|
|
356
|
+
/** The `## Decisions` skeleton — one row per finding, resolution left open. */
|
|
357
|
+
export function renderRows(findings: readonly Finding[]): string {
|
|
358
|
+
const header = '| ID | ownership | resolved by | decision | evidence | revisit if |';
|
|
359
|
+
const sep = '|---|---|---|---|---|---|';
|
|
360
|
+
const rows = findings.map(
|
|
361
|
+
(f) => `| ${f.id} | ${f.ownership} | <resolver> | <decision> | ${f.kind}:${String(f.line)} | <condition> |`,
|
|
362
|
+
);
|
|
363
|
+
return [header, sep, ...rows].join('\n');
|
|
364
|
+
}
|
|
365
|
+
|
|
366
|
+
export function ownerQuestionCount(findings: readonly Finding[]): number {
|
|
367
|
+
return findings.filter((f) => OWNER_ROUTED.has(f.ownership)).length;
|
|
368
|
+
}
|
|
369
|
+
|
|
370
|
+
// --------------------------------------------------------------------------
|
|
371
|
+
// CLI.
|
|
372
|
+
// --------------------------------------------------------------------------
|
|
373
|
+
|
|
374
|
+
function _usage(): string {
|
|
375
|
+
return (
|
|
376
|
+
`usage: ${_PROG} [-h] [--json] [--rows] [--strict] <roadmap.md>\n\n` +
|
|
377
|
+
`Find and classify the open decisions in a plan. Resolves nothing.\n`
|
|
378
|
+
);
|
|
379
|
+
}
|
|
380
|
+
|
|
381
|
+
export function main(argv?: readonly string[]): number {
|
|
382
|
+
const args = argv ?? process.argv.slice(2);
|
|
383
|
+
let json = false;
|
|
384
|
+
let rows = false;
|
|
385
|
+
let strict = false;
|
|
386
|
+
const paths: string[] = [];
|
|
387
|
+
for (const a of args) {
|
|
388
|
+
if (a === '-h' || a === '--help') {
|
|
389
|
+
process.stdout.write(_usage());
|
|
390
|
+
return 0;
|
|
391
|
+
} else if (a === '--json') json = true;
|
|
392
|
+
else if (a === '--rows') rows = true;
|
|
393
|
+
else if (a === '--strict') strict = true;
|
|
394
|
+
else if (a.startsWith('-')) {
|
|
395
|
+
process.stderr.write(`${_usage()}${_PROG}: error: unrecognized arguments: ${a}\n`);
|
|
396
|
+
return 2;
|
|
397
|
+
} else paths.push(a);
|
|
398
|
+
}
|
|
399
|
+
if (paths.length !== 1) {
|
|
400
|
+
process.stderr.write(`${_usage()}${_PROG}: error: exactly one roadmap path is required\n`);
|
|
401
|
+
return 2;
|
|
402
|
+
}
|
|
403
|
+
const target = path.resolve(paths[0] as string);
|
|
404
|
+
let text: string;
|
|
405
|
+
try {
|
|
406
|
+
text = fs.readFileSync(target, 'utf-8');
|
|
407
|
+
} catch {
|
|
408
|
+
process.stderr.write(`${_PROG}: error: cannot read ${paths[0] as string}\n`);
|
|
409
|
+
return 2;
|
|
410
|
+
}
|
|
411
|
+
|
|
412
|
+
const findings = scan(text);
|
|
413
|
+
const owner = ownerQuestionCount(findings);
|
|
414
|
+
|
|
415
|
+
if (json) {
|
|
416
|
+
process.stdout.write(
|
|
417
|
+
JSON.stringify({ findings, open: findings.length, owner_questions: owner }, null, 2) + '\n',
|
|
418
|
+
);
|
|
419
|
+
} else if (rows) {
|
|
420
|
+
process.stdout.write(renderRows(findings) + '\n');
|
|
421
|
+
} else {
|
|
422
|
+
process.stdout.write(`open decisions: ${String(findings.length)}\n`);
|
|
423
|
+
process.stdout.write(`owner questions: ${String(owner)}\n`);
|
|
424
|
+
for (const f of findings) {
|
|
425
|
+
process.stdout.write(
|
|
426
|
+
` ${f.id} ${String(f.line).padStart(4)} ${f.kind.padEnd(20)} ${f.ownership.padEnd(22)} ${f.excerpt}\n`,
|
|
427
|
+
);
|
|
428
|
+
}
|
|
429
|
+
}
|
|
430
|
+
if (!findings.length) return 0;
|
|
431
|
+
// `--strict` goes red on the families that gate, never on the listing.
|
|
432
|
+
const blocking = findings.filter((f) => !NON_BLOCKING_KINDS.has(f.kind));
|
|
433
|
+
return strict && blocking.length > 0 ? 1 : 0;
|
|
434
|
+
}
|
|
435
|
+
|
|
436
|
+
function _isCliEntry(): boolean {
|
|
437
|
+
if (!process.argv[1]) return false;
|
|
438
|
+
try {
|
|
439
|
+
return fs.realpathSync(_HERE) === fs.realpathSync(path.resolve(process.argv[1]));
|
|
440
|
+
} catch {
|
|
441
|
+
return false;
|
|
442
|
+
}
|
|
443
|
+
}
|
|
444
|
+
|
|
445
|
+
if (_isCliEntry() || process.argv[1] === _HERE) {
|
|
446
|
+
process.exitCode = main();
|
|
447
|
+
}
|