@event4u/agent-config 17.2.1 → 17.4.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (225) hide show
  1. package/.augment-plugin/marketplace.json +3 -3
  2. package/.augment-plugin/plugin.json +1 -1
  3. package/.claude-plugin/marketplace.json +1 -1
  4. package/.claude-plugin/skills/install-agent-config/SKILL.md +9 -9
  5. package/CHANGELOG.md +167 -193
  6. package/CREDITS.md +2 -2
  7. package/README.md +1 -1
  8. package/dist/agent-src/skills/code-intelligence/SKILL.md +16 -12
  9. package/dist/agent-src/templates/agents/agent-project-settings.example.yml +1 -1
  10. package/dist/agent-src/templates/scripts/check_memory.ts +1 -1
  11. package/dist/cli/commands/recordTriggerEval.js +1 -1
  12. package/dist/cli/registry.js +2 -2
  13. package/dist/cli-delegate/{chunk-KR5ZIFPO.js → chunk-5AYJGUA3.js} +1 -3
  14. package/dist/cli-delegate/chunk-DIFZBP4G.js +1336 -0
  15. package/dist/cli-delegate/chunk-E7ACTJTJ.js +6792 -0
  16. package/dist/cli-delegate/{chunk-VYZGJLCA.js → chunk-RKNT4LIK.js} +22 -1286
  17. package/dist/cli-delegate/{chunk-BZJ4Y3CI.js → chunk-WY5ZZPGL.js} +2 -2
  18. package/dist/cli-delegate/{chunk-CMZEKSNN.js → chunk-X6LZ7W5A.js} +73 -10
  19. package/dist/cli-delegate/cmd_conformance.js +190 -192
  20. package/dist/cli-delegate/cmd_doctor.js +5 -5
  21. package/dist/cli-delegate/cmd_doctor_neighbours.js +3 -3
  22. package/dist/cli-delegate/cmd_explain.js +1 -1
  23. package/dist/cli-delegate/cmd_export.js +2 -2
  24. package/dist/cli-delegate/cmd_handoff.js +2 -2
  25. package/dist/cli-delegate/cmd_memory_get.js +1 -1
  26. package/dist/cli-delegate/cmd_prune.js +2 -2
  27. package/dist/cli-delegate/cmd_route_audit.js +1 -1
  28. package/dist/cli-delegate/cmd_session_recycle.js +2 -2
  29. package/dist/cli-delegate/cmd_settings_migrate.js +2 -2
  30. package/dist/cli-delegate/cmd_settings_set.js +8 -4
  31. package/dist/cli-delegate/cmd_sync.js +2 -2
  32. package/dist/cli-delegate/cmd_uninstall.js +2 -2
  33. package/dist/cli-delegate/cmd_update.js +24 -13
  34. package/dist/cli-delegate/cmd_upgrade.js +2 -2
  35. package/dist/cli-delegate/cmd_validate.js +2 -2
  36. package/dist/cli-delegate/dist-25AZAHOP.js +3240 -0
  37. package/dist/cli-delegate/dist-P7MFPMF3.js +411 -0
  38. package/dist/cli-delegate/dist-XAS5ZLQB.js +6 -0
  39. package/dist/cli-delegate/{upgrade_unattended-VVL6JLSX.js → upgrade_unattended-QCWOKTUN.js} +178 -163
  40. package/dist/discovery/deprecation-report.md +1 -1
  41. package/dist/discovery/discovery-manifest.json +3 -3
  42. package/dist/discovery/discovery-manifest.json.sha256 +1 -1
  43. package/dist/discovery/discovery-manifest.summary.md +1 -1
  44. package/dist/discovery/dormancy-report.md +1 -1
  45. package/dist/discovery/orphan-report.md +1 -1
  46. package/dist/discovery/packs.json +2 -2
  47. package/dist/discovery/trust-report.md +1 -1
  48. package/dist/discovery/workspaces.json +2 -2
  49. package/dist/hooks/code_graph_refresh.chunk.cjs +443 -0
  50. package/dist/hooks/code_graph_refresh.chunk.cjs.sha256 +1 -0
  51. package/dist/hooks/dispatch.js +1001 -938
  52. package/dist/hooks/dispatch.sha256 +1 -1
  53. package/dist/hooks/egress_engine.chunk.cjs +57 -7
  54. package/dist/hooks/egress_engine.chunk.cjs.sha256 +1 -1
  55. package/dist/hooks/update_notice.chunk.cjs +914 -0
  56. package/dist/hooks/update_notice.chunk.cjs.sha256 +1 -0
  57. package/dist/install/install.mjs +6974 -6037
  58. package/dist/mcp/mcp-cloudflare-catalogue.json +1 -0
  59. package/dist/mcp/registry-manifest.json +3 -2
  60. package/dist/mcp/server.json +13 -4
  61. package/dist/mcp/server.mjs +71202 -63282
  62. package/dist/scripts/_lib/egress_rules.js +8 -2
  63. package/dist/scripts/condense.js +18 -15
  64. package/dist/server/routes/discovery.js +1 -1
  65. package/dist/server/routes/install.js +1 -1
  66. package/dist/server/routes/ping.js +1 -1
  67. package/dist/server/routes/schema.js +4 -4
  68. package/dist/server/routes/settings.js +2 -6
  69. package/dist/server/routes/wizard.js +6 -6
  70. package/dist/server/schemas/settings.js +42 -42
  71. package/dist/shared/settingsSurface.js +1 -1
  72. package/dist/shared/userMd/schema.js +1 -1
  73. package/dist/shared/zodJsonSchema.js +47 -0
  74. package/dist/ui/assets/index-B0bcIgi0.js +77 -0
  75. package/dist/ui/index.html +1 -1
  76. package/package.json +6 -3
  77. package/src/agent-src/templates/scripts/check_memory.ts +1 -1
  78. package/src/config/ci-local-parity.yml +11 -0
  79. package/src/config/continuity-surface.json +9 -2
  80. package/src/config/evaluator-budgets.json +9 -8
  81. package/src/config/gate-violation-baselines.json +3 -3
  82. package/src/config/gitignore-block.txt +6 -5
  83. package/src/config/hook-bundle-budget.json +46 -6
  84. package/src/config/pack-size-budget.json +12 -1
  85. package/src/config/packed-binary-manifest.json +18 -0
  86. package/src/config/sigstore/npm_keys.json +50 -0
  87. package/src/config/sigstore/trust-material.json +9 -0
  88. package/src/config/sigstore/trusted_root.json +126 -0
  89. package/src/config/skill-body-budget.json +7 -1
  90. package/src/scripts/_cli/cmd_settings_set.ts +3 -2
  91. package/src/scripts/_cli/cmd_update.ts +48 -20
  92. package/src/scripts/_cli/cmd_upgrade.ts +7 -1
  93. package/src/scripts/_cli/upgrade_unattended.ts +90 -76
  94. package/src/scripts/_dispatch.bash +4 -3
  95. package/src/scripts/_lib/confusables.ts +0 -19
  96. package/src/scripts/_lib/egress_normalize.ts +53 -23
  97. package/src/scripts/_lib/egress_rules.ts +8 -2
  98. package/src/scripts/_lib/egress_text.ts +5 -3
  99. package/src/scripts/_lib/egress_token.ts +7 -5
  100. package/src/scripts/_lib/gate_collector_experimental.ts +633 -0
  101. package/src/scripts/_lib/git_common_dir.ts +10 -0
  102. package/src/scripts/_lib/graph_feeder_record.ts +3 -2
  103. package/src/scripts/_lib/install_reach_checks.ts +32 -2
  104. package/src/scripts/_lib/lineage_backfill.ts +136 -0
  105. package/src/scripts/_lib/lineage_ledger.ts +211 -0
  106. package/src/scripts/_lib/lineage_map.ts +55 -0
  107. package/src/scripts/_lib/lineage_query.ts +140 -0
  108. package/src/scripts/_lib/lineage_seed.ts +116 -0
  109. package/src/scripts/_lib/lineage_watch.ts +249 -0
  110. package/src/scripts/_lib/link_crypto.ts +8 -4
  111. package/src/scripts/_lib/listing_manifests.ts +85 -0
  112. package/src/scripts/_lib/neighbour_census.ts +1 -0
  113. package/src/scripts/_lib/production_reach.ts +3 -3
  114. package/src/scripts/_lib/project_registry.ts +227 -0
  115. package/src/scripts/_lib/provenance_verify.ts +14 -242
  116. package/src/scripts/_lib/release_settings_surface.ts +2 -6
  117. package/src/scripts/_lib/sigstore_provenance.ts +177 -0
  118. package/src/scripts/_lib/source_id.ts +48 -0
  119. package/src/scripts/_lib/source_shape.ts +4 -0
  120. package/src/scripts/_lib/unattended_gates.ts +6 -1
  121. package/src/scripts/adoption_status.ts +231 -27
  122. package/src/scripts/advisories.ts +26 -10
  123. package/src/scripts/ai_council/council_egress.ts +5 -6
  124. package/src/scripts/bench_graph_feeder_latency.ts +10 -3
  125. package/src/scripts/build_mcp_registry_manifest.ts +23 -12
  126. package/src/scripts/check_dependency_floors.ts +14 -0
  127. package/src/scripts/check_egress_mutations.ts +1 -0
  128. package/src/scripts/check_release_manifest_content.ts +17 -2
  129. package/src/scripts/check_release_pr_shape.ts +5 -0
  130. package/src/scripts/code_graph/build.ts +145 -45
  131. package/src/scripts/code_graph/cli.ts +382 -35
  132. package/src/scripts/code_graph/conventions_default.ts +247 -0
  133. package/src/scripts/code_graph/detect.ts +5 -5
  134. package/src/scripts/code_graph/estate_cli.ts +135 -0
  135. package/src/scripts/code_graph/estate_index.ts +394 -0
  136. package/src/scripts/code_graph/estate_manifests.ts +223 -0
  137. package/src/scripts/code_graph/estate_map.ts +141 -0
  138. package/src/scripts/code_graph/extract.ts +8 -2
  139. package/src/scripts/code_graph/extract_more.ts +241 -2
  140. package/src/scripts/code_graph/graph_budget.ts +158 -0
  141. package/src/scripts/code_graph/graph_home.ts +80 -0
  142. package/src/scripts/code_graph/lineage_cli.ts +315 -0
  143. package/src/scripts/code_graph/loader.ts +10 -2
  144. package/src/scripts/code_graph/map.ts +195 -53
  145. package/src/scripts/code_graph/map_model.ts +113 -20
  146. package/src/scripts/code_graph/map_verify.ts +80 -5
  147. package/src/scripts/code_graph/map_viewer/viewer.css +18 -0
  148. package/src/scripts/code_graph/map_viewer/viewer.js +146 -15
  149. package/src/scripts/code_graph/passes.ts +173 -0
  150. package/src/scripts/code_graph/project_ref.ts +25 -0
  151. package/src/scripts/code_graph/projects_cli.ts +254 -0
  152. package/src/scripts/code_graph/providers/angular.ts +55 -0
  153. package/src/scripts/code_graph/providers/aspnetcore.ts +182 -0
  154. package/src/scripts/code_graph/providers/catalog.ts +299 -0
  155. package/src/scripts/code_graph/providers/django.ts +206 -0
  156. package/src/scripts/code_graph/providers/express.ts +25 -0
  157. package/src/scripts/code_graph/providers/fastapi.ts +49 -0
  158. package/src/scripts/code_graph/providers/flask.ts +99 -0
  159. package/src/scripts/code_graph/providers/go_http.ts +121 -0
  160. package/src/scripts/code_graph/providers/index.ts +103 -0
  161. package/src/scripts/code_graph/{laravel_overlay.ts → providers/laravel.ts} +113 -7
  162. package/src/scripts/code_graph/providers/nestjs.ts +34 -0
  163. package/src/scripts/code_graph/providers/nextjs.ts +91 -0
  164. package/src/scripts/code_graph/providers/nuxt.ts +55 -0
  165. package/src/scripts/code_graph/providers/rails.ts +204 -0
  166. package/src/scripts/code_graph/providers/react-router.ts +58 -0
  167. package/src/scripts/code_graph/providers/shared/detect.ts +78 -0
  168. package/src/scripts/code_graph/providers/shared/extractors.ts +249 -0
  169. package/src/scripts/code_graph/providers/shared/js.ts +252 -0
  170. package/src/scripts/code_graph/providers/shared/kit.ts +210 -0
  171. package/src/scripts/code_graph/providers/shared/php.ts +64 -0
  172. package/src/scripts/code_graph/providers/shared/python.ts +223 -0
  173. package/src/scripts/code_graph/providers/spring.ts +175 -0
  174. package/src/scripts/code_graph/providers/sqlalchemy.ts +62 -0
  175. package/src/scripts/code_graph/providers/sveltekit.ts +59 -0
  176. package/src/scripts/code_graph/providers/symfony.ts +142 -0
  177. package/src/scripts/code_graph/providers/typeorm.ts +58 -0
  178. package/src/scripts/code_graph/providers/types.ts +56 -0
  179. package/src/scripts/code_graph/providers/vue-router.ts +21 -0
  180. package/src/scripts/code_graph/providers/wordpress.ts +128 -0
  181. package/src/scripts/code_graph/query.ts +9 -9
  182. package/src/scripts/code_graph/refresh_cli.ts +81 -0
  183. package/src/scripts/code_graph/refresh_lock.ts +197 -0
  184. package/src/scripts/code_graph/repo_identity.ts +169 -0
  185. package/src/scripts/code_graph/resolution_tiers.ts +19 -7
  186. package/src/scripts/code_graph/scip_reader.ts +2 -2
  187. package/src/scripts/code_graph/sqlite_store.ts +31 -28
  188. package/src/scripts/code_graph/types.ts +79 -41
  189. package/src/scripts/code_graph/validate.ts +6 -8
  190. package/src/scripts/condense.ts +15 -15
  191. package/src/scripts/generate_settings_reference.ts +2 -6
  192. package/src/scripts/hook_chunks.mjs +11 -0
  193. package/src/scripts/hook_manifest.json +1 -1
  194. package/src/scripts/hook_manifest.yaml +43 -23
  195. package/src/scripts/hooks/code_graph_refresh_hook.ts +163 -0
  196. package/src/scripts/hooks/dispatch_hook.ts +6 -7
  197. package/src/scripts/hooks/host_semantics.ts +72 -0
  198. package/src/scripts/hooks/lazy_concern.ts +8 -0
  199. package/src/scripts/hooks/user_notice.ts +105 -0
  200. package/src/scripts/install-hooks.sh +54 -33
  201. package/src/scripts/install.ts +2 -6
  202. package/src/scripts/lint_harvest_provenance.ts +137 -11
  203. package/src/scripts/lint_hook_manifest.ts +2 -0
  204. package/src/scripts/lint_marketplace.ts +33 -0
  205. package/src/scripts/lint_mcp_registry_manifest.ts +36 -0
  206. package/src/scripts/mcp_server/consumer_tool_catalog.json +70 -15
  207. package/src/scripts/mcp_server/graph_tools.ts +165 -41
  208. package/src/scripts/network_sources.ts +24 -6
  209. package/src/scripts/release.ts +2 -5
  210. package/src/scripts/release_drill.ts +4 -1
  211. package/src/scripts/release_env.ts +17 -0
  212. package/src/scripts/release_mix_taxonomy.json +3 -0
  213. package/src/scripts/schemas/provenance-mechanism.schema.json +46 -0
  214. package/src/scripts/update_notice_hook.ts +75 -7
  215. package/src/server/schemas/settings.ts +42 -42
  216. package/src/shared/settingsSurface.ts +1 -1
  217. package/src/shared/userMd/schema.ts +1 -1
  218. package/src/shared/zodJsonSchema.ts +41 -0
  219. package/src/vendor/grammars/README.md +8 -6
  220. package/src/vendor/grammars/tree-sitter-c_sharp.wasm +0 -0
  221. package/src/vendor/grammars/tree-sitter-ruby.wasm +0 -0
  222. package/dist/cli-delegate/chunk-XIAPY44N.js +0 -4587
  223. package/dist/ui/assets/index-w70Ado09.js +0 -23
  224. /package/dist/cli-delegate/{chunk-FKYZOWVD.js → chunk-QBAFP67O.js} +0 -0
  225. /package/dist/cli-delegate/{chunk-BMGUGSY6.js → chunk-TQG6UMPC.js} +0 -0
@@ -0,0 +1,252 @@
1
+ /**
2
+ * Mechanics the JavaScript providers share and the five extractors do not
3
+ * carry: resolving a module specifier and a default import, reading a route
4
+ * object literal, constructor injection, and the verb functions a route module
5
+ * exports.
6
+ *
7
+ * Same contract as the extractors: an edge only when its target exists; a
8
+ * specifier that is not relative, or a value built at runtime, gets no edge.
9
+ */
10
+ import * as path from 'node:path';
11
+
12
+ import type { CodeNode } from '../../types.js';
13
+ import { joinRoute } from './extractors.js';
14
+ import { type Kit, lineIndex } from './kit.js';
15
+
16
+ const MODULE_EXTS = ['.ts', '.tsx', '.js', '.jsx', '.mjs', '.cjs', '.vue', '.svelte'];
17
+
18
+ /** A kit that sees only the code files `keep` accepts (one ORM among several sharing a decorator name). */
19
+ export function scopeKit(kit: Kit, keep: (source: string) => boolean): Kit {
20
+ const files = kit.files.filter((f) => keep(f.source));
21
+ return { ...kit, files, filesWithExt: (...exts) => files.filter((f) => exts.some((x) => f.path.endsWith(x))) };
22
+ }
23
+
24
+ /** The project file a relative specifier written in `file` names; null for a package or alias specifier. */
25
+ export function resolveModule(kit: Kit, file: string, spec: string): string | null {
26
+ if (!spec.startsWith('./') && !spec.startsWith('../')) return null;
27
+ const base = path.posix.join(path.posix.dirname(file), spec);
28
+ if (base.startsWith('..')) return null;
29
+ const known = (p: string): boolean => kit.fileIds.has(p) || kit.nodeById.has(p) || kit.hasPath(p);
30
+ if (/\.[A-Za-z0-9]+$/.test(path.posix.basename(base)) && known(base)) return base;
31
+ for (const c of [...MODULE_EXTS.map((x) => `${base}${x}`), ...MODULE_EXTS.map((x) => `${base}/index${x}`)]) if (known(c)) return c;
32
+ return null;
33
+ }
34
+
35
+ function symbolIn(kit: Kit, file: string, name: string): string | null {
36
+ for (const n of kit.nodesInFile(file)) if (n.label === name && n.kind !== 'method' && n.kind !== 'file') return n.id;
37
+ return null;
38
+ }
39
+
40
+ /** A module target: the named symbol inside it when it declares one, else the module file. */
41
+ export function moduleTarget(kit: Kit, file: string, spec: string, member?: string): string | null {
42
+ const mod = resolveModule(kit, file, spec);
43
+ if (!mod) return null;
44
+ const sym = member ? symbolIn(kit, mod, member) : null;
45
+ if (sym) return sym;
46
+ kit.fileNode(mod);
47
+ return mod;
48
+ }
49
+
50
+ /**
51
+ * A component name as written in `file`: a same-file or named-import symbol
52
+ * (`kit.resolveName`), else a default import — the symbol of that name in the
53
+ * imported module, or the module file itself.
54
+ */
55
+ export function resolveComponent(kit: Kit, file: string, name: string): string | null {
56
+ const named = kit.resolveName(file, name);
57
+ if (named) return named;
58
+ const src = kit.files.find((f) => f.path === file)?.source ?? '';
59
+ const esc = name.replace(/\$/g, '\\$');
60
+ const m = new RegExp(`\\bimport\\s+${esc}\\s*(?:,\\s*\\{[^}]*\\})?\\s+from\\s+(['"])([^'"]+)\\1`).exec(src);
61
+ return m ? moduleTarget(kit, file, m[2] as string, name) : null;
62
+ }
63
+
64
+ /**
65
+ * The object literal around `index`: its bounds, and its own top-level text with
66
+ * every nested `{}` and `[]` blanked (parentheses kept, so `() => import('./x')`
67
+ * stays readable). Null when `index` sits inside parentheses or a bracket first.
68
+ */
69
+ export function objectAt(src: string, index: number): { start: number; end: number; flat: string } | null {
70
+ let depth = 0;
71
+ let start = -1;
72
+ for (let i = index - 1; i >= 0; i--) {
73
+ const c = src[i];
74
+ if (c === ')' || c === ']' || c === '}') depth += 1;
75
+ else if (c === '(' || c === '[' || c === '{') {
76
+ if (depth === 0) {
77
+ if (c !== '{') return null;
78
+ start = i;
79
+ break;
80
+ }
81
+ depth -= 1;
82
+ }
83
+ }
84
+ if (start < 0) return null;
85
+ const out: string[] = [];
86
+ const stack: string[] = [];
87
+ for (let i = start + 1; i < src.length; i++) {
88
+ const c = src[i] as string;
89
+ const nested = (): boolean => stack.some((s) => s !== '(');
90
+ if (c === '{' || c === '[' || c === '(') {
91
+ const blank = nested() || c !== '(';
92
+ stack.push(c);
93
+ out.push(blank ? ' ' : c);
94
+ } else if (c === '}' || c === ']' || c === ')') {
95
+ if (stack.length === 0) return { start, end: i, flat: out.join('') };
96
+ stack.pop();
97
+ out.push(nested() || c !== ')' ? ' ' : c);
98
+ } else out.push(nested() ? ' ' : c);
99
+ }
100
+ return null;
101
+ }
102
+
103
+ /** The text of `key: <value>` in a flat object, up to the next top-level comma. */
104
+ export function valueOf(flat: string, key: string): string | null {
105
+ const m = new RegExp(`(?:^|[\\s,])${key}\\s*:`).exec(flat);
106
+ if (!m) return null;
107
+ let depth = 0;
108
+ const from = m.index + m[0].length;
109
+ for (let i = from; i < flat.length; i++) {
110
+ const c = flat[i];
111
+ if (c === '(') depth += 1;
112
+ else if (c === ')') depth -= 1;
113
+ else if (c === ',' && depth === 0) return flat.slice(from, i).trim();
114
+ }
115
+ return flat.slice(from).trim();
116
+ }
117
+
118
+ /** What a component value names: a dynamic import (with an optional `.then(m => m.X)`), a JSX element, or a name. */
119
+ export function componentTarget(kit: Kit, file: string, value: string): string | null {
120
+ const imp = /\bimport\(\s*(['"`])([^'"`]+)\1\s*\)(?:\s*\.then\(\s*\(?\s*(\w+)\s*\)?\s*=>\s*\3\.(\w+)\s*\))?/.exec(value);
121
+ if (imp) return moduleTarget(kit, file, imp[2] as string, imp[4]);
122
+ const jsx = /^<\s*([A-Z][\w$]*)/.exec(value);
123
+ if (jsx) return resolveComponent(kit, file, jsx[1] as string);
124
+ if (/^[A-Za-z_$][\w$]*$/.test(value)) return resolveComponent(kit, file, value);
125
+ return null;
126
+ }
127
+
128
+ export interface RouteObjectSpec {
129
+ exts: readonly string[];
130
+ /** Keys whose value is the component a route renders; a `path` object with none of them is no route. */
131
+ componentKeys: readonly string[];
132
+ }
133
+
134
+ /** `{ path: '/x', component: X }` route objects → a PAGE route --routes-to--> the component or lazily imported module. */
135
+ export function routeObjects(kit: Kit, spec: RouteObjectSpec): number {
136
+ let count = 0;
137
+ for (const f of kit.filesWithExt(...spec.exts)) {
138
+ const at = lineIndex(f.source);
139
+ for (const m of f.source.matchAll(/(?:^|[\s,{])path\s*:\s*(['"`])(?<path>[^'"`$]*)\1/g)) {
140
+ const obj = objectAt(f.source, m.index as number);
141
+ if (!obj) continue;
142
+ const values = spec.componentKeys.map((k) => valueOf(obj.flat, k)).filter((v): v is string => v !== null);
143
+ if (values.length === 0) continue;
144
+ const id = kit.route(f.path, 'PAGE', joinRoute(m.groups?.['path']), at(m.index as number));
145
+ for (const v of values) {
146
+ const target = componentTarget(kit, f.path, v);
147
+ if (target) kit.emit(id, target, 'routes-to', 'route-table');
148
+ }
149
+ count += 1;
150
+ }
151
+ }
152
+ return count;
153
+ }
154
+
155
+ function symbolsOf(kit: Kit, file: string, kind: CodeNode['kind']): CodeNode[] {
156
+ return kit.nodesInFile(file).filter((n) => n.kind === kind);
157
+ }
158
+
159
+ /** The parameter list of the first `constructor(` in `text`, balanced. */
160
+ function constructorParams(text: string): string[] {
161
+ const m = /\bconstructor\s*\(/.exec(text);
162
+ if (!m) return [];
163
+ let depth = 0;
164
+ const from = m.index + m[0].length;
165
+ const parts: string[] = [];
166
+ let cur = '';
167
+ for (let i = from; i < text.length; i++) {
168
+ const c = text[i] as string;
169
+ if (c === '(' || c === '<' || c === '[' || c === '{') depth += 1;
170
+ else if (c === ')' || c === '>' || c === ']' || c === '}') {
171
+ if (depth === 0) break;
172
+ depth -= 1;
173
+ }
174
+ if (c === ',' && depth === 0) {
175
+ parts.push(cur);
176
+ cur = '';
177
+ } else cur += c;
178
+ }
179
+ if (cur.trim()) parts.push(cur);
180
+ return parts;
181
+ }
182
+
183
+ export interface InjectionSpec {
184
+ exts: readonly string[];
185
+ /** A class decorator marking the class as container-managed, searched just above the class. */
186
+ classDecorator: RegExp;
187
+ /** How far above the class its decorator may sit, in lines. */
188
+ reach?: number;
189
+ /** A field initializer naming the dependency (`inject(X)`); group 1 is the type. */
190
+ injectCall?: RegExp;
191
+ }
192
+
193
+ /** `constructor(private readonly s: UsersService)` in a decorated class → class --injects--> UsersService. */
194
+ export function constructorInjection(kit: Kit, spec: InjectionSpec): number {
195
+ let count = 0;
196
+ for (const f of kit.filesWithExt(...spec.exts)) {
197
+ const lines = f.source.split('\n');
198
+ for (const cls of symbolsOf(kit, f.path, 'class')) {
199
+ const start = cls.source_location[0] as number;
200
+ const end = (cls.source_location[2] ?? start) as number;
201
+ const above = lines.slice(Math.max(0, start - 1 - (spec.reach ?? 6)), start).join('\n');
202
+ if (!spec.classDecorator.test(above)) continue;
203
+ const body = lines.slice(start - 1, end).join('\n');
204
+ const types: string[] = [];
205
+ for (const p of constructorParams(body)) {
206
+ const t = /^\s*(?:@\w+\([^)]*\)\s*)*(?:(?:public|private|protected|readonly)\s+)*[A-Za-z_$][\w$]*\??\s*:\s*([A-Za-z_$][\w$]*)/.exec(p);
207
+ if (t) types.push(t[1] as string);
208
+ }
209
+ if (spec.injectCall) for (const m of body.matchAll(spec.injectCall)) types.push(m[1] as string);
210
+ for (const t of types) {
211
+ const target = kit.resolveName(f.path, t);
212
+ if (!target || target === cls.id || kit.nodeById.get(target)?.kind !== 'class') continue;
213
+ kit.emit(cls.id, target, 'injects', 'di-binding');
214
+ count += 1;
215
+ }
216
+ }
217
+ }
218
+ return count;
219
+ }
220
+
221
+ export const HTTP_VERBS = ['GET', 'POST', 'PUT', 'PATCH', 'DELETE', 'HEAD', 'OPTIONS'] as const;
222
+
223
+ /** `export async function GET()` · `export const POST = …` in a route module → one route per verb → that function. */
224
+ export function exportedVerbRoutes(kit: Kit, file: string, routePath: string): number {
225
+ const src = kit.files.find((f) => f.path === file)?.source;
226
+ if (src === undefined) return 0;
227
+ const at = lineIndex(src);
228
+ let count = 0;
229
+ const re = new RegExp(`\\bexport\\s+(?:async\\s+function|function|const|let)\\s+(${HTTP_VERBS.join('|')})\\b`, 'g');
230
+ for (const m of src.matchAll(re)) {
231
+ const verb = m[1] as string;
232
+ const id = kit.route(file, verb, routePath, at(m.index as number));
233
+ const target = kit.resolveName(file, verb);
234
+ if (target) kit.emit(id, target, 'routes-to', 'route-table');
235
+ count += 1;
236
+ }
237
+ return count;
238
+ }
239
+
240
+ /** `p` relative to the deepest of `roots` it lies under, with that root; null when under none. */
241
+ export function underRoot(roots: readonly string[], p: string): { root: string; rel: string } | null {
242
+ for (const root of [...roots].sort((a, b) => b.length - a.length)) {
243
+ if (root === '') return { root, rel: p };
244
+ if (p.startsWith(`${root}/`)) return { root, rel: p.slice(root.length + 1) };
245
+ }
246
+ return null;
247
+ }
248
+
249
+ /** Route segments as the default pass writes them: groups, slots and `index` dropped. */
250
+ export function cleanRoute(parts: readonly string[]): string {
251
+ return `/${parts.filter((s) => !/^\(.*\)$/.test(s) && !s.startsWith('@') && s !== 'index').join('/')}`.replace(/\/$/, '') || '/';
252
+ }
@@ -0,0 +1,210 @@
1
+ /**
2
+ * The building blocks every convention provider shares: emitting deduplicated
3
+ * edges, declaring route and table nodes, finding the symbol a line belongs to
4
+ * or the one a decorator precedes, and resolving a handler name the way the
5
+ * file itself states it (same file, then its imports).
6
+ *
7
+ * A provider stays a parameter table plus the semantics only it knows; the
8
+ * mechanics live here once (road-to-graph-providers-for-every-stack 4.1).
9
+ */
10
+ import * as path from 'node:path';
11
+
12
+ import type { SourceFile } from '../../build.js';
13
+ import type { CodeEdge, CodeNode, ResolvedVia } from '../../types.js';
14
+ import type { ProviderInput, ProviderResult } from '../types.js';
15
+
16
+ /** A string matched literally inside a `RegExp` built from a symbol name. */
17
+ export function escapeRe(s: string): string {
18
+ return s.replace(/[.*+?^${}()|[\]\\]/g, '\\$&');
19
+ }
20
+
21
+ /** Line-start offsets, so many `lineAt` lookups stay linear. */
22
+ export function lineIndex(src: string): (index: number) => number {
23
+ const starts = [0];
24
+ for (let i = 0; i < src.length; i++) if (src.charCodeAt(i) === 10) starts.push(i + 1);
25
+ return (index: number): number => {
26
+ let lo = 0;
27
+ let hi = starts.length - 1;
28
+ while (lo < hi) {
29
+ const mid = (lo + hi + 1) >> 1;
30
+ if ((starts[mid] as number) <= index) lo = mid;
31
+ else hi = mid - 1;
32
+ }
33
+ return lo + 1;
34
+ };
35
+ }
36
+
37
+ export interface Kit {
38
+ files: readonly SourceFile[];
39
+ fileIds: ReadonlySet<string>;
40
+ nodeById: ReadonlyMap<string, CodeNode>;
41
+ /** Every project path under the provider's roots, code or not. */
42
+ paths: readonly string[];
43
+ /** The package roots the provider was detected in (`''` is the repository root). */
44
+ roots: readonly string[];
45
+ /** The code files whose path ends with one of `exts`. */
46
+ filesWithExt(...exts: string[]): SourceFile[];
47
+ /** Make sure a file node exists for a non-code target (a template). */
48
+ fileNode(p: string): void;
49
+ /** Every base node declared in `file` (indexed once; never a scan of the whole graph). */
50
+ nodesInFile(file: string): readonly CodeNode[];
51
+ /** Every base node with this label (indexed once). */
52
+ nodesNamed(label: string): readonly CodeNode[];
53
+ /** Whether a project path is in the inventory (indexed once). */
54
+ hasPath(p: string): boolean;
55
+ emit(source: string, target: string, relation: CodeEdge['relation'], via: ResolvedVia): void;
56
+ /** Declare a route node in `file`; returns its id (`<file>#route:<VERB> <path>`, `@n` when repeated). */
57
+ route(file: string, verb: string, routePath: string, line: number): string;
58
+ /** Declare (once) the table node `table:<name>`, first seen in `file`. */
59
+ table(name: string, file: string, line: number): string;
60
+ /** The innermost function or method node whose span holds `line`, else the file. */
61
+ enclosing(file: string, line: number): string;
62
+ /** The first function, method or class node starting at or after `line` (what a decorator decorates). */
63
+ nextSymbol(file: string, line: number, kinds?: readonly CodeNode['kind'][]): CodeNode | null;
64
+ /** The class node whose span holds `line`. */
65
+ classAt(file: string, line: number): CodeNode | null;
66
+ /**
67
+ * A name as written in `file`, resolved the way the file states it: a
68
+ * same-file symbol first, then a symbol an import edge of the file binds.
69
+ */
70
+ resolveName(file: string, name: string): string | null;
71
+ /** A method `member` of the class node `classId`, if the class declares it. */
72
+ methodOf(classId: string, member: string): string | null;
73
+ result(): ProviderResult;
74
+ }
75
+
76
+ export function makeKit(input: ProviderInput): Kit {
77
+ const files = input.files;
78
+ const fileIds = new Set(files.map((f) => f.path));
79
+ const nodeById = new Map(input.baseNodes.map((n) => [n.id, n] as const));
80
+ const nodes: CodeNode[] = [];
81
+ const edges: CodeEdge[] = [];
82
+ const seenEdge = new Set<string>();
83
+ const seenNode = new Set<string>();
84
+ const routeOrdinal = new Map<string, number>();
85
+ let byFile: Map<string, CodeNode[]> | null = null;
86
+ let byLabel: Map<string, CodeNode[]> | null = null;
87
+ let pathSet: Set<string> | null = null;
88
+ const index = (key: (n: CodeNode) => string): Map<string, CodeNode[]> => {
89
+ const m = new Map<string, CodeNode[]>();
90
+ for (const n of input.baseNodes) {
91
+ const k = key(n);
92
+ const list = m.get(k);
93
+ if (list) list.push(n);
94
+ else m.set(k, [n]);
95
+ }
96
+ return m;
97
+ };
98
+
99
+ const symbolsByFile = new Map<string, CodeNode[]>();
100
+ for (const n of input.baseNodes) {
101
+ if (n.kind === 'file' || n.source_location.length === 0) continue;
102
+ const list = symbolsByFile.get(n.source_file) ?? [];
103
+ list.push(n);
104
+ symbolsByFile.set(n.source_file, list);
105
+ }
106
+ for (const list of symbolsByFile.values()) list.sort((a, b) => (a.source_location[0] as number) - (b.source_location[0] as number));
107
+
108
+ const importsByFile = new Map<string, Map<string, string>>();
109
+ for (const e of input.baseEdges) {
110
+ if (e.relation !== 'imports') continue;
111
+ const from = nodeById.get(e.source)?.source_file;
112
+ const to = nodeById.get(e.target);
113
+ if (!from || !to) continue;
114
+ const m = importsByFile.get(from) ?? new Map<string, string>();
115
+ if (!m.has(to.label)) m.set(to.label, to.id);
116
+ importsByFile.set(from, m);
117
+ }
118
+
119
+ const emit: Kit['emit'] = (source, target, relation, via) => {
120
+ const key = `${source}\0${relation}\0${target}`;
121
+ if (seenEdge.has(key)) return;
122
+ seenEdge.add(key);
123
+ edges.push({ source, target, relation, confidence: 'EXTRACTED', resolved_via: via, provider: 'native' });
124
+ };
125
+ const span = (n: CodeNode): [number, number] => [n.source_location[0] as number, (n.source_location[2] ?? n.source_location[0]) as number];
126
+
127
+ return {
128
+ files,
129
+ fileIds,
130
+ nodeById,
131
+ paths: input.paths,
132
+ roots: input.roots,
133
+ nodesInFile: (file) => (byFile ??= index((n) => n.source_file)).get(file) ?? [],
134
+ nodesNamed: (label) => (byLabel ??= index((n) => n.label)).get(label) ?? [],
135
+ hasPath: (p) => (pathSet ??= new Set(input.paths)).has(p),
136
+ filesWithExt: (...exts) => files.filter((f) => exts.some((x) => f.path.endsWith(x))),
137
+ fileNode(p) {
138
+ if (nodeById.has(p) || seenNode.has(p)) return;
139
+ seenNode.add(p);
140
+ nodes.push({ id: p, label: p, kind: 'file', source_file: p, source_location: [] });
141
+ },
142
+ emit,
143
+ route(file, verb, routePath, line) {
144
+ const base = `${file}#route:${verb} ${routePath}`;
145
+ const n = (routeOrdinal.get(base) ?? 0) + 1;
146
+ routeOrdinal.set(base, n);
147
+ const id = n === 1 ? base : `${base}@${n}`;
148
+ nodes.push({ id, label: `${verb} ${routePath}`, kind: 'route', source_file: file, source_location: [line, 1, line, 1] });
149
+ emit(file, id, 'member', 'same-file');
150
+ return id;
151
+ },
152
+ table(name, file, line) {
153
+ const id = `table:${name}`;
154
+ if (!seenNode.has(id)) {
155
+ seenNode.add(id);
156
+ nodes.push({ id, label: name, kind: 'table', source_file: file, source_location: [line, 1, line, 1] });
157
+ }
158
+ return id;
159
+ },
160
+ enclosing(file, line) {
161
+ let best: CodeNode | null = null;
162
+ for (const n of symbolsByFile.get(file) ?? []) {
163
+ if (n.kind !== 'method' && n.kind !== 'function') continue;
164
+ const [s, e] = span(n);
165
+ if (s <= line && line <= e && (!best || s >= (best.source_location[0] as number))) best = n;
166
+ }
167
+ return best ? best.id : file;
168
+ },
169
+ nextSymbol(file, line, kinds = ['method', 'function', 'class']) {
170
+ for (const n of symbolsByFile.get(file) ?? []) if (kinds.includes(n.kind) && span(n)[0] >= line) return n;
171
+ return null;
172
+ },
173
+ classAt(file, line) {
174
+ let best: CodeNode | null = null;
175
+ for (const n of symbolsByFile.get(file) ?? []) {
176
+ if (n.kind !== 'class') continue;
177
+ const [s, e] = span(n);
178
+ if (s <= line && line <= e && (!best || s >= (best.source_location[0] as number))) best = n;
179
+ }
180
+ return best;
181
+ },
182
+ resolveName(file, name) {
183
+ const local = (symbolsByFile.get(file) ?? []).find((n) => n.label === name && n.kind !== 'method');
184
+ if (local) return local.id;
185
+ return importsByFile.get(file)?.get(name) ?? null;
186
+ },
187
+ methodOf(classId, member) {
188
+ const id = `${classId}::${member}`;
189
+ return nodeById.has(id) ? id : null;
190
+ },
191
+ result: () => ({ nodes, edges }),
192
+ };
193
+ }
194
+
195
+ /**
196
+ * The one symbol named `name` declared by a file in `file`'s own directory with
197
+ * the same extension — what a language whose package is its directory (Go, a
198
+ * Java package) sees without an import. Null when none or several declare it.
199
+ */
200
+ export function packageSymbol(kit: Kit, file: string, name: string, kinds: readonly CodeNode['kind'][]): string | null {
201
+ const dir = path.posix.dirname(file);
202
+ const ext = path.posix.extname(file);
203
+ const hits: string[] = [];
204
+ for (const n of kit.nodesNamed(name)) {
205
+ if (!kinds.includes(n.kind)) continue;
206
+ if (path.posix.dirname(n.source_file) !== dir || path.posix.extname(n.source_file) !== ext) continue;
207
+ hits.push(n.id);
208
+ }
209
+ return hits.length === 1 ? (hits[0] as string) : null;
210
+ }
@@ -0,0 +1,64 @@
1
+ /**
2
+ * PHP name resolution shared by the PHP providers: a class reference read the
3
+ * way its file states it (namespace + `use` aliases), then located through the
4
+ * project's PSR-4 map. A reference that resolves to no class node in the graph
5
+ * resolves to nothing — never to a same-named class in another namespace.
6
+ */
7
+ import { resolvePsr4, type ResolutionConfig } from '../../resolution_tiers.js';
8
+ import type { Kit } from './kit.js';
9
+
10
+ export interface PhpScope {
11
+ namespace: string;
12
+ uses: Map<string, string>;
13
+ }
14
+
15
+ export function phpScope(src: string): PhpScope {
16
+ const ns = /^\s*namespace\s+([\\\w]+)\s*;/m.exec(src)?.[1] ?? '';
17
+ const uses = new Map<string, string>();
18
+ for (const m of src.matchAll(/^\s*use\s+([\\\w]+)(?:\s+as\s+(\w+))?\s*;/gm)) {
19
+ const fq = (m[1] as string).replace(/^\\/, '');
20
+ uses.set(m[2] ?? fq.split('\\').pop() ?? fq, fq);
21
+ }
22
+ return { namespace: ns, uses };
23
+ }
24
+
25
+ /** Fully-qualified name of a class reference as written under `scope`. */
26
+ export function qualify(ref: string, scope: PhpScope): string {
27
+ if (ref.startsWith('\\')) return ref.slice(1);
28
+ const [head, ...rest] = ref.split('\\');
29
+ const aliased = scope.uses.get(head as string);
30
+ if (aliased) return [aliased, ...rest].join('\\');
31
+ return scope.namespace ? `${scope.namespace}\\${ref}` : ref;
32
+ }
33
+
34
+ export function shortName(fq: string): string {
35
+ return fq.split('\\').pop() ?? fq;
36
+ }
37
+
38
+ /** The class node a fully-qualified name names, through PSR-4; null when absent. */
39
+ export function classNode(kit: Kit, fq: string, resolution: ResolutionConfig): string | null {
40
+ const file = resolvePsr4(fq, resolution, kit.fileIds);
41
+ const id = file ? `${file}#${shortName(fq)}` : null;
42
+ return id && kit.nodeById.has(id) ? id : null;
43
+ }
44
+
45
+ /** Offset of the bracket closing the one at `open` (`(`, `[` or `{`), skipping string literals. */
46
+ export function matchingClose(src: string, open: number): number {
47
+ const pair: Record<string, string> = { '(': ')', '[': ']', '{': '}' };
48
+ const o = src[open] as string;
49
+ const c = pair[o];
50
+ let depth = 0;
51
+ for (let i = open; i < src.length; i++) {
52
+ const ch = src[i];
53
+ if (ch === "'" || ch === '"') {
54
+ for (i += 1; i < src.length && src[i] !== ch; i++) if (src[i] === '\\') i += 1;
55
+ continue;
56
+ }
57
+ if (ch === o) depth += 1;
58
+ else if (ch === c) {
59
+ depth -= 1;
60
+ if (depth === 0) return i;
61
+ }
62
+ }
63
+ return src.length;
64
+ }