@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,111 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The only host environment variables this package will ever write.
|
|
3
|
+
*
|
|
4
|
+
* WHY ITS OWN MODULE. One place owns the question "which host variable may this
|
|
5
|
+
* package write", and the answer is a two-row allow table rather than a rule
|
|
6
|
+
* applied at a call site. `install.ts` imports it; nothing else writes host env.
|
|
7
|
+
* The move out of `install.ts` also keeps that file, already far over its size
|
|
8
|
+
* cap, from paying for this feature — the same reason the doctor's
|
|
9
|
+
* network-posture block moved into its own module.
|
|
10
|
+
*
|
|
11
|
+
* THE GUARANTEE IS THE SHAPE, not a check. Both traffic variables —
|
|
12
|
+
* `CLAUDE_CODE_DISABLE_NONESSENTIAL_TRAFFIC` and `DISABLE_AUTOUPDATER` — reach
|
|
13
|
+
* the host's update-disabled resolver, the blanket one as its THIRD RUNG
|
|
14
|
+
* (measured on Claude Code 2.1.284, `docs/setup/host-traffic-environment.md`).
|
|
15
|
+
* A writer able to express either could disable a consumer's security updates.
|
|
16
|
+
* Owner-decided 2026-09-30 that it never may, and an allow table of two holds
|
|
17
|
+
* that where a deny list of two would have to stay complete as the host grows
|
|
18
|
+
* more variables.
|
|
19
|
+
*
|
|
20
|
+
* Both writable entries are request SIZE caps. Neither suppresses a call, an
|
|
21
|
+
* update check or a download — they bound how big a request is, never how many.
|
|
22
|
+
*/
|
|
23
|
+
|
|
24
|
+
import * as fs from 'node:fs';
|
|
25
|
+
import * as path from 'node:path';
|
|
26
|
+
|
|
27
|
+
import * as YAML from 'yaml';
|
|
28
|
+
|
|
29
|
+
/** The allow table. Two rows, and adding a third is a governance change. */
|
|
30
|
+
export const WRITABLE_HOST_ENV: readonly { readonly variable: string; readonly settingsKey: string }[] = [
|
|
31
|
+
{ variable: 'BASH_MAX_OUTPUT_LENGTH', settingsKey: 'bash_max_output_length' },
|
|
32
|
+
{ variable: 'MAX_MCP_OUTPUT_TOKENS', settingsKey: 'max_mcp_output_tokens' },
|
|
33
|
+
];
|
|
34
|
+
|
|
35
|
+
/** Where a project's settings document lives, canonical first. */
|
|
36
|
+
const SETTINGS_CANDIDATES: readonly string[][] = [
|
|
37
|
+
['agents', 'settings', '.agent-settings.yml'],
|
|
38
|
+
['.agent-settings.yml'],
|
|
39
|
+
];
|
|
40
|
+
|
|
41
|
+
/**
|
|
42
|
+
* The project settings document, or `null` when there is none to read.
|
|
43
|
+
*
|
|
44
|
+
* `null` on an absent or unparseable file rather than `{}`: the caller reads a
|
|
45
|
+
* key whose ABSENCE means "write nothing", and collapsing an unreadable file to
|
|
46
|
+
* an empty object would give the same answer as a readable file with the key
|
|
47
|
+
* unset. Both write nothing, but only one of them is a fact.
|
|
48
|
+
*/
|
|
49
|
+
export function readSettingsDoc(projectRoot: string): unknown | null {
|
|
50
|
+
for (const parts of SETTINGS_CANDIDATES) {
|
|
51
|
+
const p = path.join(projectRoot, ...parts);
|
|
52
|
+
let text: string;
|
|
53
|
+
try {
|
|
54
|
+
text = fs.readFileSync(p, 'utf-8');
|
|
55
|
+
} catch {
|
|
56
|
+
continue;
|
|
57
|
+
}
|
|
58
|
+
let doc: unknown;
|
|
59
|
+
try {
|
|
60
|
+
doc = YAML.parse(text, { version: '1.1' });
|
|
61
|
+
} catch {
|
|
62
|
+
return null;
|
|
63
|
+
}
|
|
64
|
+
return doc === null || doc === undefined || typeof doc !== 'object' ? null : doc;
|
|
65
|
+
}
|
|
66
|
+
return null;
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
/**
|
|
70
|
+
* The `env` block for the host settings file, or `null` when nothing is set.
|
|
71
|
+
*
|
|
72
|
+
* `null` rather than `{}`: an empty object would still be merged into the
|
|
73
|
+
* consumer's file, leaving an `env: {}` nobody asked for and making "this
|
|
74
|
+
* package wrote nothing" indistinguishable from "this package wrote a blank".
|
|
75
|
+
*
|
|
76
|
+
* A non-integer or non-positive value is IGNORED rather than written. The schema
|
|
77
|
+
* already refuses it; this branch is for a hand-edited settings file, where
|
|
78
|
+
* writing a value the host would silently reinterpret is worse than writing none.
|
|
79
|
+
*/
|
|
80
|
+
export function hostEnvBlock(settings: unknown): Record<string, string> | null {
|
|
81
|
+
const root = settings as Record<string, unknown> | null | undefined;
|
|
82
|
+
const he = root?.['host_environment'] as Record<string, unknown> | undefined;
|
|
83
|
+
const caps = he?.['request_size_caps'] as Record<string, unknown> | undefined;
|
|
84
|
+
if (caps === undefined) return null;
|
|
85
|
+
const out: Record<string, string> = {};
|
|
86
|
+
for (const { variable, settingsKey } of WRITABLE_HOST_ENV) {
|
|
87
|
+
const v = caps[settingsKey];
|
|
88
|
+
if (typeof v === 'number' && Number.isInteger(v) && v > 0) out[variable] = String(v);
|
|
89
|
+
}
|
|
90
|
+
return Object.keys(out).length === 0 ? null : out;
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
/**
|
|
94
|
+
* The host bridge object with the `env` block merged in, if there is one.
|
|
95
|
+
*
|
|
96
|
+
* Takes the bridge and returns it rather than being called for its side effect,
|
|
97
|
+
* so the caller is one expression and `install.ts` — already far over its size
|
|
98
|
+
* cap — pays one line for this feature instead of six. `announce` is injected
|
|
99
|
+
* rather than imported: this module has no opinion about how a caller reports,
|
|
100
|
+
* and importing the installer's logger would make a leaf depend on its consumer.
|
|
101
|
+
*/
|
|
102
|
+
export function withHostEnv(
|
|
103
|
+
bridge: Record<string, unknown>,
|
|
104
|
+
projectRoot: string,
|
|
105
|
+
announce: (msg: string) => void,
|
|
106
|
+
): Record<string, unknown> {
|
|
107
|
+
const env = hostEnvBlock(readSettingsDoc(projectRoot));
|
|
108
|
+
if (env === null) return bridge;
|
|
109
|
+
announce(`.claude/settings.json: wrote ${Object.keys(env).join(', ')} (request-size caps)`);
|
|
110
|
+
return { ...bridge, env };
|
|
111
|
+
}
|
|
@@ -0,0 +1,127 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* `json.dumps` byte-parity helpers — the emit half of the ADR-200 port.
|
|
3
|
+
*
|
|
4
|
+
* The retired Python installer wrote every JSON artefact through
|
|
5
|
+
* `json.dumps(..., ensure_ascii=False)`, and the port's contract is
|
|
6
|
+
* byte-identical output, not merely equivalent JSON. `JSON.stringify` does not
|
|
7
|
+
* satisfy it: it escapes nothing above `\u001f` the same way, renders `NaN` /
|
|
8
|
+
* `Infinity` as `null` rather than as Python's bare tokens, and offers no
|
|
9
|
+
* separator control for the compact NDJSON form. So the three dump shapes the
|
|
10
|
+
* installer needs are written out here, mirroring CPython's encoder:
|
|
11
|
+
*
|
|
12
|
+
* - {@link jsonDumpsIndent} — `json.dumps(data, indent=N, ensure_ascii=False)`
|
|
13
|
+
* with `sort_keys=False` (insertion order preserved, as JS object key order
|
|
14
|
+
* already matches Python dict order for our string keys);
|
|
15
|
+
* - {@link jsonDumpsCompact} — `json.dumps(obj, separators=(",", ":"))`;
|
|
16
|
+
* - {@link jsonStrNoAscii} — the string encoder both build on.
|
|
17
|
+
*
|
|
18
|
+
* Sibling of `src/scripts/install.ts`, which is its only caller. It lives here
|
|
19
|
+
* rather than inline because the dump shapes are pure, total functions of their
|
|
20
|
+
* input with no installer state — and because `install.ts` is far over the
|
|
21
|
+
* 1,500-line source ceiling (`check_source_size_budget`), so a self-contained
|
|
22
|
+
* unit belongs outside it.
|
|
23
|
+
*
|
|
24
|
+
* This module is in the installer's bundled import closure
|
|
25
|
+
* (`npm run build:install-bundle`), so it carries no module-level side effect
|
|
26
|
+
* and no `process.exit` — see `check_installer_import_purity`.
|
|
27
|
+
*/
|
|
28
|
+
|
|
29
|
+
export function jsonStrNoAscii(s: string): string {
|
|
30
|
+
// json.dumps(ensure_ascii=False): escape control chars + " + \, keep >=0x20
|
|
31
|
+
let out = '"';
|
|
32
|
+
for (const ch of s) {
|
|
33
|
+
const code = ch.codePointAt(0) as number;
|
|
34
|
+
switch (ch) {
|
|
35
|
+
case '"':
|
|
36
|
+
out += '\\"';
|
|
37
|
+
break;
|
|
38
|
+
case '\\':
|
|
39
|
+
out += '\\\\';
|
|
40
|
+
break;
|
|
41
|
+
case '\n':
|
|
42
|
+
out += '\\n';
|
|
43
|
+
break;
|
|
44
|
+
case '\r':
|
|
45
|
+
out += '\\r';
|
|
46
|
+
break;
|
|
47
|
+
case '\t':
|
|
48
|
+
out += '\\t';
|
|
49
|
+
break;
|
|
50
|
+
case '\b':
|
|
51
|
+
out += '\\b';
|
|
52
|
+
break;
|
|
53
|
+
case '\f':
|
|
54
|
+
out += '\\f';
|
|
55
|
+
break;
|
|
56
|
+
default:
|
|
57
|
+
if (code < 0x20) {
|
|
58
|
+
out += '\\u' + code.toString(16).padStart(4, '0');
|
|
59
|
+
} else {
|
|
60
|
+
out += ch;
|
|
61
|
+
}
|
|
62
|
+
}
|
|
63
|
+
}
|
|
64
|
+
return out + '"';
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
function _jsonScalar(value: unknown): string | null {
|
|
68
|
+
if (value === null || value === undefined) return 'null';
|
|
69
|
+
if (typeof value === 'boolean') return value ? 'true' : 'false';
|
|
70
|
+
if (typeof value === 'number') {
|
|
71
|
+
if (!Number.isFinite(value)) {
|
|
72
|
+
if (Number.isNaN(value)) return 'NaN';
|
|
73
|
+
return value > 0 ? 'Infinity' : '-Infinity';
|
|
74
|
+
}
|
|
75
|
+
// Our payloads carry only integers; render as-is.
|
|
76
|
+
return String(value);
|
|
77
|
+
}
|
|
78
|
+
if (typeof value === 'string') return jsonStrNoAscii(value);
|
|
79
|
+
return null;
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
function _dumpIndent(value: unknown, indent: number, depth: number): string {
|
|
83
|
+
const scalar = _jsonScalar(value);
|
|
84
|
+
if (scalar !== null) return scalar;
|
|
85
|
+
const pad = ' '.repeat(indent * (depth + 1));
|
|
86
|
+
const closePad = ' '.repeat(indent * depth);
|
|
87
|
+
if (Array.isArray(value)) {
|
|
88
|
+
if (value.length === 0) return '[]';
|
|
89
|
+
const items = value.map((v) => pad + _dumpIndent(v, indent, depth + 1));
|
|
90
|
+
return `[\n${items.join(',\n')}\n${closePad}]`;
|
|
91
|
+
}
|
|
92
|
+
if (typeof value === 'object' && value !== null) {
|
|
93
|
+
const obj = value as Record<string, unknown>;
|
|
94
|
+
const keys = Object.keys(obj);
|
|
95
|
+
if (keys.length === 0) return '{}';
|
|
96
|
+
const items = keys.map(
|
|
97
|
+
(k) => `${pad}${jsonStrNoAscii(k)}: ${_dumpIndent(obj[k], indent, depth + 1)}`,
|
|
98
|
+
);
|
|
99
|
+
return `{\n${items.join(',\n')}\n${closePad}}`;
|
|
100
|
+
}
|
|
101
|
+
return jsonStrNoAscii(String(value));
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
/** `json.dumps(data, indent=N, ensure_ascii=False)` (sort_keys=False). */
|
|
105
|
+
export function jsonDumpsIndent(value: unknown, indent: number): string {
|
|
106
|
+
return _dumpIndent(value, indent, 0);
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
/** `json.dumps(obj, separators=(",", ":"))` — compact, ensure_ascii=False here. */
|
|
110
|
+
export function jsonDumpsCompact(value: unknown): string {
|
|
111
|
+
const scalar = _jsonScalar(value);
|
|
112
|
+
if (scalar !== null) return scalar;
|
|
113
|
+
if (Array.isArray(value)) {
|
|
114
|
+
return '[' + value.map((v) => jsonDumpsCompact(v)).join(',') + ']';
|
|
115
|
+
}
|
|
116
|
+
if (typeof value === 'object' && value !== null) {
|
|
117
|
+
const obj = value as Record<string, unknown>;
|
|
118
|
+
return (
|
|
119
|
+
'{' +
|
|
120
|
+
Object.keys(obj)
|
|
121
|
+
.map((k) => `${jsonStrNoAscii(k)}:${jsonDumpsCompact(obj[k])}`)
|
|
122
|
+
.join(',') +
|
|
123
|
+
'}'
|
|
124
|
+
);
|
|
125
|
+
}
|
|
126
|
+
return jsonStrNoAscii(String(value));
|
|
127
|
+
}
|
|
@@ -11,11 +11,22 @@
|
|
|
11
11
|
*
|
|
12
12
|
* So each side supplies the raw string from its own reader and this module
|
|
13
13
|
* decides what it means. Anything unrecognised — absent key, typo, `null`,
|
|
14
|
-
* a non-string — is `eager-all
|
|
15
|
-
*
|
|
14
|
+
* a non-string — is `eager-all`: the parser fallback, which is NOT the value
|
|
15
|
+
* the template ships (see the type below), because a mode nobody can spell
|
|
16
|
+
* must never silently thin the standing corpus.
|
|
16
17
|
*/
|
|
17
18
|
|
|
18
|
-
/**
|
|
19
|
+
/**
|
|
20
|
+
* The three projection shapes, and the two different defaults they answer to.
|
|
21
|
+
*
|
|
22
|
+
* The TEMPLATE default — what a consumer is given — is `delivery` with
|
|
23
|
+
* `hosts: [claude-code]`, shipped in `src/config/agent-settings.template.yml`
|
|
24
|
+
* since ADR-267, so on that one host a normal read resolves to `delivery`.
|
|
25
|
+
* The PARSER FALLBACK — what applies when no value resolves from any layer —
|
|
26
|
+
* is `eager-all` (`DEFAULT_LEAN_PROJECTION_MODE` below), deliberately not
|
|
27
|
+
* flipped with the template per ADR-267 decision 4: the template says what was
|
|
28
|
+
* chosen, the constant says what happens when nothing could be read.
|
|
29
|
+
*/
|
|
19
30
|
export type LeanProjectionMode = 'eager-all' | 'thin' | 'delivery';
|
|
20
31
|
|
|
21
32
|
export const DEFAULT_LEAN_PROJECTION_MODE: LeanProjectionMode = 'eager-all';
|
|
@@ -37,6 +37,7 @@
|
|
|
37
37
|
*/
|
|
38
38
|
|
|
39
39
|
import { load_anthropic_key } from '../ai_council/clients.js';
|
|
40
|
+
import { sanitize_text } from './retrieval_sanitize.js';
|
|
40
41
|
import type {
|
|
41
42
|
GenerationRequest,
|
|
42
43
|
GenerationResult,
|
|
@@ -140,10 +141,19 @@ export function anthropicGenerator(keyPath: string | null = null): TextGenerator
|
|
|
140
141
|
);
|
|
141
142
|
}
|
|
142
143
|
const data = (await resp.json()) as AnthropicResponse;
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
144
|
+
// Sanitize AT THE TRANSPORT, which is the choke point and one call site.
|
|
145
|
+
// A flag every caller has to remember is the instruction-only
|
|
146
|
+
// enforcement this package diagnoses in itself, and a partially
|
|
147
|
+
// sanitized world — some callers filtered, some not — is worse than
|
|
148
|
+
// either end state. The floor is a no-op on ordinary text, so what
|
|
149
|
+
// changes here is only a response that carries a hidden-instruction
|
|
150
|
+
// vector; see `docs/contracts/retrieval-read-surfaces.md`.
|
|
151
|
+
const text = sanitize_text(
|
|
152
|
+
(data.content ?? [])
|
|
153
|
+
.filter((b) => b.type === 'text')
|
|
154
|
+
.map((b) => b.text ?? '')
|
|
155
|
+
.join(''),
|
|
156
|
+
);
|
|
147
157
|
// An empty body is returned AS an empty body rather than repaired here.
|
|
148
158
|
// The arm's own output contract refuses it and classifies the refusal;
|
|
149
159
|
// repairing it in the transport would move a decision into the layer
|
|
@@ -0,0 +1,382 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The loop-surface inventory, and the reach question it exists to answer.
|
|
3
|
+
*
|
|
4
|
+
* Why an inventory at all.
|
|
5
|
+
*
|
|
6
|
+
* `archive/road-to-wired-instruments.md` closed four instances of one defect —
|
|
7
|
+
* an instrument this repository built, tested, and then connected to nothing —
|
|
8
|
+
* one instance at a time, and shipped five hand-written `grep` acceptance
|
|
9
|
+
* criteria instead of a detector. Two further instances were open the whole
|
|
10
|
+
* time and nothing in the tree could notice them. This module is the reader
|
|
11
|
+
* half of the fix: a declared list of loop surfaces and the exported helpers
|
|
12
|
+
* they are supposed to call, plus the one predicate that says whether a
|
|
13
|
+
* declared helper is called by anything at all.
|
|
14
|
+
*
|
|
15
|
+
* What it is NOT.
|
|
16
|
+
*
|
|
17
|
+
* Reader input. Nothing executes from it, no surface changes behaviour because
|
|
18
|
+
* of it, and there is no runner. That is Risk 3 of the roadmap that introduced
|
|
19
|
+
* it, stated as a constraint rather than a hope: the moment something
|
|
20
|
+
* dispatches off this file it has become the loop engine a recorded decision
|
|
21
|
+
* forbids.
|
|
22
|
+
*
|
|
23
|
+
* The reach predicate, and the correction that shaped it.
|
|
24
|
+
*
|
|
25
|
+
* The obvious predicate — "no file other than its own imports this export" —
|
|
26
|
+
* was measured against `loop_guards.ts` and calls nine of its fourteen exports
|
|
27
|
+
* dead. Most of those are wrong: a helper used by its own neighbours in the
|
|
28
|
+
* same file is reached every time that file is. So the criterion is
|
|
29
|
+
* occurrence-based and counts the declaration itself:
|
|
30
|
+
*
|
|
31
|
+
* an export is DEAD when it appears in `src/` exactly once,
|
|
32
|
+
* including its own declaration.
|
|
33
|
+
*
|
|
34
|
+
* Two is enough, deliberately. A second occurrence anywhere in `src/` means
|
|
35
|
+
* some production line names it; whether that line sits in the same file or
|
|
36
|
+
* another one is not a difference this predicate needs to resolve, and
|
|
37
|
+
* resolving it would mean writing a second import resolver.
|
|
38
|
+
*
|
|
39
|
+
* `tests/` is outside `src/` and therefore outside the count by construction —
|
|
40
|
+
* which is the point. A test-only caller is exactly the state both open
|
|
41
|
+
* instances are in.
|
|
42
|
+
*
|
|
43
|
+
* Why `cap` accepts a markdown anchor as well as a symbol.
|
|
44
|
+
*
|
|
45
|
+
* The roadmap step that specified this file asked for `file:symbol` and
|
|
46
|
+
* "never prose". Reproduced against the tree on 2026-09-13, that is
|
|
47
|
+
* unsatisfiable for two of the five surfaces and a category error for both:
|
|
48
|
+
* `verify-repair-loop` and `experiment-loop` are SKILLS, and a skill's bound
|
|
49
|
+
* is a documented knob, not a TypeScript constant. `grep -rn max_attempts
|
|
50
|
+
* src/` returns hits only inside the skill's own directory.
|
|
51
|
+
*
|
|
52
|
+
* The property that requirement was reaching for is preserved exactly:
|
|
53
|
+
* a bound must resolve to something that demonstrably EXISTS. So a `.ts`
|
|
54
|
+
* target must be a defined symbol, a `.md` target must be a literal token
|
|
55
|
+
* present in that file, and a markdown-bound row must additionally declare
|
|
56
|
+
* `bound_kind: prose` — the prose-ness is recorded rather than inferred, and
|
|
57
|
+
* `inventoryVerdict` reports those surfaces as a named set so a prose bound
|
|
58
|
+
* can never be read as equal to a code one.
|
|
59
|
+
*/
|
|
60
|
+
|
|
61
|
+
import * as fs from 'node:fs';
|
|
62
|
+
import * as path from 'node:path';
|
|
63
|
+
|
|
64
|
+
import { parse as parseYaml } from 'yaml';
|
|
65
|
+
|
|
66
|
+
export const INVENTORY_REL = 'src/config/loop-surfaces.yaml';
|
|
67
|
+
|
|
68
|
+
/** Fields every surface row must carry. Order is the order they are reported in. */
|
|
69
|
+
export const SURFACE_FIELDS = [
|
|
70
|
+
'cap',
|
|
71
|
+
'no_progress',
|
|
72
|
+
'success_stop',
|
|
73
|
+
'checker',
|
|
74
|
+
'human_gate',
|
|
75
|
+
'terminal_vocabulary',
|
|
76
|
+
'production_consumers',
|
|
77
|
+
] as const;
|
|
78
|
+
|
|
79
|
+
export type SurfaceField = (typeof SURFACE_FIELDS)[number];
|
|
80
|
+
|
|
81
|
+
/** A loop surface — a thing that iterates under a bound. */
|
|
82
|
+
export interface LoopSurface {
|
|
83
|
+
id: string;
|
|
84
|
+
/** `code` or `prose` — which kind of artefact the surface itself is. */
|
|
85
|
+
kind: string;
|
|
86
|
+
/** `code` when `cap`/`no_progress` name symbols, `prose` when they name doc anchors. */
|
|
87
|
+
bound_kind: string;
|
|
88
|
+
cap: string;
|
|
89
|
+
no_progress: string;
|
|
90
|
+
success_stop: string;
|
|
91
|
+
checker: string;
|
|
92
|
+
human_gate: string;
|
|
93
|
+
terminal_vocabulary: string;
|
|
94
|
+
production_consumers: string[];
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
/**
|
|
98
|
+
* An exported helper a surface is meant to call.
|
|
99
|
+
*
|
|
100
|
+
* Separated from surfaces because the reach question only applies here: a
|
|
101
|
+
* surface is reached by being bound (a hook slot, a skill invocation), an
|
|
102
|
+
* instrument is reached by being called, and only the second is answerable by
|
|
103
|
+
* counting occurrences of a symbol.
|
|
104
|
+
*/
|
|
105
|
+
export interface LoopInstrument {
|
|
106
|
+
id: string;
|
|
107
|
+
file: string;
|
|
108
|
+
role: string;
|
|
109
|
+
/** `experimental` exempts it from the reach requirement — with a deadline. */
|
|
110
|
+
status?: string | undefined;
|
|
111
|
+
/** ISO `YYYY-MM-DD`. Required under `status: experimental`; a past date reds. */
|
|
112
|
+
expires?: string | undefined;
|
|
113
|
+
note?: string | undefined;
|
|
114
|
+
}
|
|
115
|
+
|
|
116
|
+
export interface Inventory {
|
|
117
|
+
surfaces: LoopSurface[];
|
|
118
|
+
instruments: LoopInstrument[];
|
|
119
|
+
}
|
|
120
|
+
|
|
121
|
+
/** `path/to/file.ts:SYMBOL` or `path/to/file.md:ANCHOR`. */
|
|
122
|
+
const BOUND_TARGET = /^([A-Za-z0-9_./-]+\.(?:ts|js|md)):([A-Za-z_$][A-Za-z0-9_$-]*)$/;
|
|
123
|
+
|
|
124
|
+
function str(row: Record<string, unknown>, key: string): string {
|
|
125
|
+
const v = row[key];
|
|
126
|
+
return v === undefined || v === null ? '' : String(v);
|
|
127
|
+
}
|
|
128
|
+
|
|
129
|
+
export function readInventory(root: string): Inventory | null {
|
|
130
|
+
const p = path.join(root, INVENTORY_REL);
|
|
131
|
+
if (!fs.existsSync(p)) return null;
|
|
132
|
+
const doc = parseYaml(fs.readFileSync(p, 'utf-8')) as {
|
|
133
|
+
surfaces?: Record<string, Record<string, unknown>>;
|
|
134
|
+
instruments?: Record<string, Record<string, unknown>>;
|
|
135
|
+
} | null;
|
|
136
|
+
|
|
137
|
+
const surfaces: LoopSurface[] = [];
|
|
138
|
+
for (const [id, row] of Object.entries(doc?.surfaces ?? {})) {
|
|
139
|
+
surfaces.push({
|
|
140
|
+
id,
|
|
141
|
+
kind: str(row, 'kind'),
|
|
142
|
+
bound_kind: str(row, 'bound_kind'),
|
|
143
|
+
cap: str(row, 'cap'),
|
|
144
|
+
no_progress: str(row, 'no_progress'),
|
|
145
|
+
success_stop: str(row, 'success_stop'),
|
|
146
|
+
checker: str(row, 'checker'),
|
|
147
|
+
human_gate: str(row, 'human_gate'),
|
|
148
|
+
terminal_vocabulary: str(row, 'terminal_vocabulary'),
|
|
149
|
+
production_consumers: Array.isArray(row['production_consumers'])
|
|
150
|
+
? (row['production_consumers'] as unknown[]).map(String)
|
|
151
|
+
: [],
|
|
152
|
+
});
|
|
153
|
+
}
|
|
154
|
+
|
|
155
|
+
const instruments: LoopInstrument[] = [];
|
|
156
|
+
for (const [id, row] of Object.entries(doc?.instruments ?? {})) {
|
|
157
|
+
instruments.push({
|
|
158
|
+
id,
|
|
159
|
+
file: str(row, 'file'),
|
|
160
|
+
role: str(row, 'role'),
|
|
161
|
+
status: row['status'] === undefined ? undefined : String(row['status']),
|
|
162
|
+
expires: row['expires'] === undefined ? undefined : String(row['expires']),
|
|
163
|
+
note: row['note'] === undefined ? undefined : String(row['note']),
|
|
164
|
+
});
|
|
165
|
+
}
|
|
166
|
+
|
|
167
|
+
surfaces.sort((a, b) => a.id.localeCompare(b.id));
|
|
168
|
+
instruments.sort((a, b) => a.id.localeCompare(b.id));
|
|
169
|
+
return { surfaces, instruments };
|
|
170
|
+
}
|
|
171
|
+
|
|
172
|
+
/** Every `.ts` / `.js` file under `src/`, sorted. Cached per root. */
|
|
173
|
+
const sourceCache = new Map<string, string[]>();
|
|
174
|
+
|
|
175
|
+
export function sourceFiles(root: string): string[] {
|
|
176
|
+
const hit = sourceCache.get(root);
|
|
177
|
+
if (hit !== undefined) return hit;
|
|
178
|
+
const out: string[] = [];
|
|
179
|
+
const walk = (dir: string): void => {
|
|
180
|
+
let entries: fs.Dirent[];
|
|
181
|
+
try {
|
|
182
|
+
entries = fs.readdirSync(dir, { withFileTypes: true });
|
|
183
|
+
} catch {
|
|
184
|
+
return;
|
|
185
|
+
}
|
|
186
|
+
for (const e of entries.sort((a, b) => a.name.localeCompare(b.name))) {
|
|
187
|
+
const full = path.join(dir, e.name);
|
|
188
|
+
if (e.isDirectory()) {
|
|
189
|
+
if (e.name === 'node_modules') continue;
|
|
190
|
+
walk(full);
|
|
191
|
+
} else if (e.name.endsWith('.ts') || e.name.endsWith('.js')) {
|
|
192
|
+
out.push(full);
|
|
193
|
+
}
|
|
194
|
+
}
|
|
195
|
+
};
|
|
196
|
+
walk(path.join(root, 'src'));
|
|
197
|
+
sourceCache.set(root, out);
|
|
198
|
+
return out;
|
|
199
|
+
}
|
|
200
|
+
|
|
201
|
+
/** Drop the file-list cache. The self-test builds throwaway trees. */
|
|
202
|
+
export function resetSourceCache(): void {
|
|
203
|
+
sourceCache.clear();
|
|
204
|
+
}
|
|
205
|
+
|
|
206
|
+
function escapeRe(s: string): string {
|
|
207
|
+
return s.replace(/[.*+?^${}()|[\]\\]/g, '\\$&');
|
|
208
|
+
}
|
|
209
|
+
|
|
210
|
+
/**
|
|
211
|
+
* How many times `symbol` occurs anywhere under `src/`, declaration included.
|
|
212
|
+
*
|
|
213
|
+
* Word-bounded, so `stallSignal` does not match `stallSignalled`. Deliberately
|
|
214
|
+
* text-level: a symbol resolver would have to model re-exports, barrel files
|
|
215
|
+
* and type-only imports to beat this, and would then be a second
|
|
216
|
+
* implementation of the compiler. The loose version's failure mode is a count
|
|
217
|
+
* that is too HIGH — reporting a dead export as live, which is the quiet
|
|
218
|
+
* direction. It never manufactures an accusation.
|
|
219
|
+
*/
|
|
220
|
+
export function occurrences(root: string, symbol: string): number {
|
|
221
|
+
if (symbol === '') return 0;
|
|
222
|
+
const re = new RegExp(`\\b${escapeRe(symbol)}\\b`, 'g');
|
|
223
|
+
let n = 0;
|
|
224
|
+
for (const f of sourceFiles(root)) {
|
|
225
|
+
n += (fs.readFileSync(f, 'utf-8').match(re) ?? []).length;
|
|
226
|
+
}
|
|
227
|
+
return n;
|
|
228
|
+
}
|
|
229
|
+
|
|
230
|
+
/** Does `file` DEFINE `symbol` (a `.ts` target) or merely contain it (a `.md` anchor)? */
|
|
231
|
+
export function resolvesTarget(root: string, file: string, token: string): boolean {
|
|
232
|
+
const p = path.join(root, file);
|
|
233
|
+
if (!fs.existsSync(p)) return false;
|
|
234
|
+
const text = fs.readFileSync(p, 'utf-8');
|
|
235
|
+
const esc = escapeRe(token);
|
|
236
|
+
if (file.endsWith('.md')) return new RegExp(`\\b${esc}\\b`).test(text);
|
|
237
|
+
const decl = new RegExp(`\\b(?:const|let|var|function|interface|type|class|enum)\\s+${esc}\\b`);
|
|
238
|
+
if (decl.test(text)) return true;
|
|
239
|
+
// Object keys, union members and class fields: `TOKEN:` / `TOKEN(` at line start.
|
|
240
|
+
return new RegExp(`^\\s*(?:readonly\\s+)?${esc}\\s*[:(]`, 'm').test(text);
|
|
241
|
+
}
|
|
242
|
+
|
|
243
|
+
export interface ReachFinding {
|
|
244
|
+
id: string;
|
|
245
|
+
file: string;
|
|
246
|
+
/** `dead` · `expired` · `no-expiry` · `bad-status` — why it reds. */
|
|
247
|
+
reason: string;
|
|
248
|
+
detail: string;
|
|
249
|
+
}
|
|
250
|
+
|
|
251
|
+
export interface ShapeFinding {
|
|
252
|
+
id: string;
|
|
253
|
+
reason: string;
|
|
254
|
+
detail: string;
|
|
255
|
+
}
|
|
256
|
+
|
|
257
|
+
export interface InventoryVerdict {
|
|
258
|
+
/** Surfaces with a missing field, or a bound that resolves to nothing. */
|
|
259
|
+
shape: ShapeFinding[];
|
|
260
|
+
/** Declared instruments with no production consumer and no valid exemption. */
|
|
261
|
+
reach: ReachFinding[];
|
|
262
|
+
/** Instruments reported live — the does-not-over-fire evidence. */
|
|
263
|
+
live: string[];
|
|
264
|
+
/** Instruments held by a valid, unexpired `experimental` exemption. */
|
|
265
|
+
exempt: string[];
|
|
266
|
+
/** Surfaces whose bound is documented prose, not a code symbol. Reported, never red. */
|
|
267
|
+
proseBound: string[];
|
|
268
|
+
}
|
|
269
|
+
|
|
270
|
+
/** An instrument is reached when it occurs more than once in `src/`. */
|
|
271
|
+
export const REACH_MINIMUM_OCCURRENCES = 2;
|
|
272
|
+
|
|
273
|
+
const BOUND_FIELDS = ['cap', 'no_progress'] as const;
|
|
274
|
+
|
|
275
|
+
function checkSurface(root: string, s: LoopSurface, shape: ShapeFinding[]): void {
|
|
276
|
+
for (const f of SURFACE_FIELDS) {
|
|
277
|
+
const v = s[f];
|
|
278
|
+
const empty = Array.isArray(v) ? v.length === 0 : String(v).trim() === '';
|
|
279
|
+
if (empty) shape.push({ id: s.id, reason: 'missing-field', detail: `\`${f}\` is empty` });
|
|
280
|
+
}
|
|
281
|
+
if (s.bound_kind !== 'code' && s.bound_kind !== 'prose') {
|
|
282
|
+
shape.push({
|
|
283
|
+
id: s.id,
|
|
284
|
+
reason: 'bad-bound-kind',
|
|
285
|
+
detail: `\`bound_kind: ${s.bound_kind}\` — must be \`code\` or \`prose\``,
|
|
286
|
+
});
|
|
287
|
+
}
|
|
288
|
+
for (const f of BOUND_FIELDS) {
|
|
289
|
+
const raw = String(s[f]).trim();
|
|
290
|
+
if (raw === '') continue;
|
|
291
|
+
const m = BOUND_TARGET.exec(raw);
|
|
292
|
+
if (m === null) {
|
|
293
|
+
shape.push({
|
|
294
|
+
id: s.id,
|
|
295
|
+
reason: 'not-a-target',
|
|
296
|
+
detail: `\`${f}: ${raw}\` is not \`path/to/file.{ts,js,md}:TOKEN\``,
|
|
297
|
+
});
|
|
298
|
+
continue;
|
|
299
|
+
}
|
|
300
|
+
const [, file, token] = m as unknown as [string, string, string];
|
|
301
|
+
// A markdown bound must SAY it is prose. Undeclared, it would read as a
|
|
302
|
+
// code bound in every report that aggregates this file.
|
|
303
|
+
if (file.endsWith('.md') && s.bound_kind !== 'prose') {
|
|
304
|
+
shape.push({
|
|
305
|
+
id: s.id,
|
|
306
|
+
reason: 'undeclared-prose-bound',
|
|
307
|
+
detail: `\`${f}\` points into ${file} but the row is \`bound_kind: ${s.bound_kind}\``,
|
|
308
|
+
});
|
|
309
|
+
}
|
|
310
|
+
if (!resolvesTarget(root, file, token)) {
|
|
311
|
+
shape.push({
|
|
312
|
+
id: s.id,
|
|
313
|
+
reason: 'unresolved-target',
|
|
314
|
+
detail: `\`${f}\` names ${token} in ${file}, which does not carry it`,
|
|
315
|
+
});
|
|
316
|
+
}
|
|
317
|
+
}
|
|
318
|
+
}
|
|
319
|
+
|
|
320
|
+
function checkInstrument(root: string, i: LoopInstrument, day: string, v: InventoryVerdict): void {
|
|
321
|
+
if (i.status !== undefined && i.status !== 'experimental') {
|
|
322
|
+
v.reach.push({
|
|
323
|
+
id: i.id,
|
|
324
|
+
file: i.file,
|
|
325
|
+
reason: 'bad-status',
|
|
326
|
+
detail: `\`status: ${i.status}\` is not a recognised exemption (only \`experimental\`)`,
|
|
327
|
+
});
|
|
328
|
+
return;
|
|
329
|
+
}
|
|
330
|
+
if (i.status === 'experimental') {
|
|
331
|
+
const exp = (i.expires ?? '').trim();
|
|
332
|
+
if (!/^\d{4}-\d{2}-\d{2}$/.test(exp)) {
|
|
333
|
+
v.reach.push({
|
|
334
|
+
id: i.id,
|
|
335
|
+
file: i.file,
|
|
336
|
+
reason: 'no-expiry',
|
|
337
|
+
detail: 'an `experimental` exemption needs `expires: YYYY-MM-DD`',
|
|
338
|
+
});
|
|
339
|
+
return;
|
|
340
|
+
}
|
|
341
|
+
if (exp <= day) {
|
|
342
|
+
v.reach.push({
|
|
343
|
+
id: i.id,
|
|
344
|
+
file: i.file,
|
|
345
|
+
reason: 'expired',
|
|
346
|
+
detail: `the \`experimental\` exemption lapsed on ${exp} — decide, or move the date with a reason`,
|
|
347
|
+
});
|
|
348
|
+
return;
|
|
349
|
+
}
|
|
350
|
+
v.exempt.push(i.id);
|
|
351
|
+
return;
|
|
352
|
+
}
|
|
353
|
+
const n = occurrences(root, i.id);
|
|
354
|
+
if (n < REACH_MINIMUM_OCCURRENCES) {
|
|
355
|
+
v.reach.push({
|
|
356
|
+
id: i.id,
|
|
357
|
+
file: i.file,
|
|
358
|
+
reason: 'dead',
|
|
359
|
+
detail: `occurs ${String(n)}× in src/ (own declaration included) — nothing calls it`,
|
|
360
|
+
});
|
|
361
|
+
} else {
|
|
362
|
+
v.live.push(i.id);
|
|
363
|
+
}
|
|
364
|
+
}
|
|
365
|
+
|
|
366
|
+
export function inventoryVerdict(root: string, today = new Date()): InventoryVerdict {
|
|
367
|
+
const v: InventoryVerdict = { shape: [], reach: [], live: [], exempt: [], proseBound: [] };
|
|
368
|
+
const inv = readInventory(root);
|
|
369
|
+
if (inv === null) return v;
|
|
370
|
+
|
|
371
|
+
for (const s of inv.surfaces) {
|
|
372
|
+
checkSurface(root, s, v.shape);
|
|
373
|
+
if (s.bound_kind === 'prose') v.proseBound.push(s.id);
|
|
374
|
+
}
|
|
375
|
+
const day = today.toISOString().slice(0, 10);
|
|
376
|
+
for (const i of inv.instruments) checkInstrument(root, i, day, v);
|
|
377
|
+
|
|
378
|
+
v.live.sort();
|
|
379
|
+
v.exempt.sort();
|
|
380
|
+
v.proseBound.sort();
|
|
381
|
+
return v;
|
|
382
|
+
}
|