@opensip-cli/graph-python 0.1.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 (76) hide show
  1. package/LICENSE +202 -0
  2. package/NOTICE +8 -0
  3. package/README.md +31 -0
  4. package/dist/__tests__/body-digest.test.d.ts +18 -0
  5. package/dist/__tests__/body-digest.test.d.ts.map +1 -0
  6. package/dist/__tests__/body-digest.test.js +61 -0
  7. package/dist/__tests__/body-digest.test.js.map +1 -0
  8. package/dist/__tests__/branch-coverage.test.d.ts +19 -0
  9. package/dist/__tests__/branch-coverage.test.d.ts.map +1 -0
  10. package/dist/__tests__/branch-coverage.test.js +292 -0
  11. package/dist/__tests__/branch-coverage.test.js.map +1 -0
  12. package/dist/__tests__/cache-key.test.d.ts +8 -0
  13. package/dist/__tests__/cache-key.test.d.ts.map +1 -0
  14. package/dist/__tests__/cache-key.test.js +55 -0
  15. package/dist/__tests__/cache-key.test.js.map +1 -0
  16. package/dist/__tests__/depends-on-emission.test.d.ts +21 -0
  17. package/dist/__tests__/depends-on-emission.test.d.ts.map +1 -0
  18. package/dist/__tests__/depends-on-emission.test.js +189 -0
  19. package/dist/__tests__/depends-on-emission.test.js.map +1 -0
  20. package/dist/__tests__/discover.test.d.ts +9 -0
  21. package/dist/__tests__/discover.test.d.ts.map +1 -0
  22. package/dist/__tests__/discover.test.js +64 -0
  23. package/dist/__tests__/discover.test.js.map +1 -0
  24. package/dist/__tests__/parse.test.d.ts +8 -0
  25. package/dist/__tests__/parse.test.d.ts.map +1 -0
  26. package/dist/__tests__/parse.test.js +37 -0
  27. package/dist/__tests__/parse.test.js.map +1 -0
  28. package/dist/__tests__/resolve.test.d.ts +11 -0
  29. package/dist/__tests__/resolve.test.d.ts.map +1 -0
  30. package/dist/__tests__/resolve.test.js +176 -0
  31. package/dist/__tests__/resolve.test.js.map +1 -0
  32. package/dist/__tests__/walk-shapes.test.d.ts +15 -0
  33. package/dist/__tests__/walk-shapes.test.d.ts.map +1 -0
  34. package/dist/__tests__/walk-shapes.test.js +156 -0
  35. package/dist/__tests__/walk-shapes.test.js.map +1 -0
  36. package/dist/__tests__/walk.test.d.ts +10 -0
  37. package/dist/__tests__/walk.test.d.ts.map +1 -0
  38. package/dist/__tests__/walk.test.js +71 -0
  39. package/dist/__tests__/walk.test.js.map +1 -0
  40. package/dist/body-digest.d.ts +18 -0
  41. package/dist/body-digest.d.ts.map +1 -0
  42. package/dist/body-digest.js +88 -0
  43. package/dist/body-digest.js.map +1 -0
  44. package/dist/cache-key.d.ts +22 -0
  45. package/dist/cache-key.d.ts.map +1 -0
  46. package/dist/cache-key.js +52 -0
  47. package/dist/cache-key.js.map +1 -0
  48. package/dist/discover.d.ts +19 -0
  49. package/dist/discover.d.ts.map +1 -0
  50. package/dist/discover.js +36 -0
  51. package/dist/discover.js.map +1 -0
  52. package/dist/index.d.ts +51 -0
  53. package/dist/index.d.ts.map +1 -0
  54. package/dist/index.js +53 -0
  55. package/dist/index.js.map +1 -0
  56. package/dist/parse.d.ts +22 -0
  57. package/dist/parse.d.ts.map +1 -0
  58. package/dist/parse.js +16 -0
  59. package/dist/parse.js.map +1 -0
  60. package/dist/resolve.d.ts +35 -0
  61. package/dist/resolve.d.ts.map +1 -0
  62. package/dist/resolve.js +294 -0
  63. package/dist/resolve.js.map +1 -0
  64. package/dist/rule-hints.d.ts +13 -0
  65. package/dist/rule-hints.d.ts.map +1 -0
  66. package/dist/rule-hints.js +70 -0
  67. package/dist/rule-hints.js.map +1 -0
  68. package/dist/walk-dependencies.d.ts +27 -0
  69. package/dist/walk-dependencies.d.ts.map +1 -0
  70. package/dist/walk-dependencies.js +152 -0
  71. package/dist/walk-dependencies.js.map +1 -0
  72. package/dist/walk.d.ts +42 -0
  73. package/dist/walk.d.ts.map +1 -0
  74. package/dist/walk.js +281 -0
  75. package/dist/walk.js.map +1 -0
  76. package/package.json +54 -0
@@ -0,0 +1,52 @@
1
+ // @fitness-ignore-file unbounded-memory -- reads pyproject.toml manifest; bounded by standard Python project metadata
2
+ /**
3
+ * Python cacheKey implementation.
4
+ *
5
+ * Produces `py-${pythonVersion}-${pyprojectContentHash || 'no-config'}`.
6
+ *
7
+ * The content-fingerprint half (`no-config` / `missing:` / `unreadable:` /
8
+ * sha256-prefix) is the byte-identical `hashConfig` contract shared with
9
+ * go/java/rust; Python imports it from
10
+ * `@opensip-cli/graph-adapter-common` and layers a best-effort
11
+ * "Python version" on top (DEC-4). The version comes from a
12
+ * `requires-python` line in `pyproject.toml` (PEP 621) — a string like
13
+ * `>=3.10,<4.0`, sanitized; absent → the literal `unknown`. It is a CACHE
14
+ * INVALIDATION key, not a source of truth — its only job is to flip when
15
+ * the toolchain intent changes.
16
+ *
17
+ * Per contract invariant I-6 the function is purely a function of
18
+ * `(pyproject content)`. Per I-8 we emit `py-`, distinct from the other
19
+ * adapters' prefixes.
20
+ */
21
+ import { existsSync, readFileSync } from 'node:fs';
22
+ import { hashConfig } from '@opensip-cli/graph-adapter-common';
23
+ // Anchored to start-of-line; horizontal whitespace ([\t ]) and the
24
+ // inner `[^"'\n]` keep matching linear. Using `\s` would cross
25
+ // newlines and let pathological inputs explore O(n^2) prefixes.
26
+ const REQUIRES_PYTHON_RE = /^[\t ]*requires-python[\t ]*=[\t ]*["']([^"'\n]+)["']/m;
27
+ export function cacheKey(input) {
28
+ const configHash = hashConfig(input.configPathAbs);
29
+ const pythonVersion = readPythonVersion(input.configPathAbs);
30
+ return `py-${pythonVersion}-${configHash}`;
31
+ }
32
+ function readPythonVersion(configPathAbs) {
33
+ if (configPathAbs === undefined || configPathAbs.length === 0)
34
+ return 'unknown';
35
+ if (!existsSync(configPathAbs))
36
+ return 'unknown';
37
+ let content;
38
+ try {
39
+ content = readFileSync(configPathAbs, 'utf8');
40
+ }
41
+ catch {
42
+ /* v8 ignore next */
43
+ return 'unknown';
44
+ }
45
+ const match = REQUIRES_PYTHON_RE.exec(content);
46
+ return match ? sanitize(match[1] ?? 'unknown') : 'unknown';
47
+ }
48
+ function sanitize(s) {
49
+ // Keep cache-key strings shell- and filename-safe.
50
+ return s.replaceAll(/[^A-Za-z0-9._+-]/g, '_').slice(0, 32);
51
+ }
52
+ //# sourceMappingURL=cache-key.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"cache-key.js","sourceRoot":"","sources":["../src/cache-key.ts"],"names":[],"mappings":"AAAA,sHAAsH;AACtH;;;;;;;;;;;;;;;;;;GAkBG;AAEH,OAAO,EAAE,UAAU,EAAE,YAAY,EAAE,MAAM,SAAS,CAAC;AAEnD,OAAO,EAAE,UAAU,EAAE,MAAM,mCAAmC,CAAC;AAI/D,mEAAmE;AACnE,+DAA+D;AAC/D,gEAAgE;AAChE,MAAM,kBAAkB,GAAG,wDAAwD,CAAC;AAEpF,MAAM,UAAU,QAAQ,CAAC,KAAoB;IAC3C,MAAM,UAAU,GAAG,UAAU,CAAC,KAAK,CAAC,aAAa,CAAC,CAAC;IACnD,MAAM,aAAa,GAAG,iBAAiB,CAAC,KAAK,CAAC,aAAa,CAAC,CAAC;IAC7D,OAAO,MAAM,aAAa,IAAI,UAAU,EAAE,CAAC;AAC7C,CAAC;AAED,SAAS,iBAAiB,CAAC,aAAiC;IAC1D,IAAI,aAAa,KAAK,SAAS,IAAI,aAAa,CAAC,MAAM,KAAK,CAAC;QAAE,OAAO,SAAS,CAAC;IAChF,IAAI,CAAC,UAAU,CAAC,aAAa,CAAC;QAAE,OAAO,SAAS,CAAC;IACjD,IAAI,OAAe,CAAC;IACpB,IAAI,CAAC;QACH,OAAO,GAAG,YAAY,CAAC,aAAa,EAAE,MAAM,CAAC,CAAC;IAChD,CAAC;IAAC,MAAM,CAAC;QACP,oBAAoB;QACpB,OAAO,SAAS,CAAC;IACnB,CAAC;IACD,MAAM,KAAK,GAAG,kBAAkB,CAAC,IAAI,CAAC,OAAO,CAAC,CAAC;IAC/C,OAAO,KAAK,CAAC,CAAC,CAAC,QAAQ,CAAC,KAAK,CAAC,CAAC,CAAC,IAAI,SAAS,CAAC,CAAC,CAAC,CAAC,SAAS,CAAC;AAC7D,CAAC;AAED,SAAS,QAAQ,CAAC,CAAS;IACzB,mDAAmD;IACnD,OAAO,CAAC,CAAC,UAAU,CAAC,mBAAmB,EAAE,GAAG,CAAC,CAAC,KAAK,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC;AAC7D,CAAC"}
@@ -0,0 +1,19 @@
1
+ /**
2
+ * Python file discovery — Stage 0 for the Python adapter.
3
+ *
4
+ * Strategy:
5
+ * 1. If `pyproject.toml` is present, use it as the language config
6
+ * anchor for cacheKey. We do NOT parse `[tool.opensip-graph].include`;
7
+ * that's a future enhancement. We just record the file path.
8
+ * 2. If `setup.py` is present (and no pyproject.toml), use it as the
9
+ * anchor instead.
10
+ * 3. Walk the project tree collecting all `.py` files, excluding
11
+ * common non-source directories (`.venv`, `venv`, `__pycache__`,
12
+ * `.tox`, `node_modules`, `dist`, `build`, `.eggs`).
13
+ *
14
+ * The collect-loop / realpath-dedup / config-precedence scaffolding lives
15
+ * in `@opensip-cli/graph-adapter-common`; this module supplies only the
16
+ * Python-specific inputs.
17
+ */
18
+ export declare const discoverFiles: (input: import("@opensip-cli/graph").DiscoverInput) => import("@opensip-cli/graph").DiscoverOutput;
19
+ //# sourceMappingURL=discover.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"discover.d.ts","sourceRoot":"","sources":["../src/discover.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;GAgBG;AAiBH,eAAO,MAAM,aAAa,oGAKxB,CAAC"}
@@ -0,0 +1,36 @@
1
+ /**
2
+ * Python file discovery — Stage 0 for the Python adapter.
3
+ *
4
+ * Strategy:
5
+ * 1. If `pyproject.toml` is present, use it as the language config
6
+ * anchor for cacheKey. We do NOT parse `[tool.opensip-graph].include`;
7
+ * that's a future enhancement. We just record the file path.
8
+ * 2. If `setup.py` is present (and no pyproject.toml), use it as the
9
+ * anchor instead.
10
+ * 3. Walk the project tree collecting all `.py` files, excluding
11
+ * common non-source directories (`.venv`, `venv`, `__pycache__`,
12
+ * `.tox`, `node_modules`, `dist`, `build`, `.eggs`).
13
+ *
14
+ * The collect-loop / realpath-dedup / config-precedence scaffolding lives
15
+ * in `@opensip-cli/graph-adapter-common`; this module supplies only the
16
+ * Python-specific inputs.
17
+ */
18
+ import { createDiscover } from '@opensip-cli/graph-adapter-common';
19
+ const EXCLUDED_DIR_GLOBS = [
20
+ '**/.venv/**',
21
+ '**/venv/**',
22
+ '**/__pycache__/**',
23
+ '**/.tox/**',
24
+ '**/node_modules/**',
25
+ '**/dist/**',
26
+ '**/build/**',
27
+ '**/.eggs/**',
28
+ ];
29
+ const CONFIG_CANDIDATES = ['pyproject.toml', 'setup.py'];
30
+ export const discoverFiles = createDiscover({
31
+ extension: 'py',
32
+ excludedDirGlobs: EXCLUDED_DIR_GLOBS,
33
+ configCandidates: CONFIG_CANDIDATES,
34
+ languageId: 'python',
35
+ });
36
+ //# sourceMappingURL=discover.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"discover.js","sourceRoot":"","sources":["../src/discover.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;GAgBG;AAEH,OAAO,EAAE,cAAc,EAAE,MAAM,mCAAmC,CAAC;AAEnE,MAAM,kBAAkB,GAAsB;IAC5C,aAAa;IACb,YAAY;IACZ,mBAAmB;IACnB,YAAY;IACZ,oBAAoB;IACpB,YAAY;IACZ,aAAa;IACb,aAAa;CACd,CAAC;AAEF,MAAM,iBAAiB,GAAsB,CAAC,gBAAgB,EAAE,UAAU,CAAC,CAAC;AAE5E,MAAM,CAAC,MAAM,aAAa,GAAG,cAAc,CAAC;IAC1C,SAAS,EAAE,IAAI;IACf,gBAAgB,EAAE,kBAAkB;IACpC,gBAAgB,EAAE,iBAAiB;IACnC,UAAU,EAAE,QAAQ;CACrB,CAAC,CAAC"}
@@ -0,0 +1,51 @@
1
+ /**
2
+ * @opensip-cli/graph — Python language adapter.
3
+ *
4
+ * Lands in PR 5 of plan docs/plans/10-graph-language-pluggability.md.
5
+ * Exposes `pythonGraphAdapter`, a `GraphLanguageAdapter` backed by
6
+ * tree-sitter-python.
7
+ *
8
+ * Per-rule fidelity (plan §6 row "Tree-sitter adapter"): mostly
9
+ * `'medium'` confidence on calls; `'low'` when multiple catalog
10
+ * entries share a simple name. The adapter has no symbol table.
11
+ *
12
+ * File layout mirrors `lang-typescript/`:
13
+ * discoverFiles → ./discover.ts
14
+ * parseProject → ./parse.ts
15
+ * walkProject → ./walk.ts
16
+ * resolveCallSites → ./resolve.ts
17
+ * cacheKey → ./cache-key.ts
18
+ * ruleHints → ./rule-hints.ts
19
+ *
20
+ * Production graph adapters are forbidden from importing `web-tree-sitter`
21
+ * directly; the ADR-0010 restricted-import rule in the shared ESLint config
22
+ * enforces it.
23
+ */
24
+ import { cacheKey as pythonCacheKey } from './cache-key.js';
25
+ import { resolveCallSites as pythonResolveCallSites } from './resolve.js';
26
+ import { walkProject as pythonWalkProject } from './walk.js';
27
+ export declare const pythonGraphAdapter: {
28
+ id: string;
29
+ fileExtensions: string[];
30
+ displayName: string;
31
+ discoverFiles: (input: import("@opensip-cli/graph").DiscoverInput) => import("@opensip-cli/graph").DiscoverOutput;
32
+ parseProject: (input: import("@opensip-cli/graph").ParseInput) => import("@opensip-cli/graph").ParseOutput<import("@opensip-cli/graph-adapter-common").TreeSitterParsedProject>;
33
+ walkProject: typeof pythonWalkProject;
34
+ resolveCallSites: typeof pythonResolveCallSites;
35
+ cacheKey: typeof pythonCacheKey;
36
+ ruleHints: import("@opensip-cli/graph").RuleHints;
37
+ };
38
+ /**
39
+ * Discovery contract: external adapter packs export `adapter` (the
40
+ * GraphLanguageAdapter) and `metadata`. The CLI bootstrap registers
41
+ * `adapter` into the adapter registry after a successful `import()`.
42
+ */
43
+ export { pythonGraphAdapter as adapter };
44
+ export declare const metadata: {
45
+ readonly id: string;
46
+ readonly displayName: string;
47
+ readonly fileExtensions: string[];
48
+ };
49
+ export type { PythonParsedProject } from './parse.js';
50
+ export { pythonRuleHints } from './rule-hints.js';
51
+ //# sourceMappingURL=index.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;GAsBG;AAEH,OAAO,EAAE,QAAQ,IAAI,cAAc,EAAE,MAAM,gBAAgB,CAAC;AAG5D,OAAO,EAAE,gBAAgB,IAAI,sBAAsB,EAAE,MAAM,cAAc,CAAC;AAE1E,OAAO,EAAE,WAAW,IAAI,iBAAiB,EAAE,MAAM,WAAW,CAAC;AAK7D,eAAO,MAAM,kBAAkB;;;;;;;;;;CAUsB,CAAC;AAEtD;;;;GAIG;AACH,OAAO,EAAE,kBAAkB,IAAI,OAAO,EAAE,CAAC;AACzC,eAAO,MAAM,QAAQ;;;;CAIX,CAAC;AAMX,YAAY,EAAE,mBAAmB,EAAE,MAAM,YAAY,CAAC;AACtD,OAAO,EAAE,eAAe,EAAE,MAAM,iBAAiB,CAAC"}
package/dist/index.js ADDED
@@ -0,0 +1,53 @@
1
+ /**
2
+ * @opensip-cli/graph — Python language adapter.
3
+ *
4
+ * Lands in PR 5 of plan docs/plans/10-graph-language-pluggability.md.
5
+ * Exposes `pythonGraphAdapter`, a `GraphLanguageAdapter` backed by
6
+ * tree-sitter-python.
7
+ *
8
+ * Per-rule fidelity (plan §6 row "Tree-sitter adapter"): mostly
9
+ * `'medium'` confidence on calls; `'low'` when multiple catalog
10
+ * entries share a simple name. The adapter has no symbol table.
11
+ *
12
+ * File layout mirrors `lang-typescript/`:
13
+ * discoverFiles → ./discover.ts
14
+ * parseProject → ./parse.ts
15
+ * walkProject → ./walk.ts
16
+ * resolveCallSites → ./resolve.ts
17
+ * cacheKey → ./cache-key.ts
18
+ * ruleHints → ./rule-hints.ts
19
+ *
20
+ * Production graph adapters are forbidden from importing `web-tree-sitter`
21
+ * directly; the ADR-0010 restricted-import rule in the shared ESLint config
22
+ * enforces it.
23
+ */
24
+ import { cacheKey as pythonCacheKey } from './cache-key.js';
25
+ import { discoverFiles as pythonDiscoverFiles } from './discover.js';
26
+ import { parseProject as pythonParseProject } from './parse.js';
27
+ import { resolveCallSites as pythonResolveCallSites } from './resolve.js';
28
+ import { pythonRuleHints } from './rule-hints.js';
29
+ import { walkProject as pythonWalkProject } from './walk.js';
30
+ export const pythonGraphAdapter = {
31
+ id: 'python',
32
+ fileExtensions: ['.py'],
33
+ displayName: 'Python',
34
+ discoverFiles: pythonDiscoverFiles,
35
+ parseProject: pythonParseProject,
36
+ walkProject: pythonWalkProject,
37
+ resolveCallSites: pythonResolveCallSites,
38
+ cacheKey: pythonCacheKey,
39
+ ruleHints: pythonRuleHints,
40
+ };
41
+ /**
42
+ * Discovery contract: external adapter packs export `adapter` (the
43
+ * GraphLanguageAdapter) and `metadata`. The CLI bootstrap registers
44
+ * `adapter` into the adapter registry after a successful `import()`.
45
+ */
46
+ export { pythonGraphAdapter as adapter };
47
+ export const metadata = {
48
+ id: pythonGraphAdapter.id,
49
+ displayName: pythonGraphAdapter.displayName,
50
+ fileExtensions: pythonGraphAdapter.fileExtensions,
51
+ };
52
+ export { pythonRuleHints } from './rule-hints.js';
53
+ //# sourceMappingURL=index.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;GAsBG;AAEH,OAAO,EAAE,QAAQ,IAAI,cAAc,EAAE,MAAM,gBAAgB,CAAC;AAC5D,OAAO,EAAE,aAAa,IAAI,mBAAmB,EAAE,MAAM,eAAe,CAAC;AACrE,OAAO,EAAE,YAAY,IAAI,kBAAkB,EAAE,MAAM,YAAY,CAAC;AAChE,OAAO,EAAE,gBAAgB,IAAI,sBAAsB,EAAE,MAAM,cAAc,CAAC;AAC1E,OAAO,EAAE,eAAe,EAAE,MAAM,iBAAiB,CAAC;AAClD,OAAO,EAAE,WAAW,IAAI,iBAAiB,EAAE,MAAM,WAAW,CAAC;AAK7D,MAAM,CAAC,MAAM,kBAAkB,GAAG;IAChC,EAAE,EAAE,QAAQ;IACZ,cAAc,EAAE,CAAC,KAAK,CAAC;IACvB,WAAW,EAAE,QAAQ;IACrB,aAAa,EAAE,mBAAmB;IAClC,YAAY,EAAE,kBAAkB;IAChC,WAAW,EAAE,iBAAiB;IAC9B,gBAAgB,EAAE,sBAAsB;IACxC,QAAQ,EAAE,cAAc;IACxB,SAAS,EAAE,eAAe;CACyB,CAAC;AAEtD;;;;GAIG;AACH,OAAO,EAAE,kBAAkB,IAAI,OAAO,EAAE,CAAC;AACzC,MAAM,CAAC,MAAM,QAAQ,GAAG;IACtB,EAAE,EAAE,kBAAkB,CAAC,EAAE;IACzB,WAAW,EAAE,kBAAkB,CAAC,WAAW;IAC3C,cAAc,EAAE,kBAAkB,CAAC,cAAc;CACzC,CAAC;AAOX,OAAO,EAAE,eAAe,EAAE,MAAM,iBAAiB,CAAC"}
@@ -0,0 +1,22 @@
1
+ /**
2
+ * Python parseProject — consumes `@opensip-cli/lang-python` (ADR-0010).
3
+ *
4
+ * `lang-python` is the canonical Python parse substrate: it owns the vendored
5
+ * tree-sitter-python grammar and produces the `{ tree, source }` parsed-file
6
+ * shape. The graph adapter no longer loads a grammar of its own; it binds the
7
+ * shared `createParseProjectFromAdapter` driver to `pythonAdapter`. This
8
+ * mirrors `graph-typescript`'s dependency on `lang-typescript`, generalizing
9
+ * the `graph-* → lang-*` edge to Python. The parsed-project shape and the
10
+ * downstream walk/resolve are unchanged.
11
+ */
12
+ import { type TreeSitterParsedFile, type TreeSitterParsedProject } from '@opensip-cli/graph-adapter-common';
13
+ /** Parsed Python source file: tree-sitter parse tree plus original source text. */
14
+ export type PythonParsedFile = TreeSitterParsedFile;
15
+ /**
16
+ * Parsed Python project: map of normalized file path → {@link PythonParsedFile}.
17
+ * Keyed by the absolute, realpath-normalized file path from discover.
18
+ */
19
+ export type PythonParsedProject = TreeSitterParsedProject<PythonParsedFile>;
20
+ /** Parses every Python source file in the input set into a {@link PythonParsedProject}. */
21
+ export declare const parseProject: (input: import("@opensip-cli/graph").ParseInput) => import("@opensip-cli/graph").ParseOutput<TreeSitterParsedProject>;
22
+ //# sourceMappingURL=parse.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"parse.d.ts","sourceRoot":"","sources":["../src/parse.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;GAUG;AAEH,OAAO,EAEL,KAAK,oBAAoB,EACzB,KAAK,uBAAuB,EAC7B,MAAM,mCAAmC,CAAC;AAG3C,mFAAmF;AACnF,MAAM,MAAM,gBAAgB,GAAG,oBAAoB,CAAC;AAEpD;;;GAGG;AACH,MAAM,MAAM,mBAAmB,GAAG,uBAAuB,CAAC,gBAAgB,CAAC,CAAC;AAE5E,2FAA2F;AAC3F,eAAO,MAAM,YAAY,uHAA+C,CAAC"}
package/dist/parse.js ADDED
@@ -0,0 +1,16 @@
1
+ /**
2
+ * Python parseProject — consumes `@opensip-cli/lang-python` (ADR-0010).
3
+ *
4
+ * `lang-python` is the canonical Python parse substrate: it owns the vendored
5
+ * tree-sitter-python grammar and produces the `{ tree, source }` parsed-file
6
+ * shape. The graph adapter no longer loads a grammar of its own; it binds the
7
+ * shared `createParseProjectFromAdapter` driver to `pythonAdapter`. This
8
+ * mirrors `graph-typescript`'s dependency on `lang-typescript`, generalizing
9
+ * the `graph-* → lang-*` edge to Python. The parsed-project shape and the
10
+ * downstream walk/resolve are unchanged.
11
+ */
12
+ import { createParseProjectFromAdapter, } from '@opensip-cli/graph-adapter-common';
13
+ import { pythonAdapter } from '@opensip-cli/lang-python';
14
+ /** Parses every Python source file in the input set into a {@link PythonParsedProject}. */
15
+ export const parseProject = createParseProjectFromAdapter(pythonAdapter);
16
+ //# sourceMappingURL=parse.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"parse.js","sourceRoot":"","sources":["../src/parse.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;GAUG;AAEH,OAAO,EACL,6BAA6B,GAG9B,MAAM,mCAAmC,CAAC;AAC3C,OAAO,EAAE,aAAa,EAAE,MAAM,0BAA0B,CAAC;AAWzD,2FAA2F;AAC3F,MAAM,CAAC,MAAM,YAAY,GAAG,6BAA6B,CAAC,aAAa,CAAC,CAAC"}
@@ -0,0 +1,35 @@
1
+ /**
2
+ * Python resolveCallSites — name-based catalog lookup.
3
+ *
4
+ * Tree-sitter has no symbol table, so we resolve by simple name. For
5
+ * each call site:
6
+ *
7
+ * 1. Decode the called expression. Three shapes matter:
8
+ * - `foo(args)` → call target is identifier `foo`
9
+ * - `obj.method(args)` → call target is attribute `method`
10
+ * - `mod.submod.fn(args)` → call target is attribute `fn`
11
+ * Other shapes (`(lambda)()`, subscript calls) are treated as
12
+ * unresolved.
13
+ *
14
+ * 2. Look up matching catalog entries by simple name. Confidence
15
+ * ladder:
16
+ * - 0 matches → `to: []`, resolution `'unknown'`, confidence `'low'`
17
+ * - 1 match → `to: [hash]`, resolution `'static'`, confidence `'medium'`
18
+ * - N matches → `to: [allHashes]`, resolution `'method-dispatch'`,
19
+ * confidence `'low'` (multiple candidates means we
20
+ * can't disambiguate without a symbol table)
21
+ *
22
+ * Confidence is mostly `'medium'`, never `'high'` — that's the
23
+ * intrinsic price of name-based resolution. The plan §6 fidelity table
24
+ * documents this (`orphan-subtree`: medium for tree-sitter adapters).
25
+ *
26
+ * Creation edges (lambda) emit a static high-confidence edge directly,
27
+ * mirroring lang-typescript's semantics.
28
+ *
29
+ * Per I-4: this function does NOT mutate the input catalog. It builds
30
+ * a `bodyHash → CallEdge[]` map and returns it.
31
+ */
32
+ import type { PythonParsedProject } from './parse.js';
33
+ import type { ResolveInput, ResolveOutput } from '@opensip-cli/graph';
34
+ export declare function resolveCallSites(input: ResolveInput<PythonParsedProject>): ResolveOutput;
35
+ //# sourceMappingURL=resolve.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"resolve.d.ts","sourceRoot":"","sources":["../src/resolve.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GA8BG;AAaH,OAAO,KAAK,EAAoB,mBAAmB,EAAE,MAAM,YAAY,CAAC;AACxE,OAAO,KAAK,EAOV,YAAY,EACZ,aAAa,EACd,MAAM,oBAAoB,CAAC;AAkB5B,wBAAgB,gBAAgB,CAAC,KAAK,EAAE,YAAY,CAAC,mBAAmB,CAAC,GAAG,aAAa,CA2CxF"}
@@ -0,0 +1,294 @@
1
+ /**
2
+ * Python resolveCallSites — name-based catalog lookup.
3
+ *
4
+ * Tree-sitter has no symbol table, so we resolve by simple name. For
5
+ * each call site:
6
+ *
7
+ * 1. Decode the called expression. Three shapes matter:
8
+ * - `foo(args)` → call target is identifier `foo`
9
+ * - `obj.method(args)` → call target is attribute `method`
10
+ * - `mod.submod.fn(args)` → call target is attribute `fn`
11
+ * Other shapes (`(lambda)()`, subscript calls) are treated as
12
+ * unresolved.
13
+ *
14
+ * 2. Look up matching catalog entries by simple name. Confidence
15
+ * ladder:
16
+ * - 0 matches → `to: []`, resolution `'unknown'`, confidence `'low'`
17
+ * - 1 match → `to: [hash]`, resolution `'static'`, confidence `'medium'`
18
+ * - N matches → `to: [allHashes]`, resolution `'method-dispatch'`,
19
+ * confidence `'low'` (multiple candidates means we
20
+ * can't disambiguate without a symbol table)
21
+ *
22
+ * Confidence is mostly `'medium'`, never `'high'` — that's the
23
+ * intrinsic price of name-based resolution. The plan §6 fidelity table
24
+ * documents this (`orphan-subtree`: medium for tree-sitter adapters).
25
+ *
26
+ * Creation edges (lambda) emit a static high-confidence edge directly,
27
+ * mirroring lang-typescript's semantics.
28
+ *
29
+ * Per I-4: this function does NOT mutate the input catalog. It builds
30
+ * a `bodyHash → CallEdge[]` map and returns it.
31
+ */
32
+ import { dirname, posix } from 'node:path';
33
+ import { logger } from '@opensip-cli/core';
34
+ import { appendEdge, createMutableStats, pushCreationEdge, truncateForCallEdge, } from '@opensip-cli/graph';
35
+ import { buildNameIndex } from '@opensip-cli/graph-adapter-common';
36
+ function pythonPosition(node, file) {
37
+ return {
38
+ line: node.startPosition.row + 1,
39
+ column: node.startPosition.column,
40
+ text: file.source.slice(node.startIndex, node.endIndex),
41
+ };
42
+ }
43
+ export function resolveCallSites(input) {
44
+ logger.info({ evt: 'graph.edges.start', module: 'graph:edges:python' });
45
+ const byName = buildNameIndex(input.catalog.functions);
46
+ const edgesByOwner = new Map();
47
+ const stats = createMutableStats();
48
+ const sink = { edgesByOwner, stats };
49
+ for (const r of input.callSites) {
50
+ const node = r.nodeRef;
51
+ const file = r.sourceFileRef;
52
+ if (r.kind === 'creation') {
53
+ if (r.childHash === undefined)
54
+ continue;
55
+ pushCreationEdge(pythonPosition(node, file), r.ownerHash, r.childHash, sink);
56
+ continue;
57
+ }
58
+ pushCallEdge(node, file, r.ownerHash, byName, sink);
59
+ }
60
+ const finalStats = {
61
+ totalCallSites: stats.totalCallSites,
62
+ resolvedHigh: stats.resolvedHigh,
63
+ resolvedMedium: stats.resolvedMedium,
64
+ resolvedLow: stats.resolvedLow,
65
+ unresolved: stats.unresolved,
66
+ };
67
+ logger.info({
68
+ evt: 'graph.edges.complete',
69
+ module: 'graph:edges:python',
70
+ ...finalStats,
71
+ });
72
+ // Phase 4 (DEC-498): resolve dependency sites if any. Mirrors the TS
73
+ // adapter's resolveDependencies pattern adapted to Python's path-based
74
+ // module discovery (no symbol table, no tsconfig resolution).
75
+ const dependenciesByOwner = input.dependencySites && input.dependencySites.length > 0
76
+ ? resolveDependencies(input.dependencySites, input.catalog)
77
+ : undefined;
78
+ return dependenciesByOwner === undefined
79
+ ? { edgesByOwner, stats: finalStats }
80
+ : { edgesByOwner, dependenciesByOwner, stats: finalStats };
81
+ }
82
+ /**
83
+ * Resolve Python import sites to target module-init bodyHashes. Imports
84
+ * resolving to an in-catalog `.py` (or `__init__.py`) source file map to
85
+ * that file's module-init occurrence; imports resolving to standard-
86
+ * library or third-party packages (outside the catalog) produce
87
+ * unresolved `DependencyEdge` entries with `to: []` and the raw specifier
88
+ * carried in `specifier` for downstream attribution.
89
+ *
90
+ * Python lacks a tsconfig-equivalent resolver, so this implements the
91
+ * standard CPython search-path rules constrained to the project:
92
+ *
93
+ * - Absolute dotted (`foo.bar`) → `foo/bar.py` or `foo/bar/__init__.py`
94
+ * relative to the project root.
95
+ * - Relative (`.foo`, `..pkg.sub`) → resolve relative to the importing
96
+ * file's directory, walking up one level per leading dot beyond the
97
+ * first (which means the current package).
98
+ *
99
+ * Phase 4 of opensip's substrate consolidation (DEC-498).
100
+ */
101
+ function resolveDependencies(sites, catalog) {
102
+ // Build filePath → module-init bodyHash map. Catalog occurrences carry
103
+ // project-relative POSIX filePath; module-init kind is filtered.
104
+ const moduleInitByFilePath = new Map();
105
+ for (const occs of Object.values(catalog.functions)) {
106
+ if (!occs)
107
+ continue;
108
+ for (const o of occs) {
109
+ if (o.kind === 'module-init')
110
+ moduleInitByFilePath.set(o.filePath, o.bodyHash);
111
+ }
112
+ }
113
+ const out = new Map();
114
+ for (const site of sites) {
115
+ const file = site.sourceFileRef;
116
+ const importerFilePath = pythonFilePathOf(file, catalog, site.ownerHash);
117
+ const to = resolvePythonModuleSpecifier(site.specifier, importerFilePath, moduleInitByFilePath);
118
+ const edge = {
119
+ to,
120
+ line: site.line,
121
+ column: site.column,
122
+ specifier: site.specifier,
123
+ };
124
+ const existing = out.get(site.ownerHash);
125
+ if (existing === undefined) {
126
+ out.set(site.ownerHash, [edge]);
127
+ }
128
+ else {
129
+ existing.push(edge);
130
+ }
131
+ }
132
+ return out;
133
+ }
134
+ /**
135
+ * Recover the project-relative filePath of the importer from the catalog
136
+ * (looking it up via the owner's module-init bodyHash). This avoids
137
+ * threading the projectDirAbs / absolute path through dependency
138
+ * resolution — the catalog already knows.
139
+ */
140
+ function pythonFilePathOf(_file, catalog, ownerHash) {
141
+ for (const occs of Object.values(catalog.functions)) {
142
+ if (!occs)
143
+ continue;
144
+ for (const o of occs) {
145
+ if (o.bodyHash === ownerHash)
146
+ return o.filePath;
147
+ }
148
+ }
149
+ /* v8 ignore next */
150
+ return null;
151
+ }
152
+ /**
153
+ * Resolve one Python import specifier to its target module-init
154
+ * bodyHash(es). Returns `[]` when no project file matches (external
155
+ * package, stdlib, or an unresolvable relative import that walks above
156
+ * the project root).
157
+ *
158
+ * Handled shapes:
159
+ * - `foo` → `foo.py` or `foo/__init__.py`
160
+ * - `foo.bar` → `foo/bar.py` or `foo/bar/__init__.py`
161
+ * - `.sibling` → same package → `sibling.py` / `sibling/__init__.py`
162
+ * - `..pkg.sub` → up one package → `pkg/sub.py` / `pkg/sub/__init__.py`
163
+ *
164
+ * NOT handled:
165
+ * - PEP 420 namespace packages (we don't enumerate directories without
166
+ * an `__init__.py`).
167
+ * - `sys.path` extensions configured in `pyproject.toml` (`[tool.…]`
168
+ * `src` layouts where the package root isn't the project root).
169
+ * Files outside the project tree always resolve to external (`[]`).
170
+ * - `importlib.import_module(...)`, `__import__(...)`, conditional /
171
+ * nested imports inside function bodies (walker only emits top-level
172
+ * imports).
173
+ */
174
+ function resolvePythonModuleSpecifier(specifier, importerFilePath, moduleInitByFilePath) {
175
+ const leadingDots = countLeadingDots(specifier);
176
+ if (leadingDots === 0) {
177
+ return resolveAbsoluteModule(specifier, moduleInitByFilePath);
178
+ }
179
+ if (importerFilePath === null) {
180
+ /* v8 ignore next */
181
+ return [];
182
+ }
183
+ return resolveRelativeModule(specifier, leadingDots, importerFilePath, moduleInitByFilePath);
184
+ }
185
+ function countLeadingDots(specifier) {
186
+ let n = 0;
187
+ while (n < specifier.length && specifier[n] === '.')
188
+ n++;
189
+ return n;
190
+ }
191
+ function resolveAbsoluteModule(specifier, moduleInitByFilePath) {
192
+ return lookupModuleCandidates(specifier.split('.'), moduleInitByFilePath);
193
+ }
194
+ function resolveRelativeModule(specifier, leadingDots, importerFilePath, moduleInitByFilePath) {
195
+ // `from .x import y` — one dot → same package directory.
196
+ // `from ..x import y` — two dots → parent package directory.
197
+ // CPython: N dots = walk up (N - 1) directories from the importer's package.
198
+ const importerDir = dirname(importerFilePath); // POSIX, project-relative
199
+ let baseDir = importerDir;
200
+ for (let i = 1; i < leadingDots; i++) {
201
+ const parent = posix.dirname(baseDir);
202
+ if (parent === baseDir) {
203
+ // Walked above the project root → unresolvable.
204
+ return [];
205
+ }
206
+ baseDir = parent;
207
+ }
208
+ const remainder = specifier.slice(leadingDots); // may be '' for `from . import x`
209
+ const segments = remainder.length > 0 ? remainder.split('.') : [];
210
+ // baseDir of '.' means project root; treat as empty prefix.
211
+ const prefix = baseDir === '.' || baseDir === '' ? [] : baseDir.split('/');
212
+ return lookupModuleCandidates([...prefix, ...segments], moduleInitByFilePath);
213
+ }
214
+ /**
215
+ * Given dotted module segments (e.g. `['foo', 'bar']`), try the two
216
+ * canonical file forms: `foo/bar.py` and `foo/bar/__init__.py`. Returns
217
+ * the matching module-init bodyHash in a single-element array, or `[]`.
218
+ */
219
+ function lookupModuleCandidates(segments, moduleInitByFilePath) {
220
+ if (segments.length === 0)
221
+ return [];
222
+ const joined = segments.join('/');
223
+ const candidates = [`${joined}.py`, `${joined}/__init__.py`];
224
+ for (const candidate of candidates) {
225
+ const hash = moduleInitByFilePath.get(candidate);
226
+ if (hash !== undefined)
227
+ return [hash];
228
+ }
229
+ return [];
230
+ }
231
+ function pushCallEdge(node, file, ownerHash, byName, sink) {
232
+ const { edgesByOwner, stats } = sink;
233
+ stats.totalCallSites++;
234
+ const target = extractCallTargetName(node);
235
+ const pos = pythonPosition(node, file);
236
+ const truncated = truncateForCallEdge(pos.text);
237
+ const discarded = isReturnValueDiscarded(node);
238
+ const edge = buildPythonCallEdge(target, byName, {
239
+ line: pos.line,
240
+ column: pos.column,
241
+ text: truncated,
242
+ discarded,
243
+ });
244
+ appendEdge(edgesByOwner, ownerHash, edge);
245
+ stats.apply(edge);
246
+ }
247
+ function buildPythonCallEdge(target, byName, loc) {
248
+ if (target === null) {
249
+ return { to: [], ...loc, resolution: 'unknown', confidence: 'low' };
250
+ }
251
+ const matches = byName.get(target);
252
+ if (!matches || matches.length === 0) {
253
+ return { to: [], ...loc, resolution: 'unknown', confidence: 'low' };
254
+ }
255
+ if (matches.length === 1) {
256
+ return { to: [...matches], ...loc, resolution: 'static', confidence: 'medium' };
257
+ }
258
+ return { to: [...matches], ...loc, resolution: 'method-dispatch', confidence: 'low' };
259
+ }
260
+ /**
261
+ * Decode a `call` node's target into a simple name. Returns null when
262
+ * we don't recognize the shape (subscript call, lambda call, etc.) —
263
+ * those become unresolved edges.
264
+ */
265
+ function extractCallTargetName(node) {
266
+ // tree-sitter-python `call` has a `function` field for the callee.
267
+ const fn = node.childForFieldName('function');
268
+ if (!fn)
269
+ return null;
270
+ if (fn.type === 'identifier')
271
+ return fn.text;
272
+ if (fn.type === 'attribute') {
273
+ const attr = fn.childForFieldName('attribute');
274
+ return attr ? attr.text : null;
275
+ }
276
+ return null;
277
+ }
278
+ /**
279
+ * The call's return value is discarded when the call expression is
280
+ * the entire expression of an expression_statement. Mirrors
281
+ * lang-typescript's logic for the `no-side-effect-path` rule.
282
+ */
283
+ function isReturnValueDiscarded(node) {
284
+ let parent = node.parent;
285
+ while (parent) {
286
+ if (parent.type === 'parenthesized_expression' || parent.type === 'await') {
287
+ parent = parent.parent;
288
+ continue;
289
+ }
290
+ return parent.type === 'expression_statement';
291
+ }
292
+ return false;
293
+ }
294
+ //# sourceMappingURL=resolve.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"resolve.js","sourceRoot":"","sources":["../src/resolve.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,13 @@
1
+ /**
2
+ * Python rule hints — declares language-specific signals for rules.
3
+ *
4
+ * Lands in PR 5 of plan docs/plans/10-graph-language-pluggability.md.
5
+ * Each hint maps a generic rule input ("what counts as a test file?",
6
+ * "what's a side-effect primitive?") onto Python conventions.
7
+ *
8
+ * Conservative on purpose: high-precision, low-recall. Rule authors may
9
+ * extend the lists over time as false negatives surface in practice.
10
+ */
11
+ import type { RuleHints } from '@opensip-cli/graph';
12
+ export declare const pythonRuleHints: RuleHints;
13
+ //# sourceMappingURL=rule-hints.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"rule-hints.d.ts","sourceRoot":"","sources":["../src/rule-hints.ts"],"names":[],"mappings":"AAAA;;;;;;;;;GASG;AAIH,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,oBAAoB,CAAC;AAyDpD,eAAO,MAAM,eAAe,EAAE,SAK7B,CAAC"}