@event4u/agent-config 14.11.0 → 14.12.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.
Files changed (350) hide show
  1. package/.augment-plugin/marketplace.json +3 -3
  2. package/.augment-plugin/plugin.json +1 -1
  3. package/.claude-plugin/marketplace.json +1 -1
  4. package/CHANGELOG.md +156 -538
  5. package/README.md +1 -1
  6. package/dist/agent-src/contexts/execution/evidence-discipline.md +26 -0
  7. package/dist/agent-src/skills/code-refactoring/SKILL.md +45 -1
  8. package/dist/agent-src/skills/code-review/SKILL.md +65 -6
  9. package/dist/agent-src/skills/command-writing/SKILL.md +35 -0
  10. package/dist/agent-src/skills/existing-ui-audit/SKILL.md +8 -1
  11. package/dist/agent-src/skills/project-analysis-core/SKILL.md +23 -0
  12. package/dist/agent-src/skills/react-shadcn-ui/SKILL.md +29 -12
  13. package/dist/agent-src/skills/skill-writing/references/procedure.md +24 -2
  14. package/dist/agent-src/skills/storybook-workshop/SKILL.md +9 -0
  15. package/dist/agent-src/skills/ui-component-architect/SKILL.md +8 -0
  16. package/dist/agent-src/templates/agents/agent-project-settings.example.yml +1 -1
  17. package/dist/cli-delegate/{chunk-L43NCYPB.js → chunk-JCW7ZLHK.js} +5 -0
  18. package/dist/cli-delegate/{chunk-236PNQZK.js → chunk-JJA2QEWA.js} +1 -1
  19. package/dist/cli-delegate/cmd_export.js +2 -2
  20. package/dist/cli-delegate/cmd_prune.js +2 -2
  21. package/dist/cli-delegate/cmd_settings_migrate.js +2 -2
  22. package/dist/cli-delegate/cmd_settings_set.js +1 -1
  23. package/dist/cli-delegate/cmd_sync.js +2 -2
  24. package/dist/cli-delegate/cmd_uninstall.js +2 -2
  25. package/dist/cli-delegate/cmd_validate.js +2 -2
  26. package/dist/discovery/deprecation-report.md +1 -1
  27. package/dist/discovery/discovery-manifest.json +10 -10
  28. package/dist/discovery/discovery-manifest.json.sha256 +1 -1
  29. package/dist/discovery/discovery-manifest.summary.md +1 -1
  30. package/dist/discovery/dormancy-report.md +1 -1
  31. package/dist/discovery/orphan-report.md +1 -1
  32. package/dist/discovery/packs.json +2 -2
  33. package/dist/discovery/trust-report.md +1 -1
  34. package/dist/discovery/workspaces.json +2 -2
  35. package/dist/hooks/dispatch.js +1081 -879
  36. package/dist/install/install.mjs +5 -0
  37. package/dist/mcp/registry-manifest.json +2 -2
  38. package/dist/mcp/server.json +2 -2
  39. package/dist/server/schemas/settings.js +3 -0
  40. package/docs/contracts/settings-classes.md +24 -4
  41. package/docs/guidelines/agent-infra/downstream-changes-mechanics.md +91 -0
  42. package/docs/guidelines/code-clarity.md +21 -0
  43. package/docs/guidelines/redundancy-taxonomy.md +180 -0
  44. package/package.json +6 -2
  45. package/src/config/agent-settings.template.yml +16 -0
  46. package/src/config/budgets.yml +19 -0
  47. package/src/config/ci-local-parity.yml +15 -0
  48. package/src/config/discovery/prompt-patterns.yml +56 -0
  49. package/src/config/estate-count-budget.json +8 -3
  50. package/src/config/gate-coverage.yml +191 -6
  51. package/src/config/gate-violation-baselines.json +3 -3
  52. package/src/config/pack-size-budget.json +39 -12
  53. package/src/config/preamble-payload-budget.json +97 -71
  54. package/src/config/routing-coverage-seed.json +29 -0
  55. package/src/config/suggestion-capture.json +28 -0
  56. package/src/config/surface-matrix.yml +21 -2
  57. package/src/config/web-launch-readiness.json +188 -0
  58. package/src/scripts/_lib/gate_baseline.ts +150 -1
  59. package/src/scripts/_lib/skill_estate.ts +115 -0
  60. package/src/scripts/ai_council/argument_exhaustion.ts +130 -0
  61. package/src/scripts/check_bundle_path_leakage.ts +162 -7
  62. package/src/scripts/check_command_examples.ts +557 -0
  63. package/src/scripts/check_estate_count.ts +122 -3
  64. package/src/scripts/check_preamble_payload_budget.ts +30 -3
  65. package/src/scripts/check_routing_coverage.ts +352 -0
  66. package/src/scripts/check_score_contract.ts +505 -0
  67. package/src/scripts/check_skill_admissions.ts +514 -0
  68. package/src/scripts/check_web_launch_readiness.ts +705 -0
  69. package/src/scripts/description_route_check.ts +494 -0
  70. package/src/scripts/hook_manifest.json +1 -1
  71. package/src/scripts/hook_manifest.yaml +29 -2
  72. package/src/scripts/hooks/concern_registry.ts +2 -0
  73. package/src/scripts/hooks/suggestion_capture_hook.ts +368 -0
  74. package/src/scripts/lint_budget_ownership.ts +54 -4
  75. package/src/scripts/lint_skill_trigger_corpus.ts +349 -0
  76. package/src/scripts/pack_install_smoke.ts +363 -0
  77. package/src/scripts/pr_conflict_census.ts +187 -0
  78. package/src/scripts/report_pack_delta.ts +204 -0
  79. package/src/scripts/select_analysis_mode.ts +112 -0
  80. package/src/scripts/sync_pr_branch.ts +40 -1
  81. package/src/server/schemas/settings.ts +5 -0
  82. package/dist/agent-src/skills/accessibility-auditor/evals/evals.json +0 -15
  83. package/dist/agent-src/skills/agent-security-review/evals/triggers.json +0 -52
  84. package/dist/agent-src/skills/ai-code-blindspots/evals/triggers.json +0 -52
  85. package/dist/agent-src/skills/alerting-doctrine/evals/triggers.json +0 -17
  86. package/dist/agent-src/skills/analysis-skill-router/evals/evals.json +0 -13
  87. package/dist/agent-src/skills/brand/evals/triggers.json +0 -17
  88. package/dist/agent-src/skills/brand-asset-generation/evals/triggers.json +0 -17
  89. package/dist/agent-src/skills/brand-audit/evals/triggers.json +0 -17
  90. package/dist/agent-src/skills/brand-identity/evals/triggers.json +0 -17
  91. package/dist/agent-src/skills/brand-strategy/evals/triggers.json +0 -17
  92. package/dist/agent-src/skills/brand-to-tokens/evals/triggers.json +0 -17
  93. package/dist/agent-src/skills/bug-analyzer/evals/evals.json +0 -13
  94. package/dist/agent-src/skills/canvas-design/evals/triggers.json +0 -17
  95. package/dist/agent-src/skills/character-consistency/evals/evals.json +0 -16
  96. package/dist/agent-src/skills/code-intelligence/evals/triggers.json +0 -52
  97. package/dist/agent-src/skills/code-review/evals/evals.json +0 -100
  98. package/dist/agent-src/skills/command-routing/evals/evals.json +0 -14
  99. package/dist/agent-src/skills/complexity-first-planning/evals/triggers.json +0 -17
  100. package/dist/agent-src/skills/contract-review/evals/triggers.json +0 -17
  101. package/dist/agent-src/skills/conventional-commits-writing/evals/evals.json +0 -15
  102. package/dist/agent-src/skills/corpus-grounding/evals/triggers.json +0 -17
  103. package/dist/agent-src/skills/dashboard-design/evals/evals.json +0 -16
  104. package/dist/agent-src/skills/dcf-modeling/evals/domain-truth.json +0 -29
  105. package/dist/agent-src/skills/dcf-modeling/evals/evals.json +0 -20
  106. package/dist/agent-src/skills/decision-record/evals/evals.json +0 -20
  107. package/dist/agent-src/skills/decision-record/evals/triggers.json +0 -87
  108. package/dist/agent-src/skills/design-intelligence/evals/evals.json +0 -14
  109. package/dist/agent-src/skills/design-intelligence/evals/triggers.json +0 -17
  110. package/dist/agent-src/skills/design-system-capture/evals/evals.json +0 -15
  111. package/dist/agent-src/skills/design-tokens/evals/triggers.json +0 -17
  112. package/dist/agent-src/skills/design-variations/evals/triggers.json +0 -17
  113. package/dist/agent-src/skills/developer-like-execution/evals/evals.json +0 -16
  114. package/dist/agent-src/skills/doc-coauthoring/evals/evals.json +0 -16
  115. package/dist/agent-src/skills/doc-coauthoring/evals/triggers.json +0 -17
  116. package/dist/agent-src/skills/docx-authoring/evals/triggers.json +0 -52
  117. package/dist/agent-src/skills/dpa-review/evals/triggers.json +0 -17
  118. package/dist/agent-src/skills/editorial-calendar/evals/domain-truth.json +0 -16
  119. package/dist/agent-src/skills/editorial-calendar/evals/evals.json +0 -16
  120. package/dist/agent-src/skills/eloquent/evals/triggers.json +0 -17
  121. package/dist/agent-src/skills/estimate-ticket/evals/evals.json +0 -16
  122. package/dist/agent-src/skills/estimate-ticket/evals/output-schema.yml +0 -20
  123. package/dist/agent-src/skills/estimate-ticket/evals/triggers.json +0 -19
  124. package/dist/agent-src/skills/evaluate-llm-feature/evals/triggers.json +0 -52
  125. package/dist/agent-src/skills/existing-ui-audit/evals/triggers.json +0 -47
  126. package/dist/agent-src/skills/experiment-loop/evals/output-schema.yml +0 -27
  127. package/dist/agent-src/skills/experiment-loop/evals/triggers.json +0 -17
  128. package/dist/agent-src/skills/fe-design/evals/triggers.json +0 -67
  129. package/dist/agent-src/skills/forecasting/evals/domain-truth.json +0 -29
  130. package/dist/agent-src/skills/forecasting/evals/evals.json +0 -20
  131. package/dist/agent-src/skills/forensics-report/evals/triggers.json +0 -17
  132. package/dist/agent-src/skills/frontend-render-security/evals/triggers.json +0 -52
  133. package/dist/agent-src/skills/fundraising-narrative/evals/domain-truth.json +0 -16
  134. package/dist/agent-src/skills/fundraising-narrative/evals/evals.json +0 -16
  135. package/dist/agent-src/skills/gated-reach/evals/triggers.json +0 -72
  136. package/dist/agent-src/skills/github-ci/evals/evals.json +0 -25
  137. package/dist/agent-src/skills/history-design/evals/triggers.json +0 -17
  138. package/dist/agent-src/skills/html-deck/evals/triggers.json +0 -17
  139. package/dist/agent-src/skills/humanizer/evals/strip_fixtures.json +0 -57
  140. package/dist/agent-src/skills/humanizer/evals/triggers.json +0 -17
  141. package/dist/agent-src/skills/iconography/evals/triggers.json +0 -17
  142. package/dist/agent-src/skills/image-analyser/evals/triggers.json +0 -17
  143. package/dist/agent-src/skills/image-creator/evals/triggers.json +0 -17
  144. package/dist/agent-src/skills/image-editing/evals/triggers.json +0 -17
  145. package/dist/agent-src/skills/image-generation/evals/triggers.json +0 -17
  146. package/dist/agent-src/skills/image-provider-routing/evals/triggers.json +0 -17
  147. package/dist/agent-src/skills/incident-commander/evals/domain-truth.json +0 -16
  148. package/dist/agent-src/skills/incident-commander/evals/evals.json +0 -20
  149. package/dist/agent-src/skills/js-library-packaging/evals/triggers.json +0 -17
  150. package/dist/agent-src/skills/judge-artifact-completeness/evals/triggers.json +0 -17
  151. package/dist/agent-src/skills/judge-injection-defense/evals/triggers.json +0 -17
  152. package/dist/agent-src/skills/judge-synthesis/evals/triggers.json +0 -17
  153. package/dist/agent-src/skills/launch-readiness/evals/evals.json +0 -21
  154. package/dist/agent-src/skills/learning-tutor/evals/triggers.json +0 -52
  155. package/dist/agent-src/skills/legal-intake-triage/evals/triggers.json +0 -17
  156. package/dist/agent-src/skills/legal-practice-profile/evals/triggers.json +0 -17
  157. package/dist/agent-src/skills/license-compliance-audit/evals/triggers.json +0 -52
  158. package/dist/agent-src/skills/license-compliance-borrow-check/evals/triggers.json +0 -52
  159. package/dist/agent-src/skills/license-compliance-credits/evals/triggers.json +0 -52
  160. package/dist/agent-src/skills/llm-provider-knowledge/evals/triggers.json +0 -20
  161. package/dist/agent-src/skills/logging-monitoring/evals/evals.json +0 -16
  162. package/dist/agent-src/skills/logo-generation/evals/triggers.json +0 -17
  163. package/dist/agent-src/skills/messaging-architecture/evals/domain-truth.json +0 -16
  164. package/dist/agent-src/skills/messaging-architecture/evals/evals.json +0 -16
  165. package/dist/agent-src/skills/monorepo-workspace/evals/triggers.json +0 -17
  166. package/dist/agent-src/skills/nda-triage/evals/triggers.json +0 -17
  167. package/dist/agent-src/skills/operational-readiness/evals/triggers.json +0 -17
  168. package/dist/agent-src/skills/pdf-tools/evals/triggers.json +0 -52
  169. package/dist/agent-src/skills/perf-feedback-craft/evals/evals.json +0 -16
  170. package/dist/agent-src/skills/persona-improvement/evals/triggers.json +0 -52
  171. package/dist/agent-src/skills/php-coder/evals/triggers.json +0 -17
  172. package/dist/agent-src/skills/prediction-pool-optimizer/evals/triggers.json +0 -21
  173. package/dist/agent-src/skills/prompt-engineering-image/evals/triggers.json +0 -17
  174. package/dist/agent-src/skills/prompt-engineering-patterns/evals/evals.json +0 -16
  175. package/dist/agent-src/skills/prompt-validator/evals/triggers.json +0 -17
  176. package/dist/agent-src/skills/quality-tools/evals/evals.json +0 -15
  177. package/dist/agent-src/skills/reasoning-orchestrator/evals/triggers.json +0 -17
  178. package/dist/agent-src/skills/refine-prompt/evals/evals.json +0 -16
  179. package/dist/agent-src/skills/refine-ticket/evals/evals.json +0 -16
  180. package/dist/agent-src/skills/refine-ticket/evals/output-schema.yml +0 -16
  181. package/dist/agent-src/skills/refine-ticket/evals/triggers.json +0 -17
  182. package/dist/agent-src/skills/release-comms/evals/evals.json +0 -16
  183. package/dist/agent-src/skills/rice-prioritization/evals/evals.json +0 -20
  184. package/dist/agent-src/skills/runway-cognition/evals/domain-truth.json +0 -29
  185. package/dist/agent-src/skills/runway-cognition/evals/evals.json +0 -20
  186. package/dist/agent-src/skills/scenario-modeling/evals/domain-truth.json +0 -29
  187. package/dist/agent-src/skills/scenario-modeling/evals/evals.json +0 -20
  188. package/dist/agent-src/skills/schema-review/evals/triggers.json +0 -17
  189. package/dist/agent-src/skills/screenshot-hygiene/evals/triggers.json +0 -52
  190. package/dist/agent-src/skills/security-maturity-assessment/evals/triggers.json +0 -52
  191. package/dist/agent-src/skills/server-hardening/evals/triggers.json +0 -17
  192. package/dist/agent-src/skills/skill-writing/evals/triggers.json +0 -17
  193. package/dist/agent-src/skills/spreadsheet-authoring/evals/triggers.json +0 -47
  194. package/dist/agent-src/skills/storybook-workshop/evals/triggers.json +0 -17
  195. package/dist/agent-src/skills/subagent-orchestration/evals/evals.json +0 -60
  196. package/dist/agent-src/skills/supply-chain-intake/evals/triggers.json +0 -52
  197. package/dist/agent-src/skills/systematic-debugging/evals/evals.json +0 -20
  198. package/dist/agent-src/skills/test-case-discovery/evals/triggers.json +0 -52
  199. package/dist/agent-src/skills/test-driven-development/evals/evals.json +0 -16
  200. package/dist/agent-src/skills/threat-modeling/evals/evals.json +0 -21
  201. package/dist/agent-src/skills/typography-system/evals/evals.json +0 -14
  202. package/dist/agent-src/skills/typography-system/evals/triggers.json +0 -17
  203. package/dist/agent-src/skills/unit-economics-modeling/evals/domain-truth.json +0 -40
  204. package/dist/agent-src/skills/unit-economics-modeling/evals/evals.json +0 -24
  205. package/dist/agent-src/skills/verify-completion-evidence/evals/evals.json +0 -16
  206. package/dist/agent-src/skills/verify-repair-loop/evals/output-schema.yml +0 -20
  207. package/dist/agent-src/skills/verify-repair-loop/evals/triggers.json +0 -17
  208. package/dist/agent-src/skills/vision-articulation/evals/evals.json +0 -16
  209. package/dist/agent-src/skills/voice-and-tone-design/evals/evals.json +0 -16
  210. package/dist/agent-src/skills/wireframe/evals/triggers.json +0 -17
  211. package/dist/agent-src/skills/workspace-link/evals/triggers.json +0 -17
  212. package/dist/agent-src/skills/worktree-lifecycle/evals/evals.json +0 -38
  213. package/dist/agent-src/skills/worktree-lifecycle/evals/triggers.json +0 -52
  214. package/dist/cli/agent-config.js.map +0 -1
  215. package/dist/cli/bash/runBash.js.map +0 -1
  216. package/dist/cli/commands/commands.js.map +0 -1
  217. package/dist/cli/commands/config.js.map +0 -1
  218. package/dist/cli/commands/doctorShell.js.map +0 -1
  219. package/dist/cli/commands/mcpServer.js.map +0 -1
  220. package/dist/cli/commands/packs.js.map +0 -1
  221. package/dist/cli/commands/profiles.js.map +0 -1
  222. package/dist/cli/commands/recordTriggerEval.js.map +0 -1
  223. package/dist/cli/commands/rtkDetect.js.map +0 -1
  224. package/dist/cli/commands/settings.js.map +0 -1
  225. package/dist/cli/commands/uiAudit.js.map +0 -1
  226. package/dist/cli/commands/uiRender.js.map +0 -1
  227. package/dist/cli/commands/uiServe.js.map +0 -1
  228. package/dist/cli/commands/versions.js.map +0 -1
  229. package/dist/cli/commands/workspaces.js.map +0 -1
  230. package/dist/cli/configRoot.js.map +0 -1
  231. package/dist/cli/discovery/loadManifest.js.map +0 -1
  232. package/dist/cli/firstRunNotice.js.map +0 -1
  233. package/dist/cli/help.js.map +0 -1
  234. package/dist/cli/initRouting.js.map +0 -1
  235. package/dist/cli/log/logger.js.map +0 -1
  236. package/dist/cli/main.js.map +0 -1
  237. package/dist/cli/mcp/content.js.map +0 -1
  238. package/dist/cli/mcp/dispatch.js.map +0 -1
  239. package/dist/cli/mcp/stdio.js.map +0 -1
  240. package/dist/cli/paths.js.map +0 -1
  241. package/dist/cli/python/knowledge_ingest.js.map +0 -1
  242. package/dist/cli/python/workspace_analytics.js.map +0 -1
  243. package/dist/cli/python/workspace_crypto.js.map +0 -1
  244. package/dist/cli/python/workspace_documents.js.map +0 -1
  245. package/dist/cli/python/workspace_drive.js.map +0 -1
  246. package/dist/cli/python/workspace_drive_health.js.map +0 -1
  247. package/dist/cli/python/workspace_explain.js.map +0 -1
  248. package/dist/cli/python/workspace_hosts.js.map +0 -1
  249. package/dist/cli/python/workspace_inbox.js.map +0 -1
  250. package/dist/cli/python/workspace_render.js.map +0 -1
  251. package/dist/cli/python/workspace_roles.js.map +0 -1
  252. package/dist/cli/python/workspace_secrets.js.map +0 -1
  253. package/dist/cli/python/workspace_sessions.js.map +0 -1
  254. package/dist/cli/python/workspace_skills.js.map +0 -1
  255. package/dist/cli/registry.js.map +0 -1
  256. package/dist/install/agentSwitchDetection.js.map +0 -1
  257. package/dist/install/agentSwitchProfile.js.map +0 -1
  258. package/dist/install/atomic.js.map +0 -1
  259. package/dist/install/claudePathsPlan.js.map +0 -1
  260. package/dist/install/conflict.js.map +0 -1
  261. package/dist/install/detect.js.map +0 -1
  262. package/dist/install/emit_host_rules_cli.js.map +0 -1
  263. package/dist/install/globalRuleLayers.js.map +0 -1
  264. package/dist/install/hostLayerFingerprint.js.map +0 -1
  265. package/dist/install/partitionEligibility.js.map +0 -1
  266. package/dist/install/paths.js.map +0 -1
  267. package/dist/install/plan.js.map +0 -1
  268. package/dist/install/preflight.js.map +0 -1
  269. package/dist/install/rtkDetection.js.map +0 -1
  270. package/dist/install/ruleInScope.js.map +0 -1
  271. package/dist/install/ruleLayerPartition.js.map +0 -1
  272. package/dist/install/rule_scope.js.map +0 -1
  273. package/dist/install/rule_scope_cli.js.map +0 -1
  274. package/dist/install/selectedTools.js.map +0 -1
  275. package/dist/install/toolDetection.js.map +0 -1
  276. package/dist/install/txlog.js.map +0 -1
  277. package/dist/install/types.js.map +0 -1
  278. package/dist/install/wizard-plan.js.map +0 -1
  279. package/dist/install/wizardDismissals.js.map +0 -1
  280. package/dist/scripts/_lib/agent_settings.js.map +0 -1
  281. package/dist/scripts/_lib/agent_src.js.map +0 -1
  282. package/dist/scripts/_lib/claude_builtin_names.js.map +0 -1
  283. package/dist/scripts/_lib/claude_plugin.js.map +0 -1
  284. package/dist/scripts/_lib/claude_settings_hooks.js.map +0 -1
  285. package/dist/scripts/_lib/compile_time_toggles.js.map +0 -1
  286. package/dist/scripts/_lib/environment_detector.js.map +0 -1
  287. package/dist/scripts/_lib/host_launch.js.map +0 -1
  288. package/dist/scripts/_lib/install_layout.js.map +0 -1
  289. package/dist/scripts/_lib/installed_lock.js.map +0 -1
  290. package/dist/scripts/_lib/lean_projection_mode.js.map +0 -1
  291. package/dist/scripts/_lib/model_tier.js.map +0 -1
  292. package/dist/scripts/_lib/prune_empty_dirs.js.map +0 -1
  293. package/dist/scripts/_lib/scoped_projection.js.map +0 -1
  294. package/dist/scripts/_lib/script_output.js.map +0 -1
  295. package/dist/scripts/_lib/spawn_env.js.map +0 -1
  296. package/dist/scripts/_lib/surface_tiers.js.map +0 -1
  297. package/dist/scripts/_lib/token_count.js.map +0 -1
  298. package/dist/scripts/_lib/trigger_eval_floors.js.map +0 -1
  299. package/dist/scripts/_lib/ui_surface.js.map +0 -1
  300. package/dist/scripts/_lib/user_global_paths.js.map +0 -1
  301. package/dist/scripts/ai_council/modes.js.map +0 -1
  302. package/dist/scripts/ai_council/transport_resolver.js.map +0 -1
  303. package/dist/scripts/condense.js.map +0 -1
  304. package/dist/scripts/project_thin_rules.js.map +0 -1
  305. package/dist/server/app.js.map +0 -1
  306. package/dist/server/io/atomicMultiWrite.js.map +0 -1
  307. package/dist/server/io/atomicWrite.js.map +0 -1
  308. package/dist/server/io/sharedWriteCheck.js.map +0 -1
  309. package/dist/server/io/substituteTemplate.js.map +0 -1
  310. package/dist/server/io/yamlIO.js.map +0 -1
  311. package/dist/server/nicknameResolver.js.map +0 -1
  312. package/dist/server/port.js.map +0 -1
  313. package/dist/server/routes/discovery.js.map +0 -1
  314. package/dist/server/routes/install.js.map +0 -1
  315. package/dist/server/routes/ping.js.map +0 -1
  316. package/dist/server/routes/schema.js.map +0 -1
  317. package/dist/server/routes/settings.js.map +0 -1
  318. package/dist/server/routes/settingsChanges.js.map +0 -1
  319. package/dist/server/routes/userMd.js.map +0 -1
  320. package/dist/server/routes/wizard.js.map +0 -1
  321. package/dist/server/routes/workspace.js.map +0 -1
  322. package/dist/server/schemas/settings.js.map +0 -1
  323. package/dist/server/serverInfo.js.map +0 -1
  324. package/dist/server/token.js.map +0 -1
  325. package/dist/server/writeRoot.js.map +0 -1
  326. package/dist/shared/capabilities.js.map +0 -1
  327. package/dist/shared/interactiveContext.js.map +0 -1
  328. package/dist/shared/nicknamePrefill.js.map +0 -1
  329. package/dist/shared/settingsAsks.js.map +0 -1
  330. package/dist/shared/settingsCarveOut.js.map +0 -1
  331. package/dist/shared/settingsClasses.js.map +0 -1
  332. package/dist/shared/settingsConsent.js.map +0 -1
  333. package/dist/shared/settingsSurface.js.map +0 -1
  334. package/dist/shared/skillRanking.js.map +0 -1
  335. package/dist/shared/userMd/formAdapter.js.map +0 -1
  336. package/dist/shared/userMd/schema.js.map +0 -1
  337. package/dist/shared/userMd/utils.js.map +0 -1
  338. package/dist/ui/assets/index-Cxgy63kz.js.map +0 -1
  339. package/src/scripts/_lib/adr_frontmatter.test.ts +0 -365
  340. package/src/scripts/_lib/adversarial_council_gate.test.ts +0 -112
  341. package/src/scripts/_lib/adversarial_reconcile.test.ts +0 -197
  342. package/src/scripts/_lib/auto_dispatch.corpus.test.ts +0 -150
  343. package/src/scripts/_lib/secret_detector.test.ts +0 -108
  344. package/src/scripts/_lib/secret_detector_adversarial.test.ts +0 -116
  345. package/src/scripts/_lib/ui_authority.test.ts +0 -251
  346. package/src/scripts/_lib/untrusted_content.test.ts +0 -175
  347. package/src/scripts/check_secret_leak.test.ts +0 -90
  348. package/src/scripts/design_slop_rules.test.ts +0 -246
  349. package/src/scripts/self_review_gate.test.ts +0 -296
  350. package/src/shared/capabilities.test.ts +0 -43
@@ -0,0 +1,705 @@
1
+ #!/usr/bin/env tsx
2
+ /**
3
+ * `check_web_launch_readiness` — the web-surface pre-ship audit, as a COMMAND.
4
+ *
5
+ * NOT a skill, and the container is a recorded decision rather than a default.
6
+ * AI council 2/2 on 2026-08-25 (`road-to-web-launch-readiness`
7
+ * § `b-estate-decision-web-launch`): the G0 evidence establishes a COVERAGE gap,
8
+ * not a SKILL-SHAPED one. A site-type-conditional config, deterministic checks,
9
+ * tiers and a hard pass/fail gate are *"the signature of a linter, not a skill"*,
10
+ * and `production-validator` carries unscoped `Bash`, so it can invoke this
11
+ * directly instead of depending on skill activation — which demonstrably is NOT
12
+ * firing for web-facing deploys, since `launch-readiness` already exists and
13
+ * scores 0 of 8 on these axes.
14
+ *
15
+ * CONDITIONAL, NOT FLAT. Every check declares `applies_to`, and a check that does
16
+ * not apply is reported as **skipped with the site type as the reason** — never
17
+ * silently dropped and never as a finding. A reader must be able to tell "this
18
+ * does not apply to you" from "this passed", which a flat audit cannot express.
19
+ *
20
+ * DEFAULT-OFF until the pre-registered benchmark returns positive
21
+ * (`claim:web-launch-readiness-finds-more`, status `unbacked`). Absent the
22
+ * opt-in, this exits 0 and says why rather than auditing.
23
+ *
24
+ * Exit codes: 0 = no critical/high finding (or not enabled) · 1 = a blocking
25
+ * finding · 2 = usage or a dead scan scope.
26
+ */
27
+ import * as fs from 'node:fs';
28
+ import * as path from 'node:path';
29
+ import process from 'node:process';
30
+ import { fileURLToPath } from 'node:url';
31
+
32
+ import { DeadScopeError, reportScanned } from './_lib/scan_scope.js';
33
+
34
+ const HERE = fileURLToPath(import.meta.url);
35
+ const REPO_ROOT = path.resolve(path.dirname(HERE), '..', '..');
36
+ export const CONFIG_REL = 'src/config/web-launch-readiness.json';
37
+
38
+ export type SiteType = 'local-business' | 'marketing-site' | 'saas-app' | 'docs' | 'internal-tool';
39
+ export type Tier = 'critical' | 'high' | 'medium' | 'situational';
40
+ /**
41
+ * The region axis (2.3), and it is deliberately NOT a second `applies_to`.
42
+ *
43
+ * `applies_to` decides WHETHER a check applies; a region decides HOW SEVERELY.
44
+ * A legal page is not more or less *applicable* in Germany — it is more or less
45
+ * *optional*, because TMG 5 and DSGVO Art. 13 make it owed rather than advisable.
46
+ * Modelling it as a tier escalation keeps one check with one implementation and
47
+ * puts the jurisdiction where it belongs: on the consequence.
48
+ */
49
+ export type Region = 'de' | 'eu' | 'us' | 'unspecified';
50
+
51
+ export interface CheckDef {
52
+ id: string;
53
+ title: string;
54
+ applies_to: SiteType[];
55
+ tier: Tier;
56
+ why: string;
57
+ remediation: string;
58
+ verification: string;
59
+ }
60
+
61
+ export interface Finding {
62
+ check: string;
63
+ tier: Tier;
64
+ /** `path:line` — the step's own verify line requires a location, not a name. */
65
+ location: string;
66
+ evidence: string;
67
+ remediation: string;
68
+ verification: string;
69
+ }
70
+
71
+ export interface Skipped {
72
+ check: string;
73
+ tier: Tier;
74
+ /** The SITE TYPE, verbatim — 2.3 requires the type to be the stated reason. */
75
+ reason: string;
76
+ }
77
+
78
+ /** A tier the region axis moved, and why. Reported so the axis is visible. */
79
+ export interface Escalated {
80
+ check: string;
81
+ from: Tier;
82
+ to: Tier;
83
+ why: string;
84
+ }
85
+
86
+ export interface Report {
87
+ site_type: SiteType;
88
+ region: Region;
89
+ enabled: boolean;
90
+ findings: Finding[];
91
+ skipped: Skipped[];
92
+ /** Checks that applied and found nothing. */
93
+ passed: string[];
94
+ /** Applicable checks with no implementation yet — never counted as passed. */
95
+ unimplemented: string[];
96
+ /** Tiers the region axis raised. Empty on `unspecified`, by construction. */
97
+ escalated: Escalated[];
98
+ scanned_files: number;
99
+ }
100
+
101
+ export interface Escalation {
102
+ check: string;
103
+ region: Region;
104
+ to: Tier;
105
+ why: string;
106
+ }
107
+
108
+ export function loadConfig(root = REPO_ROOT): {
109
+ checks: CheckDef[];
110
+ siteTypes: SiteType[];
111
+ regions: Region[];
112
+ escalations: Escalation[];
113
+ } {
114
+ const abs = path.join(root, CONFIG_REL);
115
+ let raw: string;
116
+ try {
117
+ raw = fs.readFileSync(abs, 'utf-8');
118
+ } catch {
119
+ throw new DeadScopeError(
120
+ 'check_web_launch_readiness',
121
+ `${CONFIG_REL} is missing — the config IS this command's corpus, and an absent ` +
122
+ 'one cannot be read as "no checks to run".',
123
+ );
124
+ }
125
+ const doc = JSON.parse(raw) as {
126
+ checks?: CheckDef[];
127
+ site_types?: { values?: SiteType[] };
128
+ regions?: { values?: Region[]; escalations?: Escalation[] };
129
+ };
130
+ const checks = doc.checks ?? [];
131
+ const siteTypes = doc.site_types?.values ?? [];
132
+ if (checks.length === 0 || siteTypes.length === 0) {
133
+ throw new DeadScopeError(
134
+ 'check_web_launch_readiness',
135
+ `${CONFIG_REL} carries no checks or no site types — an empty corpus checks nothing.`,
136
+ );
137
+ }
138
+ const regions = doc.regions?.values ?? [];
139
+ const escalations = doc.regions?.escalations ?? [];
140
+ if (regions.length === 0) {
141
+ throw new DeadScopeError(
142
+ 'check_web_launch_readiness',
143
+ `${CONFIG_REL} carries no region axis — a DE site would silently get the ` +
144
+ 'situational tier for a legally owed page.',
145
+ );
146
+ }
147
+ return { checks, siteTypes, regions, escalations };
148
+ }
149
+
150
+ /**
151
+ * Apply the region escalation to a check's tier.
152
+ *
153
+ * Returns the escalated tier and the reason, or the original tier and null. The
154
+ * reason travels with the tier because a reader seeing `required-legal-pages`
155
+ * as CRITICAL on one run and SITUATIONAL on another needs the axis that moved
156
+ * it, not just the outcome.
157
+ */
158
+ export function tierFor(
159
+ c: CheckDef,
160
+ region: Region,
161
+ escalations: readonly Escalation[],
162
+ ): { tier: Tier; escalatedBy: string | null } {
163
+ const e = escalations.find((x) => x.check === c.id && x.region === region);
164
+ return e === undefined ? { tier: c.tier, escalatedBy: null } : { tier: e.to, escalatedBy: e.why };
165
+ }
166
+
167
+ /** `web_launch_readiness.enabled: true` in `.agent-settings.yml` — default OFF. */
168
+ export function enabled(root: string): boolean {
169
+ let raw: string;
170
+ try {
171
+ raw = fs.readFileSync(path.join(root, '.agent-settings.yml'), 'utf-8');
172
+ } catch {
173
+ return false;
174
+ }
175
+ let inSection = false;
176
+ for (const line of raw.split('\n')) {
177
+ if (/^web_launch_readiness:\s*$/.test(line)) {
178
+ inSection = true;
179
+ continue;
180
+ }
181
+ if (/^\S/.test(line)) {
182
+ inSection = false;
183
+ continue;
184
+ }
185
+ if (inSection && /^\s{2}enabled:\s*true\s*$/.test(line)) return true;
186
+ }
187
+ return false;
188
+ }
189
+
190
+ /* ── the one implemented check: staging indexability ───────────────────────── */
191
+
192
+ const NOINDEX_RE = /<meta[^>]+name\s*=\s*["']robots["'][^>]*content\s*=\s*["'][^"']*noindex/i;
193
+ const XROBOTS_RE = /x-robots-tag[^\n]*noindex/i;
194
+ /** `Disallow: /` with nothing after it — a blanket block, not a path rule. */
195
+ const BLANKET_DISALLOW_RE = /^\s*Disallow:\s*\/\s*$/im;
196
+
197
+ export interface Located {
198
+ file: string;
199
+ line: number;
200
+ text: string;
201
+ }
202
+
203
+ /**
204
+ * Walk a build directory for indexability leftovers.
205
+ *
206
+ * Returns a LOCATION per hit, because the step's verify line asks for a
207
+ * `file:line` and a finding without one cannot be acted on.
208
+ */
209
+ export function scanIndexability(buildDir: string): { hits: Located[]; files: number } {
210
+ const hits: Located[] = [];
211
+ let files = 0;
212
+ const walk = (dir: string): void => {
213
+ let entries: fs.Dirent[];
214
+ try {
215
+ entries = fs.readdirSync(dir, { withFileTypes: true });
216
+ } catch {
217
+ return;
218
+ }
219
+ for (const e of entries.sort((a, b) => a.name.localeCompare(b.name))) {
220
+ const p = path.join(dir, e.name);
221
+ if (e.isDirectory()) {
222
+ if (e.name === 'node_modules' || e.name === '.git') continue;
223
+ walk(p);
224
+ continue;
225
+ }
226
+ if (!/\.(html?|txt|xml|js|jsx|ts|tsx|vue|svelte|astro)$/i.test(e.name)) continue;
227
+ files += 1;
228
+ let text: string;
229
+ try {
230
+ text = fs.readFileSync(p, 'utf-8');
231
+ } catch {
232
+ continue;
233
+ }
234
+ const isRobots = path.basename(p).toLowerCase() === 'robots.txt';
235
+ text.split('\n').forEach((line, i) => {
236
+ const rel = path.relative(buildDir, p);
237
+ if (NOINDEX_RE.test(line) || XROBOTS_RE.test(line)) {
238
+ hits.push({ file: rel, line: i + 1, text: line.trim().slice(0, 160) });
239
+ } else if (isRobots && BLANKET_DISALLOW_RE.test(line)) {
240
+ hits.push({ file: rel, line: i + 1, text: line.trim().slice(0, 160) });
241
+ }
242
+ });
243
+ }
244
+ };
245
+ walk(buildDir);
246
+ return { hits, files };
247
+ }
248
+
249
+
250
+ /* ── the remaining checks (2.2 and 2.3) ─────────────────────────────────────── */
251
+
252
+ /**
253
+ * Every scannable file in a build directory, read once.
254
+ *
255
+ * The single walk replaces the per-check walk the one-check version could get
256
+ * away with: eight checks each walking the tree is eight times the IO for the
257
+ * same bytes, and — worse — eight chances for the walkers to disagree about
258
+ * what counts as a file.
259
+ */
260
+ export interface SourceFile {
261
+ rel: string;
262
+ base: string;
263
+ text: string;
264
+ lines: string[];
265
+ }
266
+
267
+ const SCANNABLE_RE = /\.(html?|txt|xml|js|jsx|ts|tsx|vue|svelte|astro|md)$/i;
268
+
269
+ export function readTree(buildDir: string): SourceFile[] {
270
+ const out: SourceFile[] = [];
271
+ const walk = (dir: string): void => {
272
+ let entries: fs.Dirent[];
273
+ try {
274
+ entries = fs.readdirSync(dir, { withFileTypes: true });
275
+ } catch {
276
+ return;
277
+ }
278
+ for (const e of entries.sort((a, b) => a.name.localeCompare(b.name))) {
279
+ const abs = path.join(dir, e.name);
280
+ if (e.isDirectory()) {
281
+ if (e.name === 'node_modules' || e.name === '.git') continue;
282
+ walk(abs);
283
+ continue;
284
+ }
285
+ if (!SCANNABLE_RE.test(e.name)) continue;
286
+ let text: string;
287
+ try {
288
+ text = fs.readFileSync(abs, 'utf-8');
289
+ } catch {
290
+ continue;
291
+ }
292
+ out.push({
293
+ rel: path.relative(buildDir, abs),
294
+ base: e.name.toLowerCase(),
295
+ text,
296
+ lines: text.split('\n'),
297
+ });
298
+ }
299
+ };
300
+ walk(buildDir);
301
+ return out;
302
+ }
303
+
304
+ /** An HTML page, as opposed to a robots.txt or a source module. */
305
+ function isPage(f: SourceFile): boolean {
306
+ return /\.html?$/i.test(f.base);
307
+ }
308
+
309
+ /**
310
+ * A check's implementation: files in, located hits out.
311
+ *
312
+ * Returning an EMPTY array means "applied and found nothing" — which is a pass.
313
+ * A check that cannot decide must not return an empty array; it must not be in
314
+ * `IMPLEMENTED` at all, so it reports as unimplemented instead. That is the one
315
+ * rule keeping the silent-green defect out of this table.
316
+ */
317
+ type Impl = (files: readonly SourceFile[]) => Located[];
318
+
319
+ /** `http://` on a host we serve, excluding the XML/DTD namespace URLs. */
320
+ const INSECURE_ASSET_RE =
321
+ /(?:src|href|action)\s*=\s*["']http:\/\/(?!localhost|127\.0\.0\.1|schemas?\.|www\.w3\.org)/i;
322
+
323
+ const httpsEnforcement: Impl = (files) => {
324
+ const hits: Located[] = [];
325
+ for (const f of files) {
326
+ if (!isPage(f)) continue;
327
+ f.lines.forEach((line, i) => {
328
+ if (INSECURE_ASSET_RE.test(line)) {
329
+ hits.push({ file: f.rel, line: i + 1, text: line.trim().slice(0, 160) });
330
+ }
331
+ });
332
+ }
333
+ return hits;
334
+ };
335
+
336
+ /**
337
+ * A custom 404 page exists somewhere in the build.
338
+ *
339
+ * A whole-tree question, not a per-file one, so the hit it reports is located at
340
+ * the build root rather than at a file — an absence has no line number, and
341
+ * inventing one would be worse than admitting the shape of the finding.
342
+ */
343
+ const customErrorRoute: Impl = (files) => {
344
+ const found = files.some((f) => /^(404|not-found)(\.html?|\/index\.html?)?$/i.test(f.rel.replace(/\\/g, '/')) || /(^|\/)404\.html?$/i.test(f.rel));
345
+ return found ? [] : [{ file: '.', line: 0, text: 'no 404.html or not-found page in the build output' }];
346
+ };
347
+
348
+ const TITLE_RE = /<title[^>]*>\s*([^<]{1,200}?)\s*<\/title>/i;
349
+ const DESC_RE = /<meta[^>]+name\s*=\s*["']description["'][^>]*content\s*=\s*["']\s*([^"']{1,400}?)\s*["']/i;
350
+
351
+ /**
352
+ * Per-route title and description, and the DUPLICATE case counts.
353
+ *
354
+ * The obvious implementation checks presence. Presence is the easy half: a
355
+ * layout that puts one hard-coded title on every route passes a presence check
356
+ * and is exactly the defect the step names ("per-route"). So a title shared by
357
+ * more than one page is a hit, with both locations reported.
358
+ */
359
+ const perRouteMetadata: Impl = (files) => {
360
+ const hits: Located[] = [];
361
+ const titles = new Map<string, string[]>();
362
+ for (const f of files) {
363
+ if (!isPage(f)) continue;
364
+ const tm = TITLE_RE.exec(f.text);
365
+ if (tm === null) {
366
+ hits.push({ file: f.rel, line: lineOf(f, '<title'), text: 'no <title> on this page' });
367
+ } else {
368
+ const key = (tm[1] ?? '').toLowerCase();
369
+ titles.set(key, [...(titles.get(key) ?? []), f.rel]);
370
+ }
371
+ if (!DESC_RE.test(f.text)) {
372
+ hits.push({
373
+ file: f.rel,
374
+ line: lineOf(f, 'name="description"'),
375
+ text: 'no <meta name="description"> on this page',
376
+ });
377
+ }
378
+ }
379
+ for (const [title, where] of titles) {
380
+ if (where.length < 2) continue;
381
+ hits.push({
382
+ file: where[0] ?? '.',
383
+ line: 0,
384
+ text: `title "${title}" is shared by ${String(where.length)} pages (${where.join(', ')}) — present, but not per-route`,
385
+ });
386
+ }
387
+ return hits;
388
+ };
389
+
390
+ /** Content images without alt. `alt=""` is DECORATIVE and deliberately valid. */
391
+ const IMG_RE = /<img\b[^>]*>/gi;
392
+ const imageAlternativeText: Impl = (files) => {
393
+ const hits: Located[] = [];
394
+ for (const f of files) {
395
+ if (!isPage(f)) continue;
396
+ f.lines.forEach((line, i) => {
397
+ for (const tag of line.match(IMG_RE) ?? []) {
398
+ // `alt=""` is the ARIA-correct marking for a decorative image
399
+ // and must NOT be a finding: flagging it would push authors to
400
+ // write filler alt text, which is worse for a screen reader
401
+ // than the empty string that tells it to skip.
402
+ if (/\balt\s*=/i.test(tag)) continue;
403
+ hits.push({ file: f.rel, line: i + 1, text: tag.slice(0, 160) });
404
+ }
405
+ });
406
+ }
407
+ return hits;
408
+ };
409
+
410
+ const documentHeadBasics: Impl = (files) => {
411
+ const hits: Located[] = [];
412
+ for (const f of files) {
413
+ if (!isPage(f)) continue;
414
+ if (!/<html[^>]+\blang\s*=\s*["'][a-z]/i.test(f.text)) {
415
+ hits.push({ file: f.rel, line: lineOf(f, '<html'), text: 'no lang on <html>' });
416
+ }
417
+ if (!/<meta[^>]+charset\s*=/i.test(f.text)) {
418
+ hits.push({ file: f.rel, line: lineOf(f, '<head'), text: 'no <meta charset>' });
419
+ }
420
+ if (!/<meta[^>]+name\s*=\s*["']viewport["']/i.test(f.text)) {
421
+ hits.push({ file: f.rel, line: lineOf(f, '<head'), text: 'no <meta name="viewport">' });
422
+ }
423
+ }
424
+ return hits;
425
+ };
426
+
427
+ /**
428
+ * Canonical and sitemap agree with each other.
429
+ *
430
+ * Two failure shapes, and the second is the one a presence check misses: a
431
+ * canonical pointing at a host the sitemap never mentions means one of the two
432
+ * was copied from another project, and the page that ends up indexed is not the
433
+ * page anyone chose.
434
+ */
435
+ const canonicalAndSitemap: Impl = (files) => {
436
+ const hits: Located[] = [];
437
+ const sitemap = files.find((f) => f.base === 'sitemap.xml');
438
+ for (const f of files) {
439
+ if (!isPage(f)) continue;
440
+ const m = /<link[^>]+rel\s*=\s*["']canonical["'][^>]*href\s*=\s*["']([^"']+)["']/i.exec(f.text);
441
+ if (m === null) {
442
+ hits.push({ file: f.rel, line: lineOf(f, '<head'), text: 'no rel="canonical"' });
443
+ continue;
444
+ }
445
+ const href = m[1] ?? '';
446
+ const host = /^https?:\/\/([^/]+)/i.exec(href)?.[1];
447
+ if (sitemap !== undefined && host !== undefined && !sitemap.text.includes(host)) {
448
+ hits.push({
449
+ file: f.rel,
450
+ line: lineOf(f, 'rel="canonical"'),
451
+ text: `canonical host ${host} appears nowhere in sitemap.xml`,
452
+ });
453
+ }
454
+ }
455
+ return hits;
456
+ };
457
+
458
+ const LEGAL_DE = [/impressum/i, /datenschutz/i];
459
+ const LEGAL_GENERIC = [/privacy/i, /(terms|imprint|legal)/i];
460
+
461
+ /**
462
+ * The legally owed pages exist.
463
+ *
464
+ * Matched on the PATH, never on page text: a link to `/impressum` from a footer
465
+ * is not an Impressum, and a check that accepted the link would pass every site
466
+ * that links to a page it never built.
467
+ */
468
+ const requiredLegalPages: Impl = (files) => {
469
+ const paths = files.map((f) => f.rel.replace(/\\/g, '/'));
470
+ const missing: string[] = [];
471
+ const has = (res: RegExp[]): boolean => res.every((re) => paths.some((p) => re.test(p)));
472
+ if (!has(LEGAL_DE) && !has(LEGAL_GENERIC)) {
473
+ if (!paths.some((p) => /impressum|imprint|legal/i.test(p))) missing.push('imprint/Impressum');
474
+ if (!paths.some((p) => /datenschutz|privacy/i.test(p))) missing.push('privacy/Datenschutz');
475
+ }
476
+ return missing.length === 0
477
+ ? []
478
+ : [{ file: '.', line: 0, text: `no page found for: ${missing.join(', ')}` }];
479
+ };
480
+
481
+ /**
482
+ * Analytics loads only behind consent.
483
+ *
484
+ * The finding is analytics present with NO consent mechanism anywhere in the
485
+ * build — never "analytics present", which would flag every site that does
486
+ * consent correctly. The gate cannot see the wiring order, and says so in the
487
+ * evidence rather than implying it checked.
488
+ */
489
+ const ANALYTICS_RE = /(googletagmanager\.com|google-analytics\.com|gtag\(|plausible\.io|matomo|segment\.com|hotjar)/i;
490
+ const CONSENT_RE = /(consent|cookiebanner|cookie-banner|cookieconsent|klaro|usercentrics|borlabs|osano)/i;
491
+ const analyticsAndConsent: Impl = (files) => {
492
+ const hits: Located[] = [];
493
+ // Prose is excluded from BOTH sides, and the fixture is why: a `.md` file
494
+ // in the build tree describing the consent banner matched CONSENT_RE, so
495
+ // the check reported a pass on a fixture seeded to fire it. Documentation
496
+ // saying a thing exists is not the thing existing — and the same argument
497
+ // runs the other way, so an analytics name mentioned in a README must not
498
+ // count as analytics either.
499
+ const code = files.filter((f) => !/\.md$/i.test(f.base));
500
+ const anyConsent = code.some((f) => CONSENT_RE.test(f.text));
501
+ if (anyConsent) return hits;
502
+ for (const f of code) {
503
+ f.lines.forEach((line, i) => {
504
+ if (!ANALYTICS_RE.test(line)) return;
505
+ hits.push({
506
+ file: f.rel,
507
+ line: i + 1,
508
+ text:
509
+ `${line.trim().slice(0, 120)} — analytics present and no consent mechanism ` +
510
+ 'found anywhere in the build. Load order is NOT checked here.',
511
+ });
512
+ });
513
+ }
514
+ return hits;
515
+ };
516
+
517
+ /** First line containing `needle`, 1-indexed; 1 when absent (an absence has no line). */
518
+ function lineOf(f: SourceFile, needle: string): number {
519
+ const i = f.lines.findIndex((l) => l.toLowerCase().includes(needle.toLowerCase()));
520
+ return i < 0 ? 1 : i + 1;
521
+ }
522
+
523
+ export const IMPLS: Readonly<Record<string, Impl>> = {
524
+ 'https-enforcement': httpsEnforcement,
525
+ 'custom-error-route': customErrorRoute,
526
+ 'per-route-metadata': perRouteMetadata,
527
+ 'image-alternative-text': imageAlternativeText,
528
+ 'document-head-basics': documentHeadBasics,
529
+ 'canonical-and-sitemap-coherence': canonicalAndSitemap,
530
+ 'required-legal-pages': requiredLegalPages,
531
+ 'analytics-and-consent-wiring': analyticsAndConsent,
532
+ };
533
+
534
+ /** Checks with a real implementation. Everything else reports UNIMPLEMENTED. */
535
+ export const IMPLEMENTED: readonly string[] = [
536
+ 'staging-noindex-leftover',
537
+ ...Object.keys(IMPLS),
538
+ ];
539
+
540
+ export function audit(
541
+ buildDir: string,
542
+ siteType: SiteType,
543
+ root = REPO_ROOT,
544
+ region: Region = 'unspecified',
545
+ ): Report {
546
+ const { checks, siteTypes, regions, escalations } = loadConfig(root);
547
+ if (!siteTypes.includes(siteType)) {
548
+ throw new DeadScopeError(
549
+ 'check_web_launch_readiness',
550
+ `unknown site type "${siteType}" — registered: ${siteTypes.join(', ')}`,
551
+ );
552
+ }
553
+ if (!regions.includes(region)) {
554
+ throw new DeadScopeError(
555
+ 'check_web_launch_readiness',
556
+ `unknown region "${region}" — registered: ${regions.join(', ')}`,
557
+ );
558
+ }
559
+ const findings: Finding[] = [];
560
+ const skipped: Skipped[] = [];
561
+ const passed: string[] = [];
562
+ const unimplemented: string[] = [];
563
+ const escalated: Escalated[] = [];
564
+ // ONE walk for every check. Eight walkers over the same bytes is eight
565
+ // times the IO and eight chances to disagree about what counts as a file.
566
+ const files = readTree(buildDir);
567
+
568
+ for (const c of checks) {
569
+ const { tier, escalatedBy } = tierFor(c, region, escalations);
570
+ if (escalatedBy !== null) {
571
+ escalated.push({ check: c.id, from: c.tier, to: tier, why: escalatedBy });
572
+ }
573
+ if (!c.applies_to.includes(siteType)) {
574
+ // The site type IS the reason, verbatim. 2.3 requires exactly this.
575
+ skipped.push({ check: c.id, tier, reason: `site type is ${siteType}` });
576
+ continue;
577
+ }
578
+ if (!IMPLEMENTED.includes(c.id)) {
579
+ // Never counted as passed: an unimplemented check that reported
580
+ // "clean" would be the silent-green defect this repository names.
581
+ unimplemented.push(c.id);
582
+ continue;
583
+ }
584
+ const impl = IMPLS[c.id];
585
+ const hits =
586
+ impl === undefined
587
+ ? scanIndexability(buildDir).hits
588
+ : impl(files);
589
+ if (hits.length === 0) {
590
+ passed.push(c.id);
591
+ continue;
592
+ }
593
+ for (const h of hits) {
594
+ findings.push({
595
+ check: c.id,
596
+ tier,
597
+ location: `${h.file}:${String(h.line)}`,
598
+ evidence: h.text,
599
+ remediation: c.remediation,
600
+ verification: c.verification,
601
+ });
602
+ }
603
+ }
604
+ return {
605
+ site_type: siteType,
606
+ region,
607
+ enabled: enabled(root),
608
+ findings,
609
+ skipped,
610
+ passed,
611
+ unimplemented,
612
+ escalated,
613
+ scanned_files: files.length,
614
+ };
615
+ }
616
+
617
+ const TIER_ORDER: readonly Tier[] = ['critical', 'high', 'medium', 'situational'];
618
+
619
+ /** Report order: critical → high → medium → situational-applicable → skipped. */
620
+ export function render(r: Report): string {
621
+ const out: string[] = [];
622
+ out.push(`site type: ${r.site_type} · region: ${r.region}`);
623
+ for (const e of r.escalated) {
624
+ // Printed BEFORE the findings, because a reader who sees a check at
625
+ // CRITICAL on one run and SITUATIONAL on another needs the axis that
626
+ // moved it, not just the outcome.
627
+ out.push(` escalated by region: ${e.check} ${e.from} → ${e.to} — ${e.why}`);
628
+ }
629
+ for (const tier of TIER_ORDER) {
630
+ const f = r.findings.filter((x) => x.tier === tier);
631
+ if (f.length === 0) continue;
632
+ out.push('', `${tier.toUpperCase()} (${String(f.length)})`);
633
+ for (const x of f) {
634
+ out.push(` ${x.location} ${x.check}`);
635
+ out.push(` evidence: ${x.evidence}`);
636
+ out.push(` remediation: ${x.remediation}`);
637
+ out.push(` verification: ${x.verification}`);
638
+ }
639
+ }
640
+ if (r.passed.length > 0) out.push('', `PASSED: ${r.passed.join(', ')}`);
641
+ if (r.unimplemented.length > 0) {
642
+ out.push('', `NOT YET IMPLEMENTED (applicable, not audited): ${r.unimplemented.join(', ')}`);
643
+ }
644
+ if (r.skipped.length > 0) {
645
+ out.push('', 'SKIPPED — not applicable to this site type');
646
+ for (const s of r.skipped) out.push(` ${s.check}: ${s.reason}`);
647
+ }
648
+ return out.join('\n');
649
+ }
650
+
651
+ export function main(argv: string[] = process.argv.slice(2), root = REPO_ROOT): number {
652
+ const arg = (f: string): string | undefined => {
653
+ const i = argv.indexOf(f);
654
+ return i >= 0 ? argv[i + 1] : undefined;
655
+ };
656
+ const build = arg('--build');
657
+ const siteType = arg('--site-type') as SiteType | undefined;
658
+ const region = (arg('--region') ?? 'unspecified') as Region;
659
+ if (build === undefined || siteType === undefined) {
660
+ process.stderr.write(
661
+ 'usage: check_web_launch_readiness --build <dir> --site-type <type> ' +
662
+ '[--region <de|eu|us|unspecified>] [--json]\n',
663
+ );
664
+ return 2;
665
+ }
666
+ if (!enabled(root) && !argv.includes('--force')) {
667
+ process.stdout.write(
668
+ 'web-launch-readiness is DEFAULT-OFF until the pre-registered benchmark returns\n' +
669
+ 'positive (claim:web-launch-readiness-finds-more, status unbacked). Enable with\n' +
670
+ '`web_launch_readiness.enabled: true` in .agent-settings.yml, or pass --force.\n',
671
+ );
672
+ return 0;
673
+ }
674
+ let r: Report;
675
+ try {
676
+ r = audit(build, siteType, root, region);
677
+ reportScanned({
678
+ gate: 'check_web_launch_readiness',
679
+ scanned: r.scanned_files,
680
+ units: 'build file(s)',
681
+ roots: [build],
682
+ allowEmpty:
683
+ 'OPTIONAL_INPUT: a build directory may legitimately hold no scannable file ' +
684
+ 'when the caller points at an unbuilt tree; a missing config is a separate ' +
685
+ 'hard error in loadConfig()',
686
+ });
687
+ } catch (e) {
688
+ if (e instanceof DeadScopeError) {
689
+ process.stderr.write(`❌ ${e.message}\n`);
690
+ return 2;
691
+ }
692
+ throw e;
693
+ }
694
+ if (argv.includes('--json')) {
695
+ process.stdout.write(`${JSON.stringify(r, null, 2)}\n`);
696
+ } else {
697
+ process.stdout.write(`${render(r)}\n`);
698
+ }
699
+ const blocking = r.findings.filter((f) => f.tier === 'critical' || f.tier === 'high');
700
+ return blocking.length > 0 ? 1 : 0;
701
+ }
702
+
703
+ if (path.resolve(process.argv[1] ?? '') === path.resolve(HERE)) {
704
+ process.exit(main());
705
+ }