@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
|
@@ -0,0 +1,271 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* `probe_inputs` — what the UI-conformance probe read, in a form its reader can
|
|
3
|
+
* re-check without starting a browser.
|
|
4
|
+
*
|
|
5
|
+
* `ui-conformance.json` recorded `generated_at` and nothing about its inputs, so
|
|
6
|
+
* a probe run before the last UI edit was indistinguishable from one run after
|
|
7
|
+
* it. This module is the shared vocabulary that closes that: the probe writes an
|
|
8
|
+
* `inputs` block with a content digest per input, and the design-pass reader
|
|
9
|
+
* recomputes those digests and compares. ONE definition, imported by both sides,
|
|
10
|
+
* because a producer and a consumer with separate copies of a hash rule agree
|
|
11
|
+
* only until one of them is edited.
|
|
12
|
+
*
|
|
13
|
+
* DIGESTS, NOT MTIMES, AND THAT IS THE WHOLE DECISION (D1). The reader already
|
|
14
|
+
* applies an mtime rule to the ui-audit artefact, and reusing it here was the
|
|
15
|
+
* obvious move. It answers the wrong question: an mtime moves on a checkout, a
|
|
16
|
+
* `cp -p`-less copy, or a bare `touch`, so it would report movement on turns
|
|
17
|
+
* where no byte changed — and a reader who learns to ignore a false stale has
|
|
18
|
+
* learned to ignore a real one. A content digest reports exactly what is being
|
|
19
|
+
* asked: are the bytes the probe saw still the bytes on disk?
|
|
20
|
+
*
|
|
21
|
+
* THE TARGET DIGEST IS A TREE DIGEST, and this is the one place the roadmap's
|
|
22
|
+
* own text needed correcting. A UI surface is an entry document plus the styles
|
|
23
|
+
* and scripts beside it; a digest of `index.html` alone would be blind to a CSS
|
|
24
|
+
* edit, which is the single most common UI change there is — and the roadmap's
|
|
25
|
+
* own verify line for step 1.1 names exactly that case. So the record covers the
|
|
26
|
+
* directory the entry document sits in. It costs a small recursive read the
|
|
27
|
+
* probe does not otherwise perform (the browser, not Node, opens `styles.css`),
|
|
28
|
+
* which is a real cost the roadmap's Risk 3 mitigation did not anticipate. It is
|
|
29
|
+
* bounded: a few small text files against a run that launches Chromium and
|
|
30
|
+
* reloads the page dozens of times per node.
|
|
31
|
+
*
|
|
32
|
+
* NO BROWSER IS REACHABLE FROM HERE. This module imports `node:crypto`,
|
|
33
|
+
* `node:fs` and `node:path` and nothing else, which is what lets the hook decide
|
|
34
|
+
* staleness on a host with no browser binaries installed.
|
|
35
|
+
*/
|
|
36
|
+
import { createHash } from 'node:crypto';
|
|
37
|
+
import fs from 'node:fs';
|
|
38
|
+
import path from 'node:path';
|
|
39
|
+
|
|
40
|
+
/** Schema of the `inputs` block. A reader that does not know it says unknown. */
|
|
41
|
+
export const INPUTS_SCHEMA = 'ui-conformance-inputs/v1';
|
|
42
|
+
|
|
43
|
+
/**
|
|
44
|
+
* The probe build that wrote the block. Bumped when a change to the probe makes
|
|
45
|
+
* an older artefact's findings incomparable with a newer run's; a reader seeing
|
|
46
|
+
* a value it does not know reports unknown rather than guessing.
|
|
47
|
+
*/
|
|
48
|
+
export const PROBE_VERSION = 'ui-conformance-probe/v1';
|
|
49
|
+
|
|
50
|
+
/** The sibling file the probe reads for declared, approved deviations. */
|
|
51
|
+
export const DECLARATIONS_BASENAME = 'conformance.declared.json';
|
|
52
|
+
|
|
53
|
+
/**
|
|
54
|
+
* Bound on a tree digest. A surface directory is a handful of files; anything
|
|
55
|
+
* past this is a checkout root someone pointed the probe at by mistake, and
|
|
56
|
+
* hashing it would cost more than the probe run. Exceeding it is recorded as
|
|
57
|
+
* absent with a reason — never silently truncated, which would produce a digest
|
|
58
|
+
* that looks authoritative and covers an arbitrary subset.
|
|
59
|
+
*/
|
|
60
|
+
export const MAX_TREE_FILES = 2000;
|
|
61
|
+
|
|
62
|
+
/** Directories never worth digesting, and ruinous to walk. */
|
|
63
|
+
const SKIP_DIRS = new Set(['node_modules', '.git']);
|
|
64
|
+
|
|
65
|
+
export const INPUT_KEYS = ['target', 'reference', 'declarations'] as const;
|
|
66
|
+
export type InputKey = (typeof INPUT_KEYS)[number];
|
|
67
|
+
|
|
68
|
+
export interface InputRecord {
|
|
69
|
+
/**
|
|
70
|
+
* What was digested — a directory for `tree`, a file for `file`. `null` only
|
|
71
|
+
* when no path was supplied at all, which is itself recorded rather than
|
|
72
|
+
* omitted.
|
|
73
|
+
*/
|
|
74
|
+
path: string | null;
|
|
75
|
+
kind: 'tree' | 'file';
|
|
76
|
+
/** `absent` carries a reason and a null digest. Never an omitted key. */
|
|
77
|
+
state: 'read' | 'absent';
|
|
78
|
+
/** `sha256:<hex>` when read, `null` when absent. */
|
|
79
|
+
digest: string | null;
|
|
80
|
+
/** Files folded into a tree digest. Absent on a `file` record. */
|
|
81
|
+
files?: number;
|
|
82
|
+
reason?: string;
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
export interface ProbeInputs {
|
|
86
|
+
schema: string;
|
|
87
|
+
probe: string;
|
|
88
|
+
target: InputRecord;
|
|
89
|
+
reference: InputRecord;
|
|
90
|
+
declarations: InputRecord;
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
const sha256 = (data: Buffer | string): string => `sha256:${createHash('sha256').update(data).digest('hex')}`;
|
|
94
|
+
|
|
95
|
+
const why = (err: unknown): string => {
|
|
96
|
+
const code = (err as NodeJS.ErrnoException | undefined)?.code;
|
|
97
|
+
return code ? String(code) : String(err);
|
|
98
|
+
};
|
|
99
|
+
|
|
100
|
+
/**
|
|
101
|
+
* The ONE definition of where a target's declarations file lives. The probe's
|
|
102
|
+
* CLI computed this inline; a second copy here would be a rule that agrees with
|
|
103
|
+
* the probe only until one of the two is edited.
|
|
104
|
+
*/
|
|
105
|
+
export function declarationsPathFor(targetEntryFile: string): string {
|
|
106
|
+
return path.join(path.dirname(targetEntryFile), DECLARATIONS_BASENAME);
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
/** Digest one file. Never throws — an unreadable input is a recorded fact. */
|
|
110
|
+
export function digestFile(file: string | null, whenNull = 'no path was supplied'): InputRecord {
|
|
111
|
+
if (!file) return { path: null, kind: 'file', state: 'absent', digest: null, reason: whenNull };
|
|
112
|
+
try {
|
|
113
|
+
return { path: file, kind: 'file', state: 'read', digest: sha256(fs.readFileSync(file)) };
|
|
114
|
+
} catch (err) {
|
|
115
|
+
return { path: file, kind: 'file', state: 'absent', digest: null, reason: `unreadable: ${why(err)}` };
|
|
116
|
+
}
|
|
117
|
+
}
|
|
118
|
+
|
|
119
|
+
/**
|
|
120
|
+
* Digest a directory as one value: every file under it, sorted by relative path,
|
|
121
|
+
* folded path-and-content into a single hash. Sorted because a directory listing
|
|
122
|
+
* order is a filesystem detail, and a digest that moved with it would report
|
|
123
|
+
* movement nobody made.
|
|
124
|
+
*
|
|
125
|
+
* `exclude` drops names recorded as inputs in their own right — the declarations
|
|
126
|
+
* file — so an edit to it moves exactly one digest rather than two.
|
|
127
|
+
*/
|
|
128
|
+
export function digestTree(
|
|
129
|
+
dir: string | null,
|
|
130
|
+
exclude: readonly string[] = [],
|
|
131
|
+
whenNull = 'no path was supplied',
|
|
132
|
+
): InputRecord {
|
|
133
|
+
if (!dir) return { path: null, kind: 'tree', state: 'absent', digest: null, reason: whenNull };
|
|
134
|
+
const skip = new Set(exclude);
|
|
135
|
+
const found: string[] = [];
|
|
136
|
+
|
|
137
|
+
const walk = (rel: string): string | null => {
|
|
138
|
+
let entries: fs.Dirent[];
|
|
139
|
+
try {
|
|
140
|
+
entries = fs.readdirSync(path.join(dir, rel), { withFileTypes: true });
|
|
141
|
+
} catch (err) {
|
|
142
|
+
return `unreadable: ${why(err)}`;
|
|
143
|
+
}
|
|
144
|
+
for (const e of [...entries].sort((a, b) => (a.name < b.name ? -1 : a.name > b.name ? 1 : 0))) {
|
|
145
|
+
const child = rel ? `${rel}/${e.name}` : e.name;
|
|
146
|
+
if (rel === '' && skip.has(e.name)) continue;
|
|
147
|
+
if (e.isDirectory()) {
|
|
148
|
+
if (SKIP_DIRS.has(e.name)) continue;
|
|
149
|
+
const failed = walk(child);
|
|
150
|
+
if (failed) return failed;
|
|
151
|
+
continue;
|
|
152
|
+
}
|
|
153
|
+
if (!e.isFile()) continue;
|
|
154
|
+
found.push(child);
|
|
155
|
+
if (found.length > MAX_TREE_FILES) {
|
|
156
|
+
return `directory holds more than ${MAX_TREE_FILES} files — too large to digest`;
|
|
157
|
+
}
|
|
158
|
+
}
|
|
159
|
+
return null;
|
|
160
|
+
};
|
|
161
|
+
|
|
162
|
+
const failure = walk('');
|
|
163
|
+
if (failure) return { path: dir, kind: 'tree', state: 'absent', digest: null, reason: failure };
|
|
164
|
+
|
|
165
|
+
const h = createHash('sha256');
|
|
166
|
+
for (const rel of found.sort()) {
|
|
167
|
+
let body: Buffer;
|
|
168
|
+
try {
|
|
169
|
+
body = fs.readFileSync(path.join(dir, rel));
|
|
170
|
+
} catch (err) {
|
|
171
|
+
return { path: dir, kind: 'tree', state: 'absent', digest: null, reason: `unreadable: ${why(err)}` };
|
|
172
|
+
}
|
|
173
|
+
// The path is folded in alongside the bytes, so a rename moves the digest
|
|
174
|
+
// even when the content is byte-identical.
|
|
175
|
+
h.update(rel).update('\0').update(body).update('\0');
|
|
176
|
+
}
|
|
177
|
+
return {
|
|
178
|
+
path: dir,
|
|
179
|
+
kind: 'tree',
|
|
180
|
+
state: 'read',
|
|
181
|
+
digest: `sha256:${h.digest('hex')}`,
|
|
182
|
+
files: found.length,
|
|
183
|
+
};
|
|
184
|
+
}
|
|
185
|
+
|
|
186
|
+
/**
|
|
187
|
+
* The block the probe writes beside `generated_at`. A target or reference is
|
|
188
|
+
* digested as the surface directory it belongs to; the declarations file is
|
|
189
|
+
* digested on its own.
|
|
190
|
+
*/
|
|
191
|
+
export function collectInputs(targetEntryFile: string | null, referenceEntryFile: string | null): ProbeInputs {
|
|
192
|
+
const dirOf = (f: string | null): string | null => (f ? path.dirname(f) : null);
|
|
193
|
+
return {
|
|
194
|
+
schema: INPUTS_SCHEMA,
|
|
195
|
+
probe: PROBE_VERSION,
|
|
196
|
+
target: digestTree(dirOf(targetEntryFile), [DECLARATIONS_BASENAME], 'no --target was supplied'),
|
|
197
|
+
reference: digestTree(
|
|
198
|
+
dirOf(referenceEntryFile),
|
|
199
|
+
[DECLARATIONS_BASENAME],
|
|
200
|
+
'no --reference was given; nothing was compared against',
|
|
201
|
+
),
|
|
202
|
+
declarations: targetEntryFile
|
|
203
|
+
? digestFile(declarationsPathFor(targetEntryFile))
|
|
204
|
+
: digestFile(null, 'no --target was supplied, so no declarations file was looked for'),
|
|
205
|
+
};
|
|
206
|
+
}
|
|
207
|
+
|
|
208
|
+
export type InputComparison =
|
|
209
|
+
| { verdict: 'unknown'; reason: string; moved: readonly InputKey[] }
|
|
210
|
+
| { verdict: 'unchanged'; moved: readonly InputKey[] }
|
|
211
|
+
| { verdict: 'moved'; moved: readonly InputKey[] };
|
|
212
|
+
|
|
213
|
+
const isRecord = (v: unknown): v is InputRecord =>
|
|
214
|
+
typeof v === 'object' && v !== null && 'state' in (v as object) && 'kind' in (v as object);
|
|
215
|
+
|
|
216
|
+
/**
|
|
217
|
+
* Re-read each recorded input and say whether it moved.
|
|
218
|
+
*
|
|
219
|
+
* THREE verdicts, and `unknown` is not a hedge. An artefact written before the
|
|
220
|
+
* `inputs` block existed has nothing to compare, and calling that `moved` would
|
|
221
|
+
* report stale for every pre-existing run on the first turn after this change —
|
|
222
|
+
* Risk 1 of the roadmap. It is a version skew, not evidence of movement, and it
|
|
223
|
+
* is equally not evidence of freshness.
|
|
224
|
+
*
|
|
225
|
+
* `resolve` maps a recorded path to this host's filesystem; the caller owns that
|
|
226
|
+
* because the probe records the path it was given and only the reader knows the
|
|
227
|
+
* root it is reading from.
|
|
228
|
+
*/
|
|
229
|
+
export function compareInputs(recorded: unknown, resolve: (p: string) => string): InputComparison {
|
|
230
|
+
if (typeof recorded !== 'object' || recorded === null) {
|
|
231
|
+
return {
|
|
232
|
+
verdict: 'unknown',
|
|
233
|
+
moved: [],
|
|
234
|
+
reason: 'the artefact carries no inputs block — it predates the input digests',
|
|
235
|
+
};
|
|
236
|
+
}
|
|
237
|
+
const r = recorded as Partial<ProbeInputs>;
|
|
238
|
+
if (r.schema !== INPUTS_SCHEMA) {
|
|
239
|
+
return {
|
|
240
|
+
verdict: 'unknown',
|
|
241
|
+
moved: [],
|
|
242
|
+
reason: `inputs schema ${String(r.schema)} is not ${INPUTS_SCHEMA} — this reader cannot compare it`,
|
|
243
|
+
};
|
|
244
|
+
}
|
|
245
|
+
if (r.probe !== PROBE_VERSION) {
|
|
246
|
+
return {
|
|
247
|
+
verdict: 'unknown',
|
|
248
|
+
moved: [],
|
|
249
|
+
reason: `written by ${String(r.probe)}, read by ${PROBE_VERSION} — findings across builds are not comparable`,
|
|
250
|
+
};
|
|
251
|
+
}
|
|
252
|
+
|
|
253
|
+
const moved: InputKey[] = [];
|
|
254
|
+
for (const key of INPUT_KEYS) {
|
|
255
|
+
const rec = r[key];
|
|
256
|
+
if (!isRecord(rec)) {
|
|
257
|
+
return {
|
|
258
|
+
verdict: 'unknown',
|
|
259
|
+
moved: [],
|
|
260
|
+
reason: `the inputs block carries no usable record for ${key}`,
|
|
261
|
+
};
|
|
262
|
+
}
|
|
263
|
+
// An input that was never supplied has nothing to move to or from.
|
|
264
|
+
if (rec.path === null) continue;
|
|
265
|
+
const here = rec.kind === 'tree'
|
|
266
|
+
? digestTree(resolve(rec.path), [DECLARATIONS_BASENAME])
|
|
267
|
+
: digestFile(resolve(rec.path));
|
|
268
|
+
if (here.state !== rec.state || here.digest !== rec.digest) moved.push(key);
|
|
269
|
+
}
|
|
270
|
+
return moved.length ? { verdict: 'moved', moved } : { verdict: 'unchanged', moved: [] };
|
|
271
|
+
}
|
|
@@ -0,0 +1,322 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Read-surface scanner — derive the sanitize floor's coverage list from the
|
|
3
|
+
* imports, not from intent
|
|
4
|
+
* (road-to-a-sanitize-list-that-is-generated, Phase 1).
|
|
5
|
+
*
|
|
6
|
+
* WHY A SCANNER AND NOT A LIST
|
|
7
|
+
* ----------------------------
|
|
8
|
+
* `retrieval_sanitize.ts` used to carry a hand-maintained prose list of the
|
|
9
|
+
* read surfaces it covers, with its own header stating the rule for anything
|
|
10
|
+
* not on it: uncovered. That list went wrong in both directions — a path that
|
|
11
|
+
* reaches the outside world and IS sanitized was absent from it, and two paths
|
|
12
|
+
* carrying fetched bytes toward a model-facing surface were neither listed nor
|
|
13
|
+
* covered. The header already recorded the same failure happening once before,
|
|
14
|
+
* for the same reason: a list maintained by hand records what its author meant,
|
|
15
|
+
* and the tree records what the code does.
|
|
16
|
+
*
|
|
17
|
+
* This module derives the list. A row cannot disagree with the tree, because
|
|
18
|
+
* the tree is the only thing it reads.
|
|
19
|
+
*
|
|
20
|
+
* WHAT A ROW MEANS, AND WHAT IT DOES NOT
|
|
21
|
+
* --------------------------------------
|
|
22
|
+
* A row says a module matched an INBOUND shape — a recognised way outside bytes
|
|
23
|
+
* enter this process — and whether that module imports the sanitize floor. It
|
|
24
|
+
* does not say the floor is applied to the right string, at the right point, or
|
|
25
|
+
* at all: an import is evidence of intent, and only a read of the module
|
|
26
|
+
* establishes the rest. `covered` here means "the floor is in scope in this
|
|
27
|
+
* module", never "this module is safe".
|
|
28
|
+
*
|
|
29
|
+
* THE MISSING-ROW FAILURE, AND WHY `unclassified` EXISTS
|
|
30
|
+
* -----------------------------------------------------
|
|
31
|
+
* A generated table carries an authority a hand-written one never claimed, so
|
|
32
|
+
* its worst failure is not a wrong row — it is an ABSENT one. A fetch shape the
|
|
33
|
+
* scanner does not recognise produces no row, and a missing row reads as "no
|
|
34
|
+
* surface here".
|
|
35
|
+
*
|
|
36
|
+
* Two things push back. Inbound shapes are plural and named (a `fetch`, a
|
|
37
|
+
* `node:http(s)` request, a subprocess of a remote-fetching tool, a parse of
|
|
38
|
+
* markup arriving on stdin) rather than a single pattern. And a WEAK shape —
|
|
39
|
+
* a network call reached through an injected or computed callee, which the
|
|
40
|
+
* strong detectors cannot see — still emits a row, marked `unclassified`, so an
|
|
41
|
+
* unrecognised shape appears in the table as a visible gap instead of as
|
|
42
|
+
* silence. The residual is stated in the table's own header rather than left
|
|
43
|
+
* for a reader to discover: a module whose network access is reached through a
|
|
44
|
+
* shape matching neither set is still absent.
|
|
45
|
+
*/
|
|
46
|
+
import * as fs from 'node:fs';
|
|
47
|
+
import * as path from 'node:path';
|
|
48
|
+
|
|
49
|
+
/** Coverage verdict for one read surface. */
|
|
50
|
+
export type SurfaceCoverage = 'covered' | 'uncovered' | 'unclassified';
|
|
51
|
+
|
|
52
|
+
/** One module that brings outside bytes in and hands a string onward. */
|
|
53
|
+
export interface ReadSurfaceRow {
|
|
54
|
+
/** Path relative to `src/scripts/`, POSIX separators. */
|
|
55
|
+
readonly module: string;
|
|
56
|
+
/** Inbound shape id that matched. */
|
|
57
|
+
readonly inbound: string;
|
|
58
|
+
/** Emit shape id that matched, or `undecided`. */
|
|
59
|
+
readonly emit: string;
|
|
60
|
+
readonly coverage: SurfaceCoverage;
|
|
61
|
+
/** Why the row reads the way it does — one clause, no paraphrase. */
|
|
62
|
+
readonly evidence: string;
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
interface ShapeDetector {
|
|
66
|
+
readonly id: string;
|
|
67
|
+
/** True when this module's source carries the shape. */
|
|
68
|
+
readonly match: (src: string) => boolean;
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
/** True when the module imports node's HTTP client at all. */
|
|
72
|
+
function _importsNodeHttp(src: string): boolean {
|
|
73
|
+
return /from\s+['"]node:https?['"]/.test(src);
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
/**
|
|
77
|
+
* Strong inbound shapes — a recognised way outside bytes enter this process.
|
|
78
|
+
*
|
|
79
|
+
* Each is deliberately narrow. `await fetch(` rather than `fetch(` keeps a
|
|
80
|
+
* fetch call named inside a string literal (a spawned one-liner, a doc example)
|
|
81
|
+
* out of the table; the node-HTTP shape requires the import as well as the call
|
|
82
|
+
* so a `.get(` on an unrelated object cannot produce a row.
|
|
83
|
+
*/
|
|
84
|
+
export const STRONG_INBOUND: readonly ShapeDetector[] = [
|
|
85
|
+
{
|
|
86
|
+
id: 'network-fetch',
|
|
87
|
+
match: (s) => /\bawait\s+fetch\s*\(/.test(s),
|
|
88
|
+
},
|
|
89
|
+
{
|
|
90
|
+
id: 'node-http-request',
|
|
91
|
+
match: (s) => _importsNodeHttp(s) && /\bhttps?\s*\.\s*(?:get|request)\s*\(/.test(s),
|
|
92
|
+
},
|
|
93
|
+
{
|
|
94
|
+
id: 'remote-subprocess',
|
|
95
|
+
match: (s) =>
|
|
96
|
+
/\b(?:spawnSync|spawn|execSync|execFileSync|execFile)\s*\(\s*\[?\s*['"`](?:curl|wget|gh)['"`]/.test(s),
|
|
97
|
+
},
|
|
98
|
+
{
|
|
99
|
+
id: 'stdin-markup-parse',
|
|
100
|
+
match: (s) =>
|
|
101
|
+
/\bprocess\s*\.\s*stdin\b/.test(s) && /&(?:amp|lt|gt|quot|nbsp|#x?[0-9a-f]+);/i.test(s),
|
|
102
|
+
},
|
|
103
|
+
{
|
|
104
|
+
id: 'declared',
|
|
105
|
+
match: (s) => _DECLARATION_RE.test(s),
|
|
106
|
+
},
|
|
107
|
+
];
|
|
108
|
+
|
|
109
|
+
/**
|
|
110
|
+
* A module may declare its own inbound shape when no syntactic one captures it.
|
|
111
|
+
*
|
|
112
|
+
* The shapes above are all things the scanner can SEE. A host primitive cannot
|
|
113
|
+
* be seen: a subagent reply, an MCP tool result and a host file-read arrive
|
|
114
|
+
* through a channel this package does not own, so no call site in this tree
|
|
115
|
+
* spells them. Risk 1 of the roadmap that produced this scanner names exactly
|
|
116
|
+
* that class, and the answer it demands is a visible row rather than silence.
|
|
117
|
+
*
|
|
118
|
+
* The marker is a comment at the site — `// read-surface: <shape-id>` — not a
|
|
119
|
+
* line in a central list, which is the arrangement this whole module exists to
|
|
120
|
+
* retire. A declaration says only "outside bytes arrive here"; the coverage
|
|
121
|
+
* verdict is still read from the imports, so declaring a surface cannot make it
|
|
122
|
+
* read `covered`.
|
|
123
|
+
*/
|
|
124
|
+
const _DECLARATION_RE = /\/\/\s*read-surface:\s*([a-z0-9-]+)/;
|
|
125
|
+
|
|
126
|
+
/** `git` is deliberately NOT a remote command above. */
|
|
127
|
+
export const LOCAL_VCS_EXCLUSION =
|
|
128
|
+
'a `git` subprocess reads local repository state, not outside bytes, so it is not an ' +
|
|
129
|
+
'inbound shape. `git fetch` and `git ls-remote` move refs rather than content and are ' +
|
|
130
|
+
'excluded with the rest — a branch name is attacker-influenceable in principle, and if ' +
|
|
131
|
+
'that ever reaches a model-facing surface it needs its own row, not a widened matcher ' +
|
|
132
|
+
'that would re-add every local `git status` wrapper in the tree.';
|
|
133
|
+
|
|
134
|
+
/**
|
|
135
|
+
* Weak inbound shapes — a network call the strong detectors cannot resolve.
|
|
136
|
+
*
|
|
137
|
+
* `await this._fetch(url, …)` is a real HTTP request behind an injected
|
|
138
|
+
* dependency, which is exactly the shape a strong matcher on `await fetch(`
|
|
139
|
+
* misses. It produces a row rather than silence; the verdict is
|
|
140
|
+
* `unclassified`, because the scanner cannot tell an injected fetch from an
|
|
141
|
+
* injected stub without executing it.
|
|
142
|
+
*/
|
|
143
|
+
export const WEAK_INBOUND: readonly ShapeDetector[] = [
|
|
144
|
+
{
|
|
145
|
+
id: 'indirect-fetch',
|
|
146
|
+
match: (s) => /\bawait\s+(?:this|self|[a-z_$][\w$]*)\s*\.\s*_?fetch\s*\(/i.test(s),
|
|
147
|
+
},
|
|
148
|
+
];
|
|
149
|
+
|
|
150
|
+
/** Recognised ways a module hands a string onward. */
|
|
151
|
+
export const EMIT_SHAPES: readonly ShapeDetector[] = [
|
|
152
|
+
{ id: 'file-write', match: (s) => /\b(?:writeFileSync|appendFileSync|writeFile)\s*\(/.test(s) },
|
|
153
|
+
{ id: 'stdout-write', match: (s) => /\bprocess\s*\.\s*stdout\s*\.\s*write\s*\(/.test(s) },
|
|
154
|
+
{ id: 'exported-api', match: (s) => /^export\s+(?:async\s+)?function\s/m.test(s) },
|
|
155
|
+
];
|
|
156
|
+
|
|
157
|
+
/** The sanitize floor, as an import. One hop; a transitive import is not counted. */
|
|
158
|
+
const _SANITIZE_IMPORT_RE =
|
|
159
|
+
/import\s*\{[^}]*\b(?:sanitize_text|sanitize_entry|sanitize_markup)\b[^}]*\}\s*from\s*['"][^'"]*retrieval_sanitize(?:\.js)?['"]/;
|
|
160
|
+
|
|
161
|
+
/** Test files under `src/scripts/` — fixtures, not surfaces that ship bytes. */
|
|
162
|
+
const _EXCLUDE_RE = /(?:^|\/)(?:__tests__|node_modules)\/|\.test\.ts$/;
|
|
163
|
+
|
|
164
|
+
/**
|
|
165
|
+
* Modules that define or read the coverage rather than being a surface.
|
|
166
|
+
*
|
|
167
|
+
* The scanner and its gate quote every detector pattern above as a literal, so
|
|
168
|
+
* they match their own matchers. Excluding them by name is the honest fix: a
|
|
169
|
+
* row for the scanner would be an artefact of the scanner reading itself, and
|
|
170
|
+
* carrying it would train a reader to skip rows.
|
|
171
|
+
*/
|
|
172
|
+
const _SELF_MODULES: ReadonlySet<string> = new Set([
|
|
173
|
+
'_lib/retrieval_sanitize.ts',
|
|
174
|
+
'_lib/read_surface_scan.ts',
|
|
175
|
+
'check_read_surface_coverage.ts',
|
|
176
|
+
]);
|
|
177
|
+
|
|
178
|
+
/**
|
|
179
|
+
* Raised when a directory under the scan root cannot be read.
|
|
180
|
+
*
|
|
181
|
+
* The walk used to swallow the error and return, which made an INCOMPLETE scan
|
|
182
|
+
* indistinguishable from a complete one: a permission error on one subtree
|
|
183
|
+
* silently removed every module under it from the table, and the gate then
|
|
184
|
+
* reported the shortened list as matching the tree. An independent review named
|
|
185
|
+
* it; the direction is the same one `assertScanned` enforces at the corpus
|
|
186
|
+
* level, applied to the traversal that builds the corpus.
|
|
187
|
+
*/
|
|
188
|
+
export class ScanTraversalError extends Error {
|
|
189
|
+
constructor(readonly dir: string, cause: unknown) {
|
|
190
|
+
super(`read_surface_scan: cannot read ${dir} — the scan is incomplete, not clean (${String(cause)})`);
|
|
191
|
+
this.name = 'ScanTraversalError';
|
|
192
|
+
}
|
|
193
|
+
}
|
|
194
|
+
|
|
195
|
+
/**
|
|
196
|
+
* Directory reader, injectable for one reason: to test the failure.
|
|
197
|
+
*
|
|
198
|
+
* The fail-closed branch below can only be exercised by a `readdirSync` that
|
|
199
|
+
* throws, and on ESM `vi.spyOn(fs, 'readdirSync')` cannot redefine the
|
|
200
|
+
* namespace property. The alternative — chmod-000 on a real directory — is not
|
|
201
|
+
* portable, because under root the mode is no barrier and the test then fails
|
|
202
|
+
* in exactly the environment it was written to survive. A narrow seam is the
|
|
203
|
+
* honest shape: production passes nothing and gets `fs.readdirSync`.
|
|
204
|
+
*/
|
|
205
|
+
export type ReadDir = (dir: string) => fs.Dirent[];
|
|
206
|
+
|
|
207
|
+
const _defaultReadDir: ReadDir = (dir) => fs.readdirSync(dir, { withFileTypes: true });
|
|
208
|
+
|
|
209
|
+
function _walk(dir: string, base: string, out: string[], readDir: ReadDir): void {
|
|
210
|
+
let entries: fs.Dirent[];
|
|
211
|
+
try {
|
|
212
|
+
entries = readDir(dir);
|
|
213
|
+
} catch (exc) {
|
|
214
|
+
throw new ScanTraversalError(dir, exc);
|
|
215
|
+
}
|
|
216
|
+
for (const e of entries) {
|
|
217
|
+
const abs = path.join(dir, e.name);
|
|
218
|
+
const rel = path.relative(base, abs).split(path.sep).join('/');
|
|
219
|
+
if (e.isDirectory()) {
|
|
220
|
+
_walk(abs, base, out, readDir);
|
|
221
|
+
} else if (e.isFile() && rel.endsWith('.ts') && !rel.endsWith('.d.ts') && !_EXCLUDE_RE.test(rel)) {
|
|
222
|
+
out.push(rel);
|
|
223
|
+
}
|
|
224
|
+
}
|
|
225
|
+
}
|
|
226
|
+
|
|
227
|
+
/** The scan root, repo-relative. Published so the gate can name it in an error. */
|
|
228
|
+
export const SCAN_ROOT = 'src/scripts';
|
|
229
|
+
|
|
230
|
+
/**
|
|
231
|
+
* Scan every TypeScript module under `src/scripts/` and return one row per
|
|
232
|
+
* module that both brings outside bytes in and hands a string onward.
|
|
233
|
+
*
|
|
234
|
+
* Rows are sorted by module path so the rendered table is stable across
|
|
235
|
+
* filesystems.
|
|
236
|
+
*/
|
|
237
|
+
export function scanReadSurfaces(repoRoot: string, readDir: ReadDir = _defaultReadDir): ReadSurfaceRow[] {
|
|
238
|
+
const base = path.join(repoRoot, SCAN_ROOT);
|
|
239
|
+
const files: string[] = [];
|
|
240
|
+
_walk(base, base, files, readDir);
|
|
241
|
+
files.sort();
|
|
242
|
+
|
|
243
|
+
const rows: ReadSurfaceRow[] = [];
|
|
244
|
+
for (const rel of files) {
|
|
245
|
+
if (_SELF_MODULES.has(rel)) continue;
|
|
246
|
+
let src: string;
|
|
247
|
+
try {
|
|
248
|
+
src = fs.readFileSync(path.join(base, rel), 'utf-8');
|
|
249
|
+
} catch {
|
|
250
|
+
continue;
|
|
251
|
+
}
|
|
252
|
+
const strong = STRONG_INBOUND.find((d) => d.match(src));
|
|
253
|
+
const weak = strong === undefined ? WEAK_INBOUND.find((d) => d.match(src)) : undefined;
|
|
254
|
+
if (strong === undefined && weak === undefined) continue;
|
|
255
|
+
// A declared surface reports the shape its own marker names, so the
|
|
256
|
+
// table says `ingested-corpus` rather than the detector's own id.
|
|
257
|
+
const strongId =
|
|
258
|
+
strong?.id === 'declared' ? (_DECLARATION_RE.exec(src)?.[1] ?? 'declared') : strong?.id;
|
|
259
|
+
|
|
260
|
+
const emit = EMIT_SHAPES.find((d) => d.match(src));
|
|
261
|
+
const sanitized = _SANITIZE_IMPORT_RE.test(src);
|
|
262
|
+
|
|
263
|
+
if (weak !== undefined) {
|
|
264
|
+
rows.push({
|
|
265
|
+
module: rel,
|
|
266
|
+
inbound: weak.id,
|
|
267
|
+
emit: emit?.id ?? 'undecided',
|
|
268
|
+
coverage: 'unclassified',
|
|
269
|
+
evidence: 'network access through a callee the scanner cannot resolve — read the module',
|
|
270
|
+
});
|
|
271
|
+
continue;
|
|
272
|
+
}
|
|
273
|
+
if (emit === undefined) {
|
|
274
|
+
rows.push({
|
|
275
|
+
module: rel,
|
|
276
|
+
inbound: strongId ?? 'unknown',
|
|
277
|
+
emit: 'undecided',
|
|
278
|
+
coverage: 'unclassified',
|
|
279
|
+
evidence: 'inbound bytes with no recognised emit — the onward path is undecided',
|
|
280
|
+
});
|
|
281
|
+
continue;
|
|
282
|
+
}
|
|
283
|
+
rows.push({
|
|
284
|
+
module: rel,
|
|
285
|
+
inbound: strongId ?? 'unknown',
|
|
286
|
+
emit: emit.id,
|
|
287
|
+
coverage: sanitized ? 'covered' : 'uncovered',
|
|
288
|
+
evidence: sanitized
|
|
289
|
+
? 'imports the sanitize floor'
|
|
290
|
+
: 'no sanitize import — fetched bytes reach the emit unfiltered',
|
|
291
|
+
});
|
|
292
|
+
}
|
|
293
|
+
return rows;
|
|
294
|
+
}
|
|
295
|
+
|
|
296
|
+
/** Counts by verdict, for the gate's scanned line and for the table header. */
|
|
297
|
+
export function tally(rows: readonly ReadSurfaceRow[]): Record<SurfaceCoverage, number> {
|
|
298
|
+
const out: Record<SurfaceCoverage, number> = { covered: 0, uncovered: 0, unclassified: 0 };
|
|
299
|
+
for (const r of rows) out[r.coverage] += 1;
|
|
300
|
+
return out;
|
|
301
|
+
}
|
|
302
|
+
|
|
303
|
+
/** Render the rows as the markdown table body committed to the contract doc. */
|
|
304
|
+
export function renderTable(rows: readonly ReadSurfaceRow[]): string {
|
|
305
|
+
const lines = [
|
|
306
|
+
'| module (under `src/scripts/`) | inbound shape | emit shape | coverage | evidence |',
|
|
307
|
+
'|---|---|---|---|---|',
|
|
308
|
+
];
|
|
309
|
+
for (const r of rows) {
|
|
310
|
+
lines.push(
|
|
311
|
+
`| \`${r.module}\` | \`${r.inbound}\` | \`${r.emit}\` | **${r.coverage}** | ${r.evidence} |`,
|
|
312
|
+
);
|
|
313
|
+
}
|
|
314
|
+
const t = tally(rows);
|
|
315
|
+
lines.push('');
|
|
316
|
+
lines.push(
|
|
317
|
+
`${String(rows.length)} read surface(s): ` +
|
|
318
|
+
`${String(t.covered)} covered, ${String(t.uncovered)} uncovered, ` +
|
|
319
|
+
`${String(t.unclassified)} unclassified.`,
|
|
320
|
+
);
|
|
321
|
+
return lines.join('\n');
|
|
322
|
+
}
|