You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
Blocked on #36. Do not start until the projection contract fixes the shape.
Problem
decorators is a flat list of ast.unparse output — ["trace('hot')", "functools.lru_cache(maxsize=128)"] — on PyCallable only.
The keystone specifies the opposite, at L1, on type/callable/field, and says so in as many
words: "Structured { name, args[], span } — not flat strings." codeanalyzer-typescript-v2 already ships that shape (src/schema/schema.ts, TSDecorator) on
classes, callables, parameters, and properties. Python is the divergent analyzer.
The cost of the current shape: @app.route and @blueprint.route are unequal strings even when
they resolve to the same function, arguments are not separable from the callee, and PyClass
carries no decorators at all — so @dataclass is dropped silently (verified on main6f02581).
Scope boundary
Implements the shape settled by .github#36 in codeanalyzer-python only. Does not close
TypeScript's mirror-image gap — it has the structured data but no Neo4j decorator projection,
which is its own migration item under #35. Does not touch python-sdk
(its own work item, filed when picked up), does not add a decoration edge family (spec D5), does
not address async (#130), does not fix the PyCG call-edge gap (#131), and does not touch codeanalyzer-java's annotations.
decorators added to PyClass, PyClassAttribute, PyCallableParameter
Populated at L1 for all four, at every nesting depth
qualified_name resolved via Jedi; None when unresolvable, never raising
Neo4j: :PyDecorator gains qualified_name, keeps merging on name (spec D7); PY_DECORATED_BY start labels widened to include PyClass
Cache-invalidation guard (core.py:728) rejects caches built with the old decorator shape
.claude/SCHEMA_DECISIONS.md gains one line per decision D1–D8, linking the spec
Caveats and known risks
No schema_version bump, and none is claimed. Schema v2 is a moving target until Java,
Python, and TypeScript settle on a stable shape, so there is no frozen contract number for this
change to amend. python-sdk ships decorators: List[str] today and breaks with no version
signal — that is a real cost, and the mitigation is the SDK update, not a number.
Stale caches are the sharp edge.core.py:728 accepts any cache matching the current schema_version literal. Change the shape without moving that guard and a cached analysis
silently feeds old-shape decorators through a new analyzer.
qualified_name is best-effort. Dynamic attribute access, conditional imports, and
runtime-built decorators will not resolve. None is the normal path there, not an error.
PyCallableParameter.decorators will always be empty — Python has no parameter-decorator
syntax. Carried for cross-language parity; droppable at review if judged dead weight.
Monotonicity gate applies: decorators are L1 data and must not be gated behind a level.
Problem
decoratorsis a flat list ofast.unparseoutput —["trace('hot')", "functools.lru_cache(maxsize=128)"]— onPyCallableonly.The keystone specifies the opposite, at L1, on
type/callable/field, and says so in as manywords: "Structured
{ name, args[], span }— not flat strings."codeanalyzer-typescript-v2already ships that shape (src/schema/schema.ts,TSDecorator) onclasses, callables, parameters, and properties. Python is the divergent analyzer.
The cost of the current shape:
@app.routeand@blueprint.routeare unequal strings even whenthey resolve to the same function, arguments are not separable from the callee, and
PyClasscarries no decorators at all — so
@dataclassis dropped silently (verified onmain6f02581).Scope boundary
Implements the shape settled by .github#36 in
codeanalyzer-pythononly. Does not closeTypeScript's mirror-image gap — it has the structured data but no Neo4j decorator projection,
which is its own migration item under #35. Does not touch
python-sdk(its own work item, filed when picked up), does not add a decoration edge family (spec D5), does
not address async (#130), does not fix the PyCG call-edge gap (#131), and does not touch
codeanalyzer-java'sannotations.Goals
PyDecoratormodel:name,qualified_name: Optional[str],positional_arguments: List[str],keyword_arguments: Dict[str, str],span: Optional[Span]PyCallable.decoratorsretypedList[str]→List[PyDecorator]decoratorsadded toPyClass,PyClassAttribute,PyCallableParameterqualified_nameresolved via Jedi;Nonewhen unresolvable, never raising:PyDecoratorgainsqualified_name, keeps merging onname(spec D7);PY_DECORATED_BYstart labels widened to includePyClasscore.py:728) rejects caches built with the old decorator shape.claude/SCHEMA_DECISIONS.mdgains one line per decision D1–D8, linking the specCaveats and known risks
schema_versionbump, and none is claimed. Schema v2 is a moving target until Java,Python, and TypeScript settle on a stable shape, so there is no frozen contract number for this
change to amend.
python-sdkshipsdecorators: List[str]today and breaks with no versionsignal — that is a real cost, and the mitigation is the SDK update, not a number.
core.py:728accepts any cache matching the currentschema_versionliteral. Change the shape without moving that guard and a cached analysissilently feeds old-shape decorators through a new analyzer.
qualified_nameis best-effort. Dynamic attribute access, conditional imports, andruntime-built decorators will not resolve.
Noneis the normal path there, not an error.PyCallableParameter.decoratorswill always be empty — Python has no parameter-decoratorsyntax. Carried for cross-language parity; droppable at review if judged dead weight.
Definition of done
@functools.lru_cache(maxsize=128)emitsname: "lru_cache",qualified_name: "functools.lru_cache",keyword_arguments: {"maxsize": "128"}@trace('hot')emitspositional_arguments: ["'hot'"]and resolvesqualified_nameto thedeclaring module's
trace@dataclassemits on the class node; a decorated class attribute and a decorated nestedclass both emit
@app.routeand@blueprint.routebound to the same function produce the samequalified_namequalified_name: nulland does not raisedecoratorsas strings is updated, not deletedanalysis.json(-a 1) ⊆ ... ⊆ (-a 4)still holdsPY_DECORATED_BYtraverses from a:PyClassnode andqualified_nameis queryable