@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.
- package/.augment-plugin/marketplace.json +3 -3
- package/.augment-plugin/plugin.json +1 -1
- package/.claude-plugin/marketplace.json +1 -1
- package/.claude-plugin/skills/install-agent-config/SKILL.md +9 -9
- package/CHANGELOG.md +167 -193
- package/CREDITS.md +2 -2
- package/README.md +1 -1
- package/dist/agent-src/skills/code-intelligence/SKILL.md +16 -12
- package/dist/agent-src/templates/agents/agent-project-settings.example.yml +1 -1
- package/dist/agent-src/templates/scripts/check_memory.ts +1 -1
- package/dist/cli/commands/recordTriggerEval.js +1 -1
- package/dist/cli/registry.js +2 -2
- package/dist/cli-delegate/{chunk-KR5ZIFPO.js → chunk-5AYJGUA3.js} +1 -3
- package/dist/cli-delegate/chunk-DIFZBP4G.js +1336 -0
- package/dist/cli-delegate/chunk-E7ACTJTJ.js +6792 -0
- package/dist/cli-delegate/{chunk-VYZGJLCA.js → chunk-RKNT4LIK.js} +22 -1286
- package/dist/cli-delegate/{chunk-BZJ4Y3CI.js → chunk-WY5ZZPGL.js} +2 -2
- package/dist/cli-delegate/{chunk-CMZEKSNN.js → chunk-X6LZ7W5A.js} +73 -10
- package/dist/cli-delegate/cmd_conformance.js +190 -192
- package/dist/cli-delegate/cmd_doctor.js +5 -5
- package/dist/cli-delegate/cmd_doctor_neighbours.js +3 -3
- package/dist/cli-delegate/cmd_explain.js +1 -1
- package/dist/cli-delegate/cmd_export.js +2 -2
- package/dist/cli-delegate/cmd_handoff.js +2 -2
- package/dist/cli-delegate/cmd_memory_get.js +1 -1
- package/dist/cli-delegate/cmd_prune.js +2 -2
- package/dist/cli-delegate/cmd_route_audit.js +1 -1
- package/dist/cli-delegate/cmd_session_recycle.js +2 -2
- package/dist/cli-delegate/cmd_settings_migrate.js +2 -2
- package/dist/cli-delegate/cmd_settings_set.js +8 -4
- package/dist/cli-delegate/cmd_sync.js +2 -2
- package/dist/cli-delegate/cmd_uninstall.js +2 -2
- package/dist/cli-delegate/cmd_update.js +24 -13
- package/dist/cli-delegate/cmd_upgrade.js +2 -2
- package/dist/cli-delegate/cmd_validate.js +2 -2
- package/dist/cli-delegate/dist-25AZAHOP.js +3240 -0
- package/dist/cli-delegate/dist-P7MFPMF3.js +411 -0
- package/dist/cli-delegate/dist-XAS5ZLQB.js +6 -0
- package/dist/cli-delegate/{upgrade_unattended-VVL6JLSX.js → upgrade_unattended-QCWOKTUN.js} +178 -163
- package/dist/discovery/deprecation-report.md +1 -1
- package/dist/discovery/discovery-manifest.json +3 -3
- package/dist/discovery/discovery-manifest.json.sha256 +1 -1
- package/dist/discovery/discovery-manifest.summary.md +1 -1
- package/dist/discovery/dormancy-report.md +1 -1
- package/dist/discovery/orphan-report.md +1 -1
- package/dist/discovery/packs.json +2 -2
- package/dist/discovery/trust-report.md +1 -1
- package/dist/discovery/workspaces.json +2 -2
- package/dist/hooks/code_graph_refresh.chunk.cjs +443 -0
- package/dist/hooks/code_graph_refresh.chunk.cjs.sha256 +1 -0
- package/dist/hooks/dispatch.js +1001 -938
- package/dist/hooks/dispatch.sha256 +1 -1
- package/dist/hooks/egress_engine.chunk.cjs +57 -7
- package/dist/hooks/egress_engine.chunk.cjs.sha256 +1 -1
- package/dist/hooks/update_notice.chunk.cjs +914 -0
- package/dist/hooks/update_notice.chunk.cjs.sha256 +1 -0
- package/dist/install/install.mjs +6974 -6037
- package/dist/mcp/mcp-cloudflare-catalogue.json +1 -0
- package/dist/mcp/registry-manifest.json +3 -2
- package/dist/mcp/server.json +13 -4
- package/dist/mcp/server.mjs +71202 -63282
- package/dist/scripts/_lib/egress_rules.js +8 -2
- package/dist/scripts/condense.js +18 -15
- package/dist/server/routes/discovery.js +1 -1
- package/dist/server/routes/install.js +1 -1
- package/dist/server/routes/ping.js +1 -1
- package/dist/server/routes/schema.js +4 -4
- package/dist/server/routes/settings.js +2 -6
- package/dist/server/routes/wizard.js +6 -6
- package/dist/server/schemas/settings.js +42 -42
- package/dist/shared/settingsSurface.js +1 -1
- package/dist/shared/userMd/schema.js +1 -1
- package/dist/shared/zodJsonSchema.js +47 -0
- package/dist/ui/assets/index-B0bcIgi0.js +77 -0
- package/dist/ui/index.html +1 -1
- package/package.json +6 -3
- package/src/agent-src/templates/scripts/check_memory.ts +1 -1
- package/src/config/ci-local-parity.yml +11 -0
- package/src/config/continuity-surface.json +9 -2
- package/src/config/evaluator-budgets.json +9 -8
- package/src/config/gate-violation-baselines.json +3 -3
- package/src/config/gitignore-block.txt +6 -5
- package/src/config/hook-bundle-budget.json +46 -6
- package/src/config/pack-size-budget.json +12 -1
- package/src/config/packed-binary-manifest.json +18 -0
- package/src/config/sigstore/npm_keys.json +50 -0
- package/src/config/sigstore/trust-material.json +9 -0
- package/src/config/sigstore/trusted_root.json +126 -0
- package/src/config/skill-body-budget.json +7 -1
- package/src/scripts/_cli/cmd_settings_set.ts +3 -2
- package/src/scripts/_cli/cmd_update.ts +48 -20
- package/src/scripts/_cli/cmd_upgrade.ts +7 -1
- package/src/scripts/_cli/upgrade_unattended.ts +90 -76
- package/src/scripts/_dispatch.bash +4 -3
- package/src/scripts/_lib/confusables.ts +0 -19
- package/src/scripts/_lib/egress_normalize.ts +53 -23
- package/src/scripts/_lib/egress_rules.ts +8 -2
- package/src/scripts/_lib/egress_text.ts +5 -3
- package/src/scripts/_lib/egress_token.ts +7 -5
- package/src/scripts/_lib/gate_collector_experimental.ts +633 -0
- package/src/scripts/_lib/git_common_dir.ts +10 -0
- package/src/scripts/_lib/graph_feeder_record.ts +3 -2
- package/src/scripts/_lib/install_reach_checks.ts +32 -2
- package/src/scripts/_lib/lineage_backfill.ts +136 -0
- package/src/scripts/_lib/lineage_ledger.ts +211 -0
- package/src/scripts/_lib/lineage_map.ts +55 -0
- package/src/scripts/_lib/lineage_query.ts +140 -0
- package/src/scripts/_lib/lineage_seed.ts +116 -0
- package/src/scripts/_lib/lineage_watch.ts +249 -0
- package/src/scripts/_lib/link_crypto.ts +8 -4
- package/src/scripts/_lib/listing_manifests.ts +85 -0
- package/src/scripts/_lib/neighbour_census.ts +1 -0
- package/src/scripts/_lib/production_reach.ts +3 -3
- package/src/scripts/_lib/project_registry.ts +227 -0
- package/src/scripts/_lib/provenance_verify.ts +14 -242
- package/src/scripts/_lib/release_settings_surface.ts +2 -6
- package/src/scripts/_lib/sigstore_provenance.ts +177 -0
- package/src/scripts/_lib/source_id.ts +48 -0
- package/src/scripts/_lib/source_shape.ts +4 -0
- package/src/scripts/_lib/unattended_gates.ts +6 -1
- package/src/scripts/adoption_status.ts +231 -27
- package/src/scripts/advisories.ts +26 -10
- package/src/scripts/ai_council/council_egress.ts +5 -6
- package/src/scripts/bench_graph_feeder_latency.ts +10 -3
- package/src/scripts/build_mcp_registry_manifest.ts +23 -12
- package/src/scripts/check_dependency_floors.ts +14 -0
- package/src/scripts/check_egress_mutations.ts +1 -0
- package/src/scripts/check_release_manifest_content.ts +17 -2
- package/src/scripts/check_release_pr_shape.ts +5 -0
- package/src/scripts/code_graph/build.ts +145 -45
- package/src/scripts/code_graph/cli.ts +382 -35
- package/src/scripts/code_graph/conventions_default.ts +247 -0
- package/src/scripts/code_graph/detect.ts +5 -5
- package/src/scripts/code_graph/estate_cli.ts +135 -0
- package/src/scripts/code_graph/estate_index.ts +394 -0
- package/src/scripts/code_graph/estate_manifests.ts +223 -0
- package/src/scripts/code_graph/estate_map.ts +141 -0
- package/src/scripts/code_graph/extract.ts +8 -2
- package/src/scripts/code_graph/extract_more.ts +241 -2
- package/src/scripts/code_graph/graph_budget.ts +158 -0
- package/src/scripts/code_graph/graph_home.ts +80 -0
- package/src/scripts/code_graph/lineage_cli.ts +315 -0
- package/src/scripts/code_graph/loader.ts +10 -2
- package/src/scripts/code_graph/map.ts +195 -53
- package/src/scripts/code_graph/map_model.ts +113 -20
- package/src/scripts/code_graph/map_verify.ts +80 -5
- package/src/scripts/code_graph/map_viewer/viewer.css +18 -0
- package/src/scripts/code_graph/map_viewer/viewer.js +146 -15
- package/src/scripts/code_graph/passes.ts +173 -0
- package/src/scripts/code_graph/project_ref.ts +25 -0
- package/src/scripts/code_graph/projects_cli.ts +254 -0
- package/src/scripts/code_graph/providers/angular.ts +55 -0
- package/src/scripts/code_graph/providers/aspnetcore.ts +182 -0
- package/src/scripts/code_graph/providers/catalog.ts +299 -0
- package/src/scripts/code_graph/providers/django.ts +206 -0
- package/src/scripts/code_graph/providers/express.ts +25 -0
- package/src/scripts/code_graph/providers/fastapi.ts +49 -0
- package/src/scripts/code_graph/providers/flask.ts +99 -0
- package/src/scripts/code_graph/providers/go_http.ts +121 -0
- package/src/scripts/code_graph/providers/index.ts +103 -0
- package/src/scripts/code_graph/{laravel_overlay.ts → providers/laravel.ts} +113 -7
- package/src/scripts/code_graph/providers/nestjs.ts +34 -0
- package/src/scripts/code_graph/providers/nextjs.ts +91 -0
- package/src/scripts/code_graph/providers/nuxt.ts +55 -0
- package/src/scripts/code_graph/providers/rails.ts +204 -0
- package/src/scripts/code_graph/providers/react-router.ts +58 -0
- package/src/scripts/code_graph/providers/shared/detect.ts +78 -0
- package/src/scripts/code_graph/providers/shared/extractors.ts +249 -0
- package/src/scripts/code_graph/providers/shared/js.ts +252 -0
- package/src/scripts/code_graph/providers/shared/kit.ts +210 -0
- package/src/scripts/code_graph/providers/shared/php.ts +64 -0
- package/src/scripts/code_graph/providers/shared/python.ts +223 -0
- package/src/scripts/code_graph/providers/spring.ts +175 -0
- package/src/scripts/code_graph/providers/sqlalchemy.ts +62 -0
- package/src/scripts/code_graph/providers/sveltekit.ts +59 -0
- package/src/scripts/code_graph/providers/symfony.ts +142 -0
- package/src/scripts/code_graph/providers/typeorm.ts +58 -0
- package/src/scripts/code_graph/providers/types.ts +56 -0
- package/src/scripts/code_graph/providers/vue-router.ts +21 -0
- package/src/scripts/code_graph/providers/wordpress.ts +128 -0
- package/src/scripts/code_graph/query.ts +9 -9
- package/src/scripts/code_graph/refresh_cli.ts +81 -0
- package/src/scripts/code_graph/refresh_lock.ts +197 -0
- package/src/scripts/code_graph/repo_identity.ts +169 -0
- package/src/scripts/code_graph/resolution_tiers.ts +19 -7
- package/src/scripts/code_graph/scip_reader.ts +2 -2
- package/src/scripts/code_graph/sqlite_store.ts +31 -28
- package/src/scripts/code_graph/types.ts +79 -41
- package/src/scripts/code_graph/validate.ts +6 -8
- package/src/scripts/condense.ts +15 -15
- package/src/scripts/generate_settings_reference.ts +2 -6
- package/src/scripts/hook_chunks.mjs +11 -0
- package/src/scripts/hook_manifest.json +1 -1
- package/src/scripts/hook_manifest.yaml +43 -23
- package/src/scripts/hooks/code_graph_refresh_hook.ts +163 -0
- package/src/scripts/hooks/dispatch_hook.ts +6 -7
- package/src/scripts/hooks/host_semantics.ts +72 -0
- package/src/scripts/hooks/lazy_concern.ts +8 -0
- package/src/scripts/hooks/user_notice.ts +105 -0
- package/src/scripts/install-hooks.sh +54 -33
- package/src/scripts/install.ts +2 -6
- package/src/scripts/lint_harvest_provenance.ts +137 -11
- package/src/scripts/lint_hook_manifest.ts +2 -0
- package/src/scripts/lint_marketplace.ts +33 -0
- package/src/scripts/lint_mcp_registry_manifest.ts +36 -0
- package/src/scripts/mcp_server/consumer_tool_catalog.json +70 -15
- package/src/scripts/mcp_server/graph_tools.ts +165 -41
- package/src/scripts/network_sources.ts +24 -6
- package/src/scripts/release.ts +2 -5
- package/src/scripts/release_drill.ts +4 -1
- package/src/scripts/release_env.ts +17 -0
- package/src/scripts/release_mix_taxonomy.json +3 -0
- package/src/scripts/schemas/provenance-mechanism.schema.json +46 -0
- package/src/scripts/update_notice_hook.ts +75 -7
- package/src/server/schemas/settings.ts +42 -42
- package/src/shared/settingsSurface.ts +1 -1
- package/src/shared/userMd/schema.ts +1 -1
- package/src/shared/zodJsonSchema.ts +41 -0
- package/src/vendor/grammars/README.md +8 -6
- package/src/vendor/grammars/tree-sitter-c_sharp.wasm +0 -0
- package/src/vendor/grammars/tree-sitter-ruby.wasm +0 -0
- package/dist/cli-delegate/chunk-XIAPY44N.js +0 -4587
- package/dist/ui/assets/index-w70Ado09.js +0 -23
- /package/dist/cli-delegate/{chunk-FKYZOWVD.js → chunk-QBAFP67O.js} +0 -0
- /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 ??
|
|
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
|
+
}
|