@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,247 @@
1
+ /**
2
+ * The default convention pass (ADR-298): runs for every project, names no
3
+ * framework, and links only what a string can state about a file.
4
+ *
5
+ * literal-path './x' · 'views/a.html' code --reads--> the one code or template file it names
6
+ * literal-dotted render('a.b') code --renders--> the one template at a/b.<template ext>
7
+ * fs-route pages/a/[id].tsx route --routes-to-> the page module
8
+ *
9
+ * Every edge is INFERRED, drawn only on a single match, and never where an
10
+ * edge between the same two files already exists — an import, a provider's
11
+ * edge or an earlier literal outranks it. The two axes are independent
12
+ * (`types.ts`): the confidence is INFERRED because a convention connects the
13
+ * pair, while the mechanism is a statement — the literal names that exact file
14
+ * — so these edges count as stated for the map and the gate verbs.
15
+ */
16
+ import * as path from 'node:path';
17
+
18
+ import type { SourceFile } from './build.js';
19
+ import type { CodeEdge, CodeNode, ResolvedVia } from './types.js';
20
+ import { EXT_LANG, MAX_FILE_BYTES } from './types.js';
21
+
22
+ /** A line that is a log, print or throw call up to this point: its literals are messages, not files. */
23
+ const MESSAGE_CALL =
24
+ /(?:\bconsole\.\w+|\blog(?:ger|ging)?\.\w+|\bLog::\w+|\b(?:print|println|printf|echo|error_log)\b|\bthrow\s+new\s+\w+)\s*\(?/;
25
+
26
+ /** `src` with comments and Python docstrings blanked to spaces, every offset and newline kept. */
27
+ function maskComments(src: string, lang: string | undefined): string {
28
+ const blank = (m: string): string => m.replace(/[^\n]/g, ' ');
29
+ let out = src
30
+ .replace(/\/\*[\s\S]*?\*\//g, blank)
31
+ .replace(/(^|[^:'"\\])(\/\/[^\n]*)/g, (_m, pre: string, c: string) => pre + blank(c));
32
+ if (lang === 'python' || lang === 'php') out = out.replace(/(^|[ \t])(#[^\n]*)/gm, (_m, pre: string, c: string) => pre + blank(c));
33
+ if (lang === 'python') out = out.replace(/("""|''')[\s\S]*?\1/g, blank);
34
+ return out;
35
+ }
36
+
37
+ /** The text right before a JS/TS module specifier: `from`, `import`, `import(`, `require(`. */
38
+ const IMPORT_POSITION = /(?:\bfrom|\bimport|\bimport\s*\(|\brequire\s*\()\s*$/;
39
+
40
+ export type DefaultMechanism = 'literal-path' | 'literal-dotted' | 'fs-route';
41
+
42
+ /**
43
+ * Which mechanisms run. A mechanism under the 90 % precision bar is switched off
44
+ * here, never the pass (readings: `graph-providers-baseline-2026-10-23.md`).
45
+ * `literal-dotted` measured 3 of 6 on Fixture L — a translation key such as
46
+ * `trans('common.sort')` matches a view of the same name — so dotted names are
47
+ * left to providers, which know which call takes a template.
48
+ */
49
+ export const DEFAULT_MECHANISMS: Readonly<Record<DefaultMechanism, boolean>> = {
50
+ 'literal-path': true,
51
+ 'literal-dotted': false,
52
+ 'fs-route': true,
53
+ };
54
+
55
+ /** Template extensions a dotted name may resolve to; longest first so `.blade.php` wins over `.php`. */
56
+ const TEMPLATE_EXTS = [
57
+ '.blade.php',
58
+ '.handlebars',
59
+ '.mustache',
60
+ '.jinja2',
61
+ '.cshtml',
62
+ '.liquid',
63
+ '.jinja',
64
+ '.razor',
65
+ '.twig',
66
+ '.html',
67
+ '.htm',
68
+ '.hbs',
69
+ '.ejs',
70
+ '.pug',
71
+ '.njk',
72
+ '.erb',
73
+ '.tpl',
74
+ '.j2',
75
+ ];
76
+
77
+ const PAGE_EXTS = new Set(['.tsx', '.jsx', '.ts', '.js', '.vue', '.svelte', '.astro', '.mdx']);
78
+
79
+ export interface DefaultInput {
80
+ files: readonly SourceFile[];
81
+ /** Every path in the project, code or not; the code and doc paths when the build knows no more. */
82
+ paths: readonly string[];
83
+ nodes: readonly CodeNode[];
84
+ edges: readonly CodeEdge[];
85
+ mechanisms?: Readonly<Record<DefaultMechanism, boolean>>;
86
+ }
87
+
88
+ export interface DefaultStats {
89
+ 'literal-path': number;
90
+ 'literal-dotted': number;
91
+ 'fs-route': number;
92
+ }
93
+
94
+ export function templateBase(p: string): string | null {
95
+ for (const ext of TEMPLATE_EXTS) if (p.endsWith(ext) && p.length > ext.length) return p.slice(0, -ext.length);
96
+ return null;
97
+ }
98
+
99
+ /** `pages/a/[id].tsx` → `/a/[id]`; null when the file is no page module. */
100
+ export function fsRoutePath(p: string, pageDirs: ReadonlySet<string>): { verb: string; route: string } | null {
101
+ const ext = path.posix.extname(p);
102
+ if (!PAGE_EXTS.has(ext)) return null;
103
+ const segs = p.split('/');
104
+ const base = segs[segs.length - 1] as string;
105
+ const stem = base.slice(0, -ext.length);
106
+ const clean = (parts: string[]): string =>
107
+ `/${parts.filter((s) => !/^\(.*\)$/.test(s) && !s.startsWith('@') && s !== 'index').join('/')}`.replace(/\/$/, '') || '/';
108
+ const at = (dir: string): number => segs.lastIndexOf(dir, segs.length - 2);
109
+ const app = at('app');
110
+ if (app >= 0 && (stem === 'page' || stem === 'route') && pageDirs.has(segs.slice(0, app + 1).join('/'))) {
111
+ return { verb: stem === 'route' ? 'ROUTE' : 'PAGE', route: clean(segs.slice(app + 1, -1)) };
112
+ }
113
+ const routes = at('routes');
114
+ if (routes >= 1 && segs[routes - 1] === 'src' && /^\+(page|server)$/.test(stem)) {
115
+ return { verb: stem === '+server' ? 'ROUTE' : 'PAGE', route: clean(segs.slice(routes + 1, -1)) };
116
+ }
117
+ const pages = at('pages');
118
+ if (pages >= 0 && !stem.startsWith('_') && pageDirs.has(segs.slice(0, pages + 1).join('/'))) {
119
+ const rest = [...segs.slice(pages + 1, -1), stem];
120
+ return { verb: rest[0] === 'api' ? 'ROUTE' : 'PAGE', route: clean(rest) };
121
+ }
122
+ return null;
123
+ }
124
+
125
+ /** Directories that hold page-shaped modules: an `app/` with a `page.*`, a `pages/` with an `index.*`. */
126
+ function pageDirectories(paths: readonly string[]): Set<string> {
127
+ const dirs = new Set<string>();
128
+ for (const p of paths) {
129
+ const segs = p.split('/');
130
+ const base = segs[segs.length - 1] as string;
131
+ const ext = path.posix.extname(base);
132
+ if (!PAGE_EXTS.has(ext)) continue;
133
+ const stem = base.slice(0, -ext.length);
134
+ for (let i = segs.length - 2; i >= 0; i--) {
135
+ if (segs[i] === 'app' && stem === 'page') dirs.add(segs.slice(0, i + 1).join('/'));
136
+ if (segs[i] === 'pages' && stem === 'index') dirs.add(segs.slice(0, i + 1).join('/'));
137
+ }
138
+ }
139
+ return dirs;
140
+ }
141
+
142
+ export function conventionsDefault(input: DefaultInput): { nodes: CodeNode[]; edges: CodeEdge[]; stats: DefaultStats } {
143
+ const on = input.mechanisms ?? DEFAULT_MECHANISMS;
144
+ const stats: DefaultStats = { 'literal-path': 0, 'literal-dotted': 0, 'fs-route': 0 };
145
+ const nodes: CodeNode[] = [];
146
+ const edges: CodeEdge[] = [];
147
+ const fileNodes = new Set(input.nodes.filter((n) => n.kind === 'file').map((n) => n.id));
148
+ // A literal may name code, a document or a template — never a data file or
149
+ // a dotfile, which would fill the map with what the code merely configures.
150
+ const inventory = new Set([...input.paths.filter((p) => templateBase(p) !== null), ...fileNodes]);
151
+ const nodeIds = new Set(input.nodes.map((n) => n.id));
152
+ const fileOf = new Map(input.nodes.map((n) => [n.id, n.source_file] as const));
153
+ const linked = new Set<string>();
154
+ for (const e of input.edges) {
155
+ const a = fileOf.get(e.source);
156
+ const b = fileOf.get(e.target) ?? (inventory.has(e.target) ? e.target : undefined);
157
+ if (a && b) linked.add(`${a}\0${b}`);
158
+ }
159
+ const ensureFile = (p: string): void => {
160
+ if (nodeIds.has(p)) return;
161
+ nodeIds.add(p);
162
+ nodes.push({ id: p, label: p, kind: 'file', source_file: p, source_location: [] });
163
+ };
164
+ const link = (source: string, target: string, relation: CodeEdge['relation'], via: ResolvedVia & DefaultMechanism): void => {
165
+ const key = `${source}\0${target}`;
166
+ if (source === target || linked.has(key)) return;
167
+ linked.add(key);
168
+ ensureFile(target);
169
+ edges.push({ source, target, relation, confidence: 'INFERRED', resolved_via: via, provider: 'native' });
170
+ stats[via] += 1;
171
+ };
172
+
173
+ // Dotted names: `a/b/c` → the template files whose path ends there.
174
+ const byDotted = new Map<string, string[]>();
175
+ if (on['literal-dotted']) {
176
+ for (const p of input.paths) {
177
+ const base = templateBase(p);
178
+ if (!base) continue;
179
+ const segs = base.split('/');
180
+ for (let i = 0; i <= segs.length - 2; i++) {
181
+ const key = segs.slice(i).join('/');
182
+ byDotted.set(key, [...(byDotted.get(key) ?? []), p]);
183
+ }
184
+ }
185
+ }
186
+
187
+ for (const f of [...input.files].sort((a, b) => (a.path < b.path ? -1 : 1))) {
188
+ if (Buffer.byteLength(f.source, 'utf-8') > MAX_FILE_BYTES) continue;
189
+ const dir = path.posix.dirname(f.path);
190
+ const lang = EXT_LANG[path.posix.extname(f.path).toLowerCase()];
191
+ const infer = lang === 'php' ? ['.php'] : lang === 'python' ? ['.py'] : ['.ts', '.tsx', '.js', '.jsx', '.mjs', '.vue'];
192
+ const jsLike = lang === 'typescript' || lang === 'javascript';
193
+ if (on['literal-path']) {
194
+ // Single and double quotes only: a backtick span is usually a mention in a comment.
195
+ // Comments and docstrings are blanked first, so a path they mention is never read.
196
+ const code = maskComments(f.source, lang);
197
+ for (const m of code.matchAll(/(['"])([^'"`\s\\${}()<>|,;]{3,200})\1/g)) {
198
+ const lit = m[2] as string;
199
+ const lineStart = code.lastIndexOf('\n', m.index as number) + 1;
200
+ if (MESSAGE_CALL.test(code.slice(lineStart, m.index as number))) continue;
201
+ // A module specifier belongs to the import tier: resolved, it is an
202
+ // `imports` edge already; unresolved, it is an external module — a
203
+ // read in neither case.
204
+ if (jsLike && IMPORT_POSITION.test(f.source.slice(Math.max(0, (m.index as number) - 40), m.index as number))) continue;
205
+ if (lit.includes('://') || lit.startsWith('/') || lit.includes('*') || lit.endsWith('/')) continue;
206
+ const hasExt = /\.[A-Za-z0-9]{1,10}$/.test(path.posix.basename(lit));
207
+ if (!lit.includes('/') && !hasExt) continue;
208
+ const relative = lit.startsWith('./') || lit.startsWith('../');
209
+ const bases = relative ? [path.posix.join(dir, lit)] : [path.posix.normalize(lit), path.posix.join(dir, lit)];
210
+ const hits = new Set<string>();
211
+ for (const b of bases) {
212
+ if (b.startsWith('..')) continue;
213
+ if (inventory.has(b)) hits.add(b);
214
+ if (!hasExt) for (const ext of infer) for (const c of [`${b}${ext}`, `${b}/index${ext}`]) if (inventory.has(c)) hits.add(c);
215
+ }
216
+ if (hits.size === 1) link(f.path, [...hits][0] as string, 'reads', 'literal-path');
217
+ }
218
+ }
219
+ if (on['literal-dotted']) {
220
+ for (const m of f.source.matchAll(/\(\s*(['"])([A-Za-z_][\w-]*(?:\.[A-Za-z_][\w-]*)+)\1/g)) {
221
+ const hits = byDotted.get((m[2] as string).replace(/\./g, '/')) ?? [];
222
+ if (hits.length === 1) link(f.path, hits[0] as string, 'renders', 'literal-dotted');
223
+ }
224
+ }
225
+ }
226
+
227
+ if (on['fs-route']) {
228
+ const pageDirs = pageDirectories(input.paths);
229
+ const seen = new Set<string>();
230
+ // A provider that knows the framework already declared this module's routes.
231
+ const routedFiles = new Set(input.nodes.filter((n) => n.kind === 'route').map((n) => n.source_file));
232
+ for (const p of input.paths) {
233
+ const r = fsRoutePath(p, pageDirs);
234
+ if (!r || routedFiles.has(p)) continue;
235
+ const id = `${p}#route:${r.verb} ${r.route}`;
236
+ // A provider that knows this framework may already have drawn the page.
237
+ if (seen.has(id) || nodeIds.has(id)) continue;
238
+ seen.add(id);
239
+ ensureFile(p);
240
+ nodes.push({ id, label: `${r.verb} ${r.route}`, kind: 'route', source_file: p, source_location: [] });
241
+ edges.push({ source: p, target: id, relation: 'member', confidence: 'EXTRACTED', resolved_via: 'same-file', provider: 'native' });
242
+ edges.push({ source: id, target: p, relation: 'routes-to', confidence: 'INFERRED', resolved_via: 'fs-route', provider: 'native' });
243
+ stats['fs-route'] += 1;
244
+ }
245
+ }
246
+ return { nodes, edges, stats };
247
+ }
@@ -19,6 +19,7 @@ import * as path from 'node:path';
19
19
 
20
20
  import { load_agent_settings } from '../_lib/agent_settings.js';
21
21
  import { hardenedSpawnEnv } from '../_lib/spawn_env.js';
22
+ import { projectHome } from './graph_home.js';
22
23
  import { EXT_LANG } from './types.js';
23
24
  import { validateGraph } from './validate.js';
24
25
 
@@ -226,9 +227,6 @@ export function computeVerdict(root: string, nativeCache: string): VerdictJSON {
226
227
  };
227
228
  }
228
229
 
229
- /** Cache path a repository root's native graph lives at, repo-relative. */
230
- export const NATIVE_CACHE_REL = path.join('agents', 'runtime', 'state', 'code-graph-v1.json');
231
-
232
230
  /**
233
231
  * The four-state staleness token every consumer of the graph reports.
234
232
  *
@@ -328,10 +326,12 @@ function hasUncommittedIndexedEdit(root: string): boolean {
328
326
  * report `absent` while answering from that very file — the state of a cache
329
327
  * nobody asked about. Passing the graph the verb is actually reading makes the
330
328
  * printed staleness a statement about the answer rather than about a
331
- * convention.
329
+ * convention. The default is the project home only: the hooks that call this
330
+ * ship in the dispatch bundle, which cannot carry the user-global resolver, so
331
+ * a caller serving a user-global home passes `graphHome(root).cachePath`.
332
332
  */
333
333
  export function graphState(root: string, nativeCache?: string): GraphState {
334
- const picked = pickSource(detectSources(root, nativeCache ?? path.join(root, NATIVE_CACHE_REL)));
334
+ const picked = pickSource(detectSources(root, nativeCache ?? projectHome(root).cachePath));
335
335
  if (!picked) return 'absent';
336
336
  if (picked.stale === true) {
337
337
  const behind = picked.commits_behind;
@@ -0,0 +1,135 @@
1
+ /**
2
+ * `code-graph estate …` — questions about every registered project at once,
3
+ * answered from the estate index alone (ADR-299, step 5.3). No project graph
4
+ * is opened: an estate answer is as current as each project's last refresh,
5
+ * and says when it was indexed.
6
+ *
7
+ * estate where-used <package|symbol> who declares, imports, publishes or references it
8
+ * estate projects-using <package> projects that declare or import a package
9
+ * estate advisory-impact [--package P] advisory packages × the projects that use them
10
+ *
11
+ * `--json` prints the answer as JSON.
12
+ */
13
+ import { readState } from '../_lib/advisory_notice.js';
14
+ import { type EstateReader, openEstate } from './estate_index.js';
15
+
16
+ const NO_INDEX =
17
+ 'no estate index — switch it on with `code-graph projects index on`, then build or refresh each project once';
18
+
19
+ export interface WhereUsed {
20
+ name: string;
21
+ package_users: ReturnType<EstateReader['projectsUsing']>;
22
+ publishers: ReturnType<EstateReader['publishers']>;
23
+ declared_by: ReturnType<EstateReader['exporters']>;
24
+ referenced_by: ReturnType<EstateReader['referrers']>;
25
+ }
26
+
27
+ export interface AdvisoryImpact {
28
+ package: string;
29
+ advisories: string[];
30
+ projects: ReturnType<EstateReader['projectsUsing']>;
31
+ }
32
+
33
+ export function whereUsed(db: EstateReader, name: string): WhereUsed {
34
+ return {
35
+ name,
36
+ package_users: db.projectsUsing(name),
37
+ publishers: db.publishers(name),
38
+ declared_by: db.exporters(name),
39
+ referenced_by: db.referrers(name),
40
+ };
41
+ }
42
+
43
+ /**
44
+ * Advisory packages from each indexed project's own advisory cache (or the one
45
+ * `--package` names), joined with the index. A project that imports the
46
+ * package in code is the one an advisory reaches first; `imports` says so.
47
+ */
48
+ export function advisoryImpact(db: EstateReader, only: string | null): AdvisoryImpact[] {
49
+ const ids = new Map<string, Set<string>>();
50
+ if (only !== null) ids.set(only, new Set());
51
+ else {
52
+ for (const p of db.projects()) {
53
+ for (const f of readState(p.root)?.findings ?? []) {
54
+ if (!ids.has(f.package)) ids.set(f.package, new Set());
55
+ ids.get(f.package)?.add(f.id);
56
+ }
57
+ }
58
+ }
59
+ return [...ids.entries()]
60
+ .sort(([a], [b]) => (a < b ? -1 : a > b ? 1 : 0))
61
+ .map(([pkg, adv]) => ({ package: pkg, advisories: [...adv].sort(), projects: db.projectsUsing(pkg) }))
62
+ .filter((r) => only !== null || r.projects.length > 0);
63
+ }
64
+
65
+ function flagValue(argv: string[], name: string): string | null {
66
+ const i = argv.indexOf(name);
67
+ return i >= 0 && i + 1 < argv.length ? (argv[i + 1] as string) : null;
68
+ }
69
+
70
+ function line(p: { id: string; root: string }, extra = ''): string {
71
+ return ` ${p.id} ${p.root}${extra}\n`;
72
+ }
73
+
74
+ export function estateMain(argv: string[]): number {
75
+ const [verb, ...rest] = argv;
76
+ const json = rest.includes('--json');
77
+ const positional = rest.filter((a, i) => !a.startsWith('--') && rest[i - 1] !== '--package');
78
+ if (verb !== 'where-used' && verb !== 'projects-using' && verb !== 'advisory-impact') {
79
+ process.stderr.write('usage: code-graph estate where-used <name>|projects-using <package>|advisory-impact [--package P] [--json]\n');
80
+ return 2;
81
+ }
82
+ if (verb !== 'advisory-impact' && !positional[0]) {
83
+ process.stderr.write(`usage: code-graph estate ${verb} <name> [--json]\n`);
84
+ return 2;
85
+ }
86
+ const db = openEstate();
87
+ if (!db) {
88
+ process.stdout.write(`${NO_INDEX}\n`);
89
+ return 1;
90
+ }
91
+ try {
92
+ const name = positional[0] as string;
93
+ if (verb === 'projects-using') {
94
+ const rows = db.projectsUsing(name);
95
+ if (json) process.stdout.write(`${JSON.stringify(rows, null, 2)}\n`);
96
+ else {
97
+ process.stdout.write(`projects using ${name} (${rows.length}):\n`);
98
+ for (const r of rows) process.stdout.write(line(r, `${r.declared_in.length ? ` · declared in ${r.declared_in.join(', ')}` : ''}${r.imports ? ` · imported ${r.imports}×` : ''}`));
99
+ }
100
+ return 0;
101
+ }
102
+ if (verb === 'where-used') {
103
+ const w = whereUsed(db, name);
104
+ if (json) {
105
+ process.stdout.write(`${JSON.stringify(w, null, 2)}\n`);
106
+ return 0;
107
+ }
108
+ const section = (title: string, rows: Array<{ id: string; root: string }>): void => {
109
+ if (!rows.length) return;
110
+ process.stdout.write(`${title} (${rows.length}):\n`);
111
+ for (const r of rows) process.stdout.write(line(r));
112
+ };
113
+ section(`declare or import package ${name}`, w.package_users);
114
+ section(`publish package ${name}`, w.publishers);
115
+ section(`declare a top-level symbol ${name}`, w.declared_by);
116
+ section(`reference ${name} without resolving it (a guess)`, w.referenced_by);
117
+ if (!w.package_users.length && !w.publishers.length && !w.declared_by.length && !w.referenced_by.length) {
118
+ process.stdout.write(`no registered project uses ${name}\n`);
119
+ }
120
+ return 0;
121
+ }
122
+ const impact = advisoryImpact(db, flagValue(rest, '--package'));
123
+ if (json) process.stdout.write(`${JSON.stringify(impact, null, 2)}\n`);
124
+ else {
125
+ if (!impact.length) process.stdout.write('no advisory package is used by a registered project\n');
126
+ for (const r of impact) {
127
+ process.stdout.write(`${r.package}${r.advisories.length ? ` — ${r.advisories.join(', ')}` : ''}:\n`);
128
+ for (const p of r.projects) process.stdout.write(line(p, p.imports ? ` · imported ${p.imports}×` : ' · declared only'));
129
+ }
130
+ }
131
+ return 0;
132
+ } finally {
133
+ db.close();
134
+ }
135
+ }