diff --git a/README.md b/README.md index 7e45a78..c39fe58 100644 --- a/README.md +++ b/README.md @@ -145,7 +145,7 @@ Options: neo4j (graph.cypher or live push) | schema (the Neo4j schema.json contract) (default: "json") --app-name logical application name for the graph - :Application anchor (default: input dir name) + :TSApplication anchor (default: input dir name) --neo4j-uri push the graph to a live Neo4j over Bolt (incremental); omit to write graph.cypher (env: NEO4J_URI) @@ -232,14 +232,14 @@ Caller- and callee-side identifiers come from a single signature canonicalizer, ### Neo4j graph `--emit neo4j` projects the same analysis into a labeled property graph (declarations keyed by -their signature under a shared `:Symbol` label; calls, imports, inheritance, decorators, and call +their signature under a shared `:TSSymbol` label; calls, imports, inheritance, decorators, and call sites as relationships): - **Without `--neo4j-uri`** — writes a self-contained `graph.cypher` (constraints + indexes, a scoped wipe, then batched `MERGE`s). Load it with `cypher-shell < graph.cypher`. - **With `--neo4j-uri`** — pushes to a live Neo4j over Bolt **incrementally**: only modules whose content hash changed are rewritten, and on a full run modules whose source file vanished are - pruned. Every graph carries a `schema_version` on its `:Application` node. + pruned. Every graph carries a `schema_version` on its `:TSApplication` node. The connection options also read the standard Neo4j environment variables — `NEO4J_URI`, `NEO4J_USERNAME`, `NEO4J_PASSWORD`, `NEO4J_DATABASE` — when the corresponding flag is omitted (an diff --git a/schema.neo4j.json b/schema.neo4j.json index 232c28e..7d6c50e 100644 --- a/schema.neo4j.json +++ b/schema.neo4j.json @@ -1,13 +1,13 @@ { - "schema_version": "1.0.0", + "schema_version": "2.0.0", "generator": "codeanalyzer-typescript", "marker_labels": [ - "Entrypoint" + "TSEntrypoint" ], "node_labels": [ { - "label": "Application", - "mergeLabel": "Application", + "label": "TSApplication", + "mergeLabel": "TSApplication", "key": "name", "properties": { "name": "string", @@ -15,8 +15,8 @@ } }, { - "label": "Module", - "mergeLabel": "Module", + "label": "TSModule", + "mergeLabel": "TSModule", "key": "file_key", "properties": { "file_key": "string", @@ -30,8 +30,8 @@ } }, { - "label": "Class", - "mergeLabel": "Symbol", + "label": "TSClass", + "mergeLabel": "TSSymbol", "key": "signature", "properties": { "signature": "string", @@ -55,8 +55,8 @@ } }, { - "label": "Interface", - "mergeLabel": "Symbol", + "label": "TSInterface", + "mergeLabel": "TSSymbol", "key": "signature", "properties": { "signature": "string", @@ -75,8 +75,8 @@ } }, { - "label": "Enum", - "mergeLabel": "Symbol", + "label": "TSEnum", + "mergeLabel": "TSSymbol", "key": "signature", "properties": { "signature": "string", @@ -94,8 +94,8 @@ } }, { - "label": "TypeAlias", - "mergeLabel": "Symbol", + "label": "TSTypeAlias", + "mergeLabel": "TSSymbol", "key": "signature", "properties": { "signature": "string", @@ -112,8 +112,8 @@ } }, { - "label": "Namespace", - "mergeLabel": "Symbol", + "label": "TSNamespace", + "mergeLabel": "TSSymbol", "key": "signature", "properties": { "signature": "string", @@ -127,8 +127,8 @@ } }, { - "label": "Callable", - "mergeLabel": "Symbol", + "label": "TSCallable", + "mergeLabel": "TSSymbol", "key": "signature", "properties": { "signature": "string", @@ -165,8 +165,8 @@ } }, { - "label": "External", - "mergeLabel": "Symbol", + "label": "TSExternal", + "mergeLabel": "TSSymbol", "key": "signature", "properties": { "signature": "string", @@ -175,16 +175,16 @@ } }, { - "label": "Package", - "mergeLabel": "Package", + "label": "TSPackage", + "mergeLabel": "TSPackage", "key": "name", "properties": { "name": "string" } }, { - "label": "Decorator", - "mergeLabel": "Decorator", + "label": "TSDecorator", + "mergeLabel": "TSDecorator", "key": "qualified_name", "properties": { "qualified_name": "string", @@ -192,8 +192,8 @@ } }, { - "label": "CallSite", - "mergeLabel": "CallSite", + "label": "TSCallSite", + "mergeLabel": "TSCallSite", "key": "id", "properties": { "id": "string", @@ -214,8 +214,8 @@ } }, { - "label": "Attribute", - "mergeLabel": "Attribute", + "label": "TSAttribute", + "mergeLabel": "TSAttribute", "key": "id", "properties": { "id": "string", @@ -234,8 +234,8 @@ } }, { - "label": "Variable", - "mergeLabel": "Variable", + "label": "TSVariable", + "mergeLabel": "TSVariable", "key": "id", "properties": { "id": "string", @@ -254,96 +254,96 @@ ], "relationship_types": [ { - "type": "HAS_MODULE", + "type": "TS_HAS_MODULE", "from": [ - "Application" + "TSApplication" ], "to": [ - "Module" + "TSModule" ], "properties": {} }, { - "type": "DECLARES", + "type": "TS_DECLARES", "from": [ - "Module", - "Namespace", - "Class", - "Callable" + "TSModule", + "TSNamespace", + "TSClass", + "TSCallable" ], "to": [ - "Class", - "Interface", - "Enum", - "TypeAlias", - "Namespace", - "Callable" + "TSClass", + "TSInterface", + "TSEnum", + "TSTypeAlias", + "TSNamespace", + "TSCallable" ], "properties": {} }, { - "type": "HAS_METHOD", + "type": "TS_HAS_METHOD", "from": [ - "Class", - "Interface" + "TSClass", + "TSInterface" ], "to": [ - "Callable" + "TSCallable" ], "properties": {} }, { - "type": "HAS_ATTRIBUTE", + "type": "TS_HAS_ATTRIBUTE", "from": [ - "Class", - "Interface" + "TSClass", + "TSInterface" ], "to": [ - "Attribute" + "TSAttribute" ], "properties": {} }, { - "type": "DECLARES_VAR", + "type": "TS_DECLARES_VAR", "from": [ - "Module", - "Namespace", - "Callable" + "TSModule", + "TSNamespace", + "TSCallable" ], "to": [ - "Variable" + "TSVariable" ], "properties": {} }, { - "type": "HAS_CALLSITE", + "type": "TS_HAS_CALLSITE", "from": [ - "Callable" + "TSCallable" ], "to": [ - "CallSite" + "TSCallSite" ], "properties": {} }, { - "type": "RESOLVES_TO", + "type": "TS_RESOLVES_TO", "from": [ - "CallSite" + "TSCallSite" ], "to": [ - "Callable", - "External" + "TSCallable", + "TSExternal" ], "properties": {} }, { - "type": "CALLS", + "type": "TS_CALLS", "from": [ - "Callable" + "TSCallable" ], "to": [ - "Callable", - "External" + "TSCallable", + "TSExternal" ], "properties": { "weight": "integer", @@ -354,35 +354,35 @@ } }, { - "type": "EXTENDS", + "type": "TS_EXTENDS", "from": [ - "Class", - "Interface" + "TSClass", + "TSInterface" ], "to": [ - "Class", - "Interface" + "TSClass", + "TSInterface" ], "properties": {} }, { - "type": "IMPLEMENTS", + "type": "TS_IMPLEMENTS", "from": [ - "Class" + "TSClass" ], "to": [ - "Interface" + "TSInterface" ], "properties": {} }, { - "type": "IMPORTS", + "type": "TS_IMPORTS", "from": [ - "Module" + "TSModule" ], "to": [ - "Module", - "Package" + "TSModule", + "TSPackage" ], "properties": { "imported_names": "string[]", @@ -391,35 +391,35 @@ } }, { - "type": "RE_EXPORTS", + "type": "TS_RE_EXPORTS", "from": [ - "Module" + "TSModule" ], "to": [ - "Module", - "Package" + "TSModule", + "TSPackage" ], "properties": {} }, { - "type": "MEMBER_OF", + "type": "TS_MEMBER_OF", "from": [ - "External" + "TSExternal" ], "to": [ - "Package" + "TSPackage" ], "properties": {} }, { - "type": "DECORATED_BY", + "type": "TS_DECORATED_BY", "from": [ - "Class", - "Callable", - "Attribute" + "TSClass", + "TSCallable", + "TSAttribute" ], "to": [ - "Decorator" + "TSDecorator" ], "properties": { "positional_arguments": "string[]", @@ -430,18 +430,18 @@ } ], "constraints": [ - "CREATE CONSTRAINT symbol_sig IF NOT EXISTS FOR (s:Symbol) REQUIRE s.signature IS UNIQUE", - "CREATE CONSTRAINT app_name IF NOT EXISTS FOR (a:Application) REQUIRE a.name IS UNIQUE", - "CREATE CONSTRAINT module_key IF NOT EXISTS FOR (m:Module) REQUIRE m.file_key IS UNIQUE", - "CREATE CONSTRAINT package_name IF NOT EXISTS FOR (p:Package) REQUIRE p.name IS UNIQUE", - "CREATE CONSTRAINT decorator_qn IF NOT EXISTS FOR (d:Decorator) REQUIRE d.qualified_name IS UNIQUE", - "CREATE CONSTRAINT callsite_id IF NOT EXISTS FOR (c:CallSite) REQUIRE c.id IS UNIQUE", - "CREATE CONSTRAINT attribute_id IF NOT EXISTS FOR (a:Attribute) REQUIRE a.id IS UNIQUE", - "CREATE CONSTRAINT variable_id IF NOT EXISTS FOR (v:Variable) REQUIRE v.id IS UNIQUE" + "CREATE CONSTRAINT symbol_sig IF NOT EXISTS FOR (s:TSSymbol) REQUIRE s.signature IS UNIQUE", + "CREATE CONSTRAINT app_name IF NOT EXISTS FOR (a:TSApplication) REQUIRE a.name IS UNIQUE", + "CREATE CONSTRAINT module_key IF NOT EXISTS FOR (m:TSModule) REQUIRE m.file_key IS UNIQUE", + "CREATE CONSTRAINT package_name IF NOT EXISTS FOR (p:TSPackage) REQUIRE p.name IS UNIQUE", + "CREATE CONSTRAINT decorator_qn IF NOT EXISTS FOR (d:TSDecorator) REQUIRE d.qualified_name IS UNIQUE", + "CREATE CONSTRAINT callsite_id IF NOT EXISTS FOR (c:TSCallSite) REQUIRE c.id IS UNIQUE", + "CREATE CONSTRAINT attribute_id IF NOT EXISTS FOR (a:TSAttribute) REQUIRE a.id IS UNIQUE", + "CREATE CONSTRAINT variable_id IF NOT EXISTS FOR (v:TSVariable) REQUIRE v.id IS UNIQUE" ], "indexes": [ - "CREATE INDEX callable_name IF NOT EXISTS FOR (c:Callable) ON (c.name)", - "CREATE INDEX decorator_name IF NOT EXISTS FOR (d:Decorator) ON (d.name)", - "CREATE FULLTEXT INDEX code_fts IF NOT EXISTS FOR (c:Callable) ON EACH [c.code, c.docstring]" + "CREATE INDEX callable_name IF NOT EXISTS FOR (c:TSCallable) ON (c.name)", + "CREATE INDEX decorator_name IF NOT EXISTS FOR (d:TSDecorator) ON (d.name)", + "CREATE FULLTEXT INDEX code_fts IF NOT EXISTS FOR (c:TSCallable) ON EACH [c.code, c.docstring]" ] } diff --git a/src/build/neo4j/bolt.ts b/src/build/neo4j/bolt.ts index c8c981c..2e938cb 100644 --- a/src/build/neo4j/bolt.ts +++ b/src/build/neo4j/bolt.ts @@ -11,7 +11,7 @@ * 5. on a FULL run only, prune modules whose source file vanished. * * Nodes are MERGE-upserted, never blindly deleted, so a declaration another (unchanged) module - * still references survives and its incoming edges stay valid. `:External`/`:Package`/`:Decorator` + * still references survives and its incoming edges stay valid. `:TSExternal`/`:TSPackage`/`:TSDecorator` * are shared (no `_module`) and are MERGE-only. * * `neo4j-driver` is imported dynamically so it stays off the hot path and out of the default @@ -30,7 +30,7 @@ export interface BoltConfig { database: string | null; } -const DESCENDANTS = "[:DECLARES|HAS_METHOD|HAS_ATTRIBUTE|DECLARES_VAR|HAS_CALLSITE*1..]"; +const DESCENDANTS = "[:TS_DECLARES|TS_HAS_METHOD|TS_HAS_ATTRIBUTE|TS_DECLARES_VAR|TS_HAS_CALLSITE*1..]"; const BATCH = 1000; export async function boltWriter( @@ -68,7 +68,7 @@ export async function boltWriter( // 2. diff content_hash. const dbHash = new Map(); await withSession(session, async (s) => { - const res = await s.run("MATCH (m:Module) RETURN m.file_key AS k, m.content_hash AS h"); + const res = await s.run("MATCH (m:TSModule) RETURN m.file_key AS k, m.content_hash AS h"); for (const rec of res.records) dbHash.set(rec.get("k"), rec.get("h")); }); const changed = new Set(); @@ -112,7 +112,7 @@ export async function boltWriter( const present = [...byModule.keys()]; await withSession(session, async (s) => { const res = await s.run( - `MATCH (m:Module) WHERE NOT m.file_key IN $present ` + + `MATCH (m:TSModule) WHERE NOT m.file_key IN $present ` + `OPTIONAL MATCH (m)-${DESCENDANTS}->(x) DETACH DELETE x, m RETURN count(m) AS pruned`, { present }, ); @@ -190,7 +190,7 @@ function bucket(map: Map, key: K): V[] { } function hashOf(nodes: NodeRow[], fileKey: string): string | undefined { - const mod = nodes.find((n) => n.labels[0] === "Module" && n.value === fileKey); + const mod = nodes.find((n) => n.labels[0] === "TSModule" && n.value === fileKey); const h = mod?.props.content_hash; return typeof h === "string" ? h : undefined; } diff --git a/src/build/neo4j/catalog.ts b/src/build/neo4j/catalog.ts index 713a32e..83e03c5 100644 --- a/src/build/neo4j/catalog.ts +++ b/src/build/neo4j/catalog.ts @@ -8,12 +8,12 @@ * * SCHEMA_VERSION is the contract version: bump MAJOR on a breaking change (renamed/removed label, * relationship or key), MINOR on an additive change (new label/rel/property). It is stamped onto - * the :Application node of every emitted graph so any consumer can detect a producer/consumer + * the :TSApplication node of every emitted graph so any consumer can detect a producer/consumer * mismatch at runtime. */ import { CONSTRAINTS, INDEXES } from "./schema"; -export const SCHEMA_VERSION = "1.0.0"; +export const SCHEMA_VERSION = "2.0.0"; export type PropType = "string" | "integer" | "float" | "boolean" | "string[]" | "integer[]"; @@ -34,7 +34,7 @@ export interface RelType { } /** Labels layered onto a node in addition to its primary/specific label. */ -export const MARKER_LABELS = ["Entrypoint"] as const; +export const MARKER_LABELS = ["TSEntrypoint"] as const; const SPAN = { start_line: "integer", end_line: "integer" } as const; const ENTRYPOINT = { @@ -47,14 +47,14 @@ const ENTRYPOINT = { export const NODE_LABELS: NodeLabel[] = [ { - label: "Application", - mergeLabel: "Application", + label: "TSApplication", + mergeLabel: "TSApplication", key: "name", properties: { name: "string", schema_version: "string" }, }, { - label: "Module", - mergeLabel: "Module", + label: "TSModule", + mergeLabel: "TSModule", key: "file_key", properties: { file_key: "string", @@ -68,8 +68,8 @@ export const NODE_LABELS: NodeLabel[] = [ }, }, { - label: "Class", - mergeLabel: "Symbol", + label: "TSClass", + mergeLabel: "TSSymbol", key: "signature", properties: { signature: "string", @@ -88,8 +88,8 @@ export const NODE_LABELS: NodeLabel[] = [ }, }, { - label: "Interface", - mergeLabel: "Symbol", + label: "TSInterface", + mergeLabel: "TSSymbol", key: "signature", properties: { signature: "string", @@ -107,8 +107,8 @@ export const NODE_LABELS: NodeLabel[] = [ }, }, { - label: "Enum", - mergeLabel: "Symbol", + label: "TSEnum", + mergeLabel: "TSSymbol", key: "signature", properties: { signature: "string", @@ -125,8 +125,8 @@ export const NODE_LABELS: NodeLabel[] = [ }, }, { - label: "TypeAlias", - mergeLabel: "Symbol", + label: "TSTypeAlias", + mergeLabel: "TSSymbol", key: "signature", properties: { signature: "string", @@ -142,8 +142,8 @@ export const NODE_LABELS: NodeLabel[] = [ }, }, { - label: "Namespace", - mergeLabel: "Symbol", + label: "TSNamespace", + mergeLabel: "TSSymbol", key: "signature", properties: { signature: "string", @@ -156,8 +156,8 @@ export const NODE_LABELS: NodeLabel[] = [ }, }, { - label: "Callable", - mergeLabel: "Symbol", + label: "TSCallable", + mergeLabel: "TSSymbol", key: "signature", properties: { signature: "string", @@ -189,21 +189,21 @@ export const NODE_LABELS: NodeLabel[] = [ }, }, { - label: "External", - mergeLabel: "Symbol", + label: "TSExternal", + mergeLabel: "TSSymbol", key: "signature", properties: { signature: "string", name: "string", module: "string" }, }, - { label: "Package", mergeLabel: "Package", key: "name", properties: { name: "string" } }, + { label: "TSPackage", mergeLabel: "TSPackage", key: "name", properties: { name: "string" } }, { - label: "Decorator", - mergeLabel: "Decorator", + label: "TSDecorator", + mergeLabel: "TSDecorator", key: "qualified_name", properties: { qualified_name: "string", name: "string" }, }, { - label: "CallSite", - mergeLabel: "CallSite", + label: "TSCallSite", + mergeLabel: "TSCallSite", key: "id", properties: { id: "string", @@ -224,8 +224,8 @@ export const NODE_LABELS: NodeLabel[] = [ }, }, { - label: "Attribute", - mergeLabel: "Attribute", + label: "TSAttribute", + mergeLabel: "TSAttribute", key: "id", properties: { id: "string", @@ -243,8 +243,8 @@ export const NODE_LABELS: NodeLabel[] = [ }, }, { - label: "Variable", - mergeLabel: "Variable", + label: "TSVariable", + mergeLabel: "TSVariable", key: "id", properties: { id: "string", @@ -261,36 +261,36 @@ export const NODE_LABELS: NodeLabel[] = [ }, ]; -const DECL_TARGETS = ["Class", "Interface", "Enum", "TypeAlias", "Namespace", "Callable"]; +const DECL_TARGETS = ["TSClass", "TSInterface", "TSEnum", "TSTypeAlias", "TSNamespace", "TSCallable"]; export const REL_TYPES: RelType[] = [ - { type: "HAS_MODULE", from: ["Application"], to: ["Module"], properties: {} }, - { type: "DECLARES", from: ["Module", "Namespace", "Class", "Callable"], to: DECL_TARGETS, properties: {} }, - { type: "HAS_METHOD", from: ["Class", "Interface"], to: ["Callable"], properties: {} }, - { type: "HAS_ATTRIBUTE", from: ["Class", "Interface"], to: ["Attribute"], properties: {} }, - { type: "DECLARES_VAR", from: ["Module", "Namespace", "Callable"], to: ["Variable"], properties: {} }, - { type: "HAS_CALLSITE", from: ["Callable"], to: ["CallSite"], properties: {} }, - { type: "RESOLVES_TO", from: ["CallSite"], to: ["Callable", "External"], properties: {} }, + { type: "TS_HAS_MODULE", from: ["TSApplication"], to: ["TSModule"], properties: {} }, + { type: "TS_DECLARES", from: ["TSModule", "TSNamespace", "TSClass", "TSCallable"], to: DECL_TARGETS, properties: {} }, + { type: "TS_HAS_METHOD", from: ["TSClass", "TSInterface"], to: ["TSCallable"], properties: {} }, + { type: "TS_HAS_ATTRIBUTE", from: ["TSClass", "TSInterface"], to: ["TSAttribute"], properties: {} }, + { type: "TS_DECLARES_VAR", from: ["TSModule", "TSNamespace", "TSCallable"], to: ["TSVariable"], properties: {} }, + { type: "TS_HAS_CALLSITE", from: ["TSCallable"], to: ["TSCallSite"], properties: {} }, + { type: "TS_RESOLVES_TO", from: ["TSCallSite"], to: ["TSCallable", "TSExternal"], properties: {} }, { - type: "CALLS", - from: ["Callable"], - to: ["Callable", "External"], + type: "TS_CALLS", + from: ["TSCallable"], + to: ["TSCallable", "TSExternal"], properties: { weight: "integer", provenance: "string[]", dispatch: "string", external: "boolean", module: "string" }, }, - { type: "EXTENDS", from: ["Class", "Interface"], to: ["Class", "Interface"], properties: {} }, - { type: "IMPLEMENTS", from: ["Class"], to: ["Interface"], properties: {} }, + { type: "TS_EXTENDS", from: ["TSClass", "TSInterface"], to: ["TSClass", "TSInterface"], properties: {} }, + { type: "TS_IMPLEMENTS", from: ["TSClass"], to: ["TSInterface"], properties: {} }, { - type: "IMPORTS", - from: ["Module"], - to: ["Module", "Package"], + type: "TS_IMPORTS", + from: ["TSModule"], + to: ["TSModule", "TSPackage"], properties: { imported_names: "string[]", import_kinds: "string[]", is_type_only: "boolean" }, }, - { type: "RE_EXPORTS", from: ["Module"], to: ["Module", "Package"], properties: {} }, - { type: "MEMBER_OF", from: ["External"], to: ["Package"], properties: {} }, + { type: "TS_RE_EXPORTS", from: ["TSModule"], to: ["TSModule", "TSPackage"], properties: {} }, + { type: "TS_MEMBER_OF", from: ["TSExternal"], to: ["TSPackage"], properties: {} }, { - type: "DECORATED_BY", - from: ["Class", "Callable", "Attribute"], - to: ["Decorator"], + type: "TS_DECORATED_BY", + from: ["TSClass", "TSCallable", "TSAttribute"], + to: ["TSDecorator"], properties: { positional_arguments: "string[]", keyword_arguments_json: "string", start_line: "integer", end_line: "integer" }, }, ]; diff --git a/src/build/neo4j/cypher.ts b/src/build/neo4j/cypher.ts index aefe6f1..ec51b77 100644 --- a/src/build/neo4j/cypher.ts +++ b/src/build/neo4j/cypher.ts @@ -36,9 +36,9 @@ export function renderCypher(rows: GraphRows, appName: string): string { function wipe(appName: string): string { const name = cypherValue(appName); return [ - `MATCH (a:Application {name: ${name}})`, - "OPTIONAL MATCH (a)-[:HAS_MODULE]->(m:Module)", - "OPTIONAL MATCH (m)-[:DECLARES|HAS_METHOD|HAS_ATTRIBUTE|DECLARES_VAR|HAS_CALLSITE*1..]->(x)", + `MATCH (a:TSApplication {name: ${name}})`, + "OPTIONAL MATCH (a)-[:TS_HAS_MODULE]->(m:TSModule)", + "OPTIONAL MATCH (m)-[:TS_DECLARES|TS_HAS_METHOD|TS_HAS_ATTRIBUTE|TS_DECLARES_VAR|TS_HAS_CALLSITE*1..]->(x)", "DETACH DELETE x, m, a;", ].join("\n"); } diff --git a/src/build/neo4j/project.ts b/src/build/neo4j/project.ts index 1d26679..29c754d 100644 Binary files a/src/build/neo4j/project.ts and b/src/build/neo4j/project.ts differ diff --git a/src/build/neo4j/rows.ts b/src/build/neo4j/rows.ts index 50195ff..da035de 100644 Binary files a/src/build/neo4j/rows.ts and b/src/build/neo4j/rows.ts differ diff --git a/src/build/neo4j/schema.ts b/src/build/neo4j/schema.ts index 88c381d..b681fd6 100644 --- a/src/build/neo4j/schema.ts +++ b/src/build/neo4j/schema.ts @@ -4,18 +4,18 @@ * the database. Every statement is idempotent (`IF NOT EXISTS`). */ export const CONSTRAINTS: readonly string[] = [ - "CREATE CONSTRAINT symbol_sig IF NOT EXISTS FOR (s:Symbol) REQUIRE s.signature IS UNIQUE", - "CREATE CONSTRAINT app_name IF NOT EXISTS FOR (a:Application) REQUIRE a.name IS UNIQUE", - "CREATE CONSTRAINT module_key IF NOT EXISTS FOR (m:Module) REQUIRE m.file_key IS UNIQUE", - "CREATE CONSTRAINT package_name IF NOT EXISTS FOR (p:Package) REQUIRE p.name IS UNIQUE", - "CREATE CONSTRAINT decorator_qn IF NOT EXISTS FOR (d:Decorator) REQUIRE d.qualified_name IS UNIQUE", - "CREATE CONSTRAINT callsite_id IF NOT EXISTS FOR (c:CallSite) REQUIRE c.id IS UNIQUE", - "CREATE CONSTRAINT attribute_id IF NOT EXISTS FOR (a:Attribute) REQUIRE a.id IS UNIQUE", - "CREATE CONSTRAINT variable_id IF NOT EXISTS FOR (v:Variable) REQUIRE v.id IS UNIQUE", + "CREATE CONSTRAINT symbol_sig IF NOT EXISTS FOR (s:TSSymbol) REQUIRE s.signature IS UNIQUE", + "CREATE CONSTRAINT app_name IF NOT EXISTS FOR (a:TSApplication) REQUIRE a.name IS UNIQUE", + "CREATE CONSTRAINT module_key IF NOT EXISTS FOR (m:TSModule) REQUIRE m.file_key IS UNIQUE", + "CREATE CONSTRAINT package_name IF NOT EXISTS FOR (p:TSPackage) REQUIRE p.name IS UNIQUE", + "CREATE CONSTRAINT decorator_qn IF NOT EXISTS FOR (d:TSDecorator) REQUIRE d.qualified_name IS UNIQUE", + "CREATE CONSTRAINT callsite_id IF NOT EXISTS FOR (c:TSCallSite) REQUIRE c.id IS UNIQUE", + "CREATE CONSTRAINT attribute_id IF NOT EXISTS FOR (a:TSAttribute) REQUIRE a.id IS UNIQUE", + "CREATE CONSTRAINT variable_id IF NOT EXISTS FOR (v:TSVariable) REQUIRE v.id IS UNIQUE", ]; export const INDEXES: readonly string[] = [ - "CREATE INDEX callable_name IF NOT EXISTS FOR (c:Callable) ON (c.name)", - "CREATE INDEX decorator_name IF NOT EXISTS FOR (d:Decorator) ON (d.name)", - "CREATE FULLTEXT INDEX code_fts IF NOT EXISTS FOR (c:Callable) ON EACH [c.code, c.docstring]", + "CREATE INDEX callable_name IF NOT EXISTS FOR (c:TSCallable) ON (c.name)", + "CREATE INDEX decorator_name IF NOT EXISTS FOR (d:TSDecorator) ON (d.name)", + "CREATE FULLTEXT INDEX code_fts IF NOT EXISTS FOR (c:TSCallable) ON EACH [c.code, c.docstring]", ]; diff --git a/src/cli.ts b/src/cli.ts index 070f252..6ee6461 100644 --- a/src/cli.ts +++ b/src/cli.ts @@ -15,7 +15,7 @@ export function buildProgram(): Command { .option("-i, --input ", "project root to analyze (not required for --emit schema)") .option("-o, --output ", "output directory (omit ⇒ compact output to stdout)") .option("--emit ", "output target: json (analysis.json, default) | neo4j (graph.cypher or live push) | schema (the Neo4j schema.json contract)", "json") - .option("--app-name ", "logical application name for the graph :Application anchor (default: input dir name)") + .option("--app-name ", "logical application name for the graph :TSApplication anchor (default: input dir name)") // The four Neo4j connection options also read the standard NEO4J_* environment variables when // the flag is omitted (an explicit flag wins). Prefer NEO4J_PASSWORD over the flag — a flag // value is visible in shell history / the process list. Commander renders the `(env: …)` hint. diff --git a/src/options/options.ts b/src/options/options.ts index 4f82d51..d8c1052 100644 --- a/src/options/options.ts +++ b/src/options/options.ts @@ -9,7 +9,7 @@ export interface AnalysisOptions { output: string | null; /** Output target: json (analysis.json, default) or neo4j (graph.cypher / live Bolt push). */ emit: EmitTarget; - /** Logical application name for the graph's :Application anchor; null ⇒ derived from input. */ + /** Logical application name for the graph's :TSApplication anchor; null ⇒ derived from input. */ appName: string | null; /** Bolt URI for a live Neo4j push (incremental). null ⇒ write a graph.cypher snapshot to -o. */ neo4jUri: string | null; diff --git a/test/neo4j-bolt.test.ts b/test/neo4j-bolt.test.ts index 65c8eca..1a71595 100644 --- a/test/neo4j-bolt.test.ts +++ b/test/neo4j-bolt.test.ts @@ -93,10 +93,10 @@ containerSuite("neo4j bolt writer", () => { expect(await num("MATCH (n) RETURN count(n)")).toBe(rows.nodes.length); expect(await num("MATCH ()-[r]->() RETURN count(r)")).toBe(rows.edges.length); - // Shared :Symbol label spans the signature-keyed declaration kinds. - const symbol = await num("MATCH (s:Symbol) RETURN count(s)"); + // Shared :TSSymbol label spans the signature-keyed declaration kinds. + const symbol = await num("MATCH (s:TSSymbol) RETURN count(s)"); const kinds = await num( - "MATCH (s:Symbol) WHERE s:Callable OR s:Class OR s:Interface OR s:Enum OR s:TypeAlias OR s:Namespace OR s:External RETURN count(s)", + "MATCH (s:TSSymbol) WHERE s:TSCallable OR s:TSClass OR s:TSInterface OR s:TSEnum OR s:TSTypeAlias OR s:TSNamespace OR s:TSExternal RETURN count(s)", ); expect(symbol).toBeGreaterThan(0); expect(kinds).toBe(symbol); @@ -108,7 +108,7 @@ containerSuite("neo4j bolt writer", () => { // A known resolved call edge from the fixture (index.ts calls services.announce). expect( await num( - "MATCH (:Callable)-[:CALLS]->(t:Callable {name:$n}) RETURN count(*)", + "MATCH (:TSCallable)-[:TS_CALLS]->(t:TSCallable {name:$n}) RETURN count(*)", { n: "announce" }, ), ).toBeGreaterThan(0); @@ -140,7 +140,7 @@ containerSuite("neo4j bolt writer", () => { // The victim's nodes are gone. expect(await num("MATCH (n {_module:$m}) RETURN count(n)", { m: victim })).toBe(0); - // The surviving module-scoped graph matches the reduced projection. (Shared :External/:Package + // The surviving module-scoped graph matches the reduced projection. (Shared :TSExternal/:TSPackage // nodes are MERGE-only and intentionally never pruned, so we compare only _module-tagged nodes.) const moduleScoped = rows.nodes.filter((n) => "_module" in n.props).length; expect(await num("MATCH (n) WHERE n._module IS NOT NULL RETURN count(n)")).toBe(moduleScoped); diff --git a/test/neo4j-schema.test.ts b/test/neo4j-schema.test.ts index edafd6e..a19074e 100644 --- a/test/neo4j-schema.test.ts +++ b/test/neo4j-schema.test.ts @@ -45,8 +45,8 @@ const mergeLabelsFor = (specifics: string[]) => new Set(specifics.map((s) => mer /** The specific (catalog) label for a node row: the non-merge, non-marker label. */ function specificLabel(labels: string[]): string { const merge = labels[0]; - if (merge !== "Symbol") return merge; - return labels.find((l) => l !== "Symbol" && !markers.has(l)) ?? "Symbol"; + if (merge !== "TSSymbol") return merge; + return labels.find((l) => l !== "TSSymbol" && !markers.has(l)) ?? "TSSymbol"; } describe("neo4j schema conformance", () => {