@event4u/agent-config 14.21.0 → 14.23.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 +164 -187
- package/CREDITS.md +37 -0
- package/README.md +1 -1
- package/dist/agent-src/commands/analyze/inbox.md +380 -5
- package/dist/agent-src/commands/analyze/repo.md +4 -2
- package/dist/agent-src/commands/pr/merge.md +47 -38
- package/dist/agent-src/commands/roadmap/next.md +8 -0
- package/dist/agent-src/contexts/communication/rules-auto/token-efficiency-mechanics.md +69 -0
- package/dist/agent-src/contexts/execution/auto-dispatch-classification.md +5 -5
- package/dist/agent-src/contexts/execution/autonomy-mechanics.md +12 -0
- package/dist/agent-src/rules/external-code-graph-interop.md +19 -14
- package/dist/agent-src/rules/notes-first-reasoning.md +36 -25
- package/dist/agent-src/rules/token-efficiency.md +26 -3
- package/dist/agent-src/scripts/_cli_entry.ts +51 -0
- package/dist/agent-src/scripts/archive_completed_roadmaps.ts +120 -27
- package/dist/agent-src/scripts/roadmap_gates.ts +4 -20
- package/dist/agent-src/scripts/roadmap_set_step.ts +5 -2
- package/dist/agent-src/scripts/stubs_due.ts +4 -18
- package/dist/agent-src/scripts/update_roadmap_progress.ts +7 -24
- package/dist/agent-src/skills/code-intelligence/SKILL.md +82 -1
- package/dist/agent-src/skills/git-workflow/references/push-closes-its-loop.md +9 -0
- package/dist/agent-src/skills/roadmap-writing/SKILL.md +17 -0
- package/dist/agent-src/skills/tailwind-engineer/SKILL.md +52 -2
- package/dist/agent-src/templates/agent-settings.md +0 -1
- package/dist/agent-src/templates/agents/agent-project-settings.example.yml +1 -1
- package/dist/cli/registry.js +1 -1
- package/dist/cli-delegate/_cli_entry.js +8 -0
- package/dist/cli-delegate/_lib/pin_resolver.js +170 -0
- package/dist/cli-delegate/archival_sweep.js +8 -0
- package/dist/cli-delegate/archive_completed_roadmaps.js +542 -0
- package/dist/cli-delegate/check_update_banner.js +923 -0
- package/dist/cli-delegate/{chunk-ZVBOHSVF.js → chunk-2KKXJW25.js} +23 -9
- package/dist/cli-delegate/chunk-5EK5WQN6.js +28 -0
- package/dist/cli-delegate/chunk-7XGD2OEU.js +96 -0
- package/dist/cli-delegate/chunk-DBH4AHDK.js +155 -0
- package/dist/cli-delegate/{chunk-4FS4WRXP.js → chunk-IAI3XL43.js} +15 -3
- package/dist/cli-delegate/chunk-JO25G5H7.js +20 -0
- package/dist/cli-delegate/{chunk-643Z7FIC.js → chunk-KCNTUSPI.js} +7 -1
- package/dist/cli-delegate/chunk-LTYVIY76.js +7961 -0
- package/dist/cli-delegate/chunk-QAJS5YWY.js +999 -0
- package/dist/cli-delegate/{chunk-YS6XTLEK.js → chunk-TYSY33MD.js} +56 -6
- package/dist/cli-delegate/chunk-ULET4BYS.js +328 -0
- package/dist/cli-delegate/chunk-YDQSHTD7.js +187 -0
- package/dist/cli-delegate/chunk-YWVXLR44.js +48 -0
- package/dist/cli-delegate/chunk-ZZQBUHFK.js +273 -0
- package/dist/cli-delegate/cmd_conformance.js +2 -2
- package/dist/cli-delegate/cmd_export.js +2 -2
- package/dist/cli-delegate/cmd_handoff.js +45 -13
- package/dist/cli-delegate/cmd_prune.js +2 -2
- package/dist/cli-delegate/cmd_session_recycle.js +4 -14
- package/dist/cli-delegate/cmd_settings_migrate.js +2 -2
- package/dist/cli-delegate/cmd_settings_set.js +1 -1
- package/dist/cli-delegate/cmd_sync.js +2 -2
- package/dist/cli-delegate/cmd_uninstall.js +2 -2
- package/dist/cli-delegate/cmd_validate.js +2 -2
- package/dist/cli-delegate/dashboard_mode.js +16 -0
- package/dist/cli-delegate/gate_budget.js +24 -0
- package/dist/cli-delegate/gate_execute.js +25 -0
- package/dist/cli-delegate/guarded_baseline.js +18 -0
- package/dist/cli-delegate/resume_probe.js +22 -0
- package/dist/cli-delegate/roadmap_gates.js +638 -0
- package/dist/cli-delegate/roadmap_set_step.js +173 -0
- package/dist/cli-delegate/stub_queue.js +47 -0
- package/dist/cli-delegate/stubs_due.js +38 -0
- package/dist/cli-delegate/ticket_bundles.js +59 -0
- package/dist/cli-delegate/update_roadmap_progress.js +60 -0
- package/dist/discovery/deprecation-report.md +1 -1
- package/dist/discovery/discovery-manifest.json +13 -13
- package/dist/discovery/discovery-manifest.json.sha256 +1 -1
- package/dist/discovery/discovery-manifest.summary.md +1 -1
- package/dist/discovery/dormancy-report.md +1 -1
- package/dist/discovery/orphan-report.md +1 -1
- package/dist/discovery/packs.json +2 -2
- package/dist/discovery/trust-report.md +1 -1
- package/dist/discovery/workspaces.json +2 -2
- package/dist/hooks/dispatch.js +1813 -946
- package/dist/install/claudeLayerCarriage.js +237 -0
- package/dist/install/claudePathsPlan.js +79 -0
- package/dist/install/hostLayerFingerprint.js +1 -1
- package/dist/install/install.mjs +21 -3
- package/dist/install/partitionEligibility.js +231 -117
- package/dist/install/ruleLayerPartition.js +10 -7
- package/dist/mcp/registry-manifest.json +2 -2
- package/dist/mcp/server.json +2 -2
- package/dist/mcp/server.mjs +10870 -7641
- package/dist/scripts/_lib/lean_projection_mode.js +108 -0
- package/dist/scripts/_lib/router_match.js +243 -0
- package/dist/scripts/_lib/rule_injection.js +216 -0
- package/dist/scripts/condense.js +52 -45
- package/dist/scripts/project_thin_rules.js +139 -19
- package/dist/server/schemas/settings.js +16 -3
- package/docs/MIGRATION.md +105 -1
- package/docs/contracts/settings-classes.md +40 -8
- package/docs/guidelines/agent-infra/minimal-safe-diff-mechanics.md +73 -0
- package/docs/guidelines/agent-infra/notes-horizon-mechanics.md +90 -0
- package/llms.txt +1 -1
- package/package.json +7 -4
- package/src/agent-src/scripts/_cli_entry.ts +51 -0
- package/src/agent-src/scripts/archive_completed_roadmaps.ts +120 -27
- package/src/agent-src/scripts/roadmap_gates.ts +4 -20
- package/src/agent-src/scripts/roadmap_set_step.ts +5 -2
- package/src/agent-src/scripts/stubs_due.ts +4 -18
- package/src/agent-src/scripts/update_roadmap_progress.ts +7 -24
- package/src/config/agent-settings.template.yml +47 -1
- package/src/config/continuity-surface.json +516 -0
- package/src/config/estate-count-budget.json +1 -1
- package/src/config/evaluator-budgets.json +4 -4
- package/src/config/gate-coverage.yml +29 -8
- package/src/config/gate-violation-baselines.json +8 -8
- package/src/config/gitignore-block.txt +19 -0
- package/src/config/hook-token-budget.json +7 -6
- package/src/config/host-injection-effect.json +73 -0
- package/src/config/packed-binary-manifest.json +33 -0
- package/src/config/preamble-payload-budget.json +112 -105
- package/src/config/publish-surface.json +4 -2
- package/src/config/routing-coverage-seed.json +9 -2
- package/src/install/claudeLayerCarriage.ts +273 -0
- package/src/install/claudePathsPlan.ts +76 -0
- package/src/install/hostLayerFingerprint.ts +1 -1
- package/src/install/partitionEligibility.ts +261 -127
- package/src/install/ruleLayerPartition.ts +19 -23
- package/src/scripts/_cli/cmd_session_recycle.ts +2 -25
- package/src/scripts/_cli/handoff_sessions.ts +14 -44
- package/src/scripts/_dispatch.bash +77 -27
- package/src/scripts/_lib/auto_dispatch.ts +2 -2
- package/src/scripts/_lib/catalogue_layer_parity.ts +146 -0
- package/src/scripts/_lib/continuity_slot.ts +295 -0
- package/src/scripts/_lib/continuity_surface.ts +296 -0
- package/src/scripts/_lib/continuity_writer.ts +253 -0
- package/src/scripts/_lib/corpus_manifest.ts +26 -9
- package/src/scripts/_lib/hook_settings.ts +86 -1
- package/src/scripts/_lib/injection_effect.ts +151 -0
- package/src/scripts/_lib/layer_overlap_notice.ts +50 -6
- package/src/scripts/_lib/lean_projection_mode.ts +127 -0
- package/src/scripts/_lib/packed_binary_predicate.ts +262 -0
- package/src/scripts/_lib/pin_resolver.ts +13 -0
- package/src/scripts/_lib/recycle_envelope_paths.ts +109 -21
- package/src/scripts/_lib/regression_neighbourhood.ts +159 -22
- package/src/scripts/_lib/retired_status.ts +74 -0
- package/src/scripts/_lib/session_eol.ts +44 -0
- package/src/scripts/_lib/session_index_trust.ts +346 -0
- package/src/scripts/_lib/skill_catalogue.ts +156 -21
- package/src/scripts/_lib/standing_bound_ratchet.ts +154 -0
- package/src/scripts/_lib/subagent_capsule.ts +117 -8
- package/src/scripts/_lib/vendored_grammar_upstream.ts +280 -0
- package/src/scripts/ai_council/api_transport.ts +5 -0
- package/src/scripts/ai_council/deliberation_metrics.ts +353 -0
- package/src/scripts/ai_council/prompts.ts +9 -0
- package/src/scripts/ai_council/synthesis_disagreement.ts +206 -0
- package/src/scripts/audit_mcp_tools.ts +69 -25
- package/src/scripts/build_mcp_catalog.ts +24 -1
- package/src/scripts/capture_skill_catalogue.ts +24 -11
- package/src/scripts/check_candidate_lines.ts +499 -0
- package/src/scripts/check_continuity_surface.ts +475 -0
- package/src/scripts/check_dependency_floors.ts +13 -6
- package/src/scripts/check_estate_count.ts +40 -79
- package/src/scripts/check_host_tree_parity.ts +291 -0
- package/src/scripts/check_pack_size.ts +262 -23
- package/src/scripts/check_preamble_payload_budget.ts +136 -7
- package/src/scripts/check_roadmap_trackable.ts +19 -16
- package/src/scripts/check_rule_layer_partition.ts +54 -34
- package/src/scripts/check_rule_projection_integrity.ts +102 -2
- package/src/scripts/check_single_delivery.ts +97 -31
- package/src/scripts/check_update_banner.ts +13 -0
- package/src/scripts/ci_settle.ts +205 -5
- package/src/scripts/code_graph/build.ts +217 -15
- package/src/scripts/code_graph/cli.ts +334 -15
- package/src/scripts/code_graph/detect.ts +38 -0
- package/src/scripts/code_graph/extract.ts +11 -0
- package/src/scripts/code_graph/grammar_probe.ts +43 -0
- package/src/scripts/code_graph/loader.ts +88 -14
- package/src/scripts/code_graph/query.ts +144 -11
- package/src/scripts/code_graph/resolution_tiers.ts +238 -0
- package/src/scripts/code_graph/sqlite_store.ts +262 -6
- package/src/scripts/code_graph/types.ts +117 -3
- package/src/scripts/code_graph/validate.ts +26 -1
- package/src/scripts/code_graph/verbs.ts +590 -0
- package/src/scripts/condense.ts +54 -42
- package/src/scripts/corpus_manifest.ts +5 -5
- package/src/scripts/council_cli.ts +11 -12
- package/src/scripts/count_intent_disagreement.ts +426 -0
- package/src/scripts/handoff_context_hook.ts +118 -1
- package/src/scripts/hook_effect_doctor.ts +82 -1
- package/src/scripts/hook_manifest.json +1 -1
- package/src/scripts/hook_manifest.yaml +87 -12
- package/src/scripts/hooks/block_speaking_inbox_dir.ts +182 -16
- package/src/scripts/hooks/chain_nudge_hook.ts +282 -0
- package/src/scripts/hooks/code_graph_context_hook.ts +201 -0
- package/src/scripts/hooks/concern_registry.ts +4 -2
- package/src/scripts/hooks/host_semantics.ts +1 -1
- package/src/scripts/hooks/orchestration_record_hook.ts +2 -2
- package/src/scripts/hooks/push_settle_hook.ts +3 -2
- package/src/scripts/hooks/rule_inject_hook.ts +108 -21
- package/src/scripts/hooks/session_eol_hook.ts +120 -2
- package/src/scripts/hooks/skill_route_hook.ts +11 -4
- package/src/scripts/hot_context_hook.ts +62 -7
- package/src/scripts/inbox_source_census.ts +322 -0
- package/src/scripts/install-hooks.sh +82 -0
- package/src/scripts/{lint_carrier_integrity.ts → lint_deferral_integrity.ts} +60 -214
- package/src/scripts/lint_plan_risk_register.ts +13 -17
- package/src/scripts/lint_roadmap_blockers.ts +164 -1
- package/src/scripts/mcp_server/consumer_tool_catalog.json +152 -2
- package/src/scripts/mcp_server/graph_tools.ts +294 -0
- package/src/scripts/mcp_server/path_util.ts +31 -0
- package/src/scripts/mcp_server/suggest_skill.ts +72 -0
- package/src/scripts/mcp_server/tool_catalog_source.ts +7 -1
- package/src/scripts/mcp_server/tools.ts +5 -69
- package/src/scripts/media/lib/adapter-contract.md +7 -2
- package/src/scripts/media/lib/fixtures/flux/asset-0001.png +0 -0
- package/src/scripts/media/lib/fixtures/gemini-image/asset-0001.png +0 -0
- package/src/scripts/media/lib/fixtures/ideogram/asset-0001.png +0 -0
- package/src/scripts/model_rule_injection.ts +61 -4
- package/src/scripts/project_thin_rules.ts +149 -16
- package/src/scripts/rdp_candidates_delta.ts +276 -0
- package/src/scripts/rdp_quality_eval.ts +217 -19
- package/src/scripts/release_mix_taxonomy.json +3 -2
- package/src/scripts/report_host_injection_effect.ts +189 -0
- package/src/scripts/report_layer_overlap.ts +41 -0
- package/src/scripts/report_skill_menu_census.ts +341 -0
- package/src/scripts/report_standing_payload_by_host.ts +650 -0
- package/src/scripts/schemas/agent-settings.schema.json +7 -1
- package/src/scripts/session_memory_index.ts +81 -8
- package/src/scripts/skill_tools/score_skill_relevance.ts +70 -6
- package/src/scripts/skill_tools/suggest_skill_for_task.ts +37 -3
- package/src/server/schemas/settings.ts +23 -4
- package/src/vendor/grammars/README.md +86 -0
- package/src/vendor/grammars/tree-sitter-javascript.wasm +0 -0
- package/src/vendor/grammars/tree-sitter-php.wasm +0 -0
- package/src/vendor/grammars/tree-sitter-typescript.wasm +0 -0
- package/src/scripts/_lib/carrier_status.ts +0 -118
- package/src/scripts/_lib/estate_carrier_cases.ts +0 -72
- package/src/scripts/hooks/code_graph_nudge_hook.ts +0 -191
|
@@ -9,22 +9,135 @@ import * as fs from 'node:fs';
|
|
|
9
9
|
|
|
10
10
|
import { LexicalIndex, tokenize } from '../_lib/lexical_index.js';
|
|
11
11
|
import { sanitizeLabel } from './sanitize.js';
|
|
12
|
-
import {
|
|
13
|
-
|
|
12
|
+
import {
|
|
13
|
+
emitSqliteTwin,
|
|
14
|
+
type GraphIndex,
|
|
15
|
+
INDEXED_READ_MIN_EDGES,
|
|
16
|
+
loadSerializedFromTwin,
|
|
17
|
+
openGraphIndex,
|
|
18
|
+
} from './sqlite_store.js';
|
|
19
|
+
import type { CodeEdge, CodeGraph, CodeNode } from './types.js';
|
|
14
20
|
import { validateGraph } from './validate.js';
|
|
15
21
|
|
|
16
22
|
const APPROX_CHARS_PER_TOKEN = 4;
|
|
17
23
|
|
|
24
|
+
/**
|
|
25
|
+
* The node/adjacency reads every verb performs.
|
|
26
|
+
*
|
|
27
|
+
* Declared as interfaces rather than `Map` so one set of verbs runs over two
|
|
28
|
+
* stores. `Map` satisfies both structurally, so the in-memory path is unchanged
|
|
29
|
+
* and needed no edits; the SQLite path supplies objects with the same two
|
|
30
|
+
* methods that hit an index instead of a heap.
|
|
31
|
+
*/
|
|
32
|
+
export interface NodeLookup {
|
|
33
|
+
get(id: string): CodeNode | undefined;
|
|
34
|
+
has(id: string): boolean;
|
|
35
|
+
}
|
|
36
|
+
export interface AdjacencyLookup {
|
|
37
|
+
get(id: string): CodeEdge[] | undefined;
|
|
38
|
+
}
|
|
39
|
+
|
|
18
40
|
export interface LoadedGraph {
|
|
19
|
-
|
|
41
|
+
/**
|
|
42
|
+
* The whole deserialized graph — `null` on the indexed path, where not
|
|
43
|
+
* materializing it is the entire point. Nothing in this module reads it;
|
|
44
|
+
* the two callers that used to (`resolveSeedsTiered`, the CLI's
|
|
45
|
+
* changed-file seeds) go through `idsByLabel` / `idsInFiles` instead, which
|
|
46
|
+
* answer the same questions from an index.
|
|
47
|
+
*/
|
|
48
|
+
graph: CodeGraph | null;
|
|
20
49
|
source: string; // attribution path
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
50
|
+
/**
|
|
51
|
+
* The on-disk path the answer came from.
|
|
52
|
+
*
|
|
53
|
+
* Distinct from `source`, which is a display string (`native:relpath`) and
|
|
54
|
+
* cannot be handed to a freshness check. A verb that prints staleness has
|
|
55
|
+
* to describe THIS file, not a conventional cache path — see
|
|
56
|
+
* `cli.ts::stateOf`.
|
|
57
|
+
*/
|
|
58
|
+
answeredBy: string;
|
|
59
|
+
byId: NodeLookup;
|
|
60
|
+
out: AdjacencyLookup;
|
|
61
|
+
in: AdjacencyLookup;
|
|
62
|
+
/** Exact-label resolution. */
|
|
63
|
+
idsByLabel(label: string, limit: number): string[];
|
|
64
|
+
/** Non-file nodes declared in any of `files`. */
|
|
65
|
+
idsInFiles(files: readonly string[]): string[];
|
|
66
|
+
/**
|
|
67
|
+
* Every node id, sorted.
|
|
68
|
+
*
|
|
69
|
+
* A whole-table read, and unlike {@link LoadedGraph.lex} it is not lazy —
|
|
70
|
+
* because the one verb that calls it (`dead`, 3.3) is defined over every
|
|
71
|
+
* node by construction: "zero accepted in-edges" is not a question you can
|
|
72
|
+
* ask about a frontier. Nothing else may use it; a verb that walks from a
|
|
73
|
+
* seed has `idsByLabel` / `idsInFiles` for that.
|
|
74
|
+
*/
|
|
75
|
+
allNodeIds(): string[];
|
|
76
|
+
/**
|
|
77
|
+
* BM25 fallback corpus, LAZY.
|
|
78
|
+
*
|
|
79
|
+
* A function rather than a field because on the indexed path building it
|
|
80
|
+
* reads every node — the one whole-table scan left — and a seed that
|
|
81
|
+
* resolves by exact id or exact label must never pay for it. On the
|
|
82
|
+
* in-memory path the corpus is already in hand and this just returns it.
|
|
83
|
+
*/
|
|
84
|
+
lex(): LexicalIndex;
|
|
85
|
+
/** Release any handle the store holds. No-op for the in-memory path. */
|
|
86
|
+
close(): void;
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
/**
|
|
90
|
+
* A `LoadedGraph` backed by the SQLite index — no `JSON.parse`, no whole-graph
|
|
91
|
+
* `Map`.
|
|
92
|
+
*
|
|
93
|
+
* The adjacency lookups are per-call SQL against `edges_source` / `edges_target`
|
|
94
|
+
* rather than a materialized map, which is the difference the threshold buys: a
|
|
95
|
+
* 2-hop `affected` touches the rows for its frontier, not every edge in the
|
|
96
|
+
* repository. Node reads are memoized per handle because a BFS asks for the
|
|
97
|
+
* same label repeatedly while rendering edge lines, and that repetition is
|
|
98
|
+
* cheap to remove and expensive to leave.
|
|
99
|
+
*/
|
|
100
|
+
function indexBackedGraph(index: GraphIndex, source: string, answeredBy: string): LoadedGraph {
|
|
101
|
+
const nodeCache = new Map<string, CodeNode | undefined>();
|
|
102
|
+
const readNode = (id: string): CodeNode | undefined => {
|
|
103
|
+
if (nodeCache.has(id)) return nodeCache.get(id);
|
|
104
|
+
const n = index.getNode(id);
|
|
105
|
+
nodeCache.set(id, n);
|
|
106
|
+
return n;
|
|
107
|
+
};
|
|
108
|
+
let lexical: LexicalIndex | null = null;
|
|
109
|
+
return {
|
|
110
|
+
graph: null,
|
|
111
|
+
source,
|
|
112
|
+
answeredBy,
|
|
113
|
+
byId: { get: readNode, has: (id) => readNode(id) !== undefined },
|
|
114
|
+
out: { get: (id) => index.edgesFrom(id) },
|
|
115
|
+
in: { get: (id) => index.edgesTo(id) },
|
|
116
|
+
idsByLabel: (labelText, limit) => index.idsByLabel(labelText, limit),
|
|
117
|
+
idsInFiles: (files) => index.idsInFiles(files),
|
|
118
|
+
allNodeIds: () => index.allNodeIds(),
|
|
119
|
+
lex: () => {
|
|
120
|
+
lexical ??= new LexicalIndex(index.lexicalCorpus());
|
|
121
|
+
return lexical;
|
|
122
|
+
},
|
|
123
|
+
close: () => index.close(),
|
|
124
|
+
};
|
|
25
125
|
}
|
|
26
126
|
|
|
27
127
|
export function loadGraph(graphPath: string, source = graphPath): LoadedGraph {
|
|
128
|
+
// Above the threshold, read through the INDEX: the twin's rows answer
|
|
129
|
+
// per-node and per-edge questions directly, so neither `JSON.parse` nor the
|
|
130
|
+
// two whole-graph `Map`s below ever run. This is the D6 repair — the twin
|
|
131
|
+
// used to be preferred and still returned the entire serialization, which
|
|
132
|
+
// made it a cheaper blob transport rather than an index.
|
|
133
|
+
const index = openGraphIndex(graphPath);
|
|
134
|
+
if (index) {
|
|
135
|
+
if (index.edgeCount >= INDEXED_READ_MIN_EDGES) return indexBackedGraph(index, source, graphPath);
|
|
136
|
+
// Small graph: the in-memory path is cheaper. Release the handle rather
|
|
137
|
+
// than leaking a database for the life of the process.
|
|
138
|
+
index.close();
|
|
139
|
+
}
|
|
140
|
+
|
|
28
141
|
// Prefer the derived SQLite twin (ADR-129): checksum/stat-verified against
|
|
29
142
|
// the canonical JSON, byte-identical content, ~1/90 the load cost at
|
|
30
143
|
// consumer scale. Fallback = parse the JSON — and best-effort re-emit the
|
|
@@ -51,7 +164,25 @@ export function loadGraph(graphPath: string, source = graphPath): LoadedGraph {
|
|
|
51
164
|
add(inM, e.target, e);
|
|
52
165
|
}
|
|
53
166
|
const lex = new LexicalIndex(graph.nodes.map((n) => ({ id: n.id, text: `${n.label} ${n.id}` })));
|
|
54
|
-
return {
|
|
167
|
+
return {
|
|
168
|
+
graph,
|
|
169
|
+
source,
|
|
170
|
+
answeredBy: graphPath,
|
|
171
|
+
byId,
|
|
172
|
+
out: outM,
|
|
173
|
+
in: inM,
|
|
174
|
+
idsByLabel: (labelText, limit) =>
|
|
175
|
+
graph.nodes.filter((n) => n.label === labelText).slice(0, limit).map((n) => n.id),
|
|
176
|
+
idsInFiles: (files) => {
|
|
177
|
+
const set = new Set(files);
|
|
178
|
+
return graph.nodes.filter((n) => set.has(n.source_file) && n.kind !== 'file').map((n) => n.id);
|
|
179
|
+
},
|
|
180
|
+
allNodeIds: () => graph.nodes.map((n) => n.id).sort((a, b) => (a < b ? -1 : a > b ? 1 : 0)),
|
|
181
|
+
lex: () => lex,
|
|
182
|
+
close: () => {
|
|
183
|
+
/* nothing held */
|
|
184
|
+
},
|
|
185
|
+
};
|
|
55
186
|
}
|
|
56
187
|
|
|
57
188
|
interface SeedResolution {
|
|
@@ -64,9 +195,11 @@ interface SeedResolution {
|
|
|
64
195
|
/** Resolve a free-text seed to node ids: exact id → exact label → BM25. */
|
|
65
196
|
function resolveSeedsTiered(g: LoadedGraph, seed: string, limit = 5): SeedResolution {
|
|
66
197
|
if (g.byId.has(seed)) return { ids: [seed], weak: false };
|
|
67
|
-
const exactLabel = g.
|
|
68
|
-
if (exactLabel.length) return { ids: exactLabel
|
|
69
|
-
|
|
198
|
+
const exactLabel = g.idsByLabel(seed, limit);
|
|
199
|
+
if (exactLabel.length) return { ids: exactLabel, weak: false };
|
|
200
|
+
// Only here does the BM25 corpus get built — the one whole-table read on
|
|
201
|
+
// the indexed path, and unreachable when the seed resolved exactly.
|
|
202
|
+
const ranked = g.lex().rank(tokenize(seed)).filter((r) => r.score > 0);
|
|
70
203
|
return { ids: ranked.slice(0, limit).map((r) => r.id), weak: true };
|
|
71
204
|
}
|
|
72
205
|
|
|
@@ -0,0 +1,238 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Resolution tiers that run BEFORE the repo-wide name lookup
|
|
3
|
+
* (road-to-a-graph-that-is-shipped 2.3).
|
|
4
|
+
*
|
|
5
|
+
* The extractor's ladder ends in a same-name table across the whole
|
|
6
|
+
* repository. That rung is a guess and is labelled one — `INFERRED`, via
|
|
7
|
+
* `name-lookup`. Two configuration files in a normal project state the answer
|
|
8
|
+
* outright, and reading them turns a guess into a fact:
|
|
9
|
+
*
|
|
10
|
+
* · `tsconfig.json` `compilerOptions.paths` — `@shared/x` IS `src/shared/x`,
|
|
11
|
+
* because the project says so. Without this tier the specifier is non-relative,
|
|
12
|
+
* so `resolveSpecifier` returns null and the import binds to `external:@shared/x`
|
|
13
|
+
* — a real module, correctly named, and the wrong one.
|
|
14
|
+
* · `composer.json` `autoload.psr-4` — `App\Services\Mailer` IS
|
|
15
|
+
* `app/Services/Mailer.php`. Without this tier a PHP `use` is matched by BASE
|
|
16
|
+
* NAME with the namespace discarded, so two classes called `Mailer` in
|
|
17
|
+
* different namespaces are indistinguishable.
|
|
18
|
+
*
|
|
19
|
+
* Both are DECLARED mappings, so an edge resolved through one is EXTRACTED, not
|
|
20
|
+
* INFERRED: the project's own config is as much a syntactic fact as an import
|
|
21
|
+
* specifier is.
|
|
22
|
+
*
|
|
23
|
+
* Deterministic and IO-free at resolution time — the configs are read once by
|
|
24
|
+
* the caller and passed in, so `buildGraph` stays pure and identical source
|
|
25
|
+
* plus identical config yields identical bytes.
|
|
26
|
+
*/
|
|
27
|
+
import type { ResolvedVia } from './types.js';
|
|
28
|
+
|
|
29
|
+
/** Extensions a TS/JS alias target may carry, in resolution order. */
|
|
30
|
+
const ALIAS_EXTS = ['.ts', '.tsx', '.mts', '.cts', '.js', '.jsx', '.mjs', '.cjs'] as const;
|
|
31
|
+
|
|
32
|
+
export interface AliasRule {
|
|
33
|
+
/** The pattern's literal prefix, e.g. `@shared/` for `@shared/*`. */
|
|
34
|
+
prefix: string;
|
|
35
|
+
/** Whether the pattern ended in `*` (a prefix rule) or was exact. */
|
|
36
|
+
wildcard: boolean;
|
|
37
|
+
/** Repo-relative substitution targets, already joined with `baseUrl`. */
|
|
38
|
+
targets: string[];
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
export interface Psr4Rule {
|
|
42
|
+
/** Namespace prefix WITH its trailing separator, e.g. `App\\`. */
|
|
43
|
+
prefix: string;
|
|
44
|
+
/** Repo-relative directories, with a trailing slash. */
|
|
45
|
+
dirs: string[];
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
export interface ResolutionConfig {
|
|
49
|
+
aliases: AliasRule[];
|
|
50
|
+
psr4: Psr4Rule[];
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
export const EMPTY_RESOLUTION_CONFIG: ResolutionConfig = { aliases: [], psr4: [] };
|
|
54
|
+
|
|
55
|
+
/**
|
|
56
|
+
* Parse JSON that may carry comments and trailing commas.
|
|
57
|
+
*
|
|
58
|
+
* `tsconfig.json` is JSONC by convention and this repository's own uses `//`
|
|
59
|
+
* comments, so `JSON.parse` on it throws. Returning null rather than throwing
|
|
60
|
+
* is deliberate: an unparseable config must degrade to "no tiers", never break
|
|
61
|
+
* a build. Strings are walked rather than regex-stripped so a `//` inside a
|
|
62
|
+
* path value survives.
|
|
63
|
+
*/
|
|
64
|
+
export function parseJsonc(text: string): unknown | null {
|
|
65
|
+
let out = '';
|
|
66
|
+
let inString = false;
|
|
67
|
+
let escaped = false;
|
|
68
|
+
let i = 0;
|
|
69
|
+
while (i < text.length) {
|
|
70
|
+
const ch = text[i] as string;
|
|
71
|
+
if (inString) {
|
|
72
|
+
out += ch;
|
|
73
|
+
if (escaped) escaped = false;
|
|
74
|
+
else if (ch === '\\') escaped = true;
|
|
75
|
+
else if (ch === '"') inString = false;
|
|
76
|
+
i += 1;
|
|
77
|
+
continue;
|
|
78
|
+
}
|
|
79
|
+
if (ch === '"') {
|
|
80
|
+
inString = true;
|
|
81
|
+
out += ch;
|
|
82
|
+
i += 1;
|
|
83
|
+
continue;
|
|
84
|
+
}
|
|
85
|
+
if (ch === '/' && text[i + 1] === '/') {
|
|
86
|
+
while (i < text.length && text[i] !== '\n') i += 1;
|
|
87
|
+
continue;
|
|
88
|
+
}
|
|
89
|
+
if (ch === '/' && text[i + 1] === '*') {
|
|
90
|
+
i += 2;
|
|
91
|
+
while (i < text.length && !(text[i] === '*' && text[i + 1] === '/')) i += 1;
|
|
92
|
+
i += 2;
|
|
93
|
+
continue;
|
|
94
|
+
}
|
|
95
|
+
out += ch;
|
|
96
|
+
i += 1;
|
|
97
|
+
}
|
|
98
|
+
// Trailing commas before a closing brace/bracket.
|
|
99
|
+
out = out.replace(/,(\s*[}\]])/g, '$1');
|
|
100
|
+
try {
|
|
101
|
+
return JSON.parse(out) as unknown;
|
|
102
|
+
} catch {
|
|
103
|
+
return null;
|
|
104
|
+
}
|
|
105
|
+
}
|
|
106
|
+
|
|
107
|
+
function isRecord(v: unknown): v is Record<string, unknown> {
|
|
108
|
+
return typeof v === 'object' && v !== null && !Array.isArray(v);
|
|
109
|
+
}
|
|
110
|
+
|
|
111
|
+
/** Normalise a repo-relative path fragment: no leading `./`, no trailing `/`. */
|
|
112
|
+
function normalizeFragment(p: string): string {
|
|
113
|
+
return p.replace(/^\.\//, '').replace(/\/+$/, '');
|
|
114
|
+
}
|
|
115
|
+
|
|
116
|
+
/**
|
|
117
|
+
* Build the alias tier from a parsed `tsconfig.json`.
|
|
118
|
+
*
|
|
119
|
+
* `baseUrl` is resolved relative to the tsconfig's own directory, which for a
|
|
120
|
+
* root tsconfig is the repo root — so `baseUrl: "./src"` plus `"@shared/*":
|
|
121
|
+
* ["shared/*"]` yields the repo-relative prefix `src/shared/`.
|
|
122
|
+
*/
|
|
123
|
+
export function aliasRulesFrom(tsconfig: unknown): AliasRule[] {
|
|
124
|
+
if (!isRecord(tsconfig)) return [];
|
|
125
|
+
const co = tsconfig['compilerOptions'];
|
|
126
|
+
if (!isRecord(co)) return [];
|
|
127
|
+
const paths = co['paths'];
|
|
128
|
+
if (!isRecord(paths)) return [];
|
|
129
|
+
const baseUrl = typeof co['baseUrl'] === 'string' ? normalizeFragment(co['baseUrl']) : '';
|
|
130
|
+
const rules: AliasRule[] = [];
|
|
131
|
+
for (const [pattern, valueRaw] of Object.entries(paths)) {
|
|
132
|
+
if (!Array.isArray(valueRaw)) continue;
|
|
133
|
+
const wildcard = pattern.endsWith('*');
|
|
134
|
+
const prefix = wildcard ? pattern.slice(0, -1) : pattern;
|
|
135
|
+
const targets: string[] = [];
|
|
136
|
+
for (const v of valueRaw) {
|
|
137
|
+
if (typeof v !== 'string') continue;
|
|
138
|
+
const tgt = wildcard ? (v.endsWith('*') ? v.slice(0, -1) : v) : v;
|
|
139
|
+
const joined = baseUrl ? `${baseUrl}/${normalizeFragment(tgt)}` : normalizeFragment(tgt);
|
|
140
|
+
targets.push(joined.replace(/\/+$/, ''));
|
|
141
|
+
}
|
|
142
|
+
if (targets.length) rules.push({ prefix, wildcard, targets });
|
|
143
|
+
}
|
|
144
|
+
// Longest prefix first: `@shared/deep/` must beat `@shared/` when both are
|
|
145
|
+
// declared, which is TypeScript's own rule and not a preference.
|
|
146
|
+
rules.sort((a, b) => b.prefix.length - a.prefix.length);
|
|
147
|
+
return rules;
|
|
148
|
+
}
|
|
149
|
+
|
|
150
|
+
/** Build the PSR-4 tier from a parsed `composer.json`. */
|
|
151
|
+
export function psr4RulesFrom(composer: unknown): Psr4Rule[] {
|
|
152
|
+
if (!isRecord(composer)) return [];
|
|
153
|
+
const rules: Psr4Rule[] = [];
|
|
154
|
+
for (const key of ['autoload', 'autoload-dev']) {
|
|
155
|
+
const block = composer[key];
|
|
156
|
+
if (!isRecord(block)) continue;
|
|
157
|
+
const psr4 = block['psr-4'];
|
|
158
|
+
if (!isRecord(psr4)) continue;
|
|
159
|
+
for (const [ns, valueRaw] of Object.entries(psr4)) {
|
|
160
|
+
const dirsRaw = Array.isArray(valueRaw) ? valueRaw : [valueRaw];
|
|
161
|
+
const dirs = dirsRaw
|
|
162
|
+
.filter((d): d is string => typeof d === 'string')
|
|
163
|
+
.map((d) => {
|
|
164
|
+
const n = normalizeFragment(d);
|
|
165
|
+
return n === '' ? '' : `${n}/`;
|
|
166
|
+
});
|
|
167
|
+
if (dirs.length) rules.push({ prefix: ns, dirs });
|
|
168
|
+
}
|
|
169
|
+
}
|
|
170
|
+
// Longest namespace first — PSR-4's own most-specific-wins rule.
|
|
171
|
+
rules.sort((a, b) => b.prefix.length - a.prefix.length);
|
|
172
|
+
return rules;
|
|
173
|
+
}
|
|
174
|
+
|
|
175
|
+
function firstExisting(candidate: string, fileIds: ReadonlySet<string>): string | null {
|
|
176
|
+
if (fileIds.has(candidate)) return candidate;
|
|
177
|
+
// TS source imports `@shared/mailer.js` and the file on disk is
|
|
178
|
+
// `mailer.ts`, so the RUNTIME extension is stripped before the candidate
|
|
179
|
+
// list is tried — the same normalisation `resolveSpecifier` does for a
|
|
180
|
+
// relative specifier. Without it every alias in an ESM-style codebase
|
|
181
|
+
// misses, which is most of them.
|
|
182
|
+
const stem = candidate.replace(/\.(js|jsx|mjs|cjs)$/, '');
|
|
183
|
+
for (const e of ALIAS_EXTS) if (fileIds.has(stem + e)) return stem + e;
|
|
184
|
+
for (const e of ALIAS_EXTS) if (fileIds.has(`${candidate}/index${e}`)) return `${candidate}/index${e}`;
|
|
185
|
+
return null;
|
|
186
|
+
}
|
|
187
|
+
|
|
188
|
+
/**
|
|
189
|
+
* Resolve a non-relative TS/JS specifier through the alias tier.
|
|
190
|
+
*
|
|
191
|
+
* Returns null when no rule matches or the mapped file is not in the graph —
|
|
192
|
+
* an alias pointing outside the built root is not a hit, and claiming it as one
|
|
193
|
+
* would be worse than the `external:` binding it replaces.
|
|
194
|
+
*/
|
|
195
|
+
export function resolveAlias(spec: string, config: ResolutionConfig, fileIds: ReadonlySet<string>): string | null {
|
|
196
|
+
for (const rule of config.aliases) {
|
|
197
|
+
if (rule.wildcard) {
|
|
198
|
+
if (!spec.startsWith(rule.prefix)) continue;
|
|
199
|
+
const rest = spec.slice(rule.prefix.length);
|
|
200
|
+
for (const t of rule.targets) {
|
|
201
|
+
const hit = firstExisting(rest ? `${t}/${rest}` : t, fileIds);
|
|
202
|
+
if (hit) return hit;
|
|
203
|
+
}
|
|
204
|
+
} else {
|
|
205
|
+
if (spec !== rule.prefix) continue;
|
|
206
|
+
for (const t of rule.targets) {
|
|
207
|
+
const hit = firstExisting(t, fileIds);
|
|
208
|
+
if (hit) return hit;
|
|
209
|
+
}
|
|
210
|
+
}
|
|
211
|
+
}
|
|
212
|
+
return null;
|
|
213
|
+
}
|
|
214
|
+
|
|
215
|
+
/**
|
|
216
|
+
* Resolve a fully-qualified PHP class name through the PSR-4 tier.
|
|
217
|
+
*
|
|
218
|
+
* `App\Services\Mailer` under `{"App\\": "app/"}` becomes
|
|
219
|
+
* `app/Services/Mailer.php`. A leading backslash is tolerated because `use
|
|
220
|
+
* \App\X` is legal and means the same thing.
|
|
221
|
+
*/
|
|
222
|
+
export function resolvePsr4(fqcn: string, config: ResolutionConfig, fileIds: ReadonlySet<string>): string | null {
|
|
223
|
+
const name = fqcn.replace(/^\\+/, '');
|
|
224
|
+
for (const rule of config.psr4) {
|
|
225
|
+
if (!name.startsWith(rule.prefix)) continue;
|
|
226
|
+
const rest = name.slice(rule.prefix.length).replace(/\\/g, '/');
|
|
227
|
+
if (!rest) continue;
|
|
228
|
+
for (const d of rule.dirs) {
|
|
229
|
+
const candidate = `${d}${rest}.php`;
|
|
230
|
+
if (fileIds.has(candidate)) return candidate;
|
|
231
|
+
}
|
|
232
|
+
}
|
|
233
|
+
return null;
|
|
234
|
+
}
|
|
235
|
+
|
|
236
|
+
/** The `resolved_via` value each tier stamps on the edges it produces. */
|
|
237
|
+
export const ALIAS_VIA: ResolvedVia = 'path-alias';
|
|
238
|
+
export const PSR4_VIA: ResolvedVia = 'psr4';
|