@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,254 @@
1
+ /**
2
+ * `code-graph projects …` — the registry of projects this machine works in
3
+ * (ADR-299). A project is added by use (a build or refresh calls
4
+ * {@link registerUse}) or by hand with `add`; nothing here walks a directory
5
+ * tree looking for repositories.
6
+ *
7
+ * projects list every entry; a root missing for 30 days is flagged
8
+ * projects add <path> register a project by hand
9
+ * projects remove <id|name|path> drop one entry and its user-global graph homes
10
+ * projects prune drop the flagged entries
11
+ * projects index on|off|status the estate index switch (council O1, default off)
12
+ * projects budget [MB] show or set the graph-home budget (default 2048)
13
+ * projects pin|unpin <id|name|path> keep a project's graph homes out of eviction
14
+ * projects evict evict least recently queried homes down to the budget
15
+ */
16
+ import * as fs from 'node:fs';
17
+ import * as path from 'node:path';
18
+
19
+ import { findProjects, mainRoot, type ProjectEntry, readRegistry, recordProject, repoIdOf, updateRegistry } from '../_lib/project_registry.js';
20
+ import { dropProjectRows, indexAfterRefresh, indexPath } from './estate_index.js';
21
+ import { enforceBudget, homeUsage } from './graph_budget.js';
22
+ import { graphHome, graphsRoot } from './graph_home.js';
23
+ import type { CodeGraph } from './types.js';
24
+
25
+ /** A root missing this long is flagged by `list` and removed by `prune` (6.2). */
26
+ export const STALE_ROOT_DAYS = 30;
27
+ const DAY_MS = 24 * 60 * 60 * 1000;
28
+
29
+ export type RootState = 'present' | 'missing' | 'stale';
30
+
31
+ export function rootState(e: ProjectEntry, now: Date = new Date()): RootState {
32
+ if (fs.existsSync(e.root)) return 'present';
33
+ return now.getTime() - Date.parse(e.last_seen) >= STALE_ROOT_DAYS * DAY_MS ? 'stale' : 'missing';
34
+ }
35
+
36
+ /**
37
+ * Record that `root`'s graph was just built or refreshed. Best-effort: the
38
+ * registry is bookkeeping, and a failure to write it never fails a build.
39
+ *
40
+ * A linked worktree shares its main checkout's repo id, so it records only its
41
+ * own home (for eviction and removal) and never the entry's graph home, commit,
42
+ * languages or estate row — a feature-branch build must not replace what the
43
+ * main checkout recorded.
44
+ */
45
+ export function registerUse(root: string, graph: CodeGraph | null, commit: string | null): string | null {
46
+ try {
47
+ const home = graphHome(root);
48
+ const real = fs.realpathSync(root);
49
+ const worktree = mainRoot(real) !== real;
50
+ const usedHome = home.kind === 'global' ? { usedHome: home.dir } : {};
51
+ const id = recordProject(
52
+ root,
53
+ worktree
54
+ ? usedHome
55
+ : {
56
+ graph_home: home.dir,
57
+ ...(commit ? { graph_commit: commit } : {}),
58
+ ...(graph ? { languages: [...graph.languages], providers: (graph.providers ?? []).map((p) => p.id) } : {}),
59
+ ...usedHome,
60
+ },
61
+ );
62
+ if (!worktree) {
63
+ // A row the pre-checkout registry wrote under the bare repo id goes once that entry was re-keyed.
64
+ if (readRegistry().projects[repoIdOf(id)] === undefined) dropProjectRows(repoIdOf(id));
65
+ indexAfterRefresh(id, real, home.cachePath, graph);
66
+ }
67
+ if (home.kind === 'global') enforceBudget(home.dir);
68
+ return id;
69
+ } catch {
70
+ return null;
71
+ }
72
+ }
73
+
74
+ /** User-global homes are removable caches; a home under a repository's own `agents/` never is. */
75
+ function removeGlobalHomes(e: ProjectEntry): string[] {
76
+ const base = graphsRoot();
77
+ const removed: string[] = [];
78
+ for (const dir of new Set([...Object.keys(e.homes), ...(e.graph_home ? [e.graph_home] : [])])) {
79
+ const rel = path.relative(base, dir);
80
+ if (rel === '' || rel.startsWith('..') || path.isAbsolute(rel)) continue;
81
+ fs.rmSync(dir, { recursive: true, force: true });
82
+ removed.push(dir);
83
+ }
84
+ return removed;
85
+ }
86
+
87
+ function cmdList(): number {
88
+ const reg = readRegistry();
89
+ const rows = Object.entries(reg.projects);
90
+ if (!rows.length) {
91
+ process.stdout.write('no registered projects — a `code-graph build` or `code-graph projects add <path>` registers one\n');
92
+ return 0;
93
+ }
94
+ for (const [id, e] of rows) {
95
+ const state = rootState(e);
96
+ const flag = state === 'stale' ? ` · STALE (missing ${STALE_ROOT_DAYS}+ days — \`projects prune\` removes it)` : state === 'missing' ? ' · missing' : '';
97
+ process.stdout.write(
98
+ `${id} ${e.root}${e.pinned ? ' · pinned' : ''}${flag}\n` +
99
+ ` last seen ${e.last_seen}${e.graph_commit ? ` · commit ${e.graph_commit.slice(0, 12)}` : ''}` +
100
+ `${e.languages.length ? ` · ${e.languages.join(', ')}` : ''}${e.providers.length ? ` · providers ${e.providers.join(', ')}` : ''}\n`,
101
+ );
102
+ }
103
+ process.stdout.write(`estate index: ${reg.index ? 'on' : 'off'} · budget ${reg.budget_mb} MB\n`);
104
+ return 0;
105
+ }
106
+
107
+ function cmdAdd(target: string | undefined): number {
108
+ if (!target) {
109
+ process.stderr.write('usage: code-graph projects add <path>\n');
110
+ return 2;
111
+ }
112
+ const abs = path.resolve(target);
113
+ if (!fs.existsSync(abs) || !fs.statSync(abs).isDirectory()) {
114
+ process.stderr.write(`code-graph: not a directory: ${abs}\n`);
115
+ return 1;
116
+ }
117
+ const id = recordProject(abs);
118
+ process.stdout.write(`registered ${id} ${fs.realpathSync(abs)}\n`);
119
+ return 0;
120
+ }
121
+
122
+ function cmdRemove(ref: string | undefined): number {
123
+ if (!ref) {
124
+ process.stderr.write('usage: code-graph projects remove <id|name|path>\n');
125
+ return 2;
126
+ }
127
+ const hits = findProjects(readRegistry(), ref);
128
+ if (hits.length === 0) {
129
+ process.stderr.write(`code-graph: no registered project matches '${ref}'\n`);
130
+ return 1;
131
+ }
132
+ if (hits.length > 1) {
133
+ process.stderr.write(`code-graph: '${ref}' names ${hits.length} projects — use an id:\n`);
134
+ for (const [id, e] of hits) process.stderr.write(` ${id} ${e.root}\n`);
135
+ return 1;
136
+ }
137
+ const [id, entry] = hits[0] as [string, ProjectEntry];
138
+ updateRegistry((reg) => {
139
+ delete reg.projects[id];
140
+ });
141
+ const removed = removeGlobalHomes(entry);
142
+ dropProjectRows(id);
143
+ process.stdout.write(`removed ${id} ${entry.root}${removed.length ? ` · ${removed.length} graph home(s) deleted` : ''}\n`);
144
+ return 0;
145
+ }
146
+
147
+ function cmdPrune(): number {
148
+ const now = new Date();
149
+ const dropped = updateRegistry((reg) => {
150
+ const out: Array<[string, ProjectEntry]> = [];
151
+ for (const [id, e] of Object.entries(reg.projects)) {
152
+ if (rootState(e, now) !== 'stale') continue;
153
+ out.push([id, e]);
154
+ delete reg.projects[id];
155
+ }
156
+ return out;
157
+ });
158
+ for (const [id, e] of dropped) {
159
+ removeGlobalHomes(e);
160
+ dropProjectRows(id);
161
+ process.stdout.write(`pruned ${id} ${e.root}\n`);
162
+ }
163
+ process.stdout.write(`${dropped.length} pruned\n`);
164
+ return 0;
165
+ }
166
+
167
+ function cmdIndex(action: string | undefined): number {
168
+ if (action === 'on' || action === 'off') {
169
+ updateRegistry((reg) => {
170
+ reg.index = action === 'on';
171
+ });
172
+ if (action === 'off') fs.rmSync(indexPath(), { force: true });
173
+ process.stdout.write(
174
+ action === 'on'
175
+ ? 'estate index on — each registered project is indexed on its next build or refresh. It names client projects by path and never leaves this machine.\n'
176
+ : 'estate index off — the index file is deleted and nothing is indexed.\n',
177
+ );
178
+ return 0;
179
+ }
180
+ if (action !== undefined && action !== 'status') {
181
+ process.stderr.write('usage: code-graph projects index on|off|status\n');
182
+ return 2;
183
+ }
184
+ const reg = readRegistry();
185
+ process.stdout.write(`estate index: ${reg.index ? 'on' : 'off'} · ${fs.existsSync(indexPath()) ? indexPath() : 'no index file'}\n`);
186
+ return 0;
187
+ }
188
+
189
+ function one(ref: string | undefined, verb: string): [string, ProjectEntry] | null {
190
+ if (!ref) {
191
+ process.stderr.write(`usage: code-graph projects ${verb} <id|name|path>\n`);
192
+ return null;
193
+ }
194
+ const hits = findProjects(readRegistry(), ref);
195
+ if (hits.length !== 1) {
196
+ process.stderr.write(`code-graph: '${ref}' matches ${hits.length} registered projects — use an id from \`projects list\`\n`);
197
+ return null;
198
+ }
199
+ return hits[0] as [string, ProjectEntry];
200
+ }
201
+
202
+ function cmdPin(ref: string | undefined, pinned: boolean): number {
203
+ const hit = one(ref, pinned ? 'pin' : 'unpin');
204
+ if (!hit) return 1;
205
+ updateRegistry((reg) => {
206
+ const e = reg.projects[hit[0]];
207
+ if (e) e.pinned = pinned;
208
+ });
209
+ process.stdout.write(`${pinned ? 'pinned' : 'unpinned'} ${hit[0]} ${hit[1].root}\n`);
210
+ return 0;
211
+ }
212
+
213
+ function cmdBudget(value: string | undefined): number {
214
+ if (value !== undefined) {
215
+ const mb = Number(value);
216
+ if (!Number.isFinite(mb) || mb <= 0) {
217
+ process.stderr.write('usage: code-graph projects budget [MB] — a positive number\n');
218
+ return 2;
219
+ }
220
+ updateRegistry((reg) => {
221
+ reg.budget_mb = mb;
222
+ });
223
+ }
224
+ const used = homeUsage().reduce((n, h) => n + h.bytes, 0);
225
+ process.stdout.write(`graph-home budget: ${readRegistry().budget_mb} MB · in use ${(used / (1024 * 1024)).toFixed(1)} MB under ${graphsRoot()}\n`);
226
+ return 0;
227
+ }
228
+
229
+ function cmdEvict(): number {
230
+ const r = enforceBudget();
231
+ for (const h of r.evicted) process.stdout.write(`evicted ${h.dir} (${(h.bytes / (1024 * 1024)).toFixed(1)} MB)\n`);
232
+ const over = r.total > r.budget;
233
+ process.stdout.write(
234
+ `${r.evicted.length} evicted · ${(r.total / (1024 * 1024)).toFixed(1)} of ${(r.budget / (1024 * 1024)).toFixed(1)} MB` +
235
+ (over ? ' — still over: the rest is pinned, lock-held or being written' : '') +
236
+ '\n',
237
+ );
238
+ return 0;
239
+ }
240
+
241
+ export function projectsMain(argv: string[]): number {
242
+ const [verb, ...rest] = argv;
243
+ const positional = rest.filter((a) => !a.startsWith('--'));
244
+ if (verb === undefined || verb === 'list') return cmdList();
245
+ if (verb === 'add') return cmdAdd(positional[0]);
246
+ if (verb === 'remove') return cmdRemove(positional[0]);
247
+ if (verb === 'prune') return cmdPrune();
248
+ if (verb === 'index') return cmdIndex(positional[0]);
249
+ if (verb === 'budget') return cmdBudget(positional[0]);
250
+ if (verb === 'pin' || verb === 'unpin') return cmdPin(positional[0], verb === 'pin');
251
+ if (verb === 'evict') return cmdEvict();
252
+ process.stderr.write('usage: code-graph projects list|add|remove|prune|index|budget|pin|unpin|evict\n');
253
+ return 2;
254
+ }
@@ -0,0 +1,55 @@
1
+ /**
2
+ * Angular:
3
+ *
4
+ * { path: 'x', component: XComponent } PAGE /x --routes-to--> XComponent
5
+ * { path: 'x', loadComponent: () => import('./x').then((m) => m.X) } → X, else the module
6
+ * @Component({ templateUrl: './x.html' }) class --renders--> the template file
7
+ * constructor(private s: S) · s = inject(S) --injects--> S (in a decorated class)
8
+ *
9
+ * Child route paths are not joined to their parent's; `loadChildren` modules,
10
+ * inline `template:` strings and `styleUrls` are not followed.
11
+ */
12
+ import * as path from 'node:path';
13
+
14
+ import { npmRoots } from './shared/detect.js';
15
+ import { constructorInjection, routeObjects } from './shared/js.js';
16
+ import { type Kit, lineIndex, makeKit } from './shared/kit.js';
17
+ import type { ConventionProvider } from './types.js';
18
+
19
+ const EXTS = ['.ts'];
20
+
21
+ function templateUrls(kit: Kit): number {
22
+ const inventory = new Set(kit.paths);
23
+ let count = 0;
24
+ for (const f of kit.filesWithExt(...EXTS)) {
25
+ const at = lineIndex(f.source);
26
+ for (const m of f.source.matchAll(/@Component\(\s*\{(?<body>[^]*?)\}\s*\)/g)) {
27
+ const url = /\btemplateUrl\s*:\s*(['"`])([^'"`]+)\1/.exec(m.groups?.['body'] ?? '');
28
+ if (!url) continue;
29
+ const cls = kit.nextSymbol(f.path, at(m.index as number), ['class']);
30
+ const target = path.posix.join(path.posix.dirname(f.path), url[2] as string);
31
+ if (!cls || cls.kind !== 'class' || !inventory.has(target)) continue;
32
+ kit.fileNode(target);
33
+ kit.emit(cls.id, target, 'renders', 'directive-scan');
34
+ count += 1;
35
+ }
36
+ }
37
+ return count;
38
+ }
39
+
40
+ export const angularProvider: ConventionProvider = {
41
+ id: 'angular',
42
+ detect: (probe) => npmRoots(probe, ['@angular/core']),
43
+ build: (input) => {
44
+ const kit = makeKit(input);
45
+ routeObjects(kit, { exts: EXTS, componentKeys: ['component', 'loadComponent'] });
46
+ templateUrls(kit);
47
+ constructorInjection(kit, {
48
+ exts: EXTS,
49
+ classDecorator: /@(?:Component|Injectable|Directive|Pipe)\(/,
50
+ reach: 12,
51
+ injectCall: /=\s*inject\(\s*([A-Za-z_$][\w$]*)\s*\)/g,
52
+ });
53
+ return kit.result();
54
+ },
55
+ };
@@ -0,0 +1,182 @@
1
+ /**
2
+ * ASP.NET Core, read by its conventions:
3
+ *
4
+ * routing `[HttpGet("x")]` / `[HttpPost]` / `[Route("x")]` on an action,
5
+ * under a class `[Route("api/[controller]")]` → route --routes-to--> the action;
6
+ * `app.MapGet("/x", Handler)` → the handler when it is a method group
7
+ * EF Core `DbSet<User> Users` in a `DbContext` → User --persists--> table:Users
8
+ * (`[Table("x")]` on the entity wins)
9
+ * views `View()` / `View("Name")` in a controller action
10
+ * → `Views/<Controller>/<Name or action>.cshtml`, else `Views/Shared/`
11
+ * DI a constructor parameter typed as an in-repo class or interface → injects
12
+ *
13
+ * Not followed: conventional routing tables (`MapControllerRoute`), a view named
14
+ * by a variable or `View(model)`, primary constructors, and a type name two
15
+ * in-repo declarations share — each gets no edge rather than a guess.
16
+ */
17
+ import * as path from 'node:path';
18
+
19
+ import type { CodeNode } from '../types.js';
20
+ import { dotnetRoots } from './shared/detect.js';
21
+ import { callRoutes, joinRoute, lastArgName } from './shared/extractors.js';
22
+ import { escapeRe, type Kit, lineIndex, makeKit } from './shared/kit.js';
23
+ import type { ConventionProvider } from './types.js';
24
+
25
+ const ATTR =
26
+ /[[,]\s*(?<attr>Http(?:Get|Post|Put|Patch|Delete|Head|Options)|Route)(?:Attribute)?\b(?:\s*\(\s*(?:template\s*:\s*)?"(?<path>[^"]*)"[^)]*\))?/g;
27
+
28
+ /** In-repo types by simple name, kept only when the name is declared once. */
29
+ function uniqueTypes(kit: Kit): Map<string, CodeNode> {
30
+ const seen = new Map<string, CodeNode | null>();
31
+ for (const n of kit.nodeById.values()) {
32
+ if (!n.source_file.endsWith('.cs') || (n.kind !== 'class' && n.kind !== 'interface')) continue;
33
+ seen.set(n.label, seen.has(n.label) ? null : n);
34
+ }
35
+ const out = new Map<string, CodeNode>();
36
+ for (const [k, v] of seen) if (v) out.set(k, v);
37
+ return out;
38
+ }
39
+
40
+ const controllerName = (cls: CodeNode): string => cls.label.replace(/Controller$/, '');
41
+
42
+ function attributeRoutes(kit: Kit): void {
43
+ for (const f of kit.filesWithExt('.cs')) {
44
+ const at = lineIndex(f.source);
45
+ const prefixes = new Map<string, string>();
46
+ const actions = new Map<string, { node: CodeNode; line: number; verbs: Array<{ verb: string; path?: string }>; routes: string[] }>();
47
+ for (const m of f.source.matchAll(ATTR)) {
48
+ const line = at(m.index as number);
49
+ const attr = m.groups?.['attr'] as string;
50
+ const tpl = m.groups?.['path'];
51
+ const owner = kit.enclosing(f.path, line);
52
+ const method = owner === f.path ? null : kit.nodeById.get(owner);
53
+ if (!method) {
54
+ const cls = kit.classAt(f.path, line);
55
+ if (cls && attr === 'Route' && tpl !== undefined) prefixes.set(cls.id, tpl);
56
+ continue;
57
+ }
58
+ const a = actions.get(method.id) ?? { node: method, line, verbs: [], routes: [] };
59
+ if (attr === 'Route') {
60
+ if (tpl !== undefined) a.routes.push(tpl);
61
+ } else a.verbs.push({ verb: attr.slice(4).toUpperCase(), ...(tpl !== undefined ? { path: tpl } : {}) });
62
+ actions.set(method.id, a);
63
+ }
64
+ for (const a of actions.values()) {
65
+ const cls = kit.classAt(f.path, a.node.source_location[0] as number);
66
+ const prefix = cls ? prefixes.get(cls.id) : undefined;
67
+ const pairs: Array<[string, string | undefined]> = [];
68
+ for (const v of a.verbs) {
69
+ if (v.path !== undefined) pairs.push([v.verb, v.path]);
70
+ else if (a.routes.length > 0) for (const r of a.routes) pairs.push([v.verb, r]);
71
+ else pairs.push([v.verb, undefined]);
72
+ }
73
+ if (a.verbs.length === 0) for (const r of a.routes) pairs.push(['ANY', r]);
74
+ for (const [verb, tpl] of pairs) {
75
+ const absolute = tpl !== undefined && /^~?\//.test(tpl);
76
+ const raw = absolute ? joinRoute(tpl.replace(/^~/, '')) : joinRoute(prefix, tpl);
77
+ const routePath = raw
78
+ .replace(/\[controller\]/gi, cls ? controllerName(cls) : '[controller]')
79
+ .replace(/\[action\]/gi, a.node.label);
80
+ kit.emit(kit.route(f.path, verb, routePath, a.line), a.node.id, 'routes-to', 'route-table');
81
+ }
82
+ }
83
+ }
84
+ }
85
+
86
+ function minimalApi(kit: Kit, types: Map<string, CodeNode>): void {
87
+ callRoutes(kit, {
88
+ exts: ['.cs'],
89
+ call: /\b\w+\s*\.\s*Map(?<verb>Get|Post|Put|Patch|Delete)\(\s*"(?<path>[^"]*)"\s*,(?=(?<rest>[\s\S]{0,300}))/g,
90
+ handler: (_k, file, rest) => {
91
+ const name = lastArgName(rest);
92
+ if (!name) return null;
93
+ const [head, member] = name.split('.') as [string, string | undefined];
94
+ if (member) {
95
+ const cls = types.get(head);
96
+ return cls ? kit.methodOf(cls.id, member) : null;
97
+ }
98
+ const local = kit.nodesInFile(file).filter((n) => n.label === head && (n.kind === 'method' || n.kind === 'function'));
99
+ return local.length === 1 ? (local[0] as CodeNode).id : null;
100
+ },
101
+ });
102
+ }
103
+
104
+ function entities(kit: Kit, types: Map<string, CodeNode>): void {
105
+ const sourceOf = new Map(kit.files.map((f) => [f.path, f.source] as const));
106
+ for (const f of kit.filesWithExt('.cs')) {
107
+ if (!/\bclass\s+\w+[^{;]*:\s*[^{;]*\bDbContext\b/.test(f.source)) continue;
108
+ const at = lineIndex(f.source);
109
+ for (const m of f.source.matchAll(/\bDbSet<\s*(?:[\w.]+\.)?(?<entity>\w+)\s*>\s+(?<prop>\w+)/g)) {
110
+ const entity = types.get(m.groups?.['entity'] as string);
111
+ if (!entity || entity.kind !== 'class') continue;
112
+ const lines = (sourceOf.get(entity.source_file) ?? '').split('\n');
113
+ const start = entity.source_location[0] as number;
114
+ const head = lines.slice(start - 1, start + 5).join('\n').split(/\bclass\b/)[0] ?? '';
115
+ const explicit = /\[\s*Table\s*\(\s*"(?<name>[^"]+)"/.exec(head)?.groups?.['name'];
116
+ const table = kit.table(explicit ?? (m.groups?.['prop'] as string), f.path, at(m.index as number));
117
+ kit.emit(entity.id, table, 'persists', 'schema-call');
118
+ }
119
+ }
120
+ }
121
+
122
+ function views(kit: Kit): void {
123
+ const inventory = new Set(kit.paths);
124
+ for (const f of kit.filesWithExt('.cs')) {
125
+ const at = lineIndex(f.source);
126
+ for (const m of f.source.matchAll(/\b(?:Partial)?View\(\s*(?:"(?<name>[^"]+)"\s*)?[,)]/g)) {
127
+ const line = at(m.index as number);
128
+ const owner = kit.enclosing(f.path, line);
129
+ const action = owner === f.path ? null : kit.nodeById.get(owner);
130
+ const cls = action ? kit.classAt(f.path, line) : null;
131
+ if (!action || !cls || !cls.label.endsWith('Controller')) continue;
132
+ const name = m.groups?.['name'] ?? action.label;
133
+ const candidates: string[] = [];
134
+ for (const root of kit.roots) {
135
+ if (/^~?\//.test(name)) candidates.push(path.posix.join(root, name.replace(/^~?\//, '')));
136
+ else {
137
+ const file = name.endsWith('.cshtml') ? name : `${name}.cshtml`;
138
+ candidates.push(path.posix.join(root, 'Views', controllerName(cls), file), path.posix.join(root, 'Views', 'Shared', file));
139
+ }
140
+ }
141
+ const target = candidates.find((c) => inventory.has(c));
142
+ if (!target) continue;
143
+ kit.fileNode(target);
144
+ kit.emit(action.id, target, 'renders', 'directive-scan');
145
+ }
146
+ }
147
+ }
148
+
149
+ function injection(kit: Kit, types: Map<string, CodeNode>): void {
150
+ const sourceOf = new Map(kit.files.map((f) => [f.path, f.source] as const));
151
+ for (const n of kit.nodeById.values()) {
152
+ if (n.kind !== 'method' || !n.source_file.endsWith('.cs')) continue;
153
+ const clsId = n.id.split('::').slice(0, -1).join('::');
154
+ const cls = kit.nodeById.get(clsId);
155
+ if (!cls || cls.kind !== 'class' || cls.label !== n.label) continue;
156
+ const lines = (sourceOf.get(n.source_file) ?? '').split('\n');
157
+ const start = n.source_location[0] as number;
158
+ const decl = lines.slice(start - 1, (n.source_location[2] as number) ?? start).join('\n');
159
+ const params = new RegExp(`\\b${escapeRe(n.label)}\\s*\\(([^)]*)\\)`).exec(decl)?.[1] ?? '';
160
+ for (const p of params.split(',')) {
161
+ const t = /(?:^|\s)(?:[\w.]+\.)?(?<type>[A-Za-z_]\w*)\s*(?:<[^>]*>)?\??\s+\w+\s*(?:=.*)?$/.exec(p.trim())?.groups?.['type'];
162
+ const target = t ? types.get(t) : undefined;
163
+ if (target && target.id !== cls.id) kit.emit(cls.id, target.id, 'injects', 'di-binding');
164
+ }
165
+ }
166
+ }
167
+
168
+ export const aspnetcoreProvider: ConventionProvider = {
169
+ id: 'aspnetcore',
170
+ detect: (probe) => dotnetRoots(probe, /Sdk\s*=\s*"Microsoft\.NET\.Sdk\.Web"|Include\s*=\s*"Microsoft\.AspNetCore\./),
171
+ build: (input) => {
172
+ const kit = makeKit(input);
173
+ const types = uniqueTypes(kit);
174
+ attributeRoutes(kit);
175
+ minimalApi(kit, types);
176
+ entities(kit, types);
177
+ views(kit);
178
+ injection(kit, types);
179
+ return kit.result();
180
+ },
181
+ roleOf: (p) => (p.endsWith('.cshtml') ? 'view' : path.posix.basename(p) === 'Program.cs' ? 'entry' : undefined),
182
+ };