@event4u/agent-config 16.1.0 → 16.3.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 +98 -257
- package/README.md +14 -12
- package/dist/agent-src/commands/analyze/inbox.md +1 -1
- package/dist/agent-src/commands/analyze/roadmap-repos.md +1 -1
- package/dist/agent-src/commands/bug/fix.md +17 -2
- package/dist/agent-src/commands/feature/plan.md +1 -1
- package/dist/agent-src/commands/feature/roadmap.md +1 -1
- package/dist/agent-src/commands/fix/ci.md +1 -1
- package/dist/agent-src/commands/implement-ticket.md +1 -1
- package/dist/agent-src/commands/jira-ticket.md +1 -1
- package/dist/agent-src/commands/roadmap/create.md +1 -1
- package/dist/agent-src/commands/roadmap/materialize.md +1 -1
- package/dist/agent-src/commands/roadmap/process-full.md +5 -5
- package/dist/agent-src/commands/roadmap/resolve-blockers.md +361 -0
- package/dist/agent-src/commands/roadmap/triage-parked.md +325 -0
- package/dist/agent-src/commands/roadmap.md +18 -4
- package/dist/agent-src/commands/tests/create.md +17 -3
- package/dist/agent-src/commands/tests/execute.md +22 -5
- package/dist/agent-src/commands/work.md +4 -0
- package/dist/agent-src/contexts/authority/decision-revisit-gate-enforcement.md +43 -0
- package/dist/agent-src/contexts/communication/rules-auto/design-fidelity-enforcement.md +24 -0
- package/dist/agent-src/contexts/execution/auto-dispatch-classification.md +6 -6
- package/dist/agent-src/contexts/execution/autonomy-mechanics.md +4 -8
- package/dist/agent-src/contexts/execution/roadmap-process-loop.md +32 -2
- package/dist/agent-src/contexts/execution/session-canary-enforcement-history.md +67 -0
- package/dist/agent-src/contexts/execution/toolchain-resolver.md +101 -7
- package/dist/agent-src/rules/active-remediation.md +1 -1
- package/dist/agent-src/rules/autonomous-execution.md +1 -0
- package/dist/agent-src/rules/broken-access-control.md +1 -0
- package/dist/agent-src/rules/command-suggestion-policy.md +1 -0
- package/dist/agent-src/rules/context-hygiene.md +1 -1
- package/dist/agent-src/rules/decision-revisit-gate.md +16 -33
- package/dist/agent-src/rules/design-fidelity.md +13 -16
- package/dist/agent-src/rules/doc-screenshot-hygiene.md +1 -0
- package/dist/agent-src/rules/domain-safety-disclaimer.md +1 -0
- package/dist/agent-src/rules/domain-safety-pii.md +1 -0
- package/dist/agent-src/rules/domain-safety-retention.md +1 -0
- package/dist/agent-src/rules/engineering-safety-floor.md +1 -0
- package/dist/agent-src/rules/evaluator-independence.md +3 -2
- package/dist/agent-src/rules/fast-path-marker-visibility.md +1 -0
- package/dist/agent-src/rules/finance-safety-floor.md +1 -0
- package/dist/agent-src/rules/git-history-discipline.md +1 -0
- package/dist/agent-src/rules/image-likeness-and-rights.md +1 -0
- package/dist/agent-src/rules/legal-safety-floor.md +3 -2
- package/dist/agent-src/rules/lethal-trifecta-guard.md +1 -0
- package/dist/agent-src/rules/low-impact-corpus-privacy-floor.md +1 -0
- package/dist/agent-src/rules/media-governance-routing.md +14 -17
- package/dist/agent-src/rules/persona-governance.md +2 -2
- package/dist/agent-src/rules/provider-lifecycle-discipline.md +1 -1
- package/dist/agent-src/rules/question-not-instruction.md +1 -0
- package/dist/agent-src/rules/roadmap-progress-sync.md +1 -1
- package/dist/agent-src/rules/runtime-safety.md +1 -0
- package/dist/agent-src/rules/secret-vcs-guard.md +1 -0
- package/dist/agent-src/rules/security-sensitive-stop.md +3 -2
- package/dist/agent-src/rules/session-canary.md +8 -62
- package/dist/agent-src/rules/settings-ask-protocol.md +1 -0
- package/dist/agent-src/rules/source-confidentiality.md +1 -0
- package/dist/agent-src/rules/strategy-safety-floor.md +1 -0
- package/dist/agent-src/rules/tool-safety.md +1 -0
- package/dist/agent-src/rules/ui-audit-gate.md +1 -2
- package/dist/agent-src/rules/untrusted-input-defense.md +1 -0
- package/dist/agent-src/rules/user-interrupt-priority.md +1 -0
- package/dist/agent-src/scripts/archive_completed_roadmaps.ts +115 -20
- package/dist/agent-src/scripts/deferral_carry.ts +339 -0
- package/dist/agent-src/scripts/update_roadmap_progress.ts +4 -1
- package/dist/agent-src/skills/accessibility-auditor/data/aria-patterns.csv +1 -1
- package/dist/agent-src/skills/accessibility-auditor/data/manifest.json +2 -2
- package/dist/agent-src/skills/api-design/data/manifest.json +2 -2
- package/dist/agent-src/skills/api-testing/SKILL.md +17 -0
- package/dist/agent-src/skills/code-intelligence/SKILL.md +14 -2
- package/dist/agent-src/skills/design-review/SKILL.md +30 -4
- package/dist/agent-src/skills/design-review/references/verification-automation.md +32 -0
- package/dist/agent-src/skills/design-system-capture/references/import-procedure.md +1 -1
- package/dist/agent-src/skills/existing-ui-audit/SKILL.md +18 -3
- package/dist/agent-src/skills/existing-ui-audit/references/component-taxonomy-detection.md +56 -0
- package/dist/agent-src/skills/fe-design/SKILL.md +1 -1
- package/dist/agent-src/skills/iconography/SKILL.md +1 -1
- package/dist/agent-src/skills/js-library-packaging/SKILL.md +2 -2
- package/dist/agent-src/skills/markitdown/SKILL.md +4 -0
- package/dist/agent-src/skills/monorepo-workspace/SKILL.md +1 -1
- package/dist/agent-src/skills/playwright-testing/SKILL.md +30 -6
- package/dist/agent-src/skills/quality-tools/SKILL.md +11 -1
- package/dist/agent-src/skills/quality-tools/references/go-tools.md +76 -0
- package/dist/agent-src/skills/quality-tools/references/python-tools.md +104 -0
- package/dist/agent-src/skills/review-routing/SKILL.md +2 -2
- package/dist/agent-src/skills/skill-improvement-pipeline/SKILL.md +1 -1
- package/dist/agent-src/skills/storybook-workshop/SKILL.md +3 -3
- package/dist/agent-src/skills/tailwind-engineer/SKILL.md +1 -1
- package/dist/agent-src/skills/test-case-discovery/SKILL.md +7 -0
- package/dist/agent-src/skills/test-driven-development/SKILL.md +17 -0
- package/dist/agent-src/skills/test-performance/SKILL.md +19 -1
- package/dist/agent-src/skills/ui-component-architect/SKILL.md +25 -2
- package/dist/agent-src/templates/agents/agent-project-settings.example.yml +1 -1
- package/dist/agent-src/templates/roadmaps.md +30 -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 +302 -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/stack/runner.ts +774 -109
- 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/registry.js +1 -1
- package/dist/cli-delegate/archive_completed_roadmaps.js +92 -12
- package/dist/cli-delegate/check_update_banner.js +12 -1
- package/dist/cli-delegate/chunk-523HCFYO.js +90 -0
- package/dist/cli-delegate/{chunk-M2DPGFZ4.js → chunk-6V6TA25H.js} +22 -152
- package/dist/cli-delegate/chunk-7IQE3D5S.js +191 -0
- package/dist/cli-delegate/{chunk-6KFEC3G7.js → chunk-CQWX336L.js} +1 -1
- package/dist/cli-delegate/chunk-DEIV7XP7.js +108 -0
- package/dist/cli-delegate/chunk-EEHDXSYD.js +2040 -0
- package/dist/cli-delegate/{chunk-P6ZHP5W2.js → chunk-GKE7ZVFA.js} +4 -4
- package/dist/cli-delegate/chunk-JQOM7TTM.js +1201 -0
- package/dist/cli-delegate/{chunk-UNCRJBT3.js → chunk-JQVYKIZA.js} +41 -0
- package/dist/cli-delegate/{chunk-FC75PGJE.js → chunk-JY2I2DXV.js} +12 -1
- package/dist/cli-delegate/chunk-KB5FZ655.js +208 -0
- package/dist/cli-delegate/{chunk-YUM5KURG.js → chunk-KR6EM23A.js} +235 -1455
- package/dist/cli-delegate/{chunk-LTYVIY76.js → chunk-LIYTLH6O.js} +1 -1
- package/dist/cli-delegate/chunk-NQ7WT43P.js +2729 -0
- package/dist/cli-delegate/chunk-NXEILF2K.js +2160 -0
- package/dist/cli-delegate/{chunk-5R7HOLAF.js → chunk-NYX3L2PZ.js} +1 -1
- package/dist/cli-delegate/{chunk-4E2W63DA.js → chunk-OKJUIAPS.js} +62 -1
- package/dist/cli-delegate/{chunk-QAJS5YWY.js → chunk-PHD7OOLE.js} +2 -1
- package/dist/cli-delegate/{chunk-RGNIWTEA.js → chunk-QLDHIPWR.js} +1 -1
- package/dist/cli-delegate/{chunk-HDVVGPQW.js → chunk-RQNNW7IF.js} +1405 -567
- package/dist/cli-delegate/{chunk-S3VFXMTM.js → chunk-S4UMZNES.js} +1 -1
- package/dist/cli-delegate/{chunk-6KM6ORRY.js → chunk-S4YWNRKO.js} +24 -8
- package/dist/cli-delegate/{chunk-GJCBB2UG.js → chunk-TRI6DQZ6.js} +3054 -325
- package/dist/cli-delegate/chunk-ULQRUR23.js +208 -0
- package/dist/cli-delegate/{chunk-NUHLI2RX.js → chunk-YCU3TIXH.js} +2 -0
- package/dist/cli-delegate/chunk-ZBZV2G5R.js +210 -0
- package/dist/cli-delegate/{chunk-WGXTVKCQ.js → chunk-ZUZKZYCN.js} +5 -3
- 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 +1862 -809
- package/dist/cli-delegate/cmd_doctor.js +13 -6
- package/dist/cli-delegate/cmd_doctor_neighbours.js +23 -0
- package/dist/cli-delegate/cmd_explain.js +5 -5
- package/dist/cli-delegate/cmd_export.js +11 -8
- package/dist/cli-delegate/cmd_fleet.js +3 -3
- package/dist/cli-delegate/cmd_handoff.js +10 -7
- package/dist/cli-delegate/cmd_mcp_available.js +1 -1
- package/dist/cli-delegate/cmd_memory_get.js +3 -1
- package/dist/cli-delegate/cmd_migrate.js +2 -2
- package/dist/cli-delegate/cmd_packs_active.js +3 -3
- package/dist/cli-delegate/cmd_preflight.js +6 -6
- package/dist/cli-delegate/cmd_prune.js +11 -8
- package/dist/cli-delegate/cmd_refresh.js +2 -1
- package/dist/cli-delegate/cmd_route_audit.js +8 -6
- package/dist/cli-delegate/cmd_route_explain.js +4 -4
- package/dist/cli-delegate/cmd_session_recycle.js +4 -3
- package/dist/cli-delegate/cmd_settings_get.js +1 -1
- package/dist/cli-delegate/cmd_settings_migrate.js +11 -8
- package/dist/cli-delegate/cmd_settings_set.js +4 -4
- package/dist/cli-delegate/cmd_sync.js +11 -8
- package/dist/cli-delegate/cmd_uninstall.js +11 -8
- package/dist/cli-delegate/cmd_update.js +1 -1
- package/dist/cli-delegate/cmd_upgrade.js +3 -2
- package/dist/cli-delegate/cmd_validate.js +11 -8
- package/dist/cli-delegate/cmd_versions.js +1 -1
- package/dist/cli-delegate/deferral_carry.js +16 -0
- package/dist/cli-delegate/gate_execute.js +2 -2
- package/dist/cli-delegate/roadmap_gates.js +5 -5
- package/dist/cli-delegate/update_roadmap_progress.js +1 -1
- package/dist/discovery/deprecation-report.md +1 -1
- package/dist/discovery/discovery-manifest.json +132 -80
- package/dist/discovery/discovery-manifest.json.sha256 +1 -1
- package/dist/discovery/discovery-manifest.summary.md +3 -3
- package/dist/discovery/dormancy-report.md +1 -1
- package/dist/discovery/orphan-report.md +1 -1
- package/dist/discovery/packs.json +7 -5
- package/dist/discovery/trust-report.md +2 -2
- package/dist/discovery/workspaces.json +6 -4
- package/dist/hooks/dispatch.js +7972 -5186
- package/dist/hooks/dispatch.sha256 +1 -0
- package/dist/install/claudePathsPlan.js +47 -6
- package/dist/install/claudeRuleRewrite.js +7 -0
- package/dist/install/emit_host_rules_cli.js +39 -36
- package/dist/install/globalRuleScope.js +87 -0
- package/dist/install/host_rule_ownership.js +160 -0
- package/dist/install/install.mjs +2226 -1055
- package/dist/install/installThinLayer.js +261 -0
- package/dist/install/installedRuleLinks.js +176 -0
- package/dist/install/wizard-plan.js +24 -0
- package/dist/mcp/registry-manifest.json +3 -3
- package/dist/mcp/server.json +2 -2
- package/dist/mcp/server.mjs +40913 -34865
- package/dist/scripts/_lib/agent_settings.js +11 -0
- package/dist/scripts/_lib/claude_settings_hooks.js +10 -8
- package/dist/scripts/_lib/installed_layer.js +378 -0
- package/dist/scripts/_lib/lean_projection_mode.js +218 -15
- package/dist/scripts/_lib/rule_consequence_class.js +45 -0
- package/dist/scripts/_lib/rule_injection.js +67 -10
- package/dist/scripts/_lib/rule_law_section.js +122 -0
- package/dist/scripts/_lib/thin_rules.js +458 -0
- package/dist/scripts/_lib/windsurf_render.js +60 -0
- package/dist/scripts/condense.js +14 -26
- package/dist/scripts/project_thin_rules.js +14 -294
- package/dist/server/schemas/settings.js +29 -0
- package/dist/shared/skillRanking.js +74 -13
- 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/contracts/settings-classes.md +10 -6
- package/package.json +4 -4
- package/src/agent-src/scripts/archive_completed_roadmaps.ts +115 -20
- package/src/agent-src/scripts/deferral_carry.ts +339 -0
- package/src/agent-src/scripts/update_roadmap_progress.ts +4 -1
- 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 +302 -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/stack/runner.ts +774 -109
- 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 +91 -22
- package/src/config/ci-local-parity.yml +18 -0
- package/src/config/continuity-surface.json +9 -2
- package/src/config/evaluator-budgets.json +4 -4
- package/src/config/gate-coverage.yml +327 -3
- package/src/config/gate-violation-baselines.json +21 -9
- package/src/config/hook-bundle-budget.json +25 -0
- package/src/config/hook-latency-budget.json +21 -0
- package/src/config/hook-token-budget.json +8 -3
- package/src/config/host-instruction-limits.json +33 -0
- package/src/config/metric-registry.yml +114 -0
- package/src/config/placeholder-drift-budget.json +10 -0
- package/src/config/routing-coverage-seed.json +38 -3
- package/src/config/rule-activation-census.json +7 -3
- package/src/config/rule-consequence-class.json +161 -0
- package/src/config/rule-law-ceilings.json +99 -0
- package/src/install/claudePathsPlan.ts +46 -4
- package/src/install/claudeRuleRewrite.ts +10 -1
- package/src/install/emit_host_rules_cli.ts +43 -34
- package/src/install/globalRuleScope.ts +109 -0
- package/src/install/host_rule_ownership.ts +170 -0
- package/src/install/installThinLayer.ts +312 -0
- package/src/install/installedRuleLinks.ts +220 -0
- package/src/install/wizard-plan.ts +24 -0
- package/src/scripts/_cli/cmd_doctor.ts +28 -90
- package/src/scripts/_cli/cmd_doctor_neighbours.ts +293 -0
- package/src/scripts/_cli/doctor_execution.ts +100 -17
- package/src/scripts/_cli/doctor_network_posture.ts +226 -0
- package/src/scripts/_dispatch.bash +16 -2
- package/src/scripts/_lib/agent_settings.ts +11 -0
- package/src/scripts/_lib/body_portable.ts +102 -0
- package/src/scripts/_lib/cc_transcript.ts +132 -1
- package/src/scripts/_lib/claude_settings_hooks.ts +10 -8
- package/src/scripts/_lib/concern_sla_window.ts +200 -0
- package/src/scripts/_lib/estate_offsets.ts +241 -0
- package/src/scripts/_lib/exemption_shape.ts +143 -0
- package/src/scripts/_lib/fnv.ts +23 -0
- package/src/scripts/_lib/foreign_scan.ts +106 -0
- package/src/scripts/_lib/forge_protection.ts +114 -0
- package/src/scripts/_lib/forge_reader.ts +558 -0
- package/src/scripts/_lib/gitignore_block.ts +314 -0
- package/src/scripts/_lib/global_deploy_inventory.ts +32 -0
- package/src/scripts/_lib/graph_feeder_record.ts +214 -0
- package/src/scripts/_lib/hook_settings.ts +73 -0
- package/src/scripts/_lib/host_capability.ts +40 -19
- package/src/scripts/_lib/host_env_write.ts +111 -0
- package/src/scripts/_lib/host_hook_merge.ts +165 -0
- package/src/scripts/_lib/installed_layer.ts +538 -0
- package/src/scripts/_lib/json_merge.ts +82 -0
- package/src/scripts/_lib/json_pointers.ts +93 -11
- package/src/scripts/_lib/lean_projection_mode.ts +275 -15
- package/src/scripts/_lib/llm_proposer_transport.ts +14 -4
- package/src/scripts/_lib/machine_wake.ts +16 -3
- package/src/scripts/_lib/neighbour_census.ts +760 -0
- package/src/scripts/_lib/neighbour_scan.ts +122 -0
- package/src/scripts/_lib/neighbour_tool_use.ts +189 -0
- package/src/scripts/_lib/obligations.ts +119 -8
- package/src/scripts/_lib/probe_inputs.ts +271 -0
- package/src/scripts/_lib/prompt_shape.ts +46 -0
- package/src/scripts/_lib/read_surface_scan.ts +322 -0
- package/src/scripts/_lib/release_highlights.ts +2 -1
- package/src/scripts/_lib/release_migration_gate.ts +19 -3
- package/src/scripts/_lib/reserved_name_sweep.ts +73 -0
- package/src/scripts/_lib/retrieval_sanitize.ts +77 -16
- package/src/scripts/_lib/review_coverage.ts +134 -0
- package/src/scripts/_lib/rule_consequence_class.ts +71 -0
- package/src/scripts/_lib/rule_injection.ts +130 -10
- package/src/scripts/_lib/rule_law_section.ts +147 -0
- package/src/scripts/_lib/rule_layer_overlap.ts +58 -0
- package/src/scripts/_lib/shell_write_shapes.ts +108 -0
- package/src/scripts/_lib/skill_origin.ts +254 -0
- package/src/scripts/_lib/structural_hiding.ts +622 -0
- package/src/scripts/_lib/structured_ask.ts +64 -21
- package/src/scripts/_lib/thin_rules.ts +540 -0
- package/src/scripts/_lib/touched_file_quality.ts +434 -0
- package/src/scripts/_lib/turn_end_refusals.ts +600 -4
- package/src/scripts/_lib/turn_end_transcript.ts +216 -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/_lib/windsurf_render.ts +63 -0
- package/src/scripts/ai_council/cli_call_budget.ts +13 -5
- package/src/scripts/ai_council/clients.ts +26 -57
- package/src/scripts/ai_council/config.ts +1 -1
- package/src/scripts/ai_council/vendor_defaults.ts +45 -0
- package/src/scripts/ai_team/team_dispatch.ts +4 -0
- package/src/scripts/audit_skill_overlap.ts +261 -0
- package/src/scripts/before_complete_hook.ts +573 -25
- package/src/scripts/bench_hook_latency.ts +412 -5
- package/src/scripts/bench_touched_file_quality.ts +238 -0
- 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 +621 -22
- package/src/scripts/check_estate_count.ts +130 -153
- package/src/scripts/check_finding_dispositions.ts +138 -7
- package/src/scripts/check_generator_sync.ts +71 -0
- package/src/scripts/check_hook_bundle_composition.ts +370 -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 +89 -9
- package/src/scripts/check_kill_switch_table.ts +294 -0
- package/src/scripts/check_memory.ts +7 -1
- package/src/scripts/check_placeholder_drift.ts +221 -0
- package/src/scripts/check_read_surface_coverage.ts +515 -0
- package/src/scripts/check_release_highlights.ts +10 -0
- package/src/scripts/check_release_holds.ts +2 -1
- package/src/scripts/check_release_pr_shape.ts +38 -15
- package/src/scripts/check_routing_coverage.ts +378 -11
- package/src/scripts/check_standing_rule_delivery.ts +33 -13
- package/src/scripts/check_static_parity.ts +17 -0
- package/src/scripts/check_verify_expectation_delta.ts +521 -0
- package/src/scripts/ci_settle.ts +145 -19
- package/src/scripts/closure_scan.ts +115 -1
- package/src/scripts/code_graph/detect.ts +127 -7
- package/src/scripts/code_graph/foreign.ts +194 -0
- package/src/scripts/code_graph/query.ts +217 -4
- package/src/scripts/code_graph/types.ts +26 -2
- package/src/scripts/code_graph/validate.ts +3 -1
- package/src/scripts/code_graph/verbs.ts +103 -0
- package/src/scripts/compile_hook_manifest.ts +90 -14
- package/src/scripts/condense.ts +14 -27
- package/src/scripts/estate_exemption_shape.ts +202 -0
- package/src/scripts/external_sources_denylist.json +29 -4
- package/src/scripts/generate_host_cost_table.ts +1 -1
- package/src/scripts/hook_manifest.json +1 -1
- package/src/scripts/hook_manifest.yaml +192 -14
- package/src/scripts/hooks/block_config_weakening.ts +294 -4
- package/src/scripts/hooks/block_kernel_rule_writes.ts +9 -78
- package/src/scripts/hooks/block_plumbing_writes.ts +342 -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 +253 -25
- package/src/scripts/hooks/comment_discipline_hook.ts +1 -3
- package/src/scripts/hooks/concern_failure_policy.ts +280 -0
- package/src/scripts/hooks/concern_registry.ts +2 -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 +100 -9
- package/src/scripts/hooks/design_slop_hook.ts +1 -2
- package/src/scripts/hooks/dispatch_hook.ts +77 -71
- 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 +1 -2
- package/src/scripts/hooks/exit_codes.ts +129 -0
- package/src/scripts/hooks/host_lowering.json +1 -0
- package/src/scripts/hooks/host_lowering.ts +146 -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 +1 -2
- package/src/scripts/hooks/obligation_settle_hook.ts +151 -5
- package/src/scripts/hooks/one_question_per_ask_hook.ts +43 -6
- package/src/scripts/hooks/orchestration_record_hook.ts +1 -2
- package/src/scripts/hooks/payload_stub.ts +10 -1
- 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 +765 -68
- package/src/scripts/hooks/run_continuation_hook.ts +13 -15
- 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 +70 -0
- package/src/scripts/hooks/telemetry_flush_hook.ts +1 -2
- package/src/scripts/hooks/telemetry_usage_hook.ts +65 -10
- package/src/scripts/hooks/tool_result_bytes_hook.ts +93 -9
- package/src/scripts/hooks/turn_end_gate_hook.ts +421 -324
- package/src/scripts/hooks/ui_route_nudge_hook.ts +1 -2
- package/src/scripts/hooks_status.ts +112 -0
- package/src/scripts/injection_scan_hook.ts +7 -0
- package/src/scripts/install.ts +75 -189
- package/src/scripts/installed_layer_report.ts +103 -0
- package/src/scripts/language_mirror_hook.ts +15 -6
- package/src/scripts/learning_sidecar.ts +119 -8
- 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 +108 -0
- package/src/scripts/lint_major_migration_sections.ts +156 -26
- package/src/scripts/lint_pack_boundaries.ts +63 -5
- package/src/scripts/lint_roadmap_blockers.ts +78 -12
- package/src/scripts/lint_rule_law_section.ts +517 -0
- package/src/scripts/lint_skill_descriptions.ts +1 -1
- package/src/scripts/lint_skill_scripts_readonly.ts +1 -1
- package/src/scripts/lint_workflow_security.ts +161 -5
- package/src/scripts/mcp_server/consumer_tool_catalog.json +53 -0
- package/src/scripts/mcp_server/graph_tools.ts +72 -3
- package/src/scripts/mcp_server/prompts.ts +142 -1
- package/src/scripts/measure_routing_signal.ts +54 -16
- package/src/scripts/measure_skill_ranker_baseline.ts +515 -53
- package/src/scripts/measure_turn_end_gate.ts +193 -0
- package/src/scripts/memory_learn_hook.ts +91 -6
- package/src/scripts/memory_lookup.ts +1 -1
- package/src/scripts/model_rule_injection.ts +52 -5
- package/src/scripts/prepack-check.mjs +53 -0
- package/src/scripts/project_thin_rules.ts +34 -327
- package/src/scripts/release_publication.ts +13 -1
- package/src/scripts/report_evidence_temperature.ts +657 -0
- package/src/scripts/report_installed_rule_links.ts +157 -0
- package/src/scripts/report_invocation_surface.ts +383 -0
- package/src/scripts/report_label_agreement.ts +233 -0
- package/src/scripts/report_obligation_carriers.ts +699 -0
- package/src/scripts/report_obligation_mechanism.ts +230 -0
- package/src/scripts/report_obligation_writer_split.ts +236 -0
- package/src/scripts/report_single_token_triggers.ts +277 -0
- package/src/scripts/report_skill_menu_census.ts +208 -0
- package/src/scripts/report_skill_ranker_confusion.ts +442 -0
- package/src/scripts/report_standing_payload_by_host.ts +188 -15
- package/src/scripts/report_verification_record_coverage.ts +306 -0
- package/src/scripts/roadmap_verify_share.ts +213 -0
- package/src/scripts/rule_activation_census.ts +5 -2
- package/src/scripts/rule_trigger_eval.ts +9 -9
- package/src/scripts/schemas/command.schema.json +44 -0
- package/src/scripts/schemas/review-findings.schema.json +7 -2
- package/src/scripts/schemas/rule.schema.json +5 -0
- package/src/scripts/schemas/skill.schema.json +44 -0
- package/src/scripts/self_review_gate.ts +421 -17
- package/src/scripts/semantic_parity_census.ts +314 -0
- package/src/scripts/session_eol_report.ts +54 -1
- package/src/scripts/skill_tools/score_skill_relevance.ts +290 -25
- package/src/scripts/sweep_dead_scan_roots.ts +1 -1
- package/src/scripts/sweep_skill_ranker_signals.ts +129 -0
- package/src/scripts/sync_gitignore.ts +29 -235
- package/src/scripts/trigger_eval_grandfather.json +0 -7
- package/src/scripts/trigger_eval_rotation.ts +122 -27
- package/src/scripts/ui_conformance_probe.ts +156 -4
- package/src/scripts/update_prices.ts +85 -3
- package/src/scripts/write_bundle_digest.mjs +74 -0
- package/src/server/schemas/settings.ts +41 -0
- package/src/shared/skillRanking.ts +117 -11
- package/src/templates/consumer-settings/ONBOARDING.md +1 -0
- package/dist/cli-delegate/chunk-FXYUYU4N.js +0 -4416
- package/dist/cli-delegate/chunk-QJWAUMJW.js +0 -1072
- package/dist/cli-delegate/chunk-YDPILCOL.js +0 -152
- package/dist/ui/assets/index-DH1xc62v.js +0 -29
- package/dist/ui/assets/index-jSfMW8_t.css +0 -1
|
@@ -0,0 +1,558 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The live forge read behind `doctor --json`'s `forge_protection` block.
|
|
3
|
+
*
|
|
4
|
+
* `road-to-adversarial-verification-and-long-runs` AC-5. Phase 3.2 shipped a
|
|
5
|
+
* correct pure mapper (`forge_protection.ts`) and no production caller, so
|
|
6
|
+
* `agent-config doctor --json` — the command the criterion names — reported five
|
|
7
|
+
* `unread` rows while the forge satisfied all five. The rows were established by
|
|
8
|
+
* a human running `gh api` by hand and reading the mapper's output in a test.
|
|
9
|
+
* That is the exact substitution this roadmap's Phase 3 exists to forbid: a
|
|
10
|
+
* mechanical check replaced by a person's reading of the same evidence.
|
|
11
|
+
*
|
|
12
|
+
* **This IS a reversal of Phase 3.2's recorded decision, and the roadmap carries
|
|
13
|
+
* the supersession.** An earlier draft of this header argued the opposite — that
|
|
14
|
+
* `decision-revisit-gate`'s mechanism-match test fails, so no lock applies — and
|
|
15
|
+
* a 2/2 convergent council pass refuted it: that note recorded TWO boundaries,
|
|
16
|
+
* `doctor` does not reach the network AND the reading is injected, and moving
|
|
17
|
+
* `gh api` in here reverses both. The refuted reasoning is corrected rather than
|
|
18
|
+
* left standing, for the reason the sibling docblock gives: a module header is
|
|
19
|
+
* what the next reader meets first, so it is where a dead argument gets reused.
|
|
20
|
+
*
|
|
21
|
+
* What survives is the narrower AVAILABILITY property the supersession rests on:
|
|
22
|
+
* every failure path returns {@link UNREAD_FORGE}, so offline the rows, their
|
|
23
|
+
* `source` templates and the action lines are what Phase 3.2 printed — no error,
|
|
24
|
+
* no hang. Not the whole document: the block gained a top-level `repository`
|
|
25
|
+
* key, `null` on every such path.
|
|
26
|
+
*
|
|
27
|
+
* **The degradation is the load-bearing claim, so it is tested rather than
|
|
28
|
+
* asserted.** Six distinct failures — no repository, a dead repo record, a dead
|
|
29
|
+
* ruleset detail, a dead environments call, a non-boolean flag, a throwing API —
|
|
30
|
+
* each land on `unread` for the rows they touch and leave the others alone. Two
|
|
31
|
+
* of those directions have a more obvious wrong implementation than the right
|
|
32
|
+
* one, and both are pinned: a PARTIAL ruleset list (understates protection in
|
|
33
|
+
* one direction, overstates it in the other), and ALL-OR-NOTHING across surfaces
|
|
34
|
+
* that do not depend on each other (a dead environments call says nothing about
|
|
35
|
+
* whether a ruleset protects the branch).
|
|
36
|
+
*
|
|
37
|
+
* The network lives here; the judgement lives in `forge_protection.ts`. Both
|
|
38
|
+
* polarities stay testable offline because the API is injected.
|
|
39
|
+
*/
|
|
40
|
+
|
|
41
|
+
import { spawnSync } from 'node:child_process';
|
|
42
|
+
|
|
43
|
+
import {
|
|
44
|
+
UNREAD_FORGE,
|
|
45
|
+
deployRestrictedFrom,
|
|
46
|
+
type EnvironmentPolicy,
|
|
47
|
+
type ForgeReading,
|
|
48
|
+
} from './forge_protection.js';
|
|
49
|
+
import type { RulesetDetail } from './platform_anchor.js';
|
|
50
|
+
|
|
51
|
+
/**
|
|
52
|
+
* One forge API call. `null` on ANY failure — exit status, empty output,
|
|
53
|
+
* unparseable JSON, timeout, missing binary, missing credentials.
|
|
54
|
+
*
|
|
55
|
+
* Injected rather than imported so every failure above is reachable from a test
|
|
56
|
+
* without a network, which is what makes the degradation claim checkable.
|
|
57
|
+
*/
|
|
58
|
+
export interface ForgeApi {
|
|
59
|
+
get(apiPath: string, paginate?: boolean): unknown | null;
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
/**
|
|
63
|
+
* Per-call ceiling.
|
|
64
|
+
*
|
|
65
|
+
* A diagnostic that hangs is worse than one that reports `unread`: the reader
|
|
66
|
+
* learns nothing either way, and only one of the two costs them the terminal.
|
|
67
|
+
* Ten seconds is a stated default rather than a measured optimum — it is
|
|
68
|
+
* generous for a `gh api` round trip and short enough that five of them stay
|
|
69
|
+
* inside the patience budget of a command people run to get unstuck.
|
|
70
|
+
*/
|
|
71
|
+
export const FORGE_CALL_TIMEOUT_MS = 10_000;
|
|
72
|
+
|
|
73
|
+
/** Ceiling on the one `git` spawn, which sits inside the whole-read budget. */
|
|
74
|
+
export const GIT_REMOTE_TIMEOUT_MS = 2_000;
|
|
75
|
+
|
|
76
|
+
/**
|
|
77
|
+
* Ceiling on the WHOLE read, not on one call.
|
|
78
|
+
*
|
|
79
|
+
* The call count is `3 + one per ruleset + one per environment carrying custom
|
|
80
|
+
* branch policies` — it is NOT a fixed five, and sizing the budget against a
|
|
81
|
+
* fixed five was the defect an independent review named. On a repository with
|
|
82
|
+
* many rulesets the budget is what stops the read, and the rows it did not
|
|
83
|
+
* reach report `unread`, which is the honest answer rather than a wrong one.
|
|
84
|
+
*
|
|
85
|
+
* Fifteen seconds is a stated default, not a measured optimum. It is enforced
|
|
86
|
+
* as a real ceiling rather than only at admission: {@link budgetOf} returns the
|
|
87
|
+
* milliseconds left, {@link liveForgeApi} takes that as its `timeoutFor` and
|
|
88
|
+
* caps each spawn at `min(FORGE_CALL_TIMEOUT_MS, remaining)`, and
|
|
89
|
+
* {@link withDeadline} refuses to dispatch once it reaches zero. So a call
|
|
90
|
+
* admitted near the deadline cannot run past it.
|
|
91
|
+
*/
|
|
92
|
+
export const FORGE_TOTAL_BUDGET_MS = 15_000;
|
|
93
|
+
|
|
94
|
+
/**
|
|
95
|
+
* Environment switches that skip the read entirely, before any process spawns.
|
|
96
|
+
*
|
|
97
|
+
* Both are read as the literal `1`, which is the contract `cmd_versions.ts` and
|
|
98
|
+
* `cmd_update.ts` already use for `AGENT_CONFIG_OFFLINE`. Accepting any
|
|
99
|
+
* non-empty value here would give one variable two opposite meanings inside one
|
|
100
|
+
* binary — `AGENT_CONFIG_OFFLINE=0` would mean online to `update` and offline
|
|
101
|
+
* to `doctor` — and the surprising direction is the one a user reads as "off".
|
|
102
|
+
*/
|
|
103
|
+
export const NO_FORGE_ENV = ['AGENT_CONFIG_DOCTOR_NO_FORGE', 'AGENT_CONFIG_OFFLINE'] as const;
|
|
104
|
+
|
|
105
|
+
/** `true` when a switch in {@link NO_FORGE_ENV} is set to the literal `1`. */
|
|
106
|
+
export function forgeReadDisabled(env: Readonly<Record<string, string | undefined>>): boolean {
|
|
107
|
+
return NO_FORGE_ENV.some((k) => env[k] === '1');
|
|
108
|
+
}
|
|
109
|
+
|
|
110
|
+
function asObject(v: unknown): Record<string, unknown> | null {
|
|
111
|
+
return v !== null && typeof v === 'object' && !Array.isArray(v)
|
|
112
|
+
? (v as Record<string, unknown>)
|
|
113
|
+
: null;
|
|
114
|
+
}
|
|
115
|
+
|
|
116
|
+
/** What a spawn returns, narrowed to the two fields this module reads. */
|
|
117
|
+
export interface SpawnResult {
|
|
118
|
+
readonly status: number | null;
|
|
119
|
+
readonly stdout?: string | undefined;
|
|
120
|
+
}
|
|
121
|
+
|
|
122
|
+
/** A subprocess runner. Injected so every failure branch is testable offline. */
|
|
123
|
+
export type Runner = (
|
|
124
|
+
cmd: string,
|
|
125
|
+
args: readonly string[],
|
|
126
|
+
timeoutMs: number,
|
|
127
|
+
cwd?: string,
|
|
128
|
+
) => SpawnResult;
|
|
129
|
+
|
|
130
|
+
const defaultRunner: Runner = (cmd, args, timeoutMs, cwd) =>
|
|
131
|
+
spawnSync(cmd, [...args], {
|
|
132
|
+
encoding: 'utf8',
|
|
133
|
+
timeout: timeoutMs,
|
|
134
|
+
maxBuffer: 32 * 1024 * 1024,
|
|
135
|
+
...(cwd === undefined ? {} : { cwd }),
|
|
136
|
+
});
|
|
137
|
+
|
|
138
|
+
/**
|
|
139
|
+
* The `timeout` for one call, given the milliseconds left of the whole budget.
|
|
140
|
+
*
|
|
141
|
+
* **Two Node behaviours meet here, and both bite in the same place.**
|
|
142
|
+
* `spawnSync`'s `timeout` must be an INTEGER — `budgetOf` reads
|
|
143
|
+
* `performance.now()`, which is fractional, so an unfloored value threw
|
|
144
|
+
* `ERR_OUT_OF_RANGE`, was swallowed, and the read degraded with no subprocess
|
|
145
|
+
* started. And `timeout: 0` installs NO kill timer at all, so flooring alone
|
|
146
|
+
* turned a sub-millisecond remainder into an UNBOUNDED spawn — the ceiling
|
|
147
|
+
* failing open in exactly the budget-exhaustion case it exists for, which is
|
|
148
|
+
* the worse of the two directions.
|
|
149
|
+
*
|
|
150
|
+
* So the floor is 1 ms, not 0: a call admitted with a sliver of budget left is
|
|
151
|
+
* started and killed almost immediately, which lands on `unread` the way every
|
|
152
|
+
* other failure does. Never `0`, never fractional.
|
|
153
|
+
*/
|
|
154
|
+
export function callTimeout(remainingMs: number): number {
|
|
155
|
+
return Math.max(1, Math.floor(Math.min(FORGE_CALL_TIMEOUT_MS, remainingMs)));
|
|
156
|
+
}
|
|
157
|
+
|
|
158
|
+
/** The repository to read, and the host it lives on. */
|
|
159
|
+
export interface ForgeTarget {
|
|
160
|
+
readonly host: string;
|
|
161
|
+
readonly slug: string;
|
|
162
|
+
}
|
|
163
|
+
|
|
164
|
+
/**
|
|
165
|
+
* The live `gh api` caller.
|
|
166
|
+
*
|
|
167
|
+
* `timeoutFor` returns the ceiling for THIS call, so the whole-read budget can
|
|
168
|
+
* shorten it. A fixed per-call timeout is what let a call admitted just inside
|
|
169
|
+
* the budget run well past it — the budget checked admission and nothing
|
|
170
|
+
* checked duration.
|
|
171
|
+
*
|
|
172
|
+
* `--paginate --slurp` needs `gh` 2.43 or newer. On an older client the call
|
|
173
|
+
* fails and the rulesets read blanks, which lands on `unread` rather than on a
|
|
174
|
+
* wrong answer — the degradation this module guarantees, reached by a cause it
|
|
175
|
+
* cannot name. Stated here because nothing else in the diff documents it.
|
|
176
|
+
*/
|
|
177
|
+
export function liveForgeApi(
|
|
178
|
+
timeoutFor: () => number = () => FORGE_CALL_TIMEOUT_MS,
|
|
179
|
+
run: Runner = defaultRunner,
|
|
180
|
+
where: { readonly host?: string; readonly cwd?: string } = {},
|
|
181
|
+
): ForgeApi {
|
|
182
|
+
// **`gh` is addressed at the host and directory the SLUG came from.** The
|
|
183
|
+
// slug is resolved with `cwd: root`, and a `gh` spawned with neither
|
|
184
|
+
// inherits the process cwd and gh's default host — so with `--project`,
|
|
185
|
+
// `--root` or `AGENT_CONFIG_PROJECT_ROOT` pointing elsewhere, or with a
|
|
186
|
+
// GitHub Enterprise remote, the two halves of the read addressed two
|
|
187
|
+
// different repositories and a same-named repo elsewhere produced a
|
|
188
|
+
// confident verdict about somebody else's project.
|
|
189
|
+
const hostArgs =
|
|
190
|
+
where.host === undefined || where.host.toLowerCase() === 'github.com'
|
|
191
|
+
? []
|
|
192
|
+
: ['--hostname', where.host];
|
|
193
|
+
return {
|
|
194
|
+
get(apiPath: string, paginate = false): unknown | null {
|
|
195
|
+
const budget = timeoutFor();
|
|
196
|
+
if (budget <= 0) return null;
|
|
197
|
+
const args = paginate
|
|
198
|
+
? ['api', '--paginate', '--slurp', ...hostArgs, apiPath]
|
|
199
|
+
: ['api', ...hostArgs, apiPath];
|
|
200
|
+
let r: SpawnResult;
|
|
201
|
+
try {
|
|
202
|
+
r = run('gh', args, callTimeout(budget), where.cwd);
|
|
203
|
+
} catch {
|
|
204
|
+
return null;
|
|
205
|
+
}
|
|
206
|
+
if (r.status !== 0 || typeof r.stdout !== 'string' || r.stdout.trim() === '') {
|
|
207
|
+
return null;
|
|
208
|
+
}
|
|
209
|
+
try {
|
|
210
|
+
return JSON.parse(r.stdout) as unknown;
|
|
211
|
+
} catch {
|
|
212
|
+
return null;
|
|
213
|
+
}
|
|
214
|
+
},
|
|
215
|
+
};
|
|
216
|
+
}
|
|
217
|
+
|
|
218
|
+
/**
|
|
219
|
+
* `owner/repo` from a git remote URL, or `null`.
|
|
220
|
+
*
|
|
221
|
+
* **Host-gated on purpose.** `gh api` speaks GitHub, so attempting it against a
|
|
222
|
+
* GitLab or Bitbucket remote buys a doomed subprocess on every `doctor` run and
|
|
223
|
+
* returns the same `unread` the skip returns for free. The gate is the literal
|
|
224
|
+
* `github` in the host, which covers `github.com` and the usual GitHub
|
|
225
|
+
* Enterprise naming. A GHES install on a host that does not carry the vendor
|
|
226
|
+
* name is skipped and reports `unread` — the safe degradation, and stated here
|
|
227
|
+
* rather than left for a reader to discover from a blank row.
|
|
228
|
+
*/
|
|
229
|
+
export function resolveForgeRepo(originUrl: string | null): ForgeTarget | null {
|
|
230
|
+
if (originUrl === null || originUrl.trim() === '') return null;
|
|
231
|
+
const url = originUrl.trim();
|
|
232
|
+
const m = /^(?:[A-Za-z][A-Za-z0-9+.-]*:\/\/)?(?:[^@/]+@)?([^/:]+)[:/]([^/:]+\/[^/]+?)(?:\.git)?$/.exec(
|
|
233
|
+
url,
|
|
234
|
+
);
|
|
235
|
+
const host = m?.[1];
|
|
236
|
+
const slug = m?.[2];
|
|
237
|
+
if (host === undefined || slug === undefined) return null;
|
|
238
|
+
if (!host.toLowerCase().includes('github')) return null;
|
|
239
|
+
// The slug is interpolated into an API path and used as a replacement
|
|
240
|
+
// string, so its character set is checked rather than assumed. The previous
|
|
241
|
+
// pattern admitted `$`, `?`, `#` and `..` — one of those is a path
|
|
242
|
+
// traversal and another is a `String.replace` control sequence.
|
|
243
|
+
if (!/^[A-Za-z0-9._-]+\/[A-Za-z0-9._-]+$/.test(slug)) return null;
|
|
244
|
+
if (slug.split('/').some((seg) => seg === '.' || seg === '..')) return null;
|
|
245
|
+
return { host, slug };
|
|
246
|
+
}
|
|
247
|
+
|
|
248
|
+
/**
|
|
249
|
+
* The repository's `origin` URL, or `null`.
|
|
250
|
+
*
|
|
251
|
+
* Timed out like every other spawn here. It was the one subprocess in the
|
|
252
|
+
* module with no ceiling and it ran before the budget was even constructed, so
|
|
253
|
+
* a wedged `git` sat entirely outside a read that advertised a total ceiling.
|
|
254
|
+
*/
|
|
255
|
+
export function originUrl(
|
|
256
|
+
root: string,
|
|
257
|
+
run: Runner = defaultRunner,
|
|
258
|
+
timeoutMs = GIT_REMOTE_TIMEOUT_MS,
|
|
259
|
+
): string | null {
|
|
260
|
+
let r: SpawnResult;
|
|
261
|
+
try {
|
|
262
|
+
// `ls-remote --get-url` APPLIES `url.<base>.insteadOf` rewrites and still
|
|
263
|
+
// makes no network call; `remote get-url` returns the configured URL
|
|
264
|
+
// verbatim. The difference matters in the unsafe direction: a
|
|
265
|
+
// github.com-shaped URL rewritten onto another host would otherwise be
|
|
266
|
+
// accepted and the read attributed to the wrong repository — which is a
|
|
267
|
+
// condition the Phase 3.2 amendment names as reversing it.
|
|
268
|
+
r = run('git', ['ls-remote', '--get-url', 'origin'], timeoutMs, root);
|
|
269
|
+
} catch {
|
|
270
|
+
return null;
|
|
271
|
+
}
|
|
272
|
+
if (r.status !== 0 || typeof r.stdout !== 'string') return null;
|
|
273
|
+
const out = r.stdout.trim();
|
|
274
|
+
// With no such remote, `ls-remote --get-url` echoes the name back.
|
|
275
|
+
return out === '' || out === 'origin' ? null : out;
|
|
276
|
+
}
|
|
277
|
+
|
|
278
|
+
/**
|
|
279
|
+
* Every ruleset, with its detail, or `null`.
|
|
280
|
+
*
|
|
281
|
+
* The listing endpoint returns summaries without `rules`, so each is fetched
|
|
282
|
+
* individually — and a single failed detail read blanks the WHOLE list rather
|
|
283
|
+
* than returning what was collected. Same contract as the sibling anchor
|
|
284
|
+
* source, for the same reason it states: a subset understates the effective
|
|
285
|
+
* protection in one direction and overstates it in the other, and neither is a
|
|
286
|
+
* verdict worth recording. `--paginate --slurp` because the listing pages at 30
|
|
287
|
+
* and a silently truncated reading is that same partial verdict wearing a
|
|
288
|
+
* different cause.
|
|
289
|
+
*/
|
|
290
|
+
function readRulesets(repo: string, api: ForgeApi): RulesetDetail[] | null {
|
|
291
|
+
// One flattener for the one `--paginate --slurp` contract. This read grew
|
|
292
|
+
// its own inline version first, which left two implementations of the same
|
|
293
|
+
// page shape free to drift apart.
|
|
294
|
+
const list = slurped(api.get(`repos/${repo}/rulesets`, true), 'rulesets');
|
|
295
|
+
if (list === null) return null;
|
|
296
|
+
const out: RulesetDetail[] = [];
|
|
297
|
+
for (const entry of list) {
|
|
298
|
+
const id = asObject(entry)?.['id'];
|
|
299
|
+
if (typeof id !== 'number') return null;
|
|
300
|
+
const detail = api.get(`repos/${repo}/rulesets/${String(id)}`);
|
|
301
|
+
if (asObject(detail) === null) return null;
|
|
302
|
+
out.push(detail as RulesetDetail);
|
|
303
|
+
}
|
|
304
|
+
return out;
|
|
305
|
+
}
|
|
306
|
+
|
|
307
|
+
/**
|
|
308
|
+
* Flatten a `--paginate --slurp` payload down to one list.
|
|
309
|
+
*
|
|
310
|
+
* `--slurp` yields an array of PAGES. Each page is either a bare array (a list
|
|
311
|
+
* endpoint) or an object carrying the list under `key` (`environments`,
|
|
312
|
+
* `branch_policies`). `null` when the shape is not one of those — never a
|
|
313
|
+
* partial list, for the reason {@link readRulesets} states.
|
|
314
|
+
*/
|
|
315
|
+
function slurped(payload: unknown, key: string): unknown[] | null {
|
|
316
|
+
if (Array.isArray(payload)) {
|
|
317
|
+
// An array of pages, each itself an array — a list endpoint under
|
|
318
|
+
// `--slurp`.
|
|
319
|
+
if (payload.length > 0 && payload.every((p) => Array.isArray(p))) {
|
|
320
|
+
return (payload as unknown[][]).flat();
|
|
321
|
+
}
|
|
322
|
+
// An array of pages, each an object carrying the list under `key`.
|
|
323
|
+
if (payload.length > 0 && payload.every((p) => Array.isArray(asObject(p)?.[key]))) {
|
|
324
|
+
return payload.flatMap((p) => asObject(p)?.[key] as unknown[]);
|
|
325
|
+
}
|
|
326
|
+
// A flat list of items — what a list endpoint returns UNPAGINATED, and
|
|
327
|
+
// what `gh` emits when a single page is not wrapped. Kept because
|
|
328
|
+
// collapsing it into the page shapes above was what broke the flat
|
|
329
|
+
// case when the two flatteners were merged.
|
|
330
|
+
return payload;
|
|
331
|
+
}
|
|
332
|
+
const listed = asObject(payload)?.[key];
|
|
333
|
+
return Array.isArray(listed) ? [...listed] : null;
|
|
334
|
+
}
|
|
335
|
+
|
|
336
|
+
/**
|
|
337
|
+
* Whether every environment restricts its deployment branches, or `null`.
|
|
338
|
+
*
|
|
339
|
+
* The custom-policy NAMES live behind a second call per environment, and a
|
|
340
|
+
* policy pattern of `*` restricts nothing whatever the flag says. A failed
|
|
341
|
+
* names read therefore makes the whole row `unread`, and that is a CORRECTION:
|
|
342
|
+
* the first draft fell back to trusting the flag, which `deployRestrictedFrom`
|
|
343
|
+
* calls the guarantee that WAS NOT CHECKED — the weaker reading, not the
|
|
344
|
+
* narrower one. An independent review followed the consequence through: an
|
|
345
|
+
* environment whose real policy is `*` would then report `satisfied`, which is
|
|
346
|
+
* the overstatement direction this module says it never takes, and it would be
|
|
347
|
+
* indistinguishable in the output from a genuine restriction. A read that
|
|
348
|
+
* failed is a read nobody made, so it lands where every other unmade read
|
|
349
|
+
* lands.
|
|
350
|
+
*
|
|
351
|
+
* The environment name is URL-ENCODED into the path. GitHub admits spaces and
|
|
352
|
+
* other path-unsafe characters in an environment name, and an unencoded name
|
|
353
|
+
* is exactly how the failed-read branch above gets reached by accident.
|
|
354
|
+
*/
|
|
355
|
+
function readDeployRestricted(repo: string, api: ForgeApi): boolean | null {
|
|
356
|
+
// PAGINATED. Unpaginated, a repository with more environments than one page
|
|
357
|
+
// returns a subset, `deployRestrictedFrom`'s `every()` runs over what it can
|
|
358
|
+
// see, and an unlisted unrestricted environment reads as `satisfied` — the
|
|
359
|
+
// overstatement direction this module says it never takes. `readRulesets`
|
|
360
|
+
// guards the identical hazard one function up.
|
|
361
|
+
const envsRaw = slurped(api.get(`repos/${repo}/environments`, true), 'environments');
|
|
362
|
+
if (envsRaw === null) return null;
|
|
363
|
+
const envs: EnvironmentPolicy[] = [];
|
|
364
|
+
const patternsByEnv: Record<string, readonly string[]> = {};
|
|
365
|
+
for (const raw of envsRaw) {
|
|
366
|
+
const env = asObject(raw);
|
|
367
|
+
const name = env?.['name'];
|
|
368
|
+
if (typeof name !== 'string') return null;
|
|
369
|
+
const policy = asObject(env?.['deployment_branch_policy']);
|
|
370
|
+
// A flag PRESENT but not a boolean makes the whole row `unread`. The
|
|
371
|
+
// first version omitted it, and the comment justified that with
|
|
372
|
+
// "`deployRestrictedFrom` distinguishes absent from false" — which is
|
|
373
|
+
// simply wrong: that function tests `=== true` and `!== true`, so
|
|
374
|
+
// absent, `undefined` and `false` collapse identically. The effect was
|
|
375
|
+
// the row rendering "at least one environment accepts a deployment from
|
|
376
|
+
// any branch" — a positive claim about the forge built out of data
|
|
377
|
+
// nobody could parse. Every sibling unparseable input here already
|
|
378
|
+
// takes the null route: a non-string environment name, a non-numeric
|
|
379
|
+
// ruleset id, a non-boolean `allow_auto_merge`, a non-string
|
|
380
|
+
// branch-policy name. An ABSENT flag is a different input and stays
|
|
381
|
+
// absent.
|
|
382
|
+
for (const k of ['protected_branches', 'custom_branch_policies'] as const) {
|
|
383
|
+
const v = policy?.[k];
|
|
384
|
+
if (v !== undefined && typeof v !== 'boolean') return null;
|
|
385
|
+
}
|
|
386
|
+
envs.push({
|
|
387
|
+
name,
|
|
388
|
+
deployment_branch_policy:
|
|
389
|
+
policy === null
|
|
390
|
+
? null
|
|
391
|
+
: {
|
|
392
|
+
...(typeof policy['protected_branches'] === 'boolean'
|
|
393
|
+
? { protected_branches: policy['protected_branches'] }
|
|
394
|
+
: {}),
|
|
395
|
+
...(typeof policy['custom_branch_policies'] === 'boolean'
|
|
396
|
+
? { custom_branch_policies: policy['custom_branch_policies'] }
|
|
397
|
+
: {}),
|
|
398
|
+
},
|
|
399
|
+
});
|
|
400
|
+
if (policy?.['custom_branch_policies'] !== true) continue;
|
|
401
|
+
// `protected_branches` already decides the row for this environment —
|
|
402
|
+
// `deployRestrictedFrom` short-circuits on it before consulting the
|
|
403
|
+
// pattern map — so the extra call would spend budget on an answer that
|
|
404
|
+
// cannot change the verdict, and degrade the row to `unread` if it
|
|
405
|
+
// failed.
|
|
406
|
+
if (policy['protected_branches'] === true) continue;
|
|
407
|
+
const names = api.get(
|
|
408
|
+
`repos/${repo}/environments/${encodeURIComponent(name)}/deployment-branch-policies`,
|
|
409
|
+
true,
|
|
410
|
+
);
|
|
411
|
+
const listed = slurped(names, 'branch_policies');
|
|
412
|
+
// A read that failed is a read nobody made — `unread`, never a trusted
|
|
413
|
+
// flag. Trusting it here is how a wildcard policy reports `satisfied`.
|
|
414
|
+
if (listed === null) return null;
|
|
415
|
+
const patterns = listed.map((p) => asObject(p)?.['name']);
|
|
416
|
+
// A non-string pattern name makes the row `unread`, not `false`.
|
|
417
|
+
// Filtering it away silently left an EMPTY pattern list, which
|
|
418
|
+
// `deployRestrictedFrom` maps to `false` — so the row would render
|
|
419
|
+
// "at least one environment accepts a deployment from any branch",
|
|
420
|
+
// a positive claim about the forge derived from data nobody could
|
|
421
|
+
// parse. Every sibling field here already takes the null route: a
|
|
422
|
+
// non-string environment name, a non-numeric ruleset id, a non-boolean
|
|
423
|
+
// `allow_auto_merge`. A confirmed-empty list is a different input and
|
|
424
|
+
// keeps its own `false`.
|
|
425
|
+
if (patterns.some((n) => typeof n !== 'string')) return null;
|
|
426
|
+
patternsByEnv[name] = patterns as string[];
|
|
427
|
+
}
|
|
428
|
+
return deployRestrictedFrom(envs, patternsByEnv);
|
|
429
|
+
}
|
|
430
|
+
|
|
431
|
+
/**
|
|
432
|
+
* Read the four fields the mapper consumes.
|
|
433
|
+
*
|
|
434
|
+
* The repository record is first and GATES the rest: it carries two of the four
|
|
435
|
+
* fields, and the other two are meaningless without the default branch it
|
|
436
|
+
* names. A reader that kept going after it failed would spend calls on answers
|
|
437
|
+
* it could not use, so this one returns immediately — asserted by a test over
|
|
438
|
+
* the call list rather than over the returned value, since both shapes return
|
|
439
|
+
* the same reading.
|
|
440
|
+
*/
|
|
441
|
+
export function readForge(repo: string, api: ForgeApi): ForgeReading {
|
|
442
|
+
const record = asObject(api.get(`repos/${repo}`));
|
|
443
|
+
if (record === null) return UNREAD_FORGE;
|
|
444
|
+
const branch = record['default_branch'];
|
|
445
|
+
const autoMerge = record['allow_auto_merge'];
|
|
446
|
+
return {
|
|
447
|
+
defaultBranch: typeof branch === 'string' && branch !== '' ? branch : null,
|
|
448
|
+
// Never coerced: a flag the payload did not carry as a boolean is a flag
|
|
449
|
+
// nobody read, and `unread` is the state reserved for exactly that.
|
|
450
|
+
allowAutoMerge: typeof autoMerge === 'boolean' ? autoMerge : null,
|
|
451
|
+
rulesets: readRulesets(repo, api),
|
|
452
|
+
deployRestricted: readDeployRestricted(repo, api),
|
|
453
|
+
};
|
|
454
|
+
}
|
|
455
|
+
|
|
456
|
+
/**
|
|
457
|
+
* Wrap an API in a whole-command budget.
|
|
458
|
+
*
|
|
459
|
+
* **Per-call timeouts bound each call and nothing bounds their sum.** Five
|
|
460
|
+
* calls at a ten-second ceiling can take fifty seconds without any one of them
|
|
461
|
+
* misbehaving, so the advertised ceiling is not the one a user experiences.
|
|
462
|
+
* This adds the missing deadline, and it does so by returning `null` once the
|
|
463
|
+
* budget is spent — which routes straight into the `unread` degradation the
|
|
464
|
+
* rest of this module already guarantees. Running out of time therefore reads
|
|
465
|
+
* as *nobody looked*, never as *the forge said no*; that direction is the one
|
|
466
|
+
* that matters and it is pinned by a test.
|
|
467
|
+
*/
|
|
468
|
+
export function withDeadline(api: ForgeApi, remaining: () => number): ForgeApi {
|
|
469
|
+
return {
|
|
470
|
+
get(apiPath: string, paginate?: boolean): unknown | null {
|
|
471
|
+
return remaining() <= 0 ? null : api.get(apiPath, paginate);
|
|
472
|
+
},
|
|
473
|
+
};
|
|
474
|
+
}
|
|
475
|
+
|
|
476
|
+
/**
|
|
477
|
+
* Milliseconds left of a budget of `ms`, never below zero.
|
|
478
|
+
*
|
|
479
|
+
* The clock is `performance.now`, which is monotonic. `Date.now` is wall-clock
|
|
480
|
+
* and steppable: an NTP correction backwards silently extends the budget past
|
|
481
|
+
* its stated ceiling and one forwards expires it early. The word was in the
|
|
482
|
+
* first draft's comment while the implementation used the steppable clock.
|
|
483
|
+
*/
|
|
484
|
+
export function budgetOf(
|
|
485
|
+
ms: number,
|
|
486
|
+
now: () => number = () => performance.now(),
|
|
487
|
+
): () => number {
|
|
488
|
+
const until = now() + ms;
|
|
489
|
+
return () => Math.max(0, until - now());
|
|
490
|
+
}
|
|
491
|
+
|
|
492
|
+
/** What {@link forgeReadingFor} needs. Every field injectable, so no test spawns. */
|
|
493
|
+
export interface ForgeReadRequest {
|
|
494
|
+
readonly env: Readonly<Record<string, string | undefined>>;
|
|
495
|
+
/**
|
|
496
|
+
* Resolve the repository — a THUNK rather than a value, so the opt-out can
|
|
497
|
+
* run before it. Resolving reads the git remote, which is a subprocess; an
|
|
498
|
+
* opt-out that only skipped the API calls would still spawn `git` to work
|
|
499
|
+
* out a repository it was never going to use, and "this switch makes no
|
|
500
|
+
* network calls" would be true of the HTTP traffic and false of the claim
|
|
501
|
+
* a reader takes from it.
|
|
502
|
+
*/
|
|
503
|
+
readonly resolveRepo: () => ForgeTarget | null;
|
|
504
|
+
/**
|
|
505
|
+
* Build the API for the resolved target.
|
|
506
|
+
*
|
|
507
|
+
* A FACTORY rather than a ready API, so the host and working directory the
|
|
508
|
+
* slug came from necessarily reach `gh`. Handing in a pre-built client was
|
|
509
|
+
* how the two halves of the read came to address different repositories.
|
|
510
|
+
*/
|
|
511
|
+
readonly apiFor: (target: ForgeTarget) => ForgeApi;
|
|
512
|
+
/** Milliseconds left. Defaults to a {@link FORGE_TOTAL_BUDGET_MS} budget. */
|
|
513
|
+
readonly remaining?: () => number;
|
|
514
|
+
}
|
|
515
|
+
|
|
516
|
+
/**
|
|
517
|
+
* A reading plus the repository it came from.
|
|
518
|
+
*
|
|
519
|
+
* **The slug travels with the rows, and leaving it out was the review's one
|
|
520
|
+
* high finding.** The moment the read went live, *which repository* stopped
|
|
521
|
+
* being obvious: `origin` may point at a fork, a mirror, or — in a consumer
|
|
522
|
+
* install — somebody else's project entirely, and five rows reading `satisfied`
|
|
523
|
+
* say nothing about whose branch is protected. For a block whose own header
|
|
524
|
+
* calls an unstated provenance the guess it exists to refuse, that was the one
|
|
525
|
+
* fact the provenance omitted. `null` when nothing was read.
|
|
526
|
+
*/
|
|
527
|
+
export interface ForgeRead {
|
|
528
|
+
readonly repo: string | null;
|
|
529
|
+
readonly reading: ForgeReading;
|
|
530
|
+
}
|
|
531
|
+
|
|
532
|
+
/**
|
|
533
|
+
* The production entry: read the forge, or return the unread reading.
|
|
534
|
+
*
|
|
535
|
+
* Three skips happen BEFORE any process spawns — an opt-out switch, the general
|
|
536
|
+
* offline switch, and an unresolvable repository — and each is asserted on the
|
|
537
|
+
* CALL LIST rather than on the return value, because a reader that queried and
|
|
538
|
+
* then discarded would satisfy an output-only assertion while still paying the
|
|
539
|
+
* latency the opt-out exists to avoid.
|
|
540
|
+
*
|
|
541
|
+
* The `catch` is not defensive padding. `doctor` is the command people run when
|
|
542
|
+
* something is already wrong; a diagnostic that dies because its own optional
|
|
543
|
+
* read threw is strictly worse than one reporting `unread`.
|
|
544
|
+
*/
|
|
545
|
+
export function forgeReadingFor(req: ForgeReadRequest): ForgeRead {
|
|
546
|
+
if (forgeReadDisabled(req.env)) return { repo: null, reading: UNREAD_FORGE };
|
|
547
|
+
// The budget is built BEFORE the repository is resolved, because resolving
|
|
548
|
+
// spawns `git` and a budget that starts after it does not bound it.
|
|
549
|
+
const remaining = req.remaining ?? budgetOf(FORGE_TOTAL_BUDGET_MS);
|
|
550
|
+
try {
|
|
551
|
+
const target = req.resolveRepo();
|
|
552
|
+
if (target === null) return { repo: null, reading: UNREAD_FORGE };
|
|
553
|
+
const reading = readForge(target.slug, withDeadline(req.apiFor(target), remaining));
|
|
554
|
+
return { repo: target.slug, reading };
|
|
555
|
+
} catch {
|
|
556
|
+
return { repo: null, reading: UNREAD_FORGE };
|
|
557
|
+
}
|
|
558
|
+
}
|