@event4u/agent-config 16.1.0 → 16.3.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/.augment-plugin/marketplace.json +3 -3
- package/.augment-plugin/plugin.json +1 -1
- package/.claude-plugin/marketplace.json +1 -1
- package/CHANGELOG.md +98 -257
- package/README.md +14 -12
- package/dist/agent-src/commands/analyze/inbox.md +1 -1
- package/dist/agent-src/commands/analyze/roadmap-repos.md +1 -1
- package/dist/agent-src/commands/bug/fix.md +17 -2
- package/dist/agent-src/commands/feature/plan.md +1 -1
- package/dist/agent-src/commands/feature/roadmap.md +1 -1
- package/dist/agent-src/commands/fix/ci.md +1 -1
- package/dist/agent-src/commands/implement-ticket.md +1 -1
- package/dist/agent-src/commands/jira-ticket.md +1 -1
- package/dist/agent-src/commands/roadmap/create.md +1 -1
- package/dist/agent-src/commands/roadmap/materialize.md +1 -1
- package/dist/agent-src/commands/roadmap/process-full.md +5 -5
- package/dist/agent-src/commands/roadmap/resolve-blockers.md +361 -0
- package/dist/agent-src/commands/roadmap/triage-parked.md +325 -0
- package/dist/agent-src/commands/roadmap.md +18 -4
- package/dist/agent-src/commands/tests/create.md +17 -3
- package/dist/agent-src/commands/tests/execute.md +22 -5
- package/dist/agent-src/commands/work.md +4 -0
- package/dist/agent-src/contexts/authority/decision-revisit-gate-enforcement.md +43 -0
- package/dist/agent-src/contexts/communication/rules-auto/design-fidelity-enforcement.md +24 -0
- package/dist/agent-src/contexts/execution/auto-dispatch-classification.md +6 -6
- package/dist/agent-src/contexts/execution/autonomy-mechanics.md +4 -8
- package/dist/agent-src/contexts/execution/roadmap-process-loop.md +32 -2
- package/dist/agent-src/contexts/execution/session-canary-enforcement-history.md +67 -0
- package/dist/agent-src/contexts/execution/toolchain-resolver.md +101 -7
- package/dist/agent-src/rules/active-remediation.md +1 -1
- package/dist/agent-src/rules/autonomous-execution.md +1 -0
- package/dist/agent-src/rules/broken-access-control.md +1 -0
- package/dist/agent-src/rules/command-suggestion-policy.md +1 -0
- package/dist/agent-src/rules/context-hygiene.md +1 -1
- package/dist/agent-src/rules/decision-revisit-gate.md +16 -33
- package/dist/agent-src/rules/design-fidelity.md +13 -16
- package/dist/agent-src/rules/doc-screenshot-hygiene.md +1 -0
- package/dist/agent-src/rules/domain-safety-disclaimer.md +1 -0
- package/dist/agent-src/rules/domain-safety-pii.md +1 -0
- package/dist/agent-src/rules/domain-safety-retention.md +1 -0
- package/dist/agent-src/rules/engineering-safety-floor.md +1 -0
- package/dist/agent-src/rules/evaluator-independence.md +3 -2
- package/dist/agent-src/rules/fast-path-marker-visibility.md +1 -0
- package/dist/agent-src/rules/finance-safety-floor.md +1 -0
- package/dist/agent-src/rules/git-history-discipline.md +1 -0
- package/dist/agent-src/rules/image-likeness-and-rights.md +1 -0
- package/dist/agent-src/rules/legal-safety-floor.md +3 -2
- package/dist/agent-src/rules/lethal-trifecta-guard.md +1 -0
- package/dist/agent-src/rules/low-impact-corpus-privacy-floor.md +1 -0
- package/dist/agent-src/rules/media-governance-routing.md +14 -17
- package/dist/agent-src/rules/persona-governance.md +2 -2
- package/dist/agent-src/rules/provider-lifecycle-discipline.md +1 -1
- package/dist/agent-src/rules/question-not-instruction.md +1 -0
- package/dist/agent-src/rules/roadmap-progress-sync.md +1 -1
- package/dist/agent-src/rules/runtime-safety.md +1 -0
- package/dist/agent-src/rules/secret-vcs-guard.md +1 -0
- package/dist/agent-src/rules/security-sensitive-stop.md +3 -2
- package/dist/agent-src/rules/session-canary.md +8 -62
- package/dist/agent-src/rules/settings-ask-protocol.md +1 -0
- package/dist/agent-src/rules/source-confidentiality.md +1 -0
- package/dist/agent-src/rules/strategy-safety-floor.md +1 -0
- package/dist/agent-src/rules/tool-safety.md +1 -0
- package/dist/agent-src/rules/ui-audit-gate.md +1 -2
- package/dist/agent-src/rules/untrusted-input-defense.md +1 -0
- package/dist/agent-src/rules/user-interrupt-priority.md +1 -0
- package/dist/agent-src/scripts/archive_completed_roadmaps.ts +115 -20
- package/dist/agent-src/scripts/deferral_carry.ts +339 -0
- package/dist/agent-src/scripts/update_roadmap_progress.ts +4 -1
- package/dist/agent-src/skills/accessibility-auditor/data/aria-patterns.csv +1 -1
- package/dist/agent-src/skills/accessibility-auditor/data/manifest.json +2 -2
- package/dist/agent-src/skills/api-design/data/manifest.json +2 -2
- package/dist/agent-src/skills/api-testing/SKILL.md +17 -0
- package/dist/agent-src/skills/code-intelligence/SKILL.md +14 -2
- package/dist/agent-src/skills/design-review/SKILL.md +30 -4
- package/dist/agent-src/skills/design-review/references/verification-automation.md +32 -0
- package/dist/agent-src/skills/design-system-capture/references/import-procedure.md +1 -1
- package/dist/agent-src/skills/existing-ui-audit/SKILL.md +18 -3
- package/dist/agent-src/skills/existing-ui-audit/references/component-taxonomy-detection.md +56 -0
- package/dist/agent-src/skills/fe-design/SKILL.md +1 -1
- package/dist/agent-src/skills/iconography/SKILL.md +1 -1
- package/dist/agent-src/skills/js-library-packaging/SKILL.md +2 -2
- package/dist/agent-src/skills/markitdown/SKILL.md +4 -0
- package/dist/agent-src/skills/monorepo-workspace/SKILL.md +1 -1
- package/dist/agent-src/skills/playwright-testing/SKILL.md +30 -6
- package/dist/agent-src/skills/quality-tools/SKILL.md +11 -1
- package/dist/agent-src/skills/quality-tools/references/go-tools.md +76 -0
- package/dist/agent-src/skills/quality-tools/references/python-tools.md +104 -0
- package/dist/agent-src/skills/review-routing/SKILL.md +2 -2
- package/dist/agent-src/skills/skill-improvement-pipeline/SKILL.md +1 -1
- package/dist/agent-src/skills/storybook-workshop/SKILL.md +3 -3
- package/dist/agent-src/skills/tailwind-engineer/SKILL.md +1 -1
- package/dist/agent-src/skills/test-case-discovery/SKILL.md +7 -0
- package/dist/agent-src/skills/test-driven-development/SKILL.md +17 -0
- package/dist/agent-src/skills/test-performance/SKILL.md +19 -1
- package/dist/agent-src/skills/ui-component-architect/SKILL.md +25 -2
- package/dist/agent-src/templates/agents/agent-project-settings.example.yml +1 -1
- package/dist/agent-src/templates/roadmaps.md +30 -0
- package/dist/agent-src/templates/scripts/work_engine/directives/ui/app_spec.ts +10 -4
- package/dist/agent-src/templates/scripts/work_engine/directives/ui/apply.ts +302 -21
- package/dist/agent-src/templates/scripts/work_engine/directives/ui/audit.ts +22 -3
- package/dist/agent-src/templates/scripts/work_engine/directives/ui/scaffold.ts +6 -1
- package/dist/agent-src/templates/scripts/work_engine/stack/runner.ts +774 -109
- package/dist/agent-src/templates/scripts/work_engine/state.ts +57 -7
- package/dist/agent-src/templates/scripts/work_engine/taxonomy/detect.ts +500 -0
- package/dist/agent-src/templates/scripts/work_engine/taxonomy/place.ts +183 -0
- package/dist/cli/python/knowledge_ingest.js +1 -1
- package/dist/cli/python/workspace_drive.js +1 -1
- package/dist/cli/registry.js +1 -1
- package/dist/cli-delegate/archive_completed_roadmaps.js +92 -12
- package/dist/cli-delegate/check_update_banner.js +12 -1
- package/dist/cli-delegate/chunk-523HCFYO.js +90 -0
- package/dist/cli-delegate/{chunk-M2DPGFZ4.js → chunk-6V6TA25H.js} +22 -152
- package/dist/cli-delegate/chunk-7IQE3D5S.js +191 -0
- package/dist/cli-delegate/{chunk-6KFEC3G7.js → chunk-CQWX336L.js} +1 -1
- package/dist/cli-delegate/chunk-DEIV7XP7.js +108 -0
- package/dist/cli-delegate/chunk-EEHDXSYD.js +2040 -0
- package/dist/cli-delegate/{chunk-P6ZHP5W2.js → chunk-GKE7ZVFA.js} +4 -4
- package/dist/cli-delegate/chunk-JQOM7TTM.js +1201 -0
- package/dist/cli-delegate/{chunk-UNCRJBT3.js → chunk-JQVYKIZA.js} +41 -0
- package/dist/cli-delegate/{chunk-FC75PGJE.js → chunk-JY2I2DXV.js} +12 -1
- package/dist/cli-delegate/chunk-KB5FZ655.js +208 -0
- package/dist/cli-delegate/{chunk-YUM5KURG.js → chunk-KR6EM23A.js} +235 -1455
- package/dist/cli-delegate/{chunk-LTYVIY76.js → chunk-LIYTLH6O.js} +1 -1
- package/dist/cli-delegate/chunk-NQ7WT43P.js +2729 -0
- package/dist/cli-delegate/chunk-NXEILF2K.js +2160 -0
- package/dist/cli-delegate/{chunk-5R7HOLAF.js → chunk-NYX3L2PZ.js} +1 -1
- package/dist/cli-delegate/{chunk-4E2W63DA.js → chunk-OKJUIAPS.js} +62 -1
- package/dist/cli-delegate/{chunk-QAJS5YWY.js → chunk-PHD7OOLE.js} +2 -1
- package/dist/cli-delegate/{chunk-RGNIWTEA.js → chunk-QLDHIPWR.js} +1 -1
- package/dist/cli-delegate/{chunk-HDVVGPQW.js → chunk-RQNNW7IF.js} +1405 -567
- package/dist/cli-delegate/{chunk-S3VFXMTM.js → chunk-S4UMZNES.js} +1 -1
- package/dist/cli-delegate/{chunk-6KM6ORRY.js → chunk-S4YWNRKO.js} +24 -8
- package/dist/cli-delegate/{chunk-GJCBB2UG.js → chunk-TRI6DQZ6.js} +3054 -325
- package/dist/cli-delegate/chunk-ULQRUR23.js +208 -0
- package/dist/cli-delegate/{chunk-NUHLI2RX.js → chunk-YCU3TIXH.js} +2 -0
- package/dist/cli-delegate/chunk-ZBZV2G5R.js +210 -0
- package/dist/cli-delegate/{chunk-WGXTVKCQ.js → chunk-ZUZKZYCN.js} +5 -3
- package/dist/cli-delegate/cmd_analyze_session.js +1 -1
- package/dist/cli-delegate/cmd_brand_status.js +1 -1
- package/dist/cli-delegate/cmd_conformance.js +1862 -809
- package/dist/cli-delegate/cmd_doctor.js +13 -6
- package/dist/cli-delegate/cmd_doctor_neighbours.js +23 -0
- package/dist/cli-delegate/cmd_explain.js +5 -5
- package/dist/cli-delegate/cmd_export.js +11 -8
- package/dist/cli-delegate/cmd_fleet.js +3 -3
- package/dist/cli-delegate/cmd_handoff.js +10 -7
- package/dist/cli-delegate/cmd_mcp_available.js +1 -1
- package/dist/cli-delegate/cmd_memory_get.js +3 -1
- package/dist/cli-delegate/cmd_migrate.js +2 -2
- package/dist/cli-delegate/cmd_packs_active.js +3 -3
- package/dist/cli-delegate/cmd_preflight.js +6 -6
- package/dist/cli-delegate/cmd_prune.js +11 -8
- package/dist/cli-delegate/cmd_refresh.js +2 -1
- package/dist/cli-delegate/cmd_route_audit.js +8 -6
- package/dist/cli-delegate/cmd_route_explain.js +4 -4
- package/dist/cli-delegate/cmd_session_recycle.js +4 -3
- package/dist/cli-delegate/cmd_settings_get.js +1 -1
- package/dist/cli-delegate/cmd_settings_migrate.js +11 -8
- package/dist/cli-delegate/cmd_settings_set.js +4 -4
- package/dist/cli-delegate/cmd_sync.js +11 -8
- package/dist/cli-delegate/cmd_uninstall.js +11 -8
- package/dist/cli-delegate/cmd_update.js +1 -1
- package/dist/cli-delegate/cmd_upgrade.js +3 -2
- package/dist/cli-delegate/cmd_validate.js +11 -8
- package/dist/cli-delegate/cmd_versions.js +1 -1
- package/dist/cli-delegate/deferral_carry.js +16 -0
- package/dist/cli-delegate/gate_execute.js +2 -2
- package/dist/cli-delegate/roadmap_gates.js +5 -5
- package/dist/cli-delegate/update_roadmap_progress.js +1 -1
- package/dist/discovery/deprecation-report.md +1 -1
- package/dist/discovery/discovery-manifest.json +132 -80
- package/dist/discovery/discovery-manifest.json.sha256 +1 -1
- package/dist/discovery/discovery-manifest.summary.md +3 -3
- package/dist/discovery/dormancy-report.md +1 -1
- package/dist/discovery/orphan-report.md +1 -1
- package/dist/discovery/packs.json +7 -5
- package/dist/discovery/trust-report.md +2 -2
- package/dist/discovery/workspaces.json +6 -4
- package/dist/hooks/dispatch.js +7972 -5186
- package/dist/hooks/dispatch.sha256 +1 -0
- package/dist/install/claudePathsPlan.js +47 -6
- package/dist/install/claudeRuleRewrite.js +7 -0
- package/dist/install/emit_host_rules_cli.js +39 -36
- package/dist/install/globalRuleScope.js +87 -0
- package/dist/install/host_rule_ownership.js +160 -0
- package/dist/install/install.mjs +2226 -1055
- package/dist/install/installThinLayer.js +261 -0
- package/dist/install/installedRuleLinks.js +176 -0
- package/dist/install/wizard-plan.js +24 -0
- package/dist/mcp/registry-manifest.json +3 -3
- package/dist/mcp/server.json +2 -2
- package/dist/mcp/server.mjs +40913 -34865
- package/dist/scripts/_lib/agent_settings.js +11 -0
- package/dist/scripts/_lib/claude_settings_hooks.js +10 -8
- package/dist/scripts/_lib/installed_layer.js +378 -0
- package/dist/scripts/_lib/lean_projection_mode.js +218 -15
- package/dist/scripts/_lib/rule_consequence_class.js +45 -0
- package/dist/scripts/_lib/rule_injection.js +67 -10
- package/dist/scripts/_lib/rule_law_section.js +122 -0
- package/dist/scripts/_lib/thin_rules.js +458 -0
- package/dist/scripts/_lib/windsurf_render.js +60 -0
- package/dist/scripts/condense.js +14 -26
- package/dist/scripts/project_thin_rules.js +14 -294
- package/dist/server/schemas/settings.js +29 -0
- package/dist/shared/skillRanking.js +74 -13
- package/dist/ui/assets/index-Cwq4d0hX.js +23 -0
- package/dist/ui/assets/index-Cyjy8bWO.css +1 -0
- package/dist/ui/index.html +2 -2
- package/docs/contracts/settings-classes.md +10 -6
- package/package.json +4 -4
- package/src/agent-src/scripts/archive_completed_roadmaps.ts +115 -20
- package/src/agent-src/scripts/deferral_carry.ts +339 -0
- package/src/agent-src/scripts/update_roadmap_progress.ts +4 -1
- package/src/agent-src/templates/scripts/work_engine/directives/ui/app_spec.ts +10 -4
- package/src/agent-src/templates/scripts/work_engine/directives/ui/apply.ts +302 -21
- package/src/agent-src/templates/scripts/work_engine/directives/ui/audit.ts +22 -3
- package/src/agent-src/templates/scripts/work_engine/directives/ui/scaffold.ts +6 -1
- package/src/agent-src/templates/scripts/work_engine/stack/runner.ts +774 -109
- package/src/agent-src/templates/scripts/work_engine/state.ts +57 -7
- package/src/agent-src/templates/scripts/work_engine/taxonomy/detect.ts +500 -0
- package/src/agent-src/templates/scripts/work_engine/taxonomy/place.ts +183 -0
- package/src/config/agent-settings.template.yml +91 -22
- package/src/config/ci-local-parity.yml +18 -0
- package/src/config/continuity-surface.json +9 -2
- package/src/config/evaluator-budgets.json +4 -4
- package/src/config/gate-coverage.yml +327 -3
- package/src/config/gate-violation-baselines.json +21 -9
- package/src/config/hook-bundle-budget.json +25 -0
- package/src/config/hook-latency-budget.json +21 -0
- package/src/config/hook-token-budget.json +8 -3
- package/src/config/host-instruction-limits.json +33 -0
- package/src/config/metric-registry.yml +114 -0
- package/src/config/placeholder-drift-budget.json +10 -0
- package/src/config/routing-coverage-seed.json +38 -3
- package/src/config/rule-activation-census.json +7 -3
- package/src/config/rule-consequence-class.json +161 -0
- package/src/config/rule-law-ceilings.json +99 -0
- package/src/install/claudePathsPlan.ts +46 -4
- package/src/install/claudeRuleRewrite.ts +10 -1
- package/src/install/emit_host_rules_cli.ts +43 -34
- package/src/install/globalRuleScope.ts +109 -0
- package/src/install/host_rule_ownership.ts +170 -0
- package/src/install/installThinLayer.ts +312 -0
- package/src/install/installedRuleLinks.ts +220 -0
- package/src/install/wizard-plan.ts +24 -0
- package/src/scripts/_cli/cmd_doctor.ts +28 -90
- package/src/scripts/_cli/cmd_doctor_neighbours.ts +293 -0
- package/src/scripts/_cli/doctor_execution.ts +100 -17
- package/src/scripts/_cli/doctor_network_posture.ts +226 -0
- package/src/scripts/_dispatch.bash +16 -2
- package/src/scripts/_lib/agent_settings.ts +11 -0
- package/src/scripts/_lib/body_portable.ts +102 -0
- package/src/scripts/_lib/cc_transcript.ts +132 -1
- package/src/scripts/_lib/claude_settings_hooks.ts +10 -8
- package/src/scripts/_lib/concern_sla_window.ts +200 -0
- package/src/scripts/_lib/estate_offsets.ts +241 -0
- package/src/scripts/_lib/exemption_shape.ts +143 -0
- package/src/scripts/_lib/fnv.ts +23 -0
- package/src/scripts/_lib/foreign_scan.ts +106 -0
- package/src/scripts/_lib/forge_protection.ts +114 -0
- package/src/scripts/_lib/forge_reader.ts +558 -0
- package/src/scripts/_lib/gitignore_block.ts +314 -0
- package/src/scripts/_lib/global_deploy_inventory.ts +32 -0
- package/src/scripts/_lib/graph_feeder_record.ts +214 -0
- package/src/scripts/_lib/hook_settings.ts +73 -0
- package/src/scripts/_lib/host_capability.ts +40 -19
- package/src/scripts/_lib/host_env_write.ts +111 -0
- package/src/scripts/_lib/host_hook_merge.ts +165 -0
- package/src/scripts/_lib/installed_layer.ts +538 -0
- package/src/scripts/_lib/json_merge.ts +82 -0
- package/src/scripts/_lib/json_pointers.ts +93 -11
- package/src/scripts/_lib/lean_projection_mode.ts +275 -15
- package/src/scripts/_lib/llm_proposer_transport.ts +14 -4
- package/src/scripts/_lib/machine_wake.ts +16 -3
- package/src/scripts/_lib/neighbour_census.ts +760 -0
- package/src/scripts/_lib/neighbour_scan.ts +122 -0
- package/src/scripts/_lib/neighbour_tool_use.ts +189 -0
- package/src/scripts/_lib/obligations.ts +119 -8
- package/src/scripts/_lib/probe_inputs.ts +271 -0
- package/src/scripts/_lib/prompt_shape.ts +46 -0
- package/src/scripts/_lib/read_surface_scan.ts +322 -0
- package/src/scripts/_lib/release_highlights.ts +2 -1
- package/src/scripts/_lib/release_migration_gate.ts +19 -3
- package/src/scripts/_lib/reserved_name_sweep.ts +73 -0
- package/src/scripts/_lib/retrieval_sanitize.ts +77 -16
- package/src/scripts/_lib/review_coverage.ts +134 -0
- package/src/scripts/_lib/rule_consequence_class.ts +71 -0
- package/src/scripts/_lib/rule_injection.ts +130 -10
- package/src/scripts/_lib/rule_law_section.ts +147 -0
- package/src/scripts/_lib/rule_layer_overlap.ts +58 -0
- package/src/scripts/_lib/shell_write_shapes.ts +108 -0
- package/src/scripts/_lib/skill_origin.ts +254 -0
- package/src/scripts/_lib/structural_hiding.ts +622 -0
- package/src/scripts/_lib/structured_ask.ts +64 -21
- package/src/scripts/_lib/thin_rules.ts +540 -0
- package/src/scripts/_lib/touched_file_quality.ts +434 -0
- package/src/scripts/_lib/turn_end_refusals.ts +600 -4
- package/src/scripts/_lib/turn_end_transcript.ts +216 -0
- package/src/scripts/_lib/verification_command.ts +324 -0
- package/src/scripts/_lib/verification_evidence.ts +647 -0
- package/src/scripts/_lib/verify_clause.ts +280 -0
- package/src/scripts/_lib/windsurf_render.ts +63 -0
- package/src/scripts/ai_council/cli_call_budget.ts +13 -5
- package/src/scripts/ai_council/clients.ts +26 -57
- package/src/scripts/ai_council/config.ts +1 -1
- package/src/scripts/ai_council/vendor_defaults.ts +45 -0
- package/src/scripts/ai_team/team_dispatch.ts +4 -0
- package/src/scripts/audit_skill_overlap.ts +261 -0
- package/src/scripts/before_complete_hook.ts +573 -25
- package/src/scripts/bench_hook_latency.ts +412 -5
- package/src/scripts/bench_touched_file_quality.ts +238 -0
- package/src/scripts/cache_realization_report.ts +61 -1
- package/src/scripts/check_argument_hint.ts +287 -0
- package/src/scripts/check_claims.ts +125 -1
- package/src/scripts/check_enforcement_coverage.ts +38 -9
- package/src/scripts/check_enforcement_matrix.ts +621 -22
- package/src/scripts/check_estate_count.ts +130 -153
- package/src/scripts/check_finding_dispositions.ts +138 -7
- package/src/scripts/check_generator_sync.ts +71 -0
- package/src/scripts/check_hook_bundle_composition.ts +370 -0
- package/src/scripts/check_host_docs_digest.ts +786 -0
- package/src/scripts/check_host_format_column.ts +559 -0
- package/src/scripts/check_input_references.ts +228 -0
- package/src/scripts/check_inputs_parity.ts +235 -0
- package/src/scripts/check_kernel_edit_ratified.ts +89 -9
- package/src/scripts/check_kill_switch_table.ts +294 -0
- package/src/scripts/check_memory.ts +7 -1
- package/src/scripts/check_placeholder_drift.ts +221 -0
- package/src/scripts/check_read_surface_coverage.ts +515 -0
- package/src/scripts/check_release_highlights.ts +10 -0
- package/src/scripts/check_release_holds.ts +2 -1
- package/src/scripts/check_release_pr_shape.ts +38 -15
- package/src/scripts/check_routing_coverage.ts +378 -11
- package/src/scripts/check_standing_rule_delivery.ts +33 -13
- package/src/scripts/check_static_parity.ts +17 -0
- package/src/scripts/check_verify_expectation_delta.ts +521 -0
- package/src/scripts/ci_settle.ts +145 -19
- package/src/scripts/closure_scan.ts +115 -1
- package/src/scripts/code_graph/detect.ts +127 -7
- package/src/scripts/code_graph/foreign.ts +194 -0
- package/src/scripts/code_graph/query.ts +217 -4
- package/src/scripts/code_graph/types.ts +26 -2
- package/src/scripts/code_graph/validate.ts +3 -1
- package/src/scripts/code_graph/verbs.ts +103 -0
- package/src/scripts/compile_hook_manifest.ts +90 -14
- package/src/scripts/condense.ts +14 -27
- package/src/scripts/estate_exemption_shape.ts +202 -0
- package/src/scripts/external_sources_denylist.json +29 -4
- package/src/scripts/generate_host_cost_table.ts +1 -1
- package/src/scripts/hook_manifest.json +1 -1
- package/src/scripts/hook_manifest.yaml +192 -14
- package/src/scripts/hooks/block_config_weakening.ts +294 -4
- package/src/scripts/hooks/block_kernel_rule_writes.ts +9 -78
- package/src/scripts/hooks/block_plumbing_writes.ts +342 -0
- package/src/scripts/hooks/bundle_integrity.ts +291 -0
- package/src/scripts/hooks/chain_nudge_hook.ts +1 -2
- package/src/scripts/hooks/code_graph_context_hook.ts +253 -25
- package/src/scripts/hooks/comment_discipline_hook.ts +1 -3
- package/src/scripts/hooks/concern_failure_policy.ts +280 -0
- package/src/scripts/hooks/concern_registry.ts +2 -0
- package/src/scripts/hooks/concern_timings.ts +94 -0
- package/src/scripts/hooks/delegation_nudge_hook.ts +1 -2
- package/src/scripts/hooks/design_pass_hook.ts +100 -9
- package/src/scripts/hooks/design_slop_hook.ts +1 -2
- package/src/scripts/hooks/dispatch_hook.ts +77 -71
- package/src/scripts/hooks/edit_shape_hook.ts +1 -2
- package/src/scripts/hooks/end_review_nudge_hook.ts +1 -1
- package/src/scripts/hooks/evidence_independence.ts +1 -2
- package/src/scripts/hooks/exit_codes.ts +129 -0
- package/src/scripts/hooks/host_lowering.json +1 -0
- package/src/scripts/hooks/host_lowering.ts +146 -3
- package/src/scripts/hooks/host_lowering.yaml +262 -46
- package/src/scripts/hooks/host_semantics.ts +30 -2
- package/src/scripts/hooks/injection_budget.ts +9 -2
- package/src/scripts/hooks/interruption_ledger_hook.ts +1 -2
- package/src/scripts/hooks/journal_record_hook.ts +1 -2
- package/src/scripts/hooks/memory_recall_hook.ts +1 -2
- package/src/scripts/hooks/obligation_settle_hook.ts +151 -5
- package/src/scripts/hooks/one_question_per_ask_hook.ts +43 -6
- package/src/scripts/hooks/orchestration_record_hook.ts +1 -2
- package/src/scripts/hooks/payload_stub.ts +10 -1
- package/src/scripts/hooks/permission_gate.ts +1 -2
- package/src/scripts/hooks/push_settle_hook.ts +1 -3
- package/src/scripts/hooks/replay_hook.ts +2 -1
- package/src/scripts/hooks/reread_guard_hook.ts +1 -2
- package/src/scripts/hooks/review_baseline_hook.ts +1 -2
- package/src/scripts/hooks/rtk_wrap_hook.ts +1 -2
- package/src/scripts/hooks/rule_inject_hook.ts +765 -68
- package/src/scripts/hooks/run_continuation_hook.ts +13 -15
- package/src/scripts/hooks/skill_route_hook.ts +1 -2
- package/src/scripts/hooks/source_first_gate_hook.ts +1 -2
- package/src/scripts/hooks/spawn_guard_shadow_hook.ts +1 -2
- package/src/scripts/hooks/subagent_ledger_hook.ts +1 -2
- package/src/scripts/hooks/suggestion_capture_hook.ts +1 -1
- package/src/scripts/hooks/table_fingerprint.ts +70 -0
- package/src/scripts/hooks/telemetry_flush_hook.ts +1 -2
- package/src/scripts/hooks/telemetry_usage_hook.ts +65 -10
- package/src/scripts/hooks/tool_result_bytes_hook.ts +93 -9
- package/src/scripts/hooks/turn_end_gate_hook.ts +421 -324
- package/src/scripts/hooks/ui_route_nudge_hook.ts +1 -2
- package/src/scripts/hooks_status.ts +112 -0
- package/src/scripts/injection_scan_hook.ts +7 -0
- package/src/scripts/install.ts +75 -189
- package/src/scripts/installed_layer_report.ts +103 -0
- package/src/scripts/language_mirror_hook.ts +15 -6
- package/src/scripts/learning_sidecar.ts +119 -8
- package/src/scripts/lint_deny_text.ts +419 -0
- package/src/scripts/lint_exit_codes.ts +262 -0
- package/src/scripts/lint_hook_manifest.ts +108 -0
- package/src/scripts/lint_major_migration_sections.ts +156 -26
- package/src/scripts/lint_pack_boundaries.ts +63 -5
- package/src/scripts/lint_roadmap_blockers.ts +78 -12
- package/src/scripts/lint_rule_law_section.ts +517 -0
- package/src/scripts/lint_skill_descriptions.ts +1 -1
- package/src/scripts/lint_skill_scripts_readonly.ts +1 -1
- package/src/scripts/lint_workflow_security.ts +161 -5
- package/src/scripts/mcp_server/consumer_tool_catalog.json +53 -0
- package/src/scripts/mcp_server/graph_tools.ts +72 -3
- package/src/scripts/mcp_server/prompts.ts +142 -1
- package/src/scripts/measure_routing_signal.ts +54 -16
- package/src/scripts/measure_skill_ranker_baseline.ts +515 -53
- package/src/scripts/measure_turn_end_gate.ts +193 -0
- package/src/scripts/memory_learn_hook.ts +91 -6
- package/src/scripts/memory_lookup.ts +1 -1
- package/src/scripts/model_rule_injection.ts +52 -5
- package/src/scripts/prepack-check.mjs +53 -0
- package/src/scripts/project_thin_rules.ts +34 -327
- package/src/scripts/release_publication.ts +13 -1
- package/src/scripts/report_evidence_temperature.ts +657 -0
- package/src/scripts/report_installed_rule_links.ts +157 -0
- package/src/scripts/report_invocation_surface.ts +383 -0
- package/src/scripts/report_label_agreement.ts +233 -0
- package/src/scripts/report_obligation_carriers.ts +699 -0
- package/src/scripts/report_obligation_mechanism.ts +230 -0
- package/src/scripts/report_obligation_writer_split.ts +236 -0
- package/src/scripts/report_single_token_triggers.ts +277 -0
- package/src/scripts/report_skill_menu_census.ts +208 -0
- package/src/scripts/report_skill_ranker_confusion.ts +442 -0
- package/src/scripts/report_standing_payload_by_host.ts +188 -15
- package/src/scripts/report_verification_record_coverage.ts +306 -0
- package/src/scripts/roadmap_verify_share.ts +213 -0
- package/src/scripts/rule_activation_census.ts +5 -2
- package/src/scripts/rule_trigger_eval.ts +9 -9
- package/src/scripts/schemas/command.schema.json +44 -0
- package/src/scripts/schemas/review-findings.schema.json +7 -2
- package/src/scripts/schemas/rule.schema.json +5 -0
- package/src/scripts/schemas/skill.schema.json +44 -0
- package/src/scripts/self_review_gate.ts +421 -17
- package/src/scripts/semantic_parity_census.ts +314 -0
- package/src/scripts/session_eol_report.ts +54 -1
- package/src/scripts/skill_tools/score_skill_relevance.ts +290 -25
- package/src/scripts/sweep_dead_scan_roots.ts +1 -1
- package/src/scripts/sweep_skill_ranker_signals.ts +129 -0
- package/src/scripts/sync_gitignore.ts +29 -235
- package/src/scripts/trigger_eval_grandfather.json +0 -7
- package/src/scripts/trigger_eval_rotation.ts +122 -27
- package/src/scripts/ui_conformance_probe.ts +156 -4
- package/src/scripts/update_prices.ts +85 -3
- package/src/scripts/write_bundle_digest.mjs +74 -0
- package/src/server/schemas/settings.ts +41 -0
- package/src/shared/skillRanking.ts +117 -11
- package/src/templates/consumer-settings/ONBOARDING.md +1 -0
- package/dist/cli-delegate/chunk-FXYUYU4N.js +0 -4416
- package/dist/cli-delegate/chunk-QJWAUMJW.js +0 -1072
- package/dist/cli-delegate/chunk-YDPILCOL.js +0 -152
- package/dist/ui/assets/index-DH1xc62v.js +0 -29
- package/dist/ui/assets/index-jSfMW8_t.css +0 -1
|
@@ -0,0 +1,1201 @@
|
|
|
1
|
+
import { createRequire as __acCreateRequire } from 'node:module'; const require = globalThis.require ?? __acCreateRequire(import.meta.url);
|
|
2
|
+
import {
|
|
3
|
+
DeadScopeError,
|
|
4
|
+
assertScanned
|
|
5
|
+
} from "./chunk-I3EIN2YG.js";
|
|
6
|
+
|
|
7
|
+
// src/scripts/_lib/security_lint.ts
|
|
8
|
+
import { createHash } from "node:crypto";
|
|
9
|
+
import * as fs from "node:fs";
|
|
10
|
+
import * as path from "node:path";
|
|
11
|
+
import { fileURLToPath } from "node:url";
|
|
12
|
+
var _HERE = path.dirname(fileURLToPath(import.meta.url));
|
|
13
|
+
var ROOT = path.resolve(_HERE, "..", "..", "..");
|
|
14
|
+
var EXAMPLE_FENCE_LANG = "security-example";
|
|
15
|
+
var GUIDELINE = "docs/guidelines/agent-infra/security-lint-containment.md";
|
|
16
|
+
var GUIDELINE_EPILOG = `False-positive containment (fenced security-example block, confidence weighting, per-file \`security-lint: allow\` pragma \u2014 no global allowlist): see ${GUIDELINE}.`;
|
|
17
|
+
var DEFAULT_SCAN_ROOTS = [
|
|
18
|
+
"src/skills",
|
|
19
|
+
"src/rules",
|
|
20
|
+
"src/agent-src",
|
|
21
|
+
"src/domains",
|
|
22
|
+
"dist/agent-src"
|
|
23
|
+
];
|
|
24
|
+
var _EXAMPLE_PATH = /(^|\/)(docs|evals|tests?|fixtures?)(\/|$)|example|template|sample|\/_template/i;
|
|
25
|
+
var _PRAGMA = /<!--\s*security-lint:\s*allow\s+([A-Za-z0-9_-]+)\s+"([^"]+)"((?:\s+sha256:[0-9a-f]{64})*)\s*-->/g;
|
|
26
|
+
var _FENCE = /^(\s*)(`{3,}|~{3,})\s*([A-Za-z0-9_-]*)\s*$/;
|
|
27
|
+
var SEVERITY_RANK = { LOW: 1, MED: 2, HIGH: 3 };
|
|
28
|
+
var Finding = class {
|
|
29
|
+
path;
|
|
30
|
+
// repo-relative
|
|
31
|
+
line;
|
|
32
|
+
// 1-based; 0 = file-level
|
|
33
|
+
check;
|
|
34
|
+
// stable check id, also the pragma key
|
|
35
|
+
severity;
|
|
36
|
+
// HIGH | MED | LOW
|
|
37
|
+
message;
|
|
38
|
+
weight;
|
|
39
|
+
// 1.0 or 0.25
|
|
40
|
+
constructor(pathArg, line, check, severity, message, weight = 1) {
|
|
41
|
+
this.path = pathArg;
|
|
42
|
+
this.line = line;
|
|
43
|
+
this.check = check;
|
|
44
|
+
this.severity = severity;
|
|
45
|
+
this.message = message;
|
|
46
|
+
this.weight = weight;
|
|
47
|
+
}
|
|
48
|
+
/** A HIGH-severity, full-weight finding fails the build. */
|
|
49
|
+
get is_fail() {
|
|
50
|
+
return this.severity === "HIGH" && this.weight >= 1;
|
|
51
|
+
}
|
|
52
|
+
/** Mirror Python dataclass `__dict__` key order for `--json` parity. */
|
|
53
|
+
toDict() {
|
|
54
|
+
return {
|
|
55
|
+
path: this.path,
|
|
56
|
+
line: this.line,
|
|
57
|
+
check: this.check,
|
|
58
|
+
severity: this.severity,
|
|
59
|
+
message: this.message,
|
|
60
|
+
// float in Python → render integer-valued (1.0) as `1.0`.
|
|
61
|
+
weight: new PyFloat(this.weight)
|
|
62
|
+
};
|
|
63
|
+
}
|
|
64
|
+
};
|
|
65
|
+
function is_example_path(rel_path) {
|
|
66
|
+
return _EXAMPLE_PATH.test(rel_path);
|
|
67
|
+
}
|
|
68
|
+
function path_weight(rel_path) {
|
|
69
|
+
return is_example_path(rel_path) ? 0.25 : 1;
|
|
70
|
+
}
|
|
71
|
+
var PROJECTION_PREFIX = "dist/agent-src/";
|
|
72
|
+
var PROJECTION_SOURCE_LANES = [
|
|
73
|
+
// The guidelines lane copies `docs/guidelines/` (`_lib/guidelines_lane.ts`),
|
|
74
|
+
// not `src/guidelines/`, which does not exist. Without this row a projected
|
|
75
|
+
// guideline gets a DIFFERENT identity from its own source, so a bound
|
|
76
|
+
// pragma written in the source cannot suppress the projection's copy of the
|
|
77
|
+
// very same line — measured on
|
|
78
|
+
// `untrusted-input-spotlighting.md`, a defense guideline that quotes
|
|
79
|
+
// role-takeover phrases to teach refusal and carries three accepted
|
|
80
|
+
// fingerprints for exactly that.
|
|
81
|
+
["guidelines/", "docs/guidelines/"]
|
|
82
|
+
];
|
|
83
|
+
function source_identity(rel) {
|
|
84
|
+
if (!rel.startsWith(PROJECTION_PREFIX)) return rel;
|
|
85
|
+
const tail = rel.slice(PROJECTION_PREFIX.length);
|
|
86
|
+
for (const [lane, sourceRoot] of PROJECTION_SOURCE_LANES) {
|
|
87
|
+
if (tail.startsWith(lane)) return sourceRoot + tail.slice(lane.length);
|
|
88
|
+
}
|
|
89
|
+
return `src/${tail}`;
|
|
90
|
+
}
|
|
91
|
+
var ScannedFile = class {
|
|
92
|
+
path;
|
|
93
|
+
// absolute or as-given path
|
|
94
|
+
rel;
|
|
95
|
+
lines;
|
|
96
|
+
// per-line flags (1-based index → flag); index 0 unused
|
|
97
|
+
in_example_fence;
|
|
98
|
+
in_any_fence;
|
|
99
|
+
pragmas;
|
|
100
|
+
// check id → reason
|
|
101
|
+
/** check id → the sha256 fingerprints that pragma accepts. Empty = unbound. */
|
|
102
|
+
pragma_hashes;
|
|
103
|
+
weight;
|
|
104
|
+
constructor(pathArg, rel, lines, in_example_fence, in_any_fence, pragmas, weight, pragma_hashes = {}) {
|
|
105
|
+
this.path = pathArg;
|
|
106
|
+
this.rel = rel;
|
|
107
|
+
this.lines = lines;
|
|
108
|
+
this.in_example_fence = in_example_fence;
|
|
109
|
+
this.in_any_fence = in_any_fence;
|
|
110
|
+
this.pragmas = pragmas;
|
|
111
|
+
this.weight = weight;
|
|
112
|
+
this.pragma_hashes = pragma_hashes;
|
|
113
|
+
}
|
|
114
|
+
pragma_allows(check) {
|
|
115
|
+
return Object.prototype.hasOwnProperty.call(this.pragmas, check);
|
|
116
|
+
}
|
|
117
|
+
/**
|
|
118
|
+
* Which form of pragma this file carries for `check`.
|
|
119
|
+
*
|
|
120
|
+
* `absent` — no pragma; scan normally.
|
|
121
|
+
* `legacy` — a pragma with no fingerprint. Suppresses the whole file, as it
|
|
122
|
+
* always did, and says so: a key lookup accepts whatever the
|
|
123
|
+
* line is later changed to say, which is the defect being paid
|
|
124
|
+
* down, not a mode anybody should choose.
|
|
125
|
+
* `bound` — a pragma listing fingerprints. Suppresses exactly the matches
|
|
126
|
+
* whose evidence hashes to one of them; anything else survives.
|
|
127
|
+
*/
|
|
128
|
+
pragma_form(check) {
|
|
129
|
+
if (!this.pragma_allows(check)) return "absent";
|
|
130
|
+
return (this.pragma_hashes[check] ?? []).length > 0 ? "bound" : "legacy";
|
|
131
|
+
}
|
|
132
|
+
/**
|
|
133
|
+
* The fingerprint one finding's evidence hashes to.
|
|
134
|
+
*
|
|
135
|
+
* WHAT IS HASHED, and each part earns its place:
|
|
136
|
+
* check id — the same line may legitimately be accepted for one check
|
|
137
|
+
* and not another.
|
|
138
|
+
* rel path — the location IDENTITY. Not the line NUMBER: an unrelated
|
|
139
|
+
* insert above would otherwise break every pragma in the
|
|
140
|
+
* file, and a suppression that breaks on unrelated edits
|
|
141
|
+
* gets deleted rather than re-derived. A `dist/agent-src/`
|
|
142
|
+
* path folds to its `src/` original first, because the
|
|
143
|
+
* projection is byte-exact by contract (ADR-201) and
|
|
144
|
+
* carries the pragma verbatim — without the fold, every
|
|
145
|
+
* migrated pragma would suppress the source and leave its
|
|
146
|
+
* own copy flagged, which is a gate that reds on a file
|
|
147
|
+
* nobody may edit.
|
|
148
|
+
* matched line, — the evidence. ASCII whitespace runs collapse so a reflow
|
|
149
|
+
* normalized does not invalidate the binding, `sha256:<hex>` tokens are
|
|
150
|
+
* removed, and NOTHING else is stripped: the zero-width
|
|
151
|
+
* characters this suite hunts are exactly what a wider
|
|
152
|
+
* normalizer would erase.
|
|
153
|
+
*
|
|
154
|
+
* WHY THE HASH TOKENS COME OUT. A pragma's own reason string can be the
|
|
155
|
+
* matched evidence — two in this tree are, because they quote the very
|
|
156
|
+
* phrases their check detects in order to explain themselves. Without this
|
|
157
|
+
* the fingerprint would have to be computed over a line that already
|
|
158
|
+
* contains it, which has no fixed point: writing the hash changes the line,
|
|
159
|
+
* which changes the hash. Stripping the tokens makes the fingerprint stable
|
|
160
|
+
* under the act of recording it, and costs nothing elsewhere — a `sha256:`
|
|
161
|
+
* token in ordinary prose is not evidence of anything a linter here checks.
|
|
162
|
+
*
|
|
163
|
+
* Deliberately NOT the whole file: incidental content elsewhere would make
|
|
164
|
+
* every unrelated edit re-fire the pragma, which is the noise that produces
|
|
165
|
+
* blanket suppressions.
|
|
166
|
+
*/
|
|
167
|
+
evidence_fingerprint(finding) {
|
|
168
|
+
const text = finding.line > 0 ? this.lines[finding.line - 1] ?? "" : "";
|
|
169
|
+
const normalized = text.replace(/\s*sha256:[0-9a-f]{64}/g, "").replace(/[ \t]+/g, " ").trim();
|
|
170
|
+
return createHash("sha256").update(`${finding.check}
|
|
171
|
+
${source_identity(this.rel)}
|
|
172
|
+
${normalized}`, "utf-8").digest("hex");
|
|
173
|
+
}
|
|
174
|
+
/** Drop the findings this file's BOUND pragma for `check` accepts. */
|
|
175
|
+
filter_bound_pragma(check, findings) {
|
|
176
|
+
const accepted = new Set(this.pragma_hashes[check] ?? []);
|
|
177
|
+
if (accepted.size === 0) return [...findings];
|
|
178
|
+
return findings.filter((f) => !accepted.has(this.evidence_fingerprint(f)));
|
|
179
|
+
}
|
|
180
|
+
/** Yield [lineno, text] honouring the fence masks (mirrors iter_lines). */
|
|
181
|
+
*iter_lines(opts = {}) {
|
|
182
|
+
const skip_example_fence = opts.skip_example_fence ?? true;
|
|
183
|
+
const skip_any_fence = opts.skip_any_fence ?? false;
|
|
184
|
+
for (let i = 1; i <= this.lines.length; i++) {
|
|
185
|
+
if (skip_example_fence && this.in_example_fence[i]) {
|
|
186
|
+
continue;
|
|
187
|
+
}
|
|
188
|
+
if (skip_any_fence && this.in_any_fence[i]) {
|
|
189
|
+
continue;
|
|
190
|
+
}
|
|
191
|
+
yield [i, this.lines[i - 1]];
|
|
192
|
+
}
|
|
193
|
+
}
|
|
194
|
+
};
|
|
195
|
+
function _splitlines(text) {
|
|
196
|
+
const out = [];
|
|
197
|
+
let buf = "";
|
|
198
|
+
const n = text.length;
|
|
199
|
+
let i = 0;
|
|
200
|
+
while (i < n) {
|
|
201
|
+
const ch = text[i];
|
|
202
|
+
const code = text.charCodeAt(i);
|
|
203
|
+
if (code === 13) {
|
|
204
|
+
out.push(buf);
|
|
205
|
+
buf = "";
|
|
206
|
+
if (i + 1 < n && text.charCodeAt(i + 1) === 10) {
|
|
207
|
+
i += 2;
|
|
208
|
+
} else {
|
|
209
|
+
i += 1;
|
|
210
|
+
}
|
|
211
|
+
continue;
|
|
212
|
+
}
|
|
213
|
+
if (code === 10 || code === 11 || code === 12 || code === 28 || code === 29 || code === 30 || code === 133 || code === 8232 || code === 8233) {
|
|
214
|
+
out.push(buf);
|
|
215
|
+
buf = "";
|
|
216
|
+
i += 1;
|
|
217
|
+
continue;
|
|
218
|
+
}
|
|
219
|
+
buf += ch;
|
|
220
|
+
i += 1;
|
|
221
|
+
}
|
|
222
|
+
if (buf !== "") {
|
|
223
|
+
out.push(buf);
|
|
224
|
+
}
|
|
225
|
+
return out;
|
|
226
|
+
}
|
|
227
|
+
function scan_file(filePath) {
|
|
228
|
+
let rel;
|
|
229
|
+
if (path.isAbsolute(filePath)) {
|
|
230
|
+
const root = path.resolve(ROOT);
|
|
231
|
+
const resolved = path.resolve(filePath);
|
|
232
|
+
if (resolved === root || resolved.startsWith(root + path.sep)) {
|
|
233
|
+
rel = path.relative(root, resolved).split(path.sep).join("/");
|
|
234
|
+
} else {
|
|
235
|
+
rel = path.basename(filePath);
|
|
236
|
+
}
|
|
237
|
+
} else {
|
|
238
|
+
rel = filePath.split(path.sep).join("/");
|
|
239
|
+
}
|
|
240
|
+
const raw = fs.readFileSync(filePath, "utf-8");
|
|
241
|
+
const lines = _splitlines(raw);
|
|
242
|
+
const n = lines.length;
|
|
243
|
+
const in_example = new Array(n + 1).fill(false);
|
|
244
|
+
const in_any = new Array(n + 1).fill(false);
|
|
245
|
+
let fence_open = false;
|
|
246
|
+
let fence_marker = "";
|
|
247
|
+
let fence_is_example = false;
|
|
248
|
+
for (let idx = 1; idx <= n; idx++) {
|
|
249
|
+
const text = lines[idx - 1];
|
|
250
|
+
const m = _FENCE.exec(text);
|
|
251
|
+
if (m && !fence_open) {
|
|
252
|
+
fence_open = true;
|
|
253
|
+
fence_marker = m[2][0];
|
|
254
|
+
fence_is_example = m[3] === EXAMPLE_FENCE_LANG;
|
|
255
|
+
in_any[idx] = true;
|
|
256
|
+
in_example[idx] = fence_is_example;
|
|
257
|
+
continue;
|
|
258
|
+
}
|
|
259
|
+
if (fence_open) {
|
|
260
|
+
in_any[idx] = true;
|
|
261
|
+
in_example[idx] = fence_is_example;
|
|
262
|
+
const cm = _FENCE.exec(text);
|
|
263
|
+
if (cm && cm[2][0] === fence_marker && cm[3] === "") {
|
|
264
|
+
fence_open = false;
|
|
265
|
+
fence_is_example = false;
|
|
266
|
+
}
|
|
267
|
+
}
|
|
268
|
+
}
|
|
269
|
+
const pragmas = {};
|
|
270
|
+
const pragmaHashes = {};
|
|
271
|
+
for (const text of lines) {
|
|
272
|
+
_PRAGMA.lastIndex = 0;
|
|
273
|
+
let m;
|
|
274
|
+
while ((m = _PRAGMA.exec(text)) !== null) {
|
|
275
|
+
const check = m[1];
|
|
276
|
+
pragmas[check] = m[2];
|
|
277
|
+
const hashes = (m[3] ?? "").match(/sha256:([0-9a-f]{64})/g) ?? [];
|
|
278
|
+
pragmaHashes[check] = [
|
|
279
|
+
...pragmaHashes[check] ?? [],
|
|
280
|
+
...hashes.map((h) => h.slice("sha256:".length))
|
|
281
|
+
];
|
|
282
|
+
if (m.index === _PRAGMA.lastIndex) {
|
|
283
|
+
_PRAGMA.lastIndex += 1;
|
|
284
|
+
}
|
|
285
|
+
}
|
|
286
|
+
}
|
|
287
|
+
return new ScannedFile(
|
|
288
|
+
filePath,
|
|
289
|
+
rel,
|
|
290
|
+
lines,
|
|
291
|
+
in_example,
|
|
292
|
+
in_any,
|
|
293
|
+
pragmas,
|
|
294
|
+
path_weight(rel),
|
|
295
|
+
pragmaHashes
|
|
296
|
+
);
|
|
297
|
+
}
|
|
298
|
+
function scan_path(filePath, base) {
|
|
299
|
+
const sf = scan_file(filePath);
|
|
300
|
+
let rel;
|
|
301
|
+
const baseResolved = path.resolve(base);
|
|
302
|
+
const fileResolved = path.resolve(filePath);
|
|
303
|
+
if (fileResolved === baseResolved || fileResolved.startsWith(baseResolved + path.sep)) {
|
|
304
|
+
rel = path.relative(baseResolved, fileResolved).split(path.sep).join("/");
|
|
305
|
+
} else {
|
|
306
|
+
rel = path.basename(filePath);
|
|
307
|
+
}
|
|
308
|
+
return new ScannedFile(
|
|
309
|
+
sf.path,
|
|
310
|
+
rel,
|
|
311
|
+
sf.lines,
|
|
312
|
+
sf.in_example_fence,
|
|
313
|
+
sf.in_any_fence,
|
|
314
|
+
sf.pragmas,
|
|
315
|
+
path_weight(rel),
|
|
316
|
+
sf.pragma_hashes
|
|
317
|
+
);
|
|
318
|
+
}
|
|
319
|
+
function _rglobSorted(base) {
|
|
320
|
+
const found = [];
|
|
321
|
+
const walk = (dir) => {
|
|
322
|
+
let entries;
|
|
323
|
+
try {
|
|
324
|
+
entries = fs.readdirSync(dir, { withFileTypes: true });
|
|
325
|
+
} catch {
|
|
326
|
+
return;
|
|
327
|
+
}
|
|
328
|
+
for (const e of entries) {
|
|
329
|
+
const full = path.join(dir, e.name);
|
|
330
|
+
found.push(full);
|
|
331
|
+
if (e.isDirectory()) {
|
|
332
|
+
walk(full);
|
|
333
|
+
}
|
|
334
|
+
}
|
|
335
|
+
};
|
|
336
|
+
walk(base);
|
|
337
|
+
found.sort((a, b) => _pathCmp(a, b));
|
|
338
|
+
return found;
|
|
339
|
+
}
|
|
340
|
+
function _pathCmp(a, b) {
|
|
341
|
+
const pa = a.split(path.sep);
|
|
342
|
+
const pb = b.split(path.sep);
|
|
343
|
+
const len = Math.min(pa.length, pb.length);
|
|
344
|
+
for (let i = 0; i < len; i++) {
|
|
345
|
+
if (pa[i] < pb[i]) return -1;
|
|
346
|
+
if (pa[i] > pb[i]) return 1;
|
|
347
|
+
}
|
|
348
|
+
if (pa.length < pb.length) return -1;
|
|
349
|
+
if (pa.length > pb.length) return 1;
|
|
350
|
+
return 0;
|
|
351
|
+
}
|
|
352
|
+
function* iter_corpus(roots = DEFAULT_SCAN_ROOTS, exts = [".md"], scanBase = ROOT) {
|
|
353
|
+
for (const root of roots) {
|
|
354
|
+
const base = path.join(scanBase, root);
|
|
355
|
+
if (!fs.existsSync(base)) {
|
|
356
|
+
continue;
|
|
357
|
+
}
|
|
358
|
+
for (const p of _rglobSorted(base)) {
|
|
359
|
+
let st;
|
|
360
|
+
try {
|
|
361
|
+
st = fs.statSync(p);
|
|
362
|
+
} catch {
|
|
363
|
+
continue;
|
|
364
|
+
}
|
|
365
|
+
if (st.isFile() && exts.includes(path.extname(p))) {
|
|
366
|
+
yield scan_path(p, scanBase);
|
|
367
|
+
}
|
|
368
|
+
}
|
|
369
|
+
}
|
|
370
|
+
}
|
|
371
|
+
var LEGACY_PRAGMA_CHECK = "legacy-pragma";
|
|
372
|
+
function legacy_pragma_finding(sf, check) {
|
|
373
|
+
return new Finding(
|
|
374
|
+
sf.rel,
|
|
375
|
+
0,
|
|
376
|
+
LEGACY_PRAGMA_CHECK,
|
|
377
|
+
"LOW",
|
|
378
|
+
`unbound \`security-lint: allow ${check}\` pragma suppresses this whole file \u2014 it accepts whatever the matched text is later changed to say. Bind it with \`sha256:<hex>\` per accepted match.`,
|
|
379
|
+
sf.weight
|
|
380
|
+
);
|
|
381
|
+
}
|
|
382
|
+
var CHILD_SCANNED_PREFIX = "scanned: ";
|
|
383
|
+
function report_child_scanned(count) {
|
|
384
|
+
process.stderr.write(`${CHILD_SCANNED_PREFIX}${String(count)}
|
|
385
|
+
`);
|
|
386
|
+
}
|
|
387
|
+
function report(findings, opts) {
|
|
388
|
+
const check_label = opts.check_label;
|
|
389
|
+
if (findings.length === 0) {
|
|
390
|
+
process.stdout.write(`\u2705 ${check_label}: clean (${_corpus_note(opts.scanned_roots)}).
|
|
391
|
+
`);
|
|
392
|
+
return 0;
|
|
393
|
+
}
|
|
394
|
+
const fails = findings.filter((f) => f.is_fail);
|
|
395
|
+
const warns = findings.filter((f) => !f.is_fail);
|
|
396
|
+
const sorted = [...findings].sort((a, b) => {
|
|
397
|
+
const ra = -(SEVERITY_RANK[a.severity] ?? 0);
|
|
398
|
+
const rb = -(SEVERITY_RANK[b.severity] ?? 0);
|
|
399
|
+
if (ra !== rb) return ra - rb;
|
|
400
|
+
if (a.path < b.path) return -1;
|
|
401
|
+
if (a.path > b.path) return 1;
|
|
402
|
+
return a.line - b.line;
|
|
403
|
+
});
|
|
404
|
+
for (const f of sorted) {
|
|
405
|
+
const glyph = f.is_fail ? "\u{1F534}" : "\u26A0\uFE0F";
|
|
406
|
+
const loc = f.line ? `${f.path}:${f.line}` : f.path;
|
|
407
|
+
const wnote = f.weight >= 1 ? "" : ` (weight ${_pyG(f.weight)})`;
|
|
408
|
+
process.stdout.write(` ${glyph} [${f.severity}] ${f.check} \u2014 ${loc}${wnote}: ${f.message}
|
|
409
|
+
`);
|
|
410
|
+
}
|
|
411
|
+
process.stdout.write("\n");
|
|
412
|
+
if (fails.length > 0) {
|
|
413
|
+
process.stdout.write(
|
|
414
|
+
`\u274C ${check_label}: ${fails.length} blocking finding(s), ${warns.length} warning(s). Fix, or mark a true teaching example with a \`\`\`${EXAMPLE_FENCE_LANG} fence or a \`security-lint: allow\` pragma.
|
|
415
|
+
`
|
|
416
|
+
);
|
|
417
|
+
return 1;
|
|
418
|
+
}
|
|
419
|
+
process.stdout.write(`\u26A0\uFE0F ${check_label}: ${warns.length} warning(s), 0 blocking.
|
|
420
|
+
`);
|
|
421
|
+
return 0;
|
|
422
|
+
}
|
|
423
|
+
function _pyG(n) {
|
|
424
|
+
let s = n.toPrecision(6);
|
|
425
|
+
if (s.includes(".")) {
|
|
426
|
+
s = s.replace(/0+$/, "").replace(/\.$/, "");
|
|
427
|
+
}
|
|
428
|
+
return s;
|
|
429
|
+
}
|
|
430
|
+
function _corpus_note(scanned_roots) {
|
|
431
|
+
const roots = scanned_roots && scanned_roots.length > 0 ? scanned_roots : DEFAULT_SCAN_ROOTS;
|
|
432
|
+
return "scanned " + roots.join(", ");
|
|
433
|
+
}
|
|
434
|
+
var PyFloat = class {
|
|
435
|
+
constructor(value) {
|
|
436
|
+
this.value = value;
|
|
437
|
+
}
|
|
438
|
+
value;
|
|
439
|
+
};
|
|
440
|
+
function _isPlainObject(v) {
|
|
441
|
+
return typeof v === "object" && v !== null && !Array.isArray(v) && !(v instanceof PyFloat);
|
|
442
|
+
}
|
|
443
|
+
function _pyJsonStr(s) {
|
|
444
|
+
let out = '"';
|
|
445
|
+
for (const ch of s) {
|
|
446
|
+
const code = ch.codePointAt(0);
|
|
447
|
+
switch (ch) {
|
|
448
|
+
case '"':
|
|
449
|
+
out += '\\"';
|
|
450
|
+
break;
|
|
451
|
+
case "\\":
|
|
452
|
+
out += "\\\\";
|
|
453
|
+
break;
|
|
454
|
+
case "\n":
|
|
455
|
+
out += "\\n";
|
|
456
|
+
break;
|
|
457
|
+
case "\r":
|
|
458
|
+
out += "\\r";
|
|
459
|
+
break;
|
|
460
|
+
case " ":
|
|
461
|
+
out += "\\t";
|
|
462
|
+
break;
|
|
463
|
+
case "\b":
|
|
464
|
+
out += "\\b";
|
|
465
|
+
break;
|
|
466
|
+
case "\f":
|
|
467
|
+
out += "\\f";
|
|
468
|
+
break;
|
|
469
|
+
default:
|
|
470
|
+
if (code < 32) {
|
|
471
|
+
out += `\\u${code.toString(16).padStart(4, "0")}`;
|
|
472
|
+
} else {
|
|
473
|
+
out += ch;
|
|
474
|
+
}
|
|
475
|
+
}
|
|
476
|
+
}
|
|
477
|
+
return out + '"';
|
|
478
|
+
}
|
|
479
|
+
function _pyJsonFloat(n) {
|
|
480
|
+
if (!Number.isFinite(n)) {
|
|
481
|
+
if (Number.isNaN(n)) return "NaN";
|
|
482
|
+
return n > 0 ? "Infinity" : "-Infinity";
|
|
483
|
+
}
|
|
484
|
+
return Number.isInteger(n) ? `${n}.0` : String(n);
|
|
485
|
+
}
|
|
486
|
+
function _pyJsonScalar(value) {
|
|
487
|
+
if (value === null || value === void 0) return "null";
|
|
488
|
+
if (typeof value === "boolean") return value ? "true" : "false";
|
|
489
|
+
if (value instanceof PyFloat) return _pyJsonFloat(value.value);
|
|
490
|
+
if (typeof value === "number") {
|
|
491
|
+
if (!Number.isFinite(value)) {
|
|
492
|
+
if (Number.isNaN(value)) return "NaN";
|
|
493
|
+
return value > 0 ? "Infinity" : "-Infinity";
|
|
494
|
+
}
|
|
495
|
+
return String(value);
|
|
496
|
+
}
|
|
497
|
+
if (typeof value === "string") return _pyJsonStr(value);
|
|
498
|
+
return null;
|
|
499
|
+
}
|
|
500
|
+
function py_json_dumps_indent2(value, depth = 0) {
|
|
501
|
+
return _dumpIndent2(value, depth, true);
|
|
502
|
+
}
|
|
503
|
+
function _dumpIndent2(value, depth, ensureAscii) {
|
|
504
|
+
const scalar = ensureAscii ? _pyJsonScalarAscii(value) : _pyJsonScalar(value);
|
|
505
|
+
if (scalar !== null) {
|
|
506
|
+
return scalar;
|
|
507
|
+
}
|
|
508
|
+
const pad = " ".repeat(2 * (depth + 1));
|
|
509
|
+
const closePad = " ".repeat(2 * depth);
|
|
510
|
+
if (Array.isArray(value)) {
|
|
511
|
+
if (value.length === 0) return "[]";
|
|
512
|
+
const items = value.map((v) => pad + _dumpIndent2(v, depth + 1, ensureAscii));
|
|
513
|
+
return `[
|
|
514
|
+
${items.join(",\n")}
|
|
515
|
+
${closePad}]`;
|
|
516
|
+
}
|
|
517
|
+
if (_isPlainObject(value)) {
|
|
518
|
+
const keys = Object.keys(value);
|
|
519
|
+
if (keys.length === 0) return "{}";
|
|
520
|
+
const items = keys.map(
|
|
521
|
+
(k) => `${pad}${ensureAscii ? _pyJsonStrAscii(k) : _pyJsonStr(k)}: ${_dumpIndent2(value[k], depth + 1, ensureAscii)}`
|
|
522
|
+
);
|
|
523
|
+
return `{
|
|
524
|
+
${items.join(",\n")}
|
|
525
|
+
${closePad}}`;
|
|
526
|
+
}
|
|
527
|
+
return ensureAscii ? _pyJsonStrAscii(String(value)) : _pyJsonStr(String(value));
|
|
528
|
+
}
|
|
529
|
+
function _pyJsonStrAscii(s) {
|
|
530
|
+
let out = '"';
|
|
531
|
+
for (const ch of s) {
|
|
532
|
+
const code = ch.codePointAt(0);
|
|
533
|
+
switch (ch) {
|
|
534
|
+
case '"':
|
|
535
|
+
out += '\\"';
|
|
536
|
+
break;
|
|
537
|
+
case "\\":
|
|
538
|
+
out += "\\\\";
|
|
539
|
+
break;
|
|
540
|
+
case "\n":
|
|
541
|
+
out += "\\n";
|
|
542
|
+
break;
|
|
543
|
+
case "\r":
|
|
544
|
+
out += "\\r";
|
|
545
|
+
break;
|
|
546
|
+
case " ":
|
|
547
|
+
out += "\\t";
|
|
548
|
+
break;
|
|
549
|
+
case "\b":
|
|
550
|
+
out += "\\b";
|
|
551
|
+
break;
|
|
552
|
+
case "\f":
|
|
553
|
+
out += "\\f";
|
|
554
|
+
break;
|
|
555
|
+
default:
|
|
556
|
+
if (code < 32) {
|
|
557
|
+
out += `\\u${code.toString(16).padStart(4, "0")}`;
|
|
558
|
+
} else if (code < 127) {
|
|
559
|
+
out += ch;
|
|
560
|
+
} else if (code <= 65535) {
|
|
561
|
+
out += `\\u${code.toString(16).padStart(4, "0")}`;
|
|
562
|
+
} else {
|
|
563
|
+
const cp = code - 65536;
|
|
564
|
+
const hi = 55296 + (cp >> 10);
|
|
565
|
+
const lo = 56320 + (cp & 1023);
|
|
566
|
+
out += `\\u${hi.toString(16).padStart(4, "0")}\\u${lo.toString(16).padStart(4, "0")}`;
|
|
567
|
+
}
|
|
568
|
+
}
|
|
569
|
+
}
|
|
570
|
+
return out + '"';
|
|
571
|
+
}
|
|
572
|
+
function _pyJsonScalarAscii(value) {
|
|
573
|
+
if (typeof value === "string") return _pyJsonStrAscii(value);
|
|
574
|
+
return _pyJsonScalar(value);
|
|
575
|
+
}
|
|
576
|
+
|
|
577
|
+
// src/scripts/lint_hidden_unicode.ts
|
|
578
|
+
import * as fs2 from "node:fs";
|
|
579
|
+
import * as path2 from "node:path";
|
|
580
|
+
import { spawnSync } from "node:child_process";
|
|
581
|
+
import { fileURLToPath as fileURLToPath2, pathToFileURL } from "node:url";
|
|
582
|
+
var CHECK = "hidden-unicode";
|
|
583
|
+
var _BIDI = /* @__PURE__ */ new Set([
|
|
584
|
+
8234,
|
|
585
|
+
8235,
|
|
586
|
+
8236,
|
|
587
|
+
8237,
|
|
588
|
+
8238,
|
|
589
|
+
8294,
|
|
590
|
+
8295,
|
|
591
|
+
8296,
|
|
592
|
+
8297,
|
|
593
|
+
8206,
|
|
594
|
+
8207,
|
|
595
|
+
1564
|
|
596
|
+
]);
|
|
597
|
+
var _ZERO_WIDTH = /* @__PURE__ */ new Set([8203, 8204, 8205, 8288, 65279, 173]);
|
|
598
|
+
var _DEPRECATED = /* @__PURE__ */ new Set([
|
|
599
|
+
8298,
|
|
600
|
+
8299,
|
|
601
|
+
8300,
|
|
602
|
+
8301,
|
|
603
|
+
8302,
|
|
604
|
+
8303,
|
|
605
|
+
65529,
|
|
606
|
+
65530,
|
|
607
|
+
65531
|
|
608
|
+
]);
|
|
609
|
+
function _classify(cp) {
|
|
610
|
+
if (_BIDI.has(cp)) {
|
|
611
|
+
return "bidi-control";
|
|
612
|
+
}
|
|
613
|
+
if (_ZERO_WIDTH.has(cp)) {
|
|
614
|
+
return "zero-width";
|
|
615
|
+
}
|
|
616
|
+
if (cp >= 917504 && cp <= 917631) {
|
|
617
|
+
return "unicode-tag";
|
|
618
|
+
}
|
|
619
|
+
if (_DEPRECATED.has(cp)) {
|
|
620
|
+
return "deprecated-format";
|
|
621
|
+
}
|
|
622
|
+
if (cp >= 57344 && cp <= 63743 || cp >= 983040 && cp <= 1048573 || cp >= 1048576 && cp <= 1114109) {
|
|
623
|
+
return "private-use-area";
|
|
624
|
+
}
|
|
625
|
+
if ((cp >= 0 && cp <= 31 || cp >= 127 && cp <= 159) && cp !== 9 && cp !== 10 && cp !== 13) {
|
|
626
|
+
return "control-char";
|
|
627
|
+
}
|
|
628
|
+
return null;
|
|
629
|
+
}
|
|
630
|
+
var _VS_LO = 917760;
|
|
631
|
+
var _VS_HI = 917999;
|
|
632
|
+
function _isVS(cp) {
|
|
633
|
+
return cp >= _VS_LO && cp <= _VS_HI;
|
|
634
|
+
}
|
|
635
|
+
var _CP_NAME = {
|
|
636
|
+
173: "SOFT HYPHEN",
|
|
637
|
+
1564: "ARABIC LETTER MARK",
|
|
638
|
+
8203: "ZERO WIDTH SPACE",
|
|
639
|
+
8204: "ZERO WIDTH NON-JOINER",
|
|
640
|
+
8205: "ZERO WIDTH JOINER",
|
|
641
|
+
8206: "LEFT-TO-RIGHT MARK",
|
|
642
|
+
8207: "RIGHT-TO-LEFT MARK",
|
|
643
|
+
8234: "LEFT-TO-RIGHT EMBEDDING",
|
|
644
|
+
8235: "RIGHT-TO-LEFT EMBEDDING",
|
|
645
|
+
8236: "POP DIRECTIONAL FORMATTING",
|
|
646
|
+
8237: "LEFT-TO-RIGHT OVERRIDE",
|
|
647
|
+
8238: "RIGHT-TO-LEFT OVERRIDE",
|
|
648
|
+
8288: "WORD JOINER",
|
|
649
|
+
8294: "LEFT-TO-RIGHT ISOLATE",
|
|
650
|
+
8295: "RIGHT-TO-LEFT ISOLATE",
|
|
651
|
+
8296: "FIRST STRONG ISOLATE",
|
|
652
|
+
8297: "POP DIRECTIONAL ISOLATE",
|
|
653
|
+
8298: "INHIBIT SYMMETRIC SWAPPING",
|
|
654
|
+
8299: "ACTIVATE SYMMETRIC SWAPPING",
|
|
655
|
+
8300: "INHIBIT ARABIC FORM SHAPING",
|
|
656
|
+
8301: "ACTIVATE ARABIC FORM SHAPING",
|
|
657
|
+
8302: "NATIONAL DIGIT SHAPES",
|
|
658
|
+
8303: "NOMINAL DIGIT SHAPES",
|
|
659
|
+
65279: "ZERO WIDTH NO-BREAK SPACE",
|
|
660
|
+
65529: "INTERLINEAR ANNOTATION ANCHOR",
|
|
661
|
+
65530: "INTERLINEAR ANNOTATION SEPARATOR",
|
|
662
|
+
65531: "INTERLINEAR ANNOTATION TERMINATOR",
|
|
663
|
+
917505: "LANGUAGE TAG",
|
|
664
|
+
917536: "TAG SPACE",
|
|
665
|
+
917537: "TAG EXCLAMATION MARK",
|
|
666
|
+
917538: "TAG QUOTATION MARK",
|
|
667
|
+
917539: "TAG NUMBER SIGN",
|
|
668
|
+
917540: "TAG DOLLAR SIGN",
|
|
669
|
+
917541: "TAG PERCENT SIGN",
|
|
670
|
+
917542: "TAG AMPERSAND",
|
|
671
|
+
917543: "TAG APOSTROPHE",
|
|
672
|
+
917544: "TAG LEFT PARENTHESIS",
|
|
673
|
+
917545: "TAG RIGHT PARENTHESIS",
|
|
674
|
+
917546: "TAG ASTERISK",
|
|
675
|
+
917547: "TAG PLUS SIGN",
|
|
676
|
+
917548: "TAG COMMA",
|
|
677
|
+
917549: "TAG HYPHEN-MINUS",
|
|
678
|
+
917550: "TAG FULL STOP",
|
|
679
|
+
917551: "TAG SOLIDUS",
|
|
680
|
+
917552: "TAG DIGIT ZERO",
|
|
681
|
+
917553: "TAG DIGIT ONE",
|
|
682
|
+
917554: "TAG DIGIT TWO",
|
|
683
|
+
917555: "TAG DIGIT THREE",
|
|
684
|
+
917556: "TAG DIGIT FOUR",
|
|
685
|
+
917557: "TAG DIGIT FIVE",
|
|
686
|
+
917558: "TAG DIGIT SIX",
|
|
687
|
+
917559: "TAG DIGIT SEVEN",
|
|
688
|
+
917560: "TAG DIGIT EIGHT",
|
|
689
|
+
917561: "TAG DIGIT NINE",
|
|
690
|
+
917562: "TAG COLON",
|
|
691
|
+
917563: "TAG SEMICOLON",
|
|
692
|
+
917564: "TAG LESS-THAN SIGN",
|
|
693
|
+
917565: "TAG EQUALS SIGN",
|
|
694
|
+
917566: "TAG GREATER-THAN SIGN",
|
|
695
|
+
917567: "TAG QUESTION MARK",
|
|
696
|
+
917568: "TAG COMMERCIAL AT",
|
|
697
|
+
917569: "TAG LATIN CAPITAL LETTER A",
|
|
698
|
+
917570: "TAG LATIN CAPITAL LETTER B",
|
|
699
|
+
917571: "TAG LATIN CAPITAL LETTER C",
|
|
700
|
+
917572: "TAG LATIN CAPITAL LETTER D",
|
|
701
|
+
917573: "TAG LATIN CAPITAL LETTER E",
|
|
702
|
+
917574: "TAG LATIN CAPITAL LETTER F",
|
|
703
|
+
917575: "TAG LATIN CAPITAL LETTER G",
|
|
704
|
+
917576: "TAG LATIN CAPITAL LETTER H",
|
|
705
|
+
917577: "TAG LATIN CAPITAL LETTER I",
|
|
706
|
+
917578: "TAG LATIN CAPITAL LETTER J",
|
|
707
|
+
917579: "TAG LATIN CAPITAL LETTER K",
|
|
708
|
+
917580: "TAG LATIN CAPITAL LETTER L",
|
|
709
|
+
917581: "TAG LATIN CAPITAL LETTER M",
|
|
710
|
+
917582: "TAG LATIN CAPITAL LETTER N",
|
|
711
|
+
917583: "TAG LATIN CAPITAL LETTER O",
|
|
712
|
+
917584: "TAG LATIN CAPITAL LETTER P",
|
|
713
|
+
917585: "TAG LATIN CAPITAL LETTER Q",
|
|
714
|
+
917586: "TAG LATIN CAPITAL LETTER R",
|
|
715
|
+
917587: "TAG LATIN CAPITAL LETTER S",
|
|
716
|
+
917588: "TAG LATIN CAPITAL LETTER T",
|
|
717
|
+
917589: "TAG LATIN CAPITAL LETTER U",
|
|
718
|
+
917590: "TAG LATIN CAPITAL LETTER V",
|
|
719
|
+
917591: "TAG LATIN CAPITAL LETTER W",
|
|
720
|
+
917592: "TAG LATIN CAPITAL LETTER X",
|
|
721
|
+
917593: "TAG LATIN CAPITAL LETTER Y",
|
|
722
|
+
917594: "TAG LATIN CAPITAL LETTER Z",
|
|
723
|
+
917595: "TAG LEFT SQUARE BRACKET",
|
|
724
|
+
917596: "TAG REVERSE SOLIDUS",
|
|
725
|
+
917597: "TAG RIGHT SQUARE BRACKET",
|
|
726
|
+
917598: "TAG CIRCUMFLEX ACCENT",
|
|
727
|
+
917599: "TAG LOW LINE",
|
|
728
|
+
917600: "TAG GRAVE ACCENT",
|
|
729
|
+
917601: "TAG LATIN SMALL LETTER A",
|
|
730
|
+
917602: "TAG LATIN SMALL LETTER B",
|
|
731
|
+
917603: "TAG LATIN SMALL LETTER C",
|
|
732
|
+
917604: "TAG LATIN SMALL LETTER D",
|
|
733
|
+
917605: "TAG LATIN SMALL LETTER E",
|
|
734
|
+
917606: "TAG LATIN SMALL LETTER F",
|
|
735
|
+
917607: "TAG LATIN SMALL LETTER G",
|
|
736
|
+
917608: "TAG LATIN SMALL LETTER H",
|
|
737
|
+
917609: "TAG LATIN SMALL LETTER I",
|
|
738
|
+
917610: "TAG LATIN SMALL LETTER J",
|
|
739
|
+
917611: "TAG LATIN SMALL LETTER K",
|
|
740
|
+
917612: "TAG LATIN SMALL LETTER L",
|
|
741
|
+
917613: "TAG LATIN SMALL LETTER M",
|
|
742
|
+
917614: "TAG LATIN SMALL LETTER N",
|
|
743
|
+
917615: "TAG LATIN SMALL LETTER O",
|
|
744
|
+
917616: "TAG LATIN SMALL LETTER P",
|
|
745
|
+
917617: "TAG LATIN SMALL LETTER Q",
|
|
746
|
+
917618: "TAG LATIN SMALL LETTER R",
|
|
747
|
+
917619: "TAG LATIN SMALL LETTER S",
|
|
748
|
+
917620: "TAG LATIN SMALL LETTER T",
|
|
749
|
+
917621: "TAG LATIN SMALL LETTER U",
|
|
750
|
+
917622: "TAG LATIN SMALL LETTER V",
|
|
751
|
+
917623: "TAG LATIN SMALL LETTER W",
|
|
752
|
+
917624: "TAG LATIN SMALL LETTER X",
|
|
753
|
+
917625: "TAG LATIN SMALL LETTER Y",
|
|
754
|
+
917626: "TAG LATIN SMALL LETTER Z",
|
|
755
|
+
917627: "TAG LEFT CURLY BRACKET",
|
|
756
|
+
917628: "TAG VERTICAL LINE",
|
|
757
|
+
917629: "TAG RIGHT CURLY BRACKET",
|
|
758
|
+
917630: "TAG TILDE",
|
|
759
|
+
917631: "CANCEL TAG"
|
|
760
|
+
};
|
|
761
|
+
function _cpName(cp) {
|
|
762
|
+
const n = _CP_NAME[cp];
|
|
763
|
+
return n !== void 0 ? n : "<unnamed>";
|
|
764
|
+
}
|
|
765
|
+
function _scan(sf) {
|
|
766
|
+
const pragmaForm = sf.pragma_form(CHECK);
|
|
767
|
+
if (pragmaForm === "legacy") {
|
|
768
|
+
return [legacy_pragma_finding(sf, CHECK)];
|
|
769
|
+
}
|
|
770
|
+
const finish = (hits) => pragmaForm === "bound" ? sf.filter_bound_pragma(CHECK, hits) : hits;
|
|
771
|
+
const out = [];
|
|
772
|
+
for (const [lineno, text] of sf.iter_lines({ skip_example_fence: true })) {
|
|
773
|
+
let vs_run = 0;
|
|
774
|
+
for (const ch of text) {
|
|
775
|
+
const cp = ch.codePointAt(0);
|
|
776
|
+
if (_isVS(cp)) {
|
|
777
|
+
vs_run += 1;
|
|
778
|
+
continue;
|
|
779
|
+
}
|
|
780
|
+
const kind = _classify(cp);
|
|
781
|
+
if (kind) {
|
|
782
|
+
const name = _cpName(cp);
|
|
783
|
+
out.push(
|
|
784
|
+
new Finding(
|
|
785
|
+
sf.rel,
|
|
786
|
+
lineno,
|
|
787
|
+
CHECK,
|
|
788
|
+
"HIGH",
|
|
789
|
+
`${kind} U+${cp.toString(16).toUpperCase().padStart(4, "0")} (${name})`,
|
|
790
|
+
sf.weight
|
|
791
|
+
)
|
|
792
|
+
);
|
|
793
|
+
}
|
|
794
|
+
}
|
|
795
|
+
if (vs_run >= 3) {
|
|
796
|
+
out.push(
|
|
797
|
+
new Finding(
|
|
798
|
+
sf.rel,
|
|
799
|
+
lineno,
|
|
800
|
+
CHECK,
|
|
801
|
+
"HIGH",
|
|
802
|
+
`variation-selector run x${vs_run} (steganography signature)`,
|
|
803
|
+
sf.weight
|
|
804
|
+
)
|
|
805
|
+
);
|
|
806
|
+
}
|
|
807
|
+
}
|
|
808
|
+
return finish(out);
|
|
809
|
+
}
|
|
810
|
+
var _BINARY_EXT = /* @__PURE__ */ new Set([
|
|
811
|
+
"png",
|
|
812
|
+
"jpg",
|
|
813
|
+
"jpeg",
|
|
814
|
+
"gif",
|
|
815
|
+
"ico",
|
|
816
|
+
"webp",
|
|
817
|
+
"avif",
|
|
818
|
+
"bmp",
|
|
819
|
+
"tiff",
|
|
820
|
+
"pdf",
|
|
821
|
+
"woff",
|
|
822
|
+
"woff2",
|
|
823
|
+
"ttf",
|
|
824
|
+
"otf",
|
|
825
|
+
"eot",
|
|
826
|
+
"zip",
|
|
827
|
+
"gz",
|
|
828
|
+
"tgz",
|
|
829
|
+
"bz2",
|
|
830
|
+
"xz",
|
|
831
|
+
"zst",
|
|
832
|
+
"7z",
|
|
833
|
+
"rar",
|
|
834
|
+
"wasm",
|
|
835
|
+
"mp3",
|
|
836
|
+
"mp4",
|
|
837
|
+
"wav",
|
|
838
|
+
"ogg",
|
|
839
|
+
"webm",
|
|
840
|
+
"mov",
|
|
841
|
+
"avi",
|
|
842
|
+
"so",
|
|
843
|
+
"dylib",
|
|
844
|
+
"dll",
|
|
845
|
+
"node",
|
|
846
|
+
"a",
|
|
847
|
+
"o",
|
|
848
|
+
"exe",
|
|
849
|
+
"jar",
|
|
850
|
+
"class",
|
|
851
|
+
"pyc",
|
|
852
|
+
"pyo",
|
|
853
|
+
"db",
|
|
854
|
+
"sqlite",
|
|
855
|
+
"sqlite3",
|
|
856
|
+
"bin",
|
|
857
|
+
"dat",
|
|
858
|
+
"docx",
|
|
859
|
+
"xlsx",
|
|
860
|
+
"pptx",
|
|
861
|
+
"doc",
|
|
862
|
+
"xls",
|
|
863
|
+
"ppt",
|
|
864
|
+
"odt",
|
|
865
|
+
"ods",
|
|
866
|
+
"odp"
|
|
867
|
+
]);
|
|
868
|
+
var _GENERATED_PREFIXES = [
|
|
869
|
+
"dist/",
|
|
870
|
+
".augment/",
|
|
871
|
+
".claude/",
|
|
872
|
+
".cursor/",
|
|
873
|
+
".clinerules/",
|
|
874
|
+
// Verbatim captured tool output, not authored text. These are RECORDINGS:
|
|
875
|
+
// escaping a control byte inside one would falsify the record it exists to
|
|
876
|
+
// preserve. If a capture needs to stay grep-readable, the fix is to escape
|
|
877
|
+
// at capture time — never to edit the artifact afterwards.
|
|
878
|
+
"agents/evidence/analysis/",
|
|
879
|
+
// The frozen hostile-encoding corpus. Its positives ARE raw control
|
|
880
|
+
// characters — that is the thing under test, so flagging them would demand
|
|
881
|
+
// escaping the fixtures and thereby delete the test. The corpus has its own,
|
|
882
|
+
// stronger integrity controls: a sha256 freeze plus a scope-guard test that
|
|
883
|
+
// is itself falsified by an out-of-scope fixture. Excluded here because it
|
|
884
|
+
// is DATA, on the same principle as the captures above.
|
|
885
|
+
"internal/bench/corpora/encoding-channels/"
|
|
886
|
+
];
|
|
887
|
+
function _isGenerated(rel) {
|
|
888
|
+
return _GENERATED_PREFIXES.some((p) => rel.startsWith(p));
|
|
889
|
+
}
|
|
890
|
+
function _isRawControlCodepoint(cp) {
|
|
891
|
+
if (cp === 9 || cp === 10 || cp === 13) return false;
|
|
892
|
+
return cp <= 31 || cp >= 127 && cp <= 159;
|
|
893
|
+
}
|
|
894
|
+
function _extensionOf(rel) {
|
|
895
|
+
const base = rel.slice(rel.lastIndexOf("/") + 1);
|
|
896
|
+
const dot = base.lastIndexOf(".");
|
|
897
|
+
return dot <= 0 ? "" : base.slice(dot + 1).toLowerCase();
|
|
898
|
+
}
|
|
899
|
+
function _trackedFiles() {
|
|
900
|
+
const res = spawnSync("git", ["ls-files", "-z"], {
|
|
901
|
+
cwd: ROOT,
|
|
902
|
+
maxBuffer: 64 * 1024 * 1024
|
|
903
|
+
});
|
|
904
|
+
if (res.status !== 0 || res.stdout === null) return null;
|
|
905
|
+
return res.stdout.toString("utf-8").split("\0").filter((p) => p !== "");
|
|
906
|
+
}
|
|
907
|
+
function _eligibleSourceFiles() {
|
|
908
|
+
const tracked = _trackedFiles();
|
|
909
|
+
if (tracked === null) return null;
|
|
910
|
+
return tracked.filter((rel) => !_isGenerated(rel) && !_BINARY_EXT.has(_extensionOf(rel)));
|
|
911
|
+
}
|
|
912
|
+
function _scanSourceControlBytes(files = null) {
|
|
913
|
+
const tracked = files ?? _eligibleSourceFiles();
|
|
914
|
+
if (tracked === null) return [];
|
|
915
|
+
const out = [];
|
|
916
|
+
for (const rel of tracked) {
|
|
917
|
+
if (_isGenerated(rel)) continue;
|
|
918
|
+
if (_BINARY_EXT.has(_extensionOf(rel))) continue;
|
|
919
|
+
let data;
|
|
920
|
+
try {
|
|
921
|
+
data = fs2.readFileSync(path2.join(ROOT, rel));
|
|
922
|
+
} catch {
|
|
923
|
+
continue;
|
|
924
|
+
}
|
|
925
|
+
try {
|
|
926
|
+
new TextDecoder("utf-8", { fatal: true }).decode(data);
|
|
927
|
+
} catch {
|
|
928
|
+
continue;
|
|
929
|
+
}
|
|
930
|
+
let line = 1;
|
|
931
|
+
for (const ch of data.toString("utf-8")) {
|
|
932
|
+
const cp = ch.codePointAt(0);
|
|
933
|
+
if (cp === void 0) continue;
|
|
934
|
+
if (cp === 10) {
|
|
935
|
+
line += 1;
|
|
936
|
+
continue;
|
|
937
|
+
}
|
|
938
|
+
if (!_isRawControlCodepoint(cp)) continue;
|
|
939
|
+
const hex = cp.toString(16).toUpperCase().padStart(2, "0");
|
|
940
|
+
out.push(
|
|
941
|
+
new Finding(
|
|
942
|
+
rel,
|
|
943
|
+
line,
|
|
944
|
+
CHECK,
|
|
945
|
+
"HIGH",
|
|
946
|
+
`raw control character U+${hex} in a text source \u2014 makes grep/file(1)/git treat this file as binary and skip it silently; use the language escape (e.g. \\0, \\x7F) instead`,
|
|
947
|
+
1
|
|
948
|
+
)
|
|
949
|
+
);
|
|
950
|
+
}
|
|
951
|
+
}
|
|
952
|
+
return out;
|
|
953
|
+
}
|
|
954
|
+
function _sanitize(filePath) {
|
|
955
|
+
const raw = fs2.readFileSync(filePath, "utf-8");
|
|
956
|
+
let cleaned = "";
|
|
957
|
+
for (const ch of raw) {
|
|
958
|
+
const cp = ch.codePointAt(0);
|
|
959
|
+
if (_classify(cp) === null && !_isVS(cp)) {
|
|
960
|
+
cleaned += ch;
|
|
961
|
+
}
|
|
962
|
+
}
|
|
963
|
+
cleaned = cleaned.normalize("NFKC");
|
|
964
|
+
const out = filePath + ".sanitized";
|
|
965
|
+
fs2.writeFileSync(out, cleaned, "utf-8");
|
|
966
|
+
return out;
|
|
967
|
+
}
|
|
968
|
+
function _argError(msg) {
|
|
969
|
+
process.stderr.write("usage: lint_hidden_unicode [-h] [--json] [--fix] [--root DIR]\n");
|
|
970
|
+
process.stderr.write(`lint_hidden_unicode: error: ${msg}
|
|
971
|
+
`);
|
|
972
|
+
process.exit(2);
|
|
973
|
+
}
|
|
974
|
+
function parse_args(argv) {
|
|
975
|
+
const out = { json: false, fix: false };
|
|
976
|
+
const extra = [];
|
|
977
|
+
let rootPending = false;
|
|
978
|
+
for (const a of argv) {
|
|
979
|
+
if (rootPending) {
|
|
980
|
+
out.root = a;
|
|
981
|
+
rootPending = false;
|
|
982
|
+
continue;
|
|
983
|
+
}
|
|
984
|
+
if (a === "-h" || a === "--help") {
|
|
985
|
+
process.stdout.write("usage: lint_hidden_unicode [-h] [--json] [--fix] [--root DIR]\n");
|
|
986
|
+
process.exit(0);
|
|
987
|
+
} else if (a === "--json") {
|
|
988
|
+
out.json = true;
|
|
989
|
+
} else if (a === "--fix") {
|
|
990
|
+
out.fix = true;
|
|
991
|
+
} else if (a === "--root" || a.startsWith("--root=")) {
|
|
992
|
+
const eq = a.indexOf("=");
|
|
993
|
+
if (eq !== -1) {
|
|
994
|
+
out.root = a.slice(eq + 1);
|
|
995
|
+
} else {
|
|
996
|
+
rootPending = true;
|
|
997
|
+
}
|
|
998
|
+
} else {
|
|
999
|
+
extra.push(a);
|
|
1000
|
+
}
|
|
1001
|
+
}
|
|
1002
|
+
if (extra.length > 0) {
|
|
1003
|
+
_argError(`unrecognized arguments: ${extra.join(" ")}`);
|
|
1004
|
+
}
|
|
1005
|
+
return out;
|
|
1006
|
+
}
|
|
1007
|
+
function main(argv = null) {
|
|
1008
|
+
const args = parse_args(argv ?? process.argv.slice(2));
|
|
1009
|
+
const scanBase = args.root === void 0 ? ROOT : path2.resolve(args.root);
|
|
1010
|
+
const findings = [];
|
|
1011
|
+
const flagged = /* @__PURE__ */ new Set();
|
|
1012
|
+
let corpusFiles = 0;
|
|
1013
|
+
for (const sf of iter_corpus(DEFAULT_SCAN_ROOTS, [".md"], scanBase)) {
|
|
1014
|
+
corpusFiles += 1;
|
|
1015
|
+
const hits = _scan(sf);
|
|
1016
|
+
for (const h of hits) {
|
|
1017
|
+
findings.push(h);
|
|
1018
|
+
}
|
|
1019
|
+
if (hits.length > 0) {
|
|
1020
|
+
flagged.add(sf.path);
|
|
1021
|
+
}
|
|
1022
|
+
}
|
|
1023
|
+
try {
|
|
1024
|
+
assertScanned({
|
|
1025
|
+
gate: "lint_hidden_unicode",
|
|
1026
|
+
scanned: corpusFiles,
|
|
1027
|
+
units: "corpus file(s)",
|
|
1028
|
+
roots: DEFAULT_SCAN_ROOTS
|
|
1029
|
+
});
|
|
1030
|
+
} catch (exc) {
|
|
1031
|
+
if (exc instanceof DeadScopeError) {
|
|
1032
|
+
process.stderr.write(`\u274C ${exc.message}
|
|
1033
|
+
`);
|
|
1034
|
+
return 1;
|
|
1035
|
+
}
|
|
1036
|
+
throw exc;
|
|
1037
|
+
}
|
|
1038
|
+
const sourceFiles = args.root === void 0 ? _eligibleSourceFiles() : [];
|
|
1039
|
+
for (const h of _scanSourceControlBytes(sourceFiles)) {
|
|
1040
|
+
findings.push(h);
|
|
1041
|
+
}
|
|
1042
|
+
if (!args.json) {
|
|
1043
|
+
const n = sourceFiles === null ? 0 : sourceFiles.length;
|
|
1044
|
+
process.stdout.write(
|
|
1045
|
+
sourceFiles === null ? " source pass: SKIPPED \u2014 `git ls-files` unavailable, 0 files read for raw control bytes\n" : ` source pass: ${n} tracked text file(s) read for raw C0 control bytes (generated trees + verbatim captures excluded)
|
|
1046
|
+
`
|
|
1047
|
+
);
|
|
1048
|
+
}
|
|
1049
|
+
if (args.fix) {
|
|
1050
|
+
for (const p of [...flagged].sort()) {
|
|
1051
|
+
const sanitized = _sanitize(p);
|
|
1052
|
+
const rel = path2.relative(path2.resolve(ROOT), path2.resolve(sanitized));
|
|
1053
|
+
process.stdout.write(` fixed \u2192 ${rel.split(path2.sep).join("/")}
|
|
1054
|
+
`);
|
|
1055
|
+
}
|
|
1056
|
+
}
|
|
1057
|
+
if (args.json) {
|
|
1058
|
+
report_child_scanned(corpusFiles + (sourceFiles === null ? 0 : sourceFiles.length));
|
|
1059
|
+
}
|
|
1060
|
+
if (args.json) {
|
|
1061
|
+
const payload = py_json_dumps_indent2(findings.map((f) => f.toDict()));
|
|
1062
|
+
process.stdout.write(payload + "\n");
|
|
1063
|
+
return findings.some((f) => f.is_fail) ? 1 : 0;
|
|
1064
|
+
}
|
|
1065
|
+
return report(findings, { check_label: "hidden-unicode" });
|
|
1066
|
+
}
|
|
1067
|
+
function _isCliEntry() {
|
|
1068
|
+
if (true) {
|
|
1069
|
+
return false;
|
|
1070
|
+
}
|
|
1071
|
+
if (process.argv[1] === void 0) {
|
|
1072
|
+
return false;
|
|
1073
|
+
}
|
|
1074
|
+
const argvUrl = pathToFileURL(path2.resolve(process.argv[1])).href;
|
|
1075
|
+
if (import.meta.url === argvUrl) {
|
|
1076
|
+
return true;
|
|
1077
|
+
}
|
|
1078
|
+
try {
|
|
1079
|
+
const here = fs2.realpathSync(fileURLToPath2(import.meta.url));
|
|
1080
|
+
const argv = fs2.realpathSync(path2.resolve(process.argv[1]));
|
|
1081
|
+
return here === argv;
|
|
1082
|
+
} catch {
|
|
1083
|
+
return false;
|
|
1084
|
+
}
|
|
1085
|
+
}
|
|
1086
|
+
if (_isCliEntry()) {
|
|
1087
|
+
process.exitCode = main();
|
|
1088
|
+
}
|
|
1089
|
+
|
|
1090
|
+
// src/scripts/_lib/confusables.ts
|
|
1091
|
+
var MIN_LETTERS = 3;
|
|
1092
|
+
var _LATIN = new RegExp("\\p{Script=Latin}", "u");
|
|
1093
|
+
var _CYRILLIC = new RegExp("\\p{Script=Cyrillic}", "u");
|
|
1094
|
+
var _GREEK = new RegExp("\\p{Script=Greek}", "u");
|
|
1095
|
+
var _LETTER = new RegExp("\\p{L}", "u");
|
|
1096
|
+
var TOKEN_RE = /[\p{L}\p{M}\p{Nd}]+/gu;
|
|
1097
|
+
var CONFUSABLE_FOREIGN = /* @__PURE__ */ new Set([
|
|
1098
|
+
// Cyrillic lowercase ↔ Latin
|
|
1099
|
+
1072,
|
|
1100
|
+
1077,
|
|
1101
|
+
1086,
|
|
1102
|
+
1088,
|
|
1103
|
+
1089,
|
|
1104
|
+
1091,
|
|
1105
|
+
1093,
|
|
1106
|
+
1109,
|
|
1107
|
+
1110,
|
|
1108
|
+
1112,
|
|
1109
|
+
1211,
|
|
1110
|
+
1281,
|
|
1111
|
+
1307,
|
|
1112
|
+
1399,
|
|
1113
|
+
// Cyrillic uppercase ↔ Latin
|
|
1114
|
+
1040,
|
|
1115
|
+
1042,
|
|
1116
|
+
1045,
|
|
1117
|
+
1050,
|
|
1118
|
+
1052,
|
|
1119
|
+
1053,
|
|
1120
|
+
1054,
|
|
1121
|
+
1056,
|
|
1122
|
+
1057,
|
|
1123
|
+
1058,
|
|
1124
|
+
1059,
|
|
1125
|
+
1061,
|
|
1126
|
+
1029,
|
|
1127
|
+
1030,
|
|
1128
|
+
1032,
|
|
1129
|
+
// Greek lowercase ↔ Latin
|
|
1130
|
+
945,
|
|
1131
|
+
949,
|
|
1132
|
+
959,
|
|
1133
|
+
957,
|
|
1134
|
+
961,
|
|
1135
|
+
965,
|
|
1136
|
+
967,
|
|
1137
|
+
954,
|
|
1138
|
+
// Greek uppercase ↔ Latin (only those with a real Latin twin)
|
|
1139
|
+
913,
|
|
1140
|
+
914,
|
|
1141
|
+
917,
|
|
1142
|
+
918,
|
|
1143
|
+
919,
|
|
1144
|
+
921,
|
|
1145
|
+
922,
|
|
1146
|
+
924,
|
|
1147
|
+
925,
|
|
1148
|
+
927,
|
|
1149
|
+
929,
|
|
1150
|
+
932,
|
|
1151
|
+
933,
|
|
1152
|
+
935,
|
|
1153
|
+
914
|
|
1154
|
+
]);
|
|
1155
|
+
function _script_of(ch) {
|
|
1156
|
+
if (_LATIN.test(ch)) return "latin";
|
|
1157
|
+
if (_CYRILLIC.test(ch)) return "cyrillic";
|
|
1158
|
+
if (_GREEK.test(ch)) return "greek";
|
|
1159
|
+
return "other";
|
|
1160
|
+
}
|
|
1161
|
+
function classifyToken(token) {
|
|
1162
|
+
let latin = 0;
|
|
1163
|
+
let cyrillic = 0;
|
|
1164
|
+
let greek = 0;
|
|
1165
|
+
let letters = 0;
|
|
1166
|
+
for (const ch of token) {
|
|
1167
|
+
if (!_LETTER.test(ch)) continue;
|
|
1168
|
+
letters += 1;
|
|
1169
|
+
const script = _script_of(ch);
|
|
1170
|
+
if (script === "latin") {
|
|
1171
|
+
latin += 1;
|
|
1172
|
+
} else if (script === "cyrillic" || script === "greek") {
|
|
1173
|
+
if (CONFUSABLE_FOREIGN.has(ch.codePointAt(0))) {
|
|
1174
|
+
if (script === "cyrillic") cyrillic += 1;
|
|
1175
|
+
else greek += 1;
|
|
1176
|
+
}
|
|
1177
|
+
}
|
|
1178
|
+
}
|
|
1179
|
+
if (letters < MIN_LETTERS) return null;
|
|
1180
|
+
const foreign = cyrillic + greek;
|
|
1181
|
+
if (latin === 0 || foreign === 0) return null;
|
|
1182
|
+
if (latin <= foreign) return null;
|
|
1183
|
+
const which = cyrillic > 0 && greek > 0 ? "cyrillic+greek" : cyrillic > 0 ? "cyrillic" : "greek";
|
|
1184
|
+
return `latin+${which} (${latin} latin / ${foreign} foreign-confusable)`;
|
|
1185
|
+
}
|
|
1186
|
+
|
|
1187
|
+
export {
|
|
1188
|
+
ROOT,
|
|
1189
|
+
DEFAULT_SCAN_ROOTS,
|
|
1190
|
+
Finding,
|
|
1191
|
+
scan_file,
|
|
1192
|
+
iter_corpus,
|
|
1193
|
+
legacy_pragma_finding,
|
|
1194
|
+
report_child_scanned,
|
|
1195
|
+
report,
|
|
1196
|
+
py_json_dumps_indent2,
|
|
1197
|
+
_classify,
|
|
1198
|
+
_scan,
|
|
1199
|
+
TOKEN_RE,
|
|
1200
|
+
classifyToken
|
|
1201
|
+
};
|