@event4u/agent-config 16.2.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 +89 -237
- 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 +4 -4
- 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/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/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/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/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-system-capture/references/import-procedure.md +1 -1
- package/dist/agent-src/skills/existing-ui-audit/SKILL.md +2 -2
- 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/monorepo-workspace/SKILL.md +1 -1
- 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 +2 -2
- package/dist/agent-src/templates/agents/agent-project-settings.example.yml +1 -1
- package/dist/agent-src/templates/scripts/work_engine/directives/ui/apply.ts +48 -6
- package/dist/agent-src/templates/scripts/work_engine/stack/runner.ts +774 -109
- 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-IGD6ERX6.js → chunk-JQVYKIZA.js} +10 -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-7RQP4UDH.js → chunk-KR6EM23A.js} +235 -1455
- package/dist/cli-delegate/{chunk-LTYVIY76.js → chunk-LIYTLH6O.js} +1 -1
- package/dist/cli-delegate/{chunk-YJ72UIAK.js → chunk-NQ7WT43P.js} +1186 -244
- 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-7XGBX6ZH.js → chunk-RQNNW7IF.js} +1314 -560
- package/dist/cli-delegate/{chunk-4XFP2Y3E.js → chunk-S4UMZNES.js} +1 -1
- package/dist/cli-delegate/{chunk-6KM6ORRY.js → chunk-S4YWNRKO.js} +24 -8
- package/dist/cli-delegate/{chunk-FP36SWJD.js → chunk-TRI6DQZ6.js} +2781 -340
- 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 +1167 -234
- 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 +127 -75
- 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 +5357 -6656
- package/dist/hooks/dispatch.sha256 +1 -1
- 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 +2080 -1024
- 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 +31965 -27154
- 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 +6 -0
- package/dist/shared/skillRanking.js +74 -13
- package/docs/contracts/settings-classes.md +7 -5
- package/package.json +1 -1
- 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/apply.ts +48 -6
- package/src/agent-src/templates/scripts/work_engine/stack/runner.ts +774 -109
- package/src/config/agent-settings.template.yml +62 -22
- package/src/config/continuity-surface.json +7 -0
- package/src/config/evaluator-budgets.json +4 -4
- package/src/config/gate-coverage.yml +96 -3
- package/src/config/gate-violation-baselines.json +17 -9
- package/src/config/hook-bundle-budget.json +25 -0
- package/src/config/hook-token-budget.json +6 -1
- package/src/config/host-instruction-limits.json +33 -0
- package/src/config/routing-coverage-seed.json +24 -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 +23 -71
- package/src/scripts/_cli/cmd_doctor_neighbours.ts +293 -0
- package/src/scripts/_dispatch.bash +16 -2
- package/src/scripts/_lib/agent_settings.ts +11 -0
- package/src/scripts/_lib/claude_settings_hooks.ts +10 -8
- 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/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_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/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/prompt_shape.ts +46 -0
- package/src/scripts/_lib/release_migration_gate.ts +19 -3
- package/src/scripts/_lib/reserved_name_sweep.ts +73 -0
- 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/skill_origin.ts +254 -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 +77 -2
- 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/audit_skill_overlap.ts +261 -0
- package/src/scripts/before_complete_hook.ts +243 -12
- package/src/scripts/bench_touched_file_quality.ts +238 -0
- package/src/scripts/check_enforcement_matrix.ts +377 -20
- package/src/scripts/check_estate_count.ts +130 -153
- package/src/scripts/check_finding_dispositions.ts +116 -4
- package/src/scripts/check_hook_bundle_composition.ts +370 -0
- package/src/scripts/check_kernel_edit_ratified.ts +49 -11
- package/src/scripts/check_memory.ts +7 -1
- package/src/scripts/check_release_highlights.ts +10 -0
- package/src/scripts/check_release_pr_shape.ts +38 -15
- package/src/scripts/check_standing_rule_delivery.ts +33 -13
- package/src/scripts/check_verify_expectation_delta.ts +521 -0
- 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 +33 -1
- package/src/scripts/condense.ts +14 -27
- package/src/scripts/estate_exemption_shape.ts +202 -0
- package/src/scripts/generate_host_cost_table.ts +1 -1
- package/src/scripts/hook_manifest.json +1 -1
- package/src/scripts/hook_manifest.yaml +150 -9
- package/src/scripts/hooks/block_config_weakening.ts +67 -2
- package/src/scripts/hooks/block_plumbing_writes.ts +29 -1
- package/src/scripts/hooks/code_graph_context_hook.ts +252 -23
- package/src/scripts/hooks/concern_failure_policy.ts +280 -0
- package/src/scripts/hooks/dispatch_hook.ts +52 -52
- package/src/scripts/hooks/exit_codes.ts +2 -1
- package/src/scripts/hooks/host_lowering.json +1 -1
- package/src/scripts/hooks/host_lowering.ts +19 -1
- package/src/scripts/hooks/one_question_per_ask_hook.ts +41 -25
- package/src/scripts/hooks/payload_stub.ts +10 -1
- package/src/scripts/hooks/rule_inject_hook.ts +757 -63
- package/src/scripts/hooks/table_fingerprint.ts +21 -0
- package/src/scripts/hooks/telemetry_usage_hook.ts +64 -8
- package/src/scripts/hooks/turn_end_gate_hook.ts +84 -0
- package/src/scripts/hooks_status.ts +112 -0
- package/src/scripts/injection_scan_hook.ts +7 -0
- package/src/scripts/install.ts +72 -185
- 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_hook_manifest.ts +53 -0
- package/src/scripts/lint_major_migration_sections.ts +156 -26
- package/src/scripts/lint_pack_boundaries.ts +63 -5
- package/src/scripts/lint_rule_law_section.ts +517 -0
- 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/measure_routing_signal.ts +54 -16
- package/src/scripts/measure_skill_ranker_baseline.ts +216 -8
- package/src/scripts/memory_learn_hook.ts +91 -6
- package/src/scripts/model_rule_injection.ts +52 -5
- package/src/scripts/project_thin_rules.ts +34 -327
- package/src/scripts/report_evidence_temperature.ts +657 -0
- package/src/scripts/report_installed_rule_links.ts +157 -0
- package/src/scripts/report_label_agreement.ts +233 -0
- package/src/scripts/report_obligation_mechanism.ts +230 -0
- package/src/scripts/report_single_token_triggers.ts +277 -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/rule_activation_census.ts +5 -2
- package/src/scripts/rule_trigger_eval.ts +9 -9
- package/src/scripts/schemas/review-findings.schema.json +3 -2
- package/src/scripts/schemas/rule.schema.json +5 -0
- package/src/scripts/self_review_gate.ts +62 -7
- package/src/scripts/skill_tools/score_skill_relevance.ts +290 -25
- 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 -5
- package/src/server/schemas/settings.ts +10 -0
- package/src/shared/skillRanking.ts +117 -11
- package/dist/cli-delegate/chunk-JNMVLWLR.js +0 -4457
- package/dist/cli-delegate/chunk-YDPILCOL.js +0 -152
|
@@ -88,6 +88,7 @@ import * as user_global_paths from '../_lib/user_global_paths.js';
|
|
|
88
88
|
import { jsonDumpsIndent2 as _jsonDumpsIndent2 } from './_py_json.js';
|
|
89
89
|
import * as budget from './cli_call_budget.js';
|
|
90
90
|
import { appendEvent } from './events_log.js';
|
|
91
|
+
import { OPENAI_CLI_VENDOR_DEFAULT } from './vendor_defaults.js';
|
|
91
92
|
|
|
92
93
|
export const ANTHROPIC_KEY_FILENAME = 'anthropic.key';
|
|
93
94
|
export const OPENAI_KEY_FILENAME = 'openai.key';
|
|
@@ -124,23 +125,12 @@ export const DEFAULT_PERPLEXITY_MODEL = 'sonar-pro';
|
|
|
124
125
|
// divergence is a one-line change, not a hunt through inline literals.
|
|
125
126
|
// (`xai` / `perplexity` CLI reuse their API constants: their CLIs are
|
|
126
127
|
// community wrappers around the same paid API, so the values do not diverge.)
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
* `400 invalid_request_error: The '<model>' model is not supported when using
|
|
134
|
-
* Codex with a ChatGPT account.` — `gpt-4o`, `gpt-5` (this constant's previous
|
|
135
|
-
* value) and `gpt-5.1-codex` alike. Omitting the flag answered normally.
|
|
136
|
-
*
|
|
137
|
-
* So the only value known to work on this transport is "whatever the CLI
|
|
138
|
-
* chooses", and pinning ANY name here would re-break the seat the next time
|
|
139
|
-
* the vendor rotates its lineup. The label is carried into records as-is so a
|
|
140
|
-
* reader can see that no model was pinned, rather than a name being implied
|
|
141
|
-
* that nobody verified.
|
|
142
|
-
*/
|
|
143
|
-
export const OPENAI_CLI_VENDOR_DEFAULT = 'codex-default';
|
|
128
|
+
// `OPENAI_CLI_VENDOR_DEFAULT` lives in `vendor_defaults.ts`, re-exported here
|
|
129
|
+
// so every existing importer keeps resolving. It moved because `config.ts`
|
|
130
|
+
// wanted that one string and was charged this whole module for it in the
|
|
131
|
+
// composed hook bundle — 51,523 bytes on every dispatch; the reasoning and the
|
|
132
|
+
// measurement are in that file's header.
|
|
133
|
+
export { OPENAI_CLI_VENDOR_DEFAULT } from './vendor_defaults.js';
|
|
144
134
|
|
|
145
135
|
/**
|
|
146
136
|
* Models MEASURED to be refused by the codex subscription transport, with the
|
|
@@ -1011,6 +1001,7 @@ export const CLI_CALLS_FILENAME = 'cli-calls.json';
|
|
|
1011
1001
|
export {
|
|
1012
1002
|
CLI_CALLS_ATTRIBUTION_SUFFIX,
|
|
1013
1003
|
CLI_CONSUMER_COUNCIL,
|
|
1004
|
+
CLI_CONSUMER_SELF_REVIEW,
|
|
1014
1005
|
CLI_CONSUMER_TEAM,
|
|
1015
1006
|
CLI_CONSUMER_UNKNOWN,
|
|
1016
1007
|
QUOTA_SOURCE_LOCAL_BUDGET,
|
|
@@ -1678,16 +1669,22 @@ export function _foldSystemPrompt(system_prompt: string, user_prompt: string): s
|
|
|
1678
1669
|
export class AnthropicCliClient extends CliClient {
|
|
1679
1670
|
override subscription_label = 'claude-pro';
|
|
1680
1671
|
|
|
1672
|
+
// `...opts` rather than a field list, in all five CLI subclasses, and the
|
|
1673
|
+
// spread IS the fix rather than a tidy. Each of these constructors used to
|
|
1674
|
+
// name six option fields explicitly and forward those — so when
|
|
1675
|
+
// `CliClientOptions` grew a seventh, `consumer`, every subclass silently
|
|
1676
|
+
// dropped it. Both construction sites passed it, `CliClient` read
|
|
1677
|
+
// `opts.consumer ?? CLI_CONSUMER_UNKNOWN`, and the sidecar recorded
|
|
1678
|
+
// `unknown` for every CLI call ever booked: measured 2026-10-05 as
|
|
1679
|
+
// `{"anthropic":{"unknown":2},"openai":{"unknown":2}}` after a council
|
|
1680
|
+
// round. A field list can forget a field. A spread cannot, so the eighth
|
|
1681
|
+
// option needs no edit here and cannot repeat this.
|
|
1682
|
+
//
|
|
1683
|
+
// `model` stays explicit because it is the one field each subclass
|
|
1684
|
+
// overrides with its own pinned default; everything else passes through.
|
|
1681
1685
|
constructor(opts: CliClientOptions = {}) {
|
|
1682
1686
|
super(
|
|
1683
|
-
{
|
|
1684
|
-
model: opts.model ?? DEFAULT_ANTHROPIC_CLI_MODEL,
|
|
1685
|
-
binary: opts.binary,
|
|
1686
|
-
timeout_seconds: opts.timeout_seconds,
|
|
1687
|
-
max_calls_per_day: opts.max_calls_per_day,
|
|
1688
|
-
warn_at: opts.warn_at,
|
|
1689
|
-
cli_calls_path: opts.cli_calls_path,
|
|
1690
|
-
},
|
|
1687
|
+
{ ...opts, model: opts.model ?? DEFAULT_ANTHROPIC_CLI_MODEL },
|
|
1691
1688
|
{ name: 'anthropic', default_binary: 'claude' },
|
|
1692
1689
|
);
|
|
1693
1690
|
}
|
|
@@ -1817,14 +1814,7 @@ export class OpenAICliClient extends CliClient {
|
|
|
1817
1814
|
|
|
1818
1815
|
constructor(opts: CliClientOptions = {}) {
|
|
1819
1816
|
super(
|
|
1820
|
-
{
|
|
1821
|
-
model: opts.model ?? DEFAULT_OPENAI_CLI_MODEL,
|
|
1822
|
-
binary: opts.binary,
|
|
1823
|
-
timeout_seconds: opts.timeout_seconds,
|
|
1824
|
-
max_calls_per_day: opts.max_calls_per_day,
|
|
1825
|
-
warn_at: opts.warn_at,
|
|
1826
|
-
cli_calls_path: opts.cli_calls_path,
|
|
1827
|
-
},
|
|
1817
|
+
{ ...opts, model: opts.model ?? DEFAULT_OPENAI_CLI_MODEL },
|
|
1828
1818
|
{ name: 'openai', default_binary: 'codex' },
|
|
1829
1819
|
);
|
|
1830
1820
|
}
|
|
@@ -2064,14 +2054,7 @@ export class GeminiCliClient extends CliClient {
|
|
|
2064
2054
|
|
|
2065
2055
|
constructor(opts: CliClientOptions = {}) {
|
|
2066
2056
|
super(
|
|
2067
|
-
{
|
|
2068
|
-
model: opts.model ?? DEFAULT_GEMINI_CLI_MODEL,
|
|
2069
|
-
binary: opts.binary,
|
|
2070
|
-
timeout_seconds: opts.timeout_seconds,
|
|
2071
|
-
max_calls_per_day: opts.max_calls_per_day,
|
|
2072
|
-
warn_at: opts.warn_at,
|
|
2073
|
-
cli_calls_path: opts.cli_calls_path,
|
|
2074
|
-
},
|
|
2057
|
+
{ ...opts, model: opts.model ?? DEFAULT_GEMINI_CLI_MODEL },
|
|
2075
2058
|
{ name: 'gemini', default_binary: 'gemini' },
|
|
2076
2059
|
);
|
|
2077
2060
|
}
|
|
@@ -2217,14 +2200,7 @@ export class XAICliClient extends CliClient {
|
|
|
2217
2200
|
|
|
2218
2201
|
constructor(opts: CliClientOptions = {}) {
|
|
2219
2202
|
super(
|
|
2220
|
-
{
|
|
2221
|
-
model: opts.model ?? DEFAULT_XAI_MODEL,
|
|
2222
|
-
binary: opts.binary,
|
|
2223
|
-
timeout_seconds: opts.timeout_seconds,
|
|
2224
|
-
max_calls_per_day: opts.max_calls_per_day,
|
|
2225
|
-
warn_at: opts.warn_at,
|
|
2226
|
-
cli_calls_path: opts.cli_calls_path,
|
|
2227
|
-
},
|
|
2203
|
+
{ ...opts, model: opts.model ?? DEFAULT_XAI_MODEL },
|
|
2228
2204
|
{ name: 'xai', default_binary: 'grok' },
|
|
2229
2205
|
);
|
|
2230
2206
|
}
|
|
@@ -2289,14 +2265,7 @@ export class PerplexityCliClient extends CliClient {
|
|
|
2289
2265
|
|
|
2290
2266
|
constructor(opts: CliClientOptions = {}) {
|
|
2291
2267
|
super(
|
|
2292
|
-
{
|
|
2293
|
-
model: opts.model ?? DEFAULT_PERPLEXITY_MODEL,
|
|
2294
|
-
binary: opts.binary,
|
|
2295
|
-
timeout_seconds: opts.timeout_seconds,
|
|
2296
|
-
max_calls_per_day: opts.max_calls_per_day,
|
|
2297
|
-
warn_at: opts.warn_at,
|
|
2298
|
-
cli_calls_path: opts.cli_calls_path,
|
|
2299
|
-
},
|
|
2268
|
+
{ ...opts, model: opts.model ?? DEFAULT_PERPLEXITY_MODEL },
|
|
2300
2269
|
{ name: 'perplexity', default_binary: 'perplexity' },
|
|
2301
2270
|
);
|
|
2302
2271
|
}
|
|
@@ -82,7 +82,7 @@ import * as budget from './cli_call_budget.js';
|
|
|
82
82
|
// `import type` and is erased at build, so there is no runtime cycle today.
|
|
83
83
|
// Converting it to a value import — or adding any other runtime import from
|
|
84
84
|
// quorum into config — creates a real ESM cycle in the loader's init path.
|
|
85
|
-
import { OPENAI_CLI_VENDOR_DEFAULT } from './
|
|
85
|
+
import { OPENAI_CLI_VENDOR_DEFAULT } from './vendor_defaults.js';
|
|
86
86
|
import { parseSeatFields, type PolicyExclusion } from './seat_policy.js';
|
|
87
87
|
import type { ContentClass } from './content_ceiling.js';
|
|
88
88
|
import { SOLO_FLOOR_MIN_PRESENT } from './quorum.js';
|
|
@@ -0,0 +1,45 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Vendor-CLI sentinel values that are DATA, carried in a module with no
|
|
3
|
+
* dependencies of its own.
|
|
4
|
+
*
|
|
5
|
+
* WHY THIS FILE EXISTS, measured rather than argued. `OPENAI_CLI_VENDOR_DEFAULT`
|
|
6
|
+
* is a single string, and it used to live in `clients.ts` beside the transport
|
|
7
|
+
* classes that compare against it. `ai_council/config.ts` imports that one
|
|
8
|
+
* string and nothing else from `clients.ts`; `config.ts` is reachable from
|
|
9
|
+
* `council-availability`, which is bound on `session_start`. So every hook
|
|
10
|
+
* dispatch on every slot carried the whole council transport layer —
|
|
11
|
+
* `clients.ts` and its closure, **51,523 bytes** of the composed
|
|
12
|
+
* `dist/hooks/dispatch.js` — to compare one string against another. Breaking
|
|
13
|
+
* that one import edge removes exactly that, with no capability removed
|
|
14
|
+
* anywhere: every entry point that actually talks to a vendor CLI imports
|
|
15
|
+
* `clients.ts` directly and still does.
|
|
16
|
+
*
|
|
17
|
+
* The bytes are not an abstraction. `src/config/hook-bundle-budget.json` caps
|
|
18
|
+
* the composed bundle, the cap is a shrink-only ratchet, and on 2026-10-02 the
|
|
19
|
+
* tree stood 75 bytes under it — a state in which no concern anywhere could add
|
|
20
|
+
* a line. This module is how `road-to-a-rule-carrier-that-works-outside-the-repo`
|
|
21
|
+
* Phase 1 paid for its own runtime code instead of asking for a raise.
|
|
22
|
+
*
|
|
23
|
+
* WHAT BELONGS HERE: a vendor-facing constant that a CONFIGURATION reader needs
|
|
24
|
+
* and a TRANSPORT reader also happens to use. Nothing that imports anything.
|
|
25
|
+
* The moment this file gains an import it stops being the cheap half of the
|
|
26
|
+
* split and the split stops being worth its own file.
|
|
27
|
+
*/
|
|
28
|
+
|
|
29
|
+
/**
|
|
30
|
+
* Sentinel: let the codex CLI pick its own model, by omitting `--model`.
|
|
31
|
+
*
|
|
32
|
+
* This is not a placeholder for a value nobody looked up. Measured
|
|
33
|
+
* 2026-08-15 against `codex exec --json` on a ChatGPT-account (subscription)
|
|
34
|
+
* transport, every explicitly named candidate was refused with
|
|
35
|
+
* `400 invalid_request_error: The '<model>' model is not supported when using
|
|
36
|
+
* Codex with a ChatGPT account.` — `gpt-4o`, `gpt-5` (this constant's previous
|
|
37
|
+
* value) and `gpt-5.1-codex` alike. Omitting the flag answered normally.
|
|
38
|
+
*
|
|
39
|
+
* So the only value known to work on this transport is "whatever the CLI
|
|
40
|
+
* chooses", and pinning ANY name here would re-break the seat the next time
|
|
41
|
+
* the vendor rotates its lineup. The label is carried into records as-is so a
|
|
42
|
+
* reader can see that no model was pinned, rather than a name being implied
|
|
43
|
+
* that nobody verified.
|
|
44
|
+
*/
|
|
45
|
+
export const OPENAI_CLI_VENDOR_DEFAULT = 'codex-default';
|
|
@@ -30,6 +30,7 @@
|
|
|
30
30
|
* quietly until the gate means nothing. Cross-pack pairs never block — a
|
|
31
31
|
* cross-pack merge changes install shape and is a different decision.
|
|
32
32
|
*/
|
|
33
|
+
import { createHash } from 'node:crypto';
|
|
33
34
|
import * as fs from 'node:fs';
|
|
34
35
|
import * as path from 'node:path';
|
|
35
36
|
import { fileURLToPath, pathToFileURL } from 'node:url';
|
|
@@ -47,6 +48,7 @@ export const OUT_MD_NAME = 'skill-overlap.md';
|
|
|
47
48
|
export const OUT_JSON = path.join(REPORT_DIR, OUT_JSON_NAME);
|
|
48
49
|
export const OUT_MD = path.join(REPORT_DIR, OUT_MD_NAME);
|
|
49
50
|
export const ALLOWLIST = path.join(ROOT, 'src', 'scripts', 'audit_skill_overlap_allowlist.json');
|
|
51
|
+
export const CROSS_CACHE_NAME = 'neighbour-overlap.json';
|
|
50
52
|
export const ALLOWLIST_CAP = 20;
|
|
51
53
|
// re.compile(r"^---\n(.*?)\n---", re.DOTALL) — used with .search (anywhere).
|
|
52
54
|
const FM_RE = /^---\n([\s\S]*?)\n---/m;
|
|
@@ -68,6 +70,8 @@ type Json = any;
|
|
|
68
70
|
interface Skill {
|
|
69
71
|
name: string;
|
|
70
72
|
relpath: string;
|
|
73
|
+
/** Absolute `SKILL.md` path — the key a cross-root result is returned under. */
|
|
74
|
+
abspath: string;
|
|
71
75
|
packs: Set<string>;
|
|
72
76
|
vector: Map<string, number>;
|
|
73
77
|
}
|
|
@@ -178,6 +182,7 @@ export function _parse(md: string, field: Field = 'body'): Skill {
|
|
|
178
182
|
return {
|
|
179
183
|
name: name ? String(name) : path.basename(path.dirname(md)),
|
|
180
184
|
relpath: _relPosix(md, ROOT),
|
|
185
|
+
abspath: path.resolve(md),
|
|
181
186
|
packs: Array.isArray(packsVal) ? new Set(packsVal.map((x) => String(x))) : new Set(),
|
|
182
187
|
vector: _keyword_vector(source),
|
|
183
188
|
};
|
|
@@ -322,6 +327,193 @@ export function find_pairs(skills: Skill[], threshold: number): Pair[] {
|
|
|
322
327
|
return stableSort(pairs, (p) => [p.same_domain ? 0 : 1, -p.similarity]);
|
|
323
328
|
}
|
|
324
329
|
|
|
330
|
+
/**
|
|
331
|
+
* One neighbour skill against one of ours, at or above the threshold.
|
|
332
|
+
*
|
|
333
|
+
* `neighbour_path` is absolute, because the two sides live under different roots
|
|
334
|
+
* and a repo-relative path would be a `../..` string that no caller can key on.
|
|
335
|
+
*/
|
|
336
|
+
export interface CrossPair {
|
|
337
|
+
neighbour: string;
|
|
338
|
+
neighbour_path: string;
|
|
339
|
+
ours: string;
|
|
340
|
+
similarity: number;
|
|
341
|
+
}
|
|
342
|
+
|
|
343
|
+
/**
|
|
344
|
+
* Cross-root pairs: every NEIGHBOUR skill against every one of OURS.
|
|
345
|
+
*
|
|
346
|
+
* NOT `find_pairs` over a merged list, and the difference is the whole point of
|
|
347
|
+
* the mode. `find_pairs` is `combinations(skills, 2)` over one corpus, so a
|
|
348
|
+
* merged list would also compare our skills against each other — re-deriving the
|
|
349
|
+
* same-corpus report under a different name, at the cost of the pairs the caller
|
|
350
|
+
* actually asked for being buried in it. This emits exactly the cross product,
|
|
351
|
+
* which is also the cheaper half: |neighbours| × |ours| rather than C(n+m, 2).
|
|
352
|
+
*
|
|
353
|
+
* The THRESHOLD is the caller's and defaults to {@link OVERLAP_THRESHOLD} — the
|
|
354
|
+
* same 0.70 every historical same-corpus report is calibrated against. Reusing it
|
|
355
|
+
* is deliberate: a second, softer bar invented for neighbours would make
|
|
356
|
+
* `compat: overlapping` mean something no existing number can be compared to.
|
|
357
|
+
*/
|
|
358
|
+
export function crossRootPairs(
|
|
359
|
+
neighbourRoot: string,
|
|
360
|
+
ourRoot: string,
|
|
361
|
+
threshold: number = OVERLAP_THRESHOLD,
|
|
362
|
+
field: Field = 'body',
|
|
363
|
+
): CrossPair[] {
|
|
364
|
+
return crossPairsForFiles(skillMdsUnder(neighbourRoot), ourRoot, threshold, field);
|
|
365
|
+
}
|
|
366
|
+
|
|
367
|
+
/** Non-archived `SKILL.md` paths under a root, in the collector's own order. */
|
|
368
|
+
export function skillMdsUnder(root: string): string[] {
|
|
369
|
+
return _rglobSkillMd(root).filter((md) => !md.split(path.sep).includes('_archive'));
|
|
370
|
+
}
|
|
371
|
+
|
|
372
|
+
/**
|
|
373
|
+
* The same cross product, over an EXPLICIT neighbour file list.
|
|
374
|
+
*
|
|
375
|
+
* This is the shape the census actually needs, and the root-taking wrapper above
|
|
376
|
+
* is the convenience layer rather than the other way round. A census's neighbour
|
|
377
|
+
* skills are the UNCLAIMED entries of two roots that also hold claimed ones —
|
|
378
|
+
* `agent-config install` writes our own skills into `~/.claude/skills` — so
|
|
379
|
+
* pairing a whole root would compare this package's installed copies against its
|
|
380
|
+
* own source tree and report the install as an overlap.
|
|
381
|
+
*/
|
|
382
|
+
export function crossPairsForFiles(
|
|
383
|
+
neighbourFiles: readonly string[],
|
|
384
|
+
ourRoot: string,
|
|
385
|
+
threshold: number = OVERLAP_THRESHOLD,
|
|
386
|
+
field: Field = 'body',
|
|
387
|
+
): CrossPair[] {
|
|
388
|
+
const theirs: Skill[] = [];
|
|
389
|
+
for (const md of neighbourFiles) {
|
|
390
|
+
try {
|
|
391
|
+
theirs.push(_parse(md, field));
|
|
392
|
+
} catch {
|
|
393
|
+
// Unreadable neighbour file: not a pair, and not a crash.
|
|
394
|
+
}
|
|
395
|
+
}
|
|
396
|
+
const ours = collect(ourRoot, field);
|
|
397
|
+
const out: CrossPair[] = [];
|
|
398
|
+
for (const n of theirs) {
|
|
399
|
+
for (const o of ours) {
|
|
400
|
+
const sim = _cosine(n.vector, o.vector);
|
|
401
|
+
if (sim < threshold) continue;
|
|
402
|
+
out.push({
|
|
403
|
+
neighbour: n.name,
|
|
404
|
+
neighbour_path: n.abspath,
|
|
405
|
+
ours: o.name,
|
|
406
|
+
similarity: pyRound(sim, 3),
|
|
407
|
+
});
|
|
408
|
+
}
|
|
409
|
+
}
|
|
410
|
+
return stableSort(out, (p) => [-p.similarity]);
|
|
411
|
+
}
|
|
412
|
+
|
|
413
|
+
/**
|
|
414
|
+
* Cross-root pairs folded the way the census reads them: highest-scoring hit per
|
|
415
|
+
* neighbour `SKILL.md`, with every our-skill name that crossed the bar.
|
|
416
|
+
*/
|
|
417
|
+
export function crossRootOverlapMap(
|
|
418
|
+
pairs: readonly CrossPair[],
|
|
419
|
+
): Map<string, { names: string[]; similarity: number }> {
|
|
420
|
+
const out = new Map<string, { names: string[]; similarity: number }>();
|
|
421
|
+
for (const p of pairs) {
|
|
422
|
+
const hit = out.get(p.neighbour_path);
|
|
423
|
+
if (hit === undefined) {
|
|
424
|
+
out.set(p.neighbour_path, { names: [p.ours], similarity: p.similarity });
|
|
425
|
+
continue;
|
|
426
|
+
}
|
|
427
|
+
if (!hit.names.includes(p.ours)) hit.names.push(p.ours);
|
|
428
|
+
if (p.similarity > hit.similarity) hit.similarity = p.similarity;
|
|
429
|
+
}
|
|
430
|
+
for (const hit of out.values()) hit.names.sort();
|
|
431
|
+
return out;
|
|
432
|
+
}
|
|
433
|
+
|
|
434
|
+
/**
|
|
435
|
+
* The census digest the cross-root cache is keyed by.
|
|
436
|
+
*
|
|
437
|
+
* Over the INPUTS and nothing else: every skill's absolute path and the sha256
|
|
438
|
+
* of its bytes, on both sides, plus the threshold and the field. Risk-register
|
|
439
|
+
* row 3 is why a cache exists at all — a pairwise pass per prompt is not a
|
|
440
|
+
* hook-budget shape — and keying it on the bytes is what makes "a changed digest
|
|
441
|
+
* rescans first" true rather than aspirational. A key over NAMES alone would
|
|
442
|
+
* hand a stale score to a body that was rewritten underneath it.
|
|
443
|
+
*/
|
|
444
|
+
export function censusDigest(
|
|
445
|
+
neighbourFiles: readonly string[],
|
|
446
|
+
ourRoot: string,
|
|
447
|
+
threshold: number,
|
|
448
|
+
field: Field = 'body',
|
|
449
|
+
): string {
|
|
450
|
+
const h = createHash('sha256');
|
|
451
|
+
h.update(`${String(threshold)}\n${field}\n`);
|
|
452
|
+
const sides: ReadonlyArray<readonly string[]> = [
|
|
453
|
+
[...neighbourFiles].sort(),
|
|
454
|
+
skillMdsUnder(ourRoot),
|
|
455
|
+
];
|
|
456
|
+
for (const side of sides) {
|
|
457
|
+
h.update('side\n');
|
|
458
|
+
for (const md of side) {
|
|
459
|
+
let bytes: Buffer;
|
|
460
|
+
try {
|
|
461
|
+
bytes = fs.readFileSync(md);
|
|
462
|
+
} catch {
|
|
463
|
+
continue;
|
|
464
|
+
}
|
|
465
|
+
h.update(path.resolve(md));
|
|
466
|
+
h.update('\0');
|
|
467
|
+
h.update(createHash('sha256').update(bytes).digest('hex'));
|
|
468
|
+
h.update('\n');
|
|
469
|
+
}
|
|
470
|
+
}
|
|
471
|
+
return h.digest('hex');
|
|
472
|
+
}
|
|
473
|
+
|
|
474
|
+
export interface CrossCache {
|
|
475
|
+
schema_version: 1;
|
|
476
|
+
digest: string;
|
|
477
|
+
threshold: number;
|
|
478
|
+
pairs: CrossPair[];
|
|
479
|
+
}
|
|
480
|
+
|
|
481
|
+
/**
|
|
482
|
+
* Cross-root pairs, recomputed only when the census digest moved.
|
|
483
|
+
*
|
|
484
|
+
* A cache miss is never silent: the result says `cached: false`, so a caller
|
|
485
|
+
* reporting "this came from the cache" cannot be reporting it about a pass that
|
|
486
|
+
* actually ran. A write failure is not fatal — the pairs are correct either way,
|
|
487
|
+
* and refusing to answer because a report directory is read-only would turn a
|
|
488
|
+
* cache into a dependency.
|
|
489
|
+
*/
|
|
490
|
+
export function cachedCrossRootPairs(
|
|
491
|
+
neighbourFiles: readonly string[],
|
|
492
|
+
ourRoot: string,
|
|
493
|
+
cacheFile: string,
|
|
494
|
+
threshold: number = OVERLAP_THRESHOLD,
|
|
495
|
+
field: Field = 'body',
|
|
496
|
+
): { pairs: CrossPair[]; digest: string; cached: boolean } {
|
|
497
|
+
const digest = censusDigest(neighbourFiles, ourRoot, threshold, field);
|
|
498
|
+
try {
|
|
499
|
+
const parsed = JSON.parse(fs.readFileSync(cacheFile, 'utf-8')) as Partial<CrossCache>;
|
|
500
|
+
if (parsed.digest === digest && Array.isArray(parsed.pairs)) {
|
|
501
|
+
return { pairs: parsed.pairs as CrossPair[], digest, cached: true };
|
|
502
|
+
}
|
|
503
|
+
} catch {
|
|
504
|
+
// No cache, or an unreadable one: compute.
|
|
505
|
+
}
|
|
506
|
+
const pairs = crossPairsForFiles(neighbourFiles, ourRoot, threshold, field);
|
|
507
|
+
const payload: CrossCache = { schema_version: 1, digest, threshold, pairs };
|
|
508
|
+
try {
|
|
509
|
+
fs.mkdirSync(path.dirname(cacheFile), { recursive: true });
|
|
510
|
+
fs.writeFileSync(cacheFile, `${JSON.stringify(payload, null, 2)}\n`, 'utf-8');
|
|
511
|
+
} catch {
|
|
512
|
+
// A cache that cannot be written is still a correct answer.
|
|
513
|
+
}
|
|
514
|
+
return { pairs, digest, cached: false };
|
|
515
|
+
}
|
|
516
|
+
|
|
325
517
|
/** Stable sort by a tuple-of-numbers key (Python tuple compare). */
|
|
326
518
|
function stableSort<T>(arr: T[], key: (x: T) => number[]): T[] {
|
|
327
519
|
return arr
|
|
@@ -484,6 +676,9 @@ class FloatTag {
|
|
|
484
676
|
export function main(argv: string[] | null = null): number {
|
|
485
677
|
const args = parse_args(argv ?? process.argv.slice(2));
|
|
486
678
|
const root = args.root ?? _default_skill_root();
|
|
679
|
+
if (args.neighbourRoot !== undefined) {
|
|
680
|
+
return _mainCrossRoot(args, root);
|
|
681
|
+
}
|
|
487
682
|
const skills = collect(root, args.field);
|
|
488
683
|
try {
|
|
489
684
|
assertScanned({
|
|
@@ -570,6 +765,55 @@ export function main(argv: string[] | null = null): number {
|
|
|
570
765
|
return 1;
|
|
571
766
|
}
|
|
572
767
|
|
|
768
|
+
/**
|
|
769
|
+
* `--neighbour-root` — the cross-root pair mode.
|
|
770
|
+
*
|
|
771
|
+
* A SECOND QUESTION, not a second answer to the first. The same-corpus report
|
|
772
|
+
* asks "are two of our skills so alike that one should absorb the other", and a
|
|
773
|
+
* merge is the action it leads to. This asks "is a skill another package
|
|
774
|
+
* installed close enough to one of ours that a reader of the route line needs to
|
|
775
|
+
* know", and NOTHING is merged, removed or suppressed on the answer — the
|
|
776
|
+
* neighbour is not ours to merge. So it writes its own artifact and leaves
|
|
777
|
+
* `skill-overlap.json` alone.
|
|
778
|
+
*
|
|
779
|
+
* Exit 0 always. A finding here is a report; `--strict` belongs to the
|
|
780
|
+
* same-corpus mode, where the pair is between two files this package owns.
|
|
781
|
+
*/
|
|
782
|
+
function _mainCrossRoot(args: Args, ourRoot: string): number {
|
|
783
|
+
const neighbourRoot = args.neighbourRoot as string;
|
|
784
|
+
const outDir = args.outDir ?? REPORT_DIR;
|
|
785
|
+
const cacheFile = args.crossCache ?? path.join(outDir, CROSS_CACHE_NAME);
|
|
786
|
+
const { pairs, digest, cached } = cachedCrossRootPairs(
|
|
787
|
+
skillMdsUnder(neighbourRoot),
|
|
788
|
+
ourRoot,
|
|
789
|
+
cacheFile,
|
|
790
|
+
args.threshold,
|
|
791
|
+
args.field,
|
|
792
|
+
);
|
|
793
|
+
const theirs = collect(neighbourRoot, args.field);
|
|
794
|
+
// The counts line the gate-coverage contract asks for, on stderr like its
|
|
795
|
+
// sibling, so the report body on stdout stays machine-readable.
|
|
796
|
+
process.stderr.write(`scanned: ${String(theirs.length)}\n`);
|
|
797
|
+
if (!args.quiet) {
|
|
798
|
+
process.stdout.write(
|
|
799
|
+
`\u2705 Neighbour overlap: ${theirs.length} neighbour skill(s) \u00d7 ` +
|
|
800
|
+
`${collect(ourRoot, args.field).length} of ours, ${pairs.length} pair(s) ` +
|
|
801
|
+
`\u2265 ${_pct(args.threshold)} (${cached ? 'from cache' : 'recomputed'}).\n`,
|
|
802
|
+
);
|
|
803
|
+
process.stdout.write(` census digest: ${digest.slice(0, 12)}\n`);
|
|
804
|
+
process.stdout.write(` cache: ${_relPosix(cacheFile, ROOT)}\n`);
|
|
805
|
+
}
|
|
806
|
+
const byPath = crossRootOverlapMap(pairs);
|
|
807
|
+
for (const skill of theirs) {
|
|
808
|
+
const hit = byPath.get(skill.abspath);
|
|
809
|
+
if (hit === undefined) continue;
|
|
810
|
+
process.stdout.write(
|
|
811
|
+
` ${hit.similarity.toFixed(3)} ${skill.name} also: ${hit.names.join(', ')}\n`,
|
|
812
|
+
);
|
|
813
|
+
}
|
|
814
|
+
return 0;
|
|
815
|
+
}
|
|
816
|
+
|
|
573
817
|
/** Order-independent key for an unordered pair. */
|
|
574
818
|
export function _pairKey(a: string, b: string): string {
|
|
575
819
|
return a < b ? `${a}::${b}` : `${b}::${a}`;
|
|
@@ -662,6 +906,10 @@ interface Args {
|
|
|
662
906
|
strict: boolean;
|
|
663
907
|
/** Allowlist path override (tests); `undefined` = the shipped file. */
|
|
664
908
|
allowlist?: string;
|
|
909
|
+
/** Cross-root mode: the NEIGHBOUR tree to pair against `--root`. */
|
|
910
|
+
neighbourRoot?: string;
|
|
911
|
+
/** Cross-root cache path; `undefined` = `<out-dir>/neighbour-overlap.json`. */
|
|
912
|
+
crossCache?: string;
|
|
665
913
|
/** Which text the vector is built from. `description` is NON-canonical. */
|
|
666
914
|
field: Field;
|
|
667
915
|
}
|
|
@@ -698,6 +946,19 @@ export function parse_args(argv: string[]): Args {
|
|
|
698
946
|
} else if (a === '--threshold' || a.startsWith('--threshold=')) {
|
|
699
947
|
const inline = a.startsWith('--threshold=') ? a.slice('--threshold='.length) : undefined;
|
|
700
948
|
args.threshold = Number(takeValue('--threshold', inline, () => argv[++i]));
|
|
949
|
+
} else if (a === '--neighbour-root' || a.startsWith('--neighbour-root=')) {
|
|
950
|
+
const inline = a.startsWith('--neighbour-root=')
|
|
951
|
+
? a.slice('--neighbour-root='.length)
|
|
952
|
+
: undefined;
|
|
953
|
+
args.neighbourRoot = path.resolve(
|
|
954
|
+
ROOT,
|
|
955
|
+
takeValue('--neighbour-root', inline, () => argv[++i]),
|
|
956
|
+
);
|
|
957
|
+
} else if (a === '--cross-cache' || a.startsWith('--cross-cache=')) {
|
|
958
|
+
const inline = a.startsWith('--cross-cache=')
|
|
959
|
+
? a.slice('--cross-cache='.length)
|
|
960
|
+
: undefined;
|
|
961
|
+
args.crossCache = path.resolve(ROOT, takeValue('--cross-cache', inline, () => argv[++i]));
|
|
701
962
|
} else if (a === '--root' || a.startsWith('--root=')) {
|
|
702
963
|
const inline = a.startsWith('--root=') ? a.slice('--root='.length) : undefined;
|
|
703
964
|
args.root = path.resolve(ROOT, takeValue('--root', inline, () => argv[++i]));
|