tscode-kg 0.2.0__tar.gz → 0.4.0__tar.gz

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 (39) hide show
  1. tscode_kg-0.4.0/LICENSE +94 -0
  2. {tscode_kg-0.2.0 → tscode_kg-0.4.0}/PKG-INFO +41 -18
  3. {tscode_kg-0.2.0 → tscode_kg-0.4.0}/README.md +39 -4
  4. {tscode_kg-0.2.0 → tscode_kg-0.4.0}/pyproject.toml +74 -24
  5. {tscode_kg-0.2.0 → tscode_kg-0.4.0}/src/tscode_kg/__init__.py +1 -1
  6. tscode_kg-0.4.0/src/tscode_kg/cli/cmd_build.py +229 -0
  7. tscode_kg-0.4.0/src/tscode_kg/cli/cmd_hooks.py +172 -0
  8. {tscode_kg-0.2.0 → tscode_kg-0.4.0}/src/tscode_kg/cli/cmd_snapshot.py +29 -8
  9. {tscode_kg-0.2.0 → tscode_kg-0.4.0}/src/tscode_kg/cli/main.py +4 -1
  10. {tscode_kg-0.2.0 → tscode_kg-0.4.0}/src/tscode_kg/snapshots.py +8 -4
  11. tscode_kg-0.2.0/LICENSE +0 -24
  12. tscode_kg-0.2.0/src/tscode_kg/cli/cmd_build.py +0 -86
  13. tscode_kg-0.2.0/src/tscode_kg/cli/cmd_hooks.py +0 -125
  14. {tscode_kg-0.2.0 → tscode_kg-0.4.0}/src/tscode_kg/__main__.py +0 -0
  15. {tscode_kg-0.2.0 → tscode_kg-0.4.0}/src/tscode_kg/analysis.py +0 -0
  16. {tscode_kg-0.2.0 → tscode_kg-0.4.0}/src/tscode_kg/app.py +0 -0
  17. {tscode_kg-0.2.0 → tscode_kg-0.4.0}/src/tscode_kg/bridge.py +0 -0
  18. {tscode_kg-0.2.0 → tscode_kg-0.4.0}/src/tscode_kg/centrality.py +0 -0
  19. {tscode_kg-0.2.0 → tscode_kg-0.4.0}/src/tscode_kg/cli/__init__.py +0 -0
  20. {tscode_kg-0.2.0 → tscode_kg-0.4.0}/src/tscode_kg/cli/cmd_analyze.py +0 -0
  21. {tscode_kg-0.2.0 → tscode_kg-0.4.0}/src/tscode_kg/cli/cmd_bridges.py +0 -0
  22. {tscode_kg-0.2.0 → tscode_kg-0.4.0}/src/tscode_kg/cli/cmd_centrality.py +0 -0
  23. {tscode_kg-0.2.0 → tscode_kg-0.4.0}/src/tscode_kg/cli/cmd_explain.py +0 -0
  24. {tscode_kg-0.2.0 → tscode_kg-0.4.0}/src/tscode_kg/cli/cmd_framework_nodes.py +0 -0
  25. {tscode_kg-0.2.0 → tscode_kg-0.4.0}/src/tscode_kg/cli/cmd_init.py +0 -0
  26. {tscode_kg-0.2.0 → tscode_kg-0.4.0}/src/tscode_kg/cli/cmd_mcp.py +0 -0
  27. {tscode_kg-0.2.0 → tscode_kg-0.4.0}/src/tscode_kg/cli/cmd_model.py +0 -0
  28. {tscode_kg-0.2.0 → tscode_kg-0.4.0}/src/tscode_kg/cli/cmd_query.py +0 -0
  29. {tscode_kg-0.2.0 → tscode_kg-0.4.0}/src/tscode_kg/cli/cmd_viz.py +0 -0
  30. {tscode_kg-0.2.0 → tscode_kg-0.4.0}/src/tscode_kg/coderank.py +0 -0
  31. {tscode_kg-0.2.0 → tscode_kg-0.4.0}/src/tscode_kg/config.py +0 -0
  32. {tscode_kg-0.2.0 → tscode_kg-0.4.0}/src/tscode_kg/explain.py +0 -0
  33. {tscode_kg-0.2.0 → tscode_kg-0.4.0}/src/tscode_kg/extractor.py +0 -0
  34. {tscode_kg-0.2.0 → tscode_kg-0.4.0}/src/tscode_kg/framework_detector.py +0 -0
  35. {tscode_kg-0.2.0 → tscode_kg-0.4.0}/src/tscode_kg/kg.py +0 -0
  36. {tscode_kg-0.2.0 → tscode_kg-0.4.0}/src/tscode_kg/layout3d.py +0 -0
  37. {tscode_kg-0.2.0 → tscode_kg-0.4.0}/src/tscode_kg/mcp_server.py +0 -0
  38. {tscode_kg-0.2.0 → tscode_kg-0.4.0}/src/tscode_kg/viz3d.py +0 -0
  39. {tscode_kg-0.2.0 → tscode_kg-0.4.0}/src/tscode_kg/viz3d_timeline.py +0 -0
@@ -0,0 +1,94 @@
1
+ Elastic License 2.0
2
+
3
+ URL: https://www.elastic.co/licensing/elastic-license
4
+
5
+ ## Acceptance
6
+
7
+ By using the software, you agree to all of the terms and conditions below.
8
+
9
+ ## Copyright License
10
+
11
+ The licensor grants you a non-exclusive, royalty-free, worldwide,
12
+ non-sublicensable, non-transferable license to use, copy, distribute, make
13
+ available, and prepare derivative works of the software, in each case subject to
14
+ the limitations and conditions below.
15
+
16
+ ## Limitations
17
+
18
+ **You may not provide the software to third parties as a hosted or managed
19
+ service, where the service provides users with access to any substantial set of
20
+ the features or functionality of the software.**
21
+
22
+ You may not move, change, disable, or circumvent the license key functionality
23
+ in the software, and you may not remove or obscure any functionality in the
24
+ software that is protected by the license key.
25
+
26
+ You may not alter, remove, or obscure any licensing, copyright, or other notices
27
+ of the licensor in the software. Any use of the licensor's trademarks is subject
28
+ to applicable law.
29
+
30
+ ## Patents
31
+
32
+ The licensor grants you a license, under any patent claims the licensor can
33
+ license, or becomes able to license, to make, have made, use, sell, offer for
34
+ sale, import and have imported the software, in each case subject to the
35
+ limitations and conditions in this license. This license does not cover any
36
+ patent claims that you cause to be infringed by modifications or additions to the
37
+ software. If you or your company make any written claim that the software
38
+ infringes or contributes to infringement of any patent, your patent license for
39
+ the software granted under these terms ends immediately. If your company makes
40
+ such a claim, your patent license ends immediately for work on behalf of your
41
+ company.
42
+
43
+ ## Notices
44
+
45
+ You must ensure that anyone who gets a copy of any part of the software from you
46
+ also gets a copy of these terms or the URL for them above, as well as copies of
47
+ any plain-text lines beginning with "Required Notice:" that the licensor provided
48
+ with the software. For example:
49
+
50
+ Required Notice: Copyright (c) 2026 Eric G. Suchanek, PhD
51
+
52
+ ## No Other Rights
53
+
54
+ These terms do not imply any other licenses not expressly granted in this
55
+ license.
56
+
57
+ ## Termination
58
+
59
+ If you use the software in violation of these terms, such use is not licensed,
60
+ and your licenses will automatically terminate. If the licensor provides you with
61
+ a notice of your violation, and you cease all violation of this license no later
62
+ than 30 days after you receive that notice, your licenses will be reinstated
63
+ retroactively. However, if you violate these terms after such reinstatement, any
64
+ additional violation of these terms will cause your licenses to terminate
65
+ automatically and permanently.
66
+
67
+ ## No Liability
68
+
69
+ *As far as the law allows, the software comes as is, without any warranty or
70
+ condition, and the licensor will not be liable to you for any damages arising out
71
+ of these terms or the use or nature of the software, under any kind of legal
72
+ claim.*
73
+
74
+ ## Definitions
75
+
76
+ The **licensor** is the entity offering these terms, and the **software** is the
77
+ software the licensor makes available under these terms, including any portion of
78
+ it.
79
+
80
+ **You** refers to the individual or entity agreeing to these terms.
81
+
82
+ **Your company** is any legal entity, sole proprietorship, or other kind of
83
+ organization that you work for, plus all organizations that have control over,
84
+ are under the control of, or are under common control with that organization.
85
+ Control means ownership of substantially all the assets of an entity, or the
86
+ power to direct its management and policies by vote, contract, or otherwise.
87
+ Control can be direct or indirect.
88
+
89
+ **Your licenses** are all the licenses granted to you for the software under
90
+ these terms.
91
+
92
+ **Use** means anything you do with the software requiring one of your licenses.
93
+
94
+ **Trademark** means trademarks, service marks, and similar rights.
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: tscode-kg
3
- Version: 0.2.0
3
+ Version: 0.4.0
4
4
  Summary: Knowledge graph for TypeScript/JavaScript codebases — AST extraction, hybrid semantic+structural search
5
5
  License-Expression: Elastic-2.0
6
6
  License-File: LICENSE
@@ -15,42 +15,38 @@ Classifier: Topic :: Scientific/Engineering :: Artificial Intelligence
15
15
  Classifier: Programming Language :: Python :: 3
16
16
  Classifier: Programming Language :: Python :: 3.12
17
17
  Classifier: Programming Language :: Python :: 3.13
18
- Provides-Extra: dev
19
- Provides-Extra: kgdeps
20
18
  Provides-Extra: viz
21
19
  Provides-Extra: viz3d
22
20
  Requires-Dist: PyQt5 (>=5.15.11,<5.16) ; extra == "viz3d"
23
21
  Requires-Dist: click (>=8.1.0,<9)
24
- Requires-Dist: detect-secrets (>=1.5.0) ; extra == "dev"
25
- Requires-Dist: doc-kg (>=0.18.1) ; extra == "kgdeps"
26
- Requires-Dist: kgmodule-utils[semantic,sqlite-vec] (>=0.8.0)
22
+ Requires-Dist: kgmodule-utils[semantic,sqlite-vec] (>=0.19.1)
27
23
  Requires-Dist: markdown (>=3.10,<3.11) ; extra == "viz3d"
28
24
  Requires-Dist: mcp (>=1.0.0,<2)
29
25
  Requires-Dist: networkx (>=3.0)
30
26
  Requires-Dist: param (>=2.4,<2.5) ; extra == "viz3d"
31
27
  Requires-Dist: plotly (>=6.8.0,<6.9) ; extra == "viz"
32
- Requires-Dist: pre-commit (>=4.5.1) ; extra == "dev"
33
- Requires-Dist: pycode-kg (>=0.20.0,<0.21) ; extra == "dev"
34
- Requires-Dist: pycode-kg (>=0.20.0,<0.21) ; extra == "kgdeps"
35
- Requires-Dist: pylint (>=4.0.5) ; extra == "dev"
36
- Requires-Dist: pytest (>=8.0.0) ; extra == "dev"
37
- Requires-Dist: pytest-cov (>=5.0.0) ; extra == "dev"
38
28
  Requires-Dist: pyvis (>=0.3.2,<0.4) ; extra == "viz"
39
29
  Requires-Dist: pyvista (>=0.48.4,<0.49) ; extra == "viz3d"
40
30
  Requires-Dist: pyvistaqt (>=0.12,<0.13) ; extra == "viz3d"
41
31
  Requires-Dist: rich (>=14.3.3,<15)
42
- Requires-Dist: ruff (>=0.4.0) ; extra == "dev"
43
32
  Requires-Dist: streamlit (>=1.59.0,<1.60) ; extra == "viz"
44
33
  Requires-Dist: trame-vtk (>=2.11.8,<2.12) ; extra == "viz3d"
45
34
  Requires-Dist: tree-sitter (>=0.25.0)
46
35
  Requires-Dist: tree-sitter-typescript (>=0.23.2)
47
- Requires-Dist: ty (>=0.0.41) ; extra == "dev"
48
36
  Project-URL: Homepage, https://github.com/Flux-Frontiers/tscode_kg
49
37
  Project-URL: Repository, https://github.com/Flux-Frontiers/tscode_kg
50
38
  Description-Content-Type: text/markdown
51
39
 
52
40
  # TypeScriptKG
53
41
 
42
+ [![Python](https://img.shields.io/badge/python-3.12%20%7C%203.13-blue.svg)](https://www.python.org/)
43
+ [![License: Elastic-2.0](https://img.shields.io/badge/License-Elastic%202.0-blue.svg)](https://www.elastic.co/licensing/elastic-license)
44
+ [![PyPI](https://img.shields.io/pypi/v/tscode-kg.svg)](https://pypi.org/project/tscode-kg/)
45
+ [![Version](https://img.shields.io/badge/version-0.4.0-blue.svg)](https://github.com/Flux-Frontiers/tscode_kg/releases)
46
+ [![CI](https://github.com/Flux-Frontiers/tscode_kg/actions/workflows/ci.yml/badge.svg)](https://github.com/Flux-Frontiers/tscode_kg/actions/workflows/ci.yml)
47
+ [![Poetry](https://img.shields.io/endpoint?url=https://python-poetry.org/badge/v0.json)](https://python-poetry.org/)
48
+ [![DOI](https://zenodo.org/badge/1247715028.svg)](https://zenodo.org/badge/latestdoi/1247715028)
49
+
54
50
  Knowledge graph for TypeScript and JavaScript codebases — deterministic AST extraction, hybrid semantic + structural search.
55
51
 
56
52
  ## Overview
@@ -112,7 +108,7 @@ tscodekg framework-nodes --top 20
112
108
  tscodekg explain "fn:src/utils/helpers.ts:formatDate"
113
109
 
114
110
  # Temporal metric snapshots
115
- tscodekg snapshot save --repo /path/to/ts-repo
111
+ tscodekg snapshot save 0.4.0 --repo /path/to/ts-repo --subject repo:tscode-kg
116
112
  tscodekg snapshot list
117
113
 
118
114
  # Interactive visualizers (install extras: tscode-kg[viz] / tscode-kg[viz3d])
@@ -146,9 +142,12 @@ Repo-local Claude Code skills live in `skills/`.
146
142
 
147
143
  ## Snapshots & git hook
148
144
 
149
- `tscodekg snapshot save` captures graph metrics (nodes, edges, JSDoc coverage,
150
- issues, hotspots) keyed by git tree hash into `.tscodekg/snapshots/`, with
151
- deltas computed against the previous and baseline snapshots.
145
+ `tscodekg snapshot save [VERSION]` captures graph metrics (nodes, edges,
146
+ JSDoc coverage, issues, hotspots) into `.tscodekg/snapshots/`, keyed on
147
+ VERSION -- pass a release tag at release time, or omit it for a UTC
148
+ timestamp key, which is what the per-commit hook below uses. The git tree
149
+ hash is recorded as provenance, not the key. Deltas are computed against
150
+ the previous and baseline snapshots.
152
151
  `tscodekg install-hooks` installs a pre-commit hook that rebuilds the index,
153
152
  captures a snapshot, stages the snapshot directory, and then runs the
154
153
  pre-commit framework checks — so every commit records the state of the
@@ -190,6 +189,30 @@ exclude = ["__tests__"] # extra dirs to skip
190
189
 
191
190
  Eric G. Suchanek, PhD — Flux Frontiers
192
191
 
192
+ ## Citation
193
+
194
+ If you use TypeScriptKG in your research or project, please cite it:
195
+
196
+ [![DOI](https://zenodo.org/badge/1247715028.svg)](https://zenodo.org/badge/latestdoi/1247715028)
197
+
198
+ > Suchanek, E. G. (2026). *TypeScriptKG: Semantic Knowledge Graph for TypeScript/JavaScript Codebases* (Version 0.4.0) [Software]. Flux-Frontiers. https://github.com/Flux-Frontiers/tscode_kg
199
+
200
+ ```bibtex
201
+ @software{suchanek_tscode_kg,
202
+ author = {Suchanek, Eric G.},
203
+ title = {{TypeScriptKG}: Semantic Knowledge Graph for TypeScript/JavaScript Codebases},
204
+ version = {0.4.0},
205
+ year = {2026},
206
+ publisher = {Flux-Frontiers},
207
+ url = {https://github.com/Flux-Frontiers/tscode_kg},
208
+ }
209
+ ```
210
+
211
+ Full citation metadata in [`CITATION.cff`](CITATION.cff). This is the first
212
+ release archived by Zenodo; the DOI resolves via the badge above once the
213
+ archive completes, and this section is updated with the concept DOI directly
214
+ afterward.
215
+
193
216
  ## License
194
217
 
195
218
  Elastic-2.0
@@ -1,5 +1,13 @@
1
1
  # TypeScriptKG
2
2
 
3
+ [![Python](https://img.shields.io/badge/python-3.12%20%7C%203.13-blue.svg)](https://www.python.org/)
4
+ [![License: Elastic-2.0](https://img.shields.io/badge/License-Elastic%202.0-blue.svg)](https://www.elastic.co/licensing/elastic-license)
5
+ [![PyPI](https://img.shields.io/pypi/v/tscode-kg.svg)](https://pypi.org/project/tscode-kg/)
6
+ [![Version](https://img.shields.io/badge/version-0.4.0-blue.svg)](https://github.com/Flux-Frontiers/tscode_kg/releases)
7
+ [![CI](https://github.com/Flux-Frontiers/tscode_kg/actions/workflows/ci.yml/badge.svg)](https://github.com/Flux-Frontiers/tscode_kg/actions/workflows/ci.yml)
8
+ [![Poetry](https://img.shields.io/endpoint?url=https://python-poetry.org/badge/v0.json)](https://python-poetry.org/)
9
+ [![DOI](https://zenodo.org/badge/1247715028.svg)](https://zenodo.org/badge/latestdoi/1247715028)
10
+
3
11
  Knowledge graph for TypeScript and JavaScript codebases — deterministic AST extraction, hybrid semantic + structural search.
4
12
 
5
13
  ## Overview
@@ -61,7 +69,7 @@ tscodekg framework-nodes --top 20
61
69
  tscodekg explain "fn:src/utils/helpers.ts:formatDate"
62
70
 
63
71
  # Temporal metric snapshots
64
- tscodekg snapshot save --repo /path/to/ts-repo
72
+ tscodekg snapshot save 0.4.0 --repo /path/to/ts-repo --subject repo:tscode-kg
65
73
  tscodekg snapshot list
66
74
 
67
75
  # Interactive visualizers (install extras: tscode-kg[viz] / tscode-kg[viz3d])
@@ -95,9 +103,12 @@ Repo-local Claude Code skills live in `skills/`.
95
103
 
96
104
  ## Snapshots & git hook
97
105
 
98
- `tscodekg snapshot save` captures graph metrics (nodes, edges, JSDoc coverage,
99
- issues, hotspots) keyed by git tree hash into `.tscodekg/snapshots/`, with
100
- deltas computed against the previous and baseline snapshots.
106
+ `tscodekg snapshot save [VERSION]` captures graph metrics (nodes, edges,
107
+ JSDoc coverage, issues, hotspots) into `.tscodekg/snapshots/`, keyed on
108
+ VERSION -- pass a release tag at release time, or omit it for a UTC
109
+ timestamp key, which is what the per-commit hook below uses. The git tree
110
+ hash is recorded as provenance, not the key. Deltas are computed against
111
+ the previous and baseline snapshots.
101
112
  `tscodekg install-hooks` installs a pre-commit hook that rebuilds the index,
102
113
  captures a snapshot, stages the snapshot directory, and then runs the
103
114
  pre-commit framework checks — so every commit records the state of the
@@ -139,6 +150,30 @@ exclude = ["__tests__"] # extra dirs to skip
139
150
 
140
151
  Eric G. Suchanek, PhD — Flux Frontiers
141
152
 
153
+ ## Citation
154
+
155
+ If you use TypeScriptKG in your research or project, please cite it:
156
+
157
+ [![DOI](https://zenodo.org/badge/1247715028.svg)](https://zenodo.org/badge/latestdoi/1247715028)
158
+
159
+ > Suchanek, E. G. (2026). *TypeScriptKG: Semantic Knowledge Graph for TypeScript/JavaScript Codebases* (Version 0.4.0) [Software]. Flux-Frontiers. https://github.com/Flux-Frontiers/tscode_kg
160
+
161
+ ```bibtex
162
+ @software{suchanek_tscode_kg,
163
+ author = {Suchanek, Eric G.},
164
+ title = {{TypeScriptKG}: Semantic Knowledge Graph for TypeScript/JavaScript Codebases},
165
+ version = {0.4.0},
166
+ year = {2026},
167
+ publisher = {Flux-Frontiers},
168
+ url = {https://github.com/Flux-Frontiers/tscode_kg},
169
+ }
170
+ ```
171
+
172
+ Full citation metadata in [`CITATION.cff`](CITATION.cff). This is the first
173
+ release archived by Zenodo; the DOI resolves via the badge above once the
174
+ archive completes, and this section is updated with the concept DOI directly
175
+ afterward.
176
+
142
177
  ## License
143
178
 
144
179
  Elastic-2.0
@@ -4,20 +4,30 @@
4
4
  #
5
5
  # Build system : Poetry 2.x with PEP 621 [project] table
6
6
  #
7
+ # Extras are user-facing features; dev tooling is a Poetry group, so it never
8
+ # ships in the wheel and cannot be pip-installed. See FLEET_STANDARDS.md.
9
+ #
7
10
  # Quick install (pip)
8
11
  # -------------------
9
- # pip install -e ".[dev]" core + dev tools
10
- # pip install -e "." core runtime only
12
+ # pip install -e "." core runtime only
13
+ # pip install -e ".[viz]" + Streamlit/Plotly explorer
14
+ # pip install -e ".[viz3d]" + PyVista/PyQt5 3-D visualizer
15
+ #
16
+ # Quick install (poetry)
17
+ # ----------------------
18
+ # poetry install core runtime only
19
+ # poetry install --with dev + pytest, ruff, ty, pre-commit
20
+ # poetry install --with kg + the pycodekg / dockg CLIs
21
+ # poetry install --all-extras --with dev,kg full maintainer environment
11
22
  #
12
23
  # First-time setup
13
24
  # ----------------
14
- # python -m venv .venv
15
- # source .venv/bin/activate
16
- # pip install -e ".[dev]"
25
+ # poetry install --with dev
17
26
  #
18
27
  # Build KG indices
19
28
  # ----------------
20
- # tscodekg build --repo .
29
+ # tscodekg build --repo . full rebuild (wipes)
30
+ # tscodekg update --repo . incremental upsert
21
31
 
22
32
  [build-system]
23
33
  requires = ["poetry-core>=2.0.0"]
@@ -28,7 +38,7 @@ packages = [{ include = "tscode_kg", from = "src" }]
28
38
 
29
39
  [project]
30
40
  name = "tscode-kg"
31
- version = "0.2.0"
41
+ version = "0.4.0"
32
42
  description = "Knowledge graph for TypeScript/JavaScript codebases — AST extraction, hybrid semantic+structural search"
33
43
  readme = "README.md"
34
44
  license = "Elastic-2.0"
@@ -54,7 +64,7 @@ dependencies = [
54
64
  "tree-sitter>=0.25.0",
55
65
  "tree-sitter-typescript>=0.23.2",
56
66
  "rich>=14.3.3,<15",
57
- "kgmodule-utils[semantic,sqlite-vec]>=0.8.0",
67
+ "kgmodule-utils[semantic,sqlite-vec]>=0.19.1",
58
68
  "click>=8.1.0,<9",
59
69
  # Upper-bounded: mcp 2.0 removed the bundled `mcp.server.fastmcp` module
60
70
  # (FastMCP was split out into the standalone `fastmcp` package) and rebuilt
@@ -67,11 +77,9 @@ dependencies = [
67
77
  ]
68
78
 
69
79
  [project.optional-dependencies]
70
- # Cross-KG: PyCodeKG (Python codebases) and DocKG (docs) available from within tscode_kg
71
- kgdeps = [
72
- "pycode-kg>=0.20.0,<0.21",
73
- "doc-kg>=0.18.1",
74
- ]
80
+ # pycode-kg is not an extra: it is never imported by this package, so it does
81
+ # not belong in published metadata. It lives in [tool.poetry.group.kg] below --
82
+ # see that block for why the old "install it by hand" workaround is retired.
75
83
  # Streamlit graph explorer + plotly timeline.
76
84
  # Version brackets follow pycode_kg's proven lockfile versions.
77
85
  viz = [
@@ -93,17 +101,56 @@ viz3d = [
93
101
  "pyvistaqt>=0.12,<0.13",
94
102
  "trame-vtk>=2.11.8,<2.12",
95
103
  ]
96
- dev = [
97
- "detect-secrets>=1.5.0",
98
- # This repo is Python pycodekg indexes it and runs in the pre-commit hook.
99
- "pycode-kg>=0.20.0,<0.21",
100
- "ty>=0.0.41",
101
- "pre-commit>=4.5.1",
102
- "pylint>=4.0.5",
103
- "pytest>=8.0.0",
104
- "pytest-cov>=5.0.0",
105
- "ruff>=0.4.0",
106
- ]
104
+
105
+ # ---------------------------------------------------------------------------
106
+ # Dev tooling (Poetry group, not a PEP 621 extra)
107
+ # ---------------------------------------------------------------------------
108
+ # `optional = true` keeps it out of a bare `poetry install`.
109
+ [tool.poetry.group.dev]
110
+ optional = true
111
+
112
+ [tool.poetry.group.dev.dependencies]
113
+ detect-secrets = ">=1.5.0"
114
+ ty = ">=0.0.44"
115
+ pre-commit = ">=4.5.1"
116
+ # >=9.0.3 is a security floor, not a preference: GHSA-6w46-j5rx-g56g.
117
+ pytest = ">=9.0.3"
118
+ pytest-cov = ">=5.0.0"
119
+ # Capped below 0.16 to match the fleet: 0.16 formats Python inside Markdown
120
+ # code blocks as stable behaviour. The floor was unbounded, so the lock could
121
+ # drift away from the version .pre-commit-config.yaml pins.
122
+ ruff = ">=0.4.0,<0.16"
123
+
124
+ # ---------------------------------------------------------------------------
125
+ # Maintainer tooling (Poetry group, not a PEP 621 extra)
126
+ # ---------------------------------------------------------------------------
127
+ # pycode-kg is a tool this repo *runs*, not a library it imports: PyCodeKG
128
+ # indexes this repo's Python source from the outside, via the `pycodekg` CLI in
129
+ # .git/hooks/pre-commit. It belongs in a Poetry group -- locked and installable,
130
+ # but never written into the wheel, so no published extra acquires a sibling.
131
+ #
132
+ # doc-kg rides along for fleet uniformity: every repo's `kg` group carries the
133
+ # same two CLIs, so `poetry install --with kg` means one thing everywhere. This
134
+ # repo has no DocKG index today (no .mcp.json, no `dockg` invocation), so the
135
+ # CLI is simply available rather than required -- and it is here, not in an
136
+ # extra, precisely because nothing depends on it at runtime.
137
+ #
138
+ # poetry install --with kg # get the pycodekg CLI into .venv/bin
139
+ # poetry install # default: group is optional, skipped
140
+ #
141
+ # This supersedes the standing "install it by hand" workaround. That note
142
+ # described a real deadlock -- older pycode-kg capped transformers below 4.57
143
+ # while kgmodule-utils >=0.9.0 needs >=5.5.0, which silently held this repo on
144
+ # the pre-CVE transformers line. pycode-kg 0.21.4 no longer pins transformers at
145
+ # all; it inherits the same kgmodule-utils[semantic] constraint this project
146
+ # already uses, so the two no longer conflict. Verified: locking with this group
147
+ # keeps transformers at 5.14.1.
148
+ [tool.poetry.group.kg]
149
+ optional = true
150
+
151
+ [tool.poetry.group.kg.dependencies]
152
+ doc-kg = ">=0.24.1"
153
+ pycode-kg = ">=0.26.0"
107
154
 
108
155
  [project.urls]
109
156
  Homepage = "https://github.com/Flux-Frontiers/tscode_kg"
@@ -113,6 +160,9 @@ Repository = "https://github.com/Flux-Frontiers/tscode_kg"
113
160
  tscodekg = "tscode_kg.cli.main:cli"
114
161
  tscodekg-analyze = "tscode_kg.cli.cmd_analyze:analyze"
115
162
  tscodekg-build = "tscode_kg.cli.cmd_build:build"
163
+ tscodekg-update = "tscode_kg.cli.cmd_build:update"
164
+ tscodekg-build-sqlite = "tscode_kg.cli.cmd_build:build_sqlite"
165
+ tscodekg-build-index = "tscode_kg.cli.cmd_build:build_index"
116
166
  tscodekg-centrality = "tscode_kg.cli.cmd_centrality:centrality"
117
167
  tscodekg-download-model = "tscode_kg.cli.cmd_model:download_model"
118
168
  tscodekg-init = "tscode_kg.cli.cmd_init:init"
@@ -18,7 +18,7 @@ KGExtractor SDK::
18
18
  from tscode_kg import TSCodeExtractor
19
19
  """
20
20
 
21
- __version__ = "0.2.0"
21
+ __version__ = "0.4.0"
22
22
  __author__ = "Eric G. Suchanek, PhD"
23
23
 
24
24
  from tscode_kg.extractor import TSCodeExtractor
@@ -0,0 +1,229 @@
1
+ """
2
+ cli/cmd_build.py — tscodekg build / update commands.
3
+
4
+ Builds the SQLite graph and sqlite-vec vector index from a TypeScript/JS repo.
5
+
6
+ Two commands, not one command with a flag, mirroring ``pycodekg``:
7
+
8
+ * ``build`` wipes existing data and rebuilds from scratch.
9
+ * ``update`` upserts changes without wiping.
10
+
11
+ The split matters because the two are different operations, not a switch on
12
+ one. A rebuild is correct after renames or deletions, where an upsert leaves
13
+ phantom nodes behind — the vector store upserts by node ID, so a renamed
14
+ symbol keeps its old entry forever. Making the safe operation the bare verb
15
+ means the surprising outcome has to be asked for by name.
16
+ """
17
+
18
+ from __future__ import annotations
19
+
20
+ from pathlib import Path
21
+
22
+ import click
23
+ from rich.console import Console
24
+
25
+ console = Console()
26
+
27
+
28
+ def _build_options(fn):
29
+ """Apply the option set shared by ``build`` and ``update``."""
30
+ for option in reversed(
31
+ [
32
+ click.option(
33
+ "--repo", default=".", show_default=True, help="Repository root directory."
34
+ ),
35
+ click.option(
36
+ "--db",
37
+ default=None,
38
+ help="SQLite database path (default: <repo>/.tscodekg/graph.sqlite).",
39
+ ),
40
+ click.option(
41
+ "--vectors",
42
+ default=None,
43
+ help="sqlite-vec store path (default: <repo>/.tscodekg/vectors.sqlite).",
44
+ ),
45
+ click.option(
46
+ "--graph-only",
47
+ is_flag=True,
48
+ default=False,
49
+ help="Build SQLite graph only; skip vector index.",
50
+ ),
51
+ click.option(
52
+ "--index-only",
53
+ is_flag=True,
54
+ default=False,
55
+ help="Build vector index only; graph must already exist.",
56
+ ),
57
+ ]
58
+ ):
59
+ fn = option(fn)
60
+ return fn
61
+
62
+
63
+ def _run(
64
+ *,
65
+ repo: str,
66
+ db: str | None,
67
+ vectors: str | None,
68
+ graph_only: bool,
69
+ index_only: bool,
70
+ wipe: bool,
71
+ ) -> None:
72
+ """Shared body for ``build`` and ``update``."""
73
+ from tscode_kg.kg import TypeScriptKG # pylint: disable=import-outside-toplevel
74
+
75
+ repo_path = Path(repo).resolve()
76
+ if not repo_path.is_dir():
77
+ console.print(f"[red]Error:[/red] Repository not found: {repo_path}")
78
+ raise SystemExit(1)
79
+
80
+ kg = TypeScriptKG(
81
+ repo_root=repo_path,
82
+ db_path=db,
83
+ vectors_path=vectors,
84
+ )
85
+
86
+ console.print(f"[bold]TypeScriptKG {'build' if wipe else 'update'}[/bold]")
87
+ console.print(f" repo : {repo_path}")
88
+ console.print(f" db : {kg.db_path}")
89
+ console.print(f" vectors : {kg.vectors_path}")
90
+ console.print(f" mode : {'full rebuild (wipes)' if wipe else 'incremental upsert'}")
91
+ console.print()
92
+
93
+ try:
94
+ if index_only:
95
+ console.print("[cyan]Building vector index...[/cyan]")
96
+ stats = kg.build_index(wipe=wipe)
97
+ elif graph_only:
98
+ console.print("[cyan]Building SQLite graph...[/cyan]")
99
+ stats = kg.build_graph(wipe=wipe)
100
+ else:
101
+ console.print("[cyan]Building graph + vector index...[/cyan]")
102
+ stats = kg.build(wipe=wipe)
103
+
104
+ console.print("[green]Done.[/green]")
105
+ console.print(str(stats))
106
+ except Exception as exc: # pylint: disable=broad-except
107
+ console.print(f"[red]Build failed:[/red] {exc}")
108
+ raise SystemExit(1) from exc
109
+
110
+
111
+ @click.command("build")
112
+ @_build_options
113
+ def build(
114
+ repo: str,
115
+ db: str | None,
116
+ vectors: str | None,
117
+ graph_only: bool,
118
+ index_only: bool,
119
+ ) -> None:
120
+ """Build knowledge graph from scratch: wipes existing data, then extracts
121
+ TypeScript/JS AST -> graph store -> vector index."""
122
+ _run(
123
+ repo=repo,
124
+ db=db,
125
+ vectors=vectors,
126
+ graph_only=graph_only,
127
+ index_only=index_only,
128
+ wipe=True,
129
+ )
130
+
131
+
132
+ @click.command("update")
133
+ @_build_options
134
+ def update(
135
+ repo: str,
136
+ db: str | None,
137
+ vectors: str | None,
138
+ graph_only: bool,
139
+ index_only: bool,
140
+ ) -> None:
141
+ """Update knowledge graph incrementally: upserts changes without wiping
142
+ existing data."""
143
+ _run(
144
+ repo=repo,
145
+ db=db,
146
+ vectors=vectors,
147
+ graph_only=graph_only,
148
+ index_only=index_only,
149
+ wipe=False,
150
+ )
151
+
152
+
153
+ # ---------------------------------------------------------------------------
154
+ # Granular stages, mirroring `pycodekg build-sqlite` / `pycodekg build-index`
155
+ # ---------------------------------------------------------------------------
156
+ # These reach the same two halves as `build --graph-only` / `build --index-only`,
157
+ # exposed under the names pycodekg uses so the two CLIs read alike. Unlike
158
+ # `build`/`update` they keep `--wipe`: a stage is a lower-level tool than a
159
+ # verb, and pycodekg's stages carry the flag too.
160
+ #
161
+ # One deliberate divergence: pycodekg names this option `--db` on build-sqlite
162
+ # but `--sqlite` on build-index, an inconsistency its own skill documents as a
163
+ # common mistake. Both spellings are accepted here, so muscle memory from
164
+ # either CLI works and neither is a trap.
165
+
166
+
167
+ @click.command("build-sqlite")
168
+ @click.option("--repo", default=".", show_default=True, help="Repository root directory.")
169
+ @click.option(
170
+ "--db",
171
+ "--sqlite",
172
+ "db",
173
+ default=None,
174
+ help="SQLite database path (default: <repo>/.tscodekg/graph.sqlite).",
175
+ )
176
+ @click.option(
177
+ "--wipe",
178
+ is_flag=True,
179
+ default=False,
180
+ help="Clear existing graph data before extracting.",
181
+ )
182
+ def build_sqlite(repo: str, db: str | None, wipe: bool) -> None:
183
+ """Extract a TypeScript/JS knowledge graph and store it in SQLite.
184
+
185
+ The graph half of `build`; skips the vector index.
186
+ """
187
+ _run(
188
+ repo=repo,
189
+ db=db,
190
+ vectors=None,
191
+ graph_only=True,
192
+ index_only=False,
193
+ wipe=wipe,
194
+ )
195
+
196
+
197
+ @click.command("build-index")
198
+ @click.option("--repo", default=".", show_default=True, help="Repository root directory.")
199
+ @click.option(
200
+ "--sqlite",
201
+ "--db",
202
+ "db",
203
+ default=None,
204
+ help="Path to the existing SQLite graph (default: <repo>/.tscodekg/graph.sqlite).",
205
+ )
206
+ @click.option(
207
+ "--vectors",
208
+ default=None,
209
+ help="sqlite-vec store path (default: <repo>/.tscodekg/vectors.sqlite).",
210
+ )
211
+ @click.option(
212
+ "--wipe",
213
+ is_flag=True,
214
+ default=False,
215
+ help="Clear the existing vector store before indexing.",
216
+ )
217
+ def build_index(repo: str, db: str | None, vectors: str | None, wipe: bool) -> None:
218
+ """Build the sqlite-vec semantic index from an existing SQLite graph.
219
+
220
+ The index half of `build`; the graph must already exist.
221
+ """
222
+ _run(
223
+ repo=repo,
224
+ db=db,
225
+ vectors=vectors,
226
+ graph_only=False,
227
+ index_only=True,
228
+ wipe=wipe,
229
+ )
@@ -0,0 +1,172 @@
1
+ """
2
+ cli/cmd_hooks.py — tscodekg install-hooks command.
3
+
4
+ install-hooks — install the pre-commit snapshot hook into .git/hooks/
5
+ """
6
+
7
+ from __future__ import annotations
8
+
9
+ import stat
10
+ from pathlib import Path
11
+
12
+ import click
13
+
14
+ # ---------------------------------------------------------------------------
15
+ # Hook script content (embedded so this module is self-contained when
16
+ # installed as a package in any repo, not just tscode_kg itself)
17
+ # ---------------------------------------------------------------------------
18
+
19
+ _PRE_COMMIT_HOOK = """\
20
+ #!/usr/bin/env bash
21
+ # TypeScriptKG pre-commit hook — runs quality checks. The index rebuild and
22
+ # metrics snapshot are opt-in and OFF by default; see "Why snapshots are off".
23
+ # Installed by: tscodekg install-hooks
24
+ #
25
+ # TSCODEKG_SNAPSHOT=1 git commit ... opt in to a per-commit snapshot
26
+ # TSCODEKG_SKIP_SNAPSHOT=1 git commit ... force snapshots off (wins)
27
+ #
28
+ # Note that TSCODEKG_SKIP_SNAPSHOT no longer skips the quality checks. It used
29
+ # to short-circuit the whole hook, so a variable named "skip snapshot" also
30
+ # silently skipped ruff, ty and pytest. It now gates only what it names.
31
+ #
32
+ # Why snapshots are off by default (2026-08-18)
33
+ # ---------------------------------------------
34
+ # A per-commit snapshot records `git write-tree` and is then itself staged into
35
+ # that same commit. Staging changes the index, so the recorded hash can never
36
+ # equal the tree it claims to describe — and manifest.json carries a
37
+ # `last_update` timestamp, so the `git add` is never a no-op. The drift is
38
+ # guaranteed by construction, not caused by formatting.
39
+ #
40
+ # An audit of 605 snapshots across 29 fleet manifests found 63 (10.4%) keyed to
41
+ # a tree any commit actually has. `snapshot diff` between adjacent entries has
42
+ # therefore been comparing states that never existed.
43
+ #
44
+ # The fix is to snapshot at release, keyed on the tag rather than on an
45
+ # ephemeral pre-commit tree. See kgrag_priv/docs/SNAPSHOT_STRATEGY.md. Until
46
+ # that lands, this hook runs quality checks only.
47
+ #
48
+ # This hook also used to run the rebuild and snapshot BEFORE the quality
49
+ # checks, which is the opposite of what it should do:
50
+ #
51
+ # * `pre-commit run` stashes unstaged changes and restores them afterwards.
52
+ # Building first meant the freshly-rewritten snapshots/manifest.json landed
53
+ # inside the stash window, where the restore could fail with "patch does not
54
+ # apply" and abort the commit outright — or, worse, let a staged deletion of
55
+ # a tracked snapshot slip into the commit.
56
+ # * There is no reason to pay for an index rebuild on a commit that
57
+ # ruff/ty/pytest is about to reject.
58
+ set -euo pipefail
59
+
60
+ REPO_ROOT="$(git rev-parse --show-toplevel)"
61
+ cd "$REPO_ROOT"
62
+
63
+ # Quality checks first (ruff, ty, pytest, detect-secrets, ...). Delegates to
64
+ # .pre-commit-config.yaml so quality checks stay in one place. A hook that
65
+ # rewrites files also exits non-zero here, so we never index a tree that is
66
+ # about to be reformatted.
67
+ PRECOMMIT="$REPO_ROOT/.venv/bin/pre-commit"
68
+ if [ -x "$PRECOMMIT" ]; then
69
+ "$PRECOMMIT" run || exit 1
70
+ elif command -v pre-commit &>/dev/null; then
71
+ pre-commit run || exit 1
72
+ fi
73
+
74
+ # ---------------------------------------------------------------------------
75
+ # Opt-in index rebuild + snapshot. Everything below is skipped unless
76
+ # TSCODEKG_SNAPSHOT=1 is set, and is skipped regardless if
77
+ # TSCODEKG_SKIP_SNAPSHOT=1.
78
+ # ---------------------------------------------------------------------------
79
+ [ "${TSCODEKG_SNAPSHOT:-0}" = "1" ] || exit 0
80
+ [ "${TSCODEKG_SKIP_SNAPSHOT:-0}" = "1" ] && exit 0
81
+
82
+ # Resolve the tscodekg binary: prefer the repo's .venv, fall back to PATH.
83
+ if [ -x "$REPO_ROOT/.venv/bin/tscodekg" ]; then
84
+ TSCODEKG="$REPO_ROOT/.venv/bin/tscodekg"
85
+ elif command -v tscodekg &>/dev/null; then
86
+ TSCODEKG="tscodekg"
87
+ else
88
+ echo "[tscodekg] binary not found — skipping snapshot" >&2
89
+ exit 0
90
+ fi
91
+
92
+ # Captured after the checks so nothing further modifies the working tree. Note
93
+ # the caveat above: this still cannot match the committed tree, because the
94
+ # `git add` below changes the index after this point.
95
+ TREE_HASH=$(git write-tree)
96
+ BRANCH=$(git rev-parse --abbrev-ref HEAD)
97
+
98
+ # Rebuild the local index to keep it in sync with staged content.
99
+ "$TSCODEKG" build --repo "$REPO_ROOT" || exit 1
100
+
101
+ # Snapshot TypeScriptKG (version auto-detected from installed package).
102
+ "$TSCODEKG" snapshot save \\
103
+ --repo . \\
104
+ --tree-hash "$TREE_HASH" \\
105
+ --branch "$BRANCH" \\
106
+ || { echo "[tscodekg] snapshot skipped (run 'tscodekg build' to initialize)" >&2; }
107
+
108
+ # Stage the snapshot directory so it is included in the commit. These files are
109
+ # added after `pre-commit run`, so they are not scanned by it — detect-secrets
110
+ # already excludes snapshots/ by config, which is why that is safe.
111
+ git add .tscodekg/snapshots/ 2>/dev/null || true
112
+
113
+ exit 0
114
+ """
115
+
116
+
117
+ @click.command("install-hooks")
118
+ @click.option(
119
+ "--repo",
120
+ default=".",
121
+ type=click.Path(exists=True),
122
+ show_default=True,
123
+ help="Repository root.",
124
+ )
125
+ @click.option(
126
+ "--force",
127
+ is_flag=True,
128
+ help="Overwrite an existing pre-commit hook.",
129
+ )
130
+ def install_hooks(repo: str, force: bool) -> None:
131
+ """Install the TypeScriptKG pre-commit git hook.
132
+
133
+ After installation, before each commit:
134
+
135
+ \b
136
+ 1. Rebuilds the local TypeScriptKG index (full wipe)
137
+ 2. Captures a metrics snapshot keyed by tree hash
138
+ 3. Stages the snapshot directory atomically
139
+ 4. Runs the pre-commit framework checks
140
+
141
+ This keeps the index in sync and ensures snapshots reflect the state of
142
+ the knowledge graph at commit time.
143
+
144
+ Example:
145
+ tscodekg install-hooks --repo .
146
+ """
147
+ repo_root = Path(repo).resolve()
148
+ git_dir = repo_root / ".git"
149
+
150
+ if not git_dir.is_dir():
151
+ click.echo(f"Error: {repo_root} is not a git repository.", err=True)
152
+ raise SystemExit(1)
153
+
154
+ hooks_dir = git_dir / "hooks"
155
+ hooks_dir.mkdir(exist_ok=True)
156
+ hook_path = hooks_dir / "pre-commit"
157
+
158
+ if hook_path.exists() and not force:
159
+ click.echo(f"Hook already exists: {hook_path}")
160
+ click.echo("Use --force to overwrite.")
161
+ raise SystemExit(1)
162
+
163
+ hook_path.write_text(_PRE_COMMIT_HOOK)
164
+ mode = hook_path.stat().st_mode | stat.S_IXUSR | stat.S_IXGRP | stat.S_IXOTH
165
+ hook_path.chmod(mode)
166
+
167
+ click.echo(f"OK Installed pre-commit hook: {hook_path}")
168
+ click.echo(" Quality checks run on every commit.")
169
+ click.echo(" Snapshots are OFF by default - see kgrag_priv/docs/SNAPSHOT_STRATEGY.md.")
170
+ click.echo(" Opt in with: TSCODEKG_SNAPSHOT=1 git commit ...")
171
+ click.echo(" Force off: TSCODEKG_SKIP_SNAPSHOT=1 git commit ...")
172
+ click.echo(" Run 'tscodekg build' first if you haven't built the graph yet.")
@@ -62,7 +62,13 @@ def _default_snapshots_dir(snapshots_dir: str | None, repo_root: Path | None = N
62
62
  "--tree-hash",
63
63
  default="",
64
64
  type=str,
65
- help="Git tree hash; auto-detected if not provided.",
65
+ help="Git tree hash, recorded as provenance; auto-detected if not provided.",
66
+ )
67
+ @click.option(
68
+ "--subject",
69
+ default="",
70
+ type=str,
71
+ help="What was measured, e.g. 'repo:tscode-kg'.",
66
72
  )
67
73
  def save_snapshot(
68
74
  version: str | None,
@@ -71,20 +77,23 @@ def save_snapshot(
71
77
  snapshots_dir: str | None,
72
78
  branch: str | None,
73
79
  tree_hash: str,
80
+ subject: str,
74
81
  ) -> None:
75
82
  """
76
83
  Capture current TypeScriptKG metrics and save as a temporal snapshot.
77
84
 
78
85
  Reads graph statistics and JSDoc coverage from the SQLite graph, runs the
79
- analyzer for issue counts and hotspots, then saves a snapshot tagged with
80
- the given VERSION. The tree hash is auto-detected from git when not
81
- provided.
86
+ analyzer for issue counts and hotspots, then saves a snapshot keyed on
87
+ VERSION. Omit VERSION and the snapshot is keyed on a UTC timestamp, which
88
+ is the right answer for a corpus with no release tag. The tree hash is
89
+ recorded as provenance and auto-detected from git when not provided; it
90
+ is not the key.
82
91
 
83
- Snapshots are stored in .tscodekg/snapshots/{tree_hash}.json, with a
92
+ Snapshots are stored in .tscodekg/snapshots/{key}.json, with a
84
93
  manifest.json tracking all snapshots and their metrics.
85
94
 
86
95
  Example:
87
- tscodekg snapshot save 0.1.0 --repo .
96
+ tscodekg snapshot save 0.1.0 --repo . --subject repo:tscode-kg
88
97
  """
89
98
  capture_snapshot(
90
99
  version=version,
@@ -93,6 +102,7 @@ def save_snapshot(
93
102
  snapshots_dir=snapshots_dir,
94
103
  branch=branch,
95
104
  tree_hash=tree_hash,
105
+ subject=subject,
96
106
  )
97
107
 
98
108
 
@@ -104,15 +114,22 @@ def capture_snapshot(
104
114
  snapshots_dir: str | None,
105
115
  branch: str | None,
106
116
  tree_hash: str,
117
+ subject: str = "",
107
118
  ) -> None:
108
119
  """Capture and persist a snapshot; shared by ``snapshot save`` and ``init``.
109
120
 
110
- :param version: Version tag; auto-detected from the package when falsy.
121
+ :param version: Release tag, becoming the snapshot's key; auto-detected
122
+ from the installed package when falsy, which then yields a UTC
123
+ timestamp key instead -- the right answer for a corpus, not a repo
124
+ release. Never used as the key without being explicitly passed here.
111
125
  :param repo: Repository root path.
112
126
  :param db: SQLite graph path; defaults to ``<repo>/.tscodekg/graph.sqlite``.
113
127
  :param snapshots_dir: Snapshots directory; defaults to ``<repo>/.tscodekg/snapshots``.
114
128
  :param branch: Branch name; auto-detected when ``None``.
115
- :param tree_hash: Git tree hash; auto-detected when empty.
129
+ :param tree_hash: Git tree hash, recorded as provenance; auto-detected
130
+ when empty. It is not the snapshot's key.
131
+ :param subject: What was measured, e.g. ``repo:tscode-kg``. Recorded
132
+ separately from ``version``, which names the measuring tool.
116
133
  """
117
134
  from tscode_kg.kg import TypeScriptKG # pylint: disable=import-outside-toplevel
118
135
  from tscode_kg.snapshots import SnapshotManager # pylint: disable=import-outside-toplevel
@@ -171,6 +188,10 @@ def capture_snapshot(
171
188
  hotspots=hotspots,
172
189
  issues=issue_strings,
173
190
  tree_hash=tree_hash,
191
+ # An explicit VERSION is a release tag and becomes the key. An
192
+ # auto-detected one is the measuring tool's version and must not be.
193
+ key=version or "",
194
+ subject=subject,
174
195
  )
175
196
 
176
197
  snapshot_file = snap_mgr.save_snapshot(snapshot_obj)
@@ -19,7 +19,7 @@ import click
19
19
 
20
20
  from tscode_kg.cli.cmd_analyze import analyze
21
21
  from tscode_kg.cli.cmd_bridges import bridges
22
- from tscode_kg.cli.cmd_build import build
22
+ from tscode_kg.cli.cmd_build import build, build_index, build_sqlite, update
23
23
  from tscode_kg.cli.cmd_centrality import centrality
24
24
  from tscode_kg.cli.cmd_explain import explain
25
25
  from tscode_kg.cli.cmd_framework_nodes import framework_nodes
@@ -40,6 +40,9 @@ def cli() -> None:
40
40
 
41
41
  cli.add_command(init)
42
42
  cli.add_command(build)
43
+ cli.add_command(update)
44
+ cli.add_command(build_sqlite)
45
+ cli.add_command(build_index)
43
46
  cli.add_command(query)
44
47
  cli.add_command(pack)
45
48
  cli.add_command(analyze)
@@ -8,15 +8,19 @@ those types and adds a ``SnapshotManager`` subclass that defaults
8
8
  ``package_name`` to ``"tscode-kg"`` so version auto-detection resolves to this
9
9
  package.
10
10
 
11
- Snapshots are stored in ``.tscodekg/snapshots/{tree_hash}.json`` with a
12
- ``manifest.json`` tracking all snapshots and their metrics the same layout
13
- PyCodeKG uses under ``.pycodekg/snapshots/``.
11
+ Snapshots are keyed on a caller-supplied release tag or, absent one, a UTC
12
+ timestamp -- never a git tree hash, which is read before ``git add`` stages
13
+ the snapshot and so names a tree that is never committed. Stored in
14
+ ``.tscodekg/snapshots/{key}.json`` with a ``manifest.json`` tracking all
15
+ snapshots and their metrics — the same layout PyCodeKG uses under
16
+ ``.pycodekg/snapshots/``.
14
17
 
15
18
  Usage
16
19
  -----
17
20
  >>> from tscode_kg.snapshots import SnapshotManager
18
21
  >>> mgr = SnapshotManager(".tscodekg/snapshots")
19
- >>> snapshot = mgr.capture(version="0.1.0", branch="develop", graph_stats_dict=stats)
22
+ >>> snapshot = mgr.capture(version="0.4.0", branch="main", key="0.4.0",
23
+ ... subject="repo:tscode-kg", graph_stats_dict=stats)
20
24
  >>> mgr.save_snapshot(snapshot)
21
25
  >>> manifest = mgr.load_manifest()
22
26
 
tscode_kg-0.2.0/LICENSE DELETED
@@ -1,24 +0,0 @@
1
- BSD 2-Clause License
2
-
3
- Copyright (c) 2026, Flux-Frontiers
4
-
5
- Redistribution and use in source and binary forms, with or without
6
- modification, are permitted provided that the following conditions are met:
7
-
8
- 1. Redistributions of source code must retain the above copyright notice, this
9
- list of conditions and the following disclaimer.
10
-
11
- 2. Redistributions in binary form must reproduce the above copyright notice,
12
- this list of conditions and the following disclaimer in the documentation
13
- and/or other materials provided with the distribution.
14
-
15
- THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
16
- AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
17
- IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
18
- DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
19
- FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
20
- DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
21
- SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
22
- CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
23
- OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
24
- OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
@@ -1,86 +0,0 @@
1
- """
2
- cli/cmd_build.py — tscodekg build command.
3
-
4
- Builds the SQLite graph and sqlite-vec vector index from a TypeScript/JS repo.
5
- """
6
-
7
- from __future__ import annotations
8
-
9
- from pathlib import Path
10
-
11
- import click
12
- from rich.console import Console
13
-
14
- console = Console()
15
-
16
-
17
- @click.command("build")
18
- @click.option("--repo", default=".", show_default=True, help="Repository root directory.")
19
- @click.option(
20
- "--db",
21
- default=None,
22
- help="SQLite database path (default: <repo>/.tscodekg/graph.sqlite).",
23
- )
24
- @click.option(
25
- "--vectors",
26
- default=None,
27
- help="sqlite-vec store path (default: <repo>/.tscodekg/vectors.sqlite).",
28
- )
29
- @click.option("--wipe", is_flag=True, default=False, help="Clear existing data before building.")
30
- @click.option(
31
- "--graph-only",
32
- is_flag=True,
33
- default=False,
34
- help="Build SQLite graph only; skip vector index.",
35
- )
36
- @click.option(
37
- "--index-only",
38
- is_flag=True,
39
- default=False,
40
- help="Build vector index only; graph must already exist.",
41
- )
42
- def build(
43
- repo: str,
44
- db: str | None,
45
- vectors: str | None,
46
- wipe: bool,
47
- graph_only: bool,
48
- index_only: bool,
49
- ) -> None:
50
- """Build the TypeScript/JS knowledge graph for a repository."""
51
- from tscode_kg.kg import TypeScriptKG # pylint: disable=import-outside-toplevel
52
-
53
- repo_path = Path(repo).resolve()
54
- if not repo_path.is_dir():
55
- console.print(f"[red]Error:[/red] Repository not found: {repo_path}")
56
- raise SystemExit(1)
57
-
58
- kg = TypeScriptKG(
59
- repo_root=repo_path,
60
- db_path=db,
61
- vectors_path=vectors,
62
- )
63
-
64
- console.print("[bold]TypeScriptKG build[/bold]")
65
- console.print(f" repo : {repo_path}")
66
- console.print(f" db : {kg.db_path}")
67
- console.print(f" vectors : {kg.vectors_path}")
68
- console.print(f" wipe : {wipe}")
69
- console.print()
70
-
71
- try:
72
- if index_only:
73
- console.print("[cyan]Building vector index...[/cyan]")
74
- stats = kg.build_index(wipe=wipe)
75
- elif graph_only:
76
- console.print("[cyan]Building SQLite graph...[/cyan]")
77
- stats = kg.build_graph(wipe=wipe)
78
- else:
79
- console.print("[cyan]Building graph + vector index...[/cyan]")
80
- stats = kg.build(wipe=wipe)
81
-
82
- console.print("[green]Done.[/green]")
83
- console.print(str(stats))
84
- except Exception as exc: # pylint: disable=broad-except
85
- console.print(f"[red]Build failed:[/red] {exc}")
86
- raise SystemExit(1) from exc
@@ -1,125 +0,0 @@
1
- """
2
- cli/cmd_hooks.py — tscodekg install-hooks command.
3
-
4
- install-hooks — install the pre-commit snapshot hook into .git/hooks/
5
- """
6
-
7
- from __future__ import annotations
8
-
9
- import stat
10
- from pathlib import Path
11
-
12
- import click
13
-
14
- # ---------------------------------------------------------------------------
15
- # Hook script content (embedded so this module is self-contained when
16
- # installed as a package in any repo, not just tscode_kg itself)
17
- # ---------------------------------------------------------------------------
18
-
19
- _PRE_COMMIT_HOOK = """\
20
- #!/usr/bin/env bash
21
- # TypeScriptKG pre-commit hook — keeps the local index in sync and captures
22
- # metrics snapshots BEFORE quality checks run.
23
- # Installed by: tscodekg install-hooks
24
- # Skip with: TSCODEKG_SKIP_SNAPSHOT=1 git commit ...
25
- set -euo pipefail
26
-
27
- [ "${TSCODEKG_SKIP_SNAPSHOT:-0}" = "1" ] && exit 0
28
-
29
- REPO_ROOT="$(git rev-parse --show-toplevel)"
30
- cd "$REPO_ROOT"
31
-
32
- # Resolve the tscodekg binary: prefer the repo's .venv, fall back to PATH.
33
- if [ -x "$REPO_ROOT/.venv/bin/tscodekg" ]; then
34
- TSCODEKG="$REPO_ROOT/.venv/bin/tscodekg"
35
- elif command -v tscodekg &>/dev/null; then
36
- TSCODEKG="tscodekg"
37
- else
38
- echo "[tscodekg] binary not found — skipping snapshot hook" >&2
39
- exit 0
40
- fi
41
-
42
- # Capture the tree hash of the staged index NOW — before any tool modifies files.
43
- TREE_HASH=$(git write-tree)
44
- BRANCH=$(git rev-parse --abbrev-ref HEAD)
45
-
46
- # Rebuild the local index to keep it in sync with staged content.
47
- "$TSCODEKG" build --repo "$REPO_ROOT" || exit 1
48
-
49
- # Snapshot TypeScriptKG (version auto-detected from installed package).
50
- "$TSCODEKG" snapshot save \\
51
- --repo . \\
52
- --tree-hash "$TREE_HASH" \\
53
- --branch "$BRANCH" \\
54
- || { echo "[tscodekg] snapshot skipped (run 'tscodekg build' to initialize)" >&2; }
55
-
56
- # Stage the snapshot directory so it is included in the commit.
57
- git add .tscodekg/snapshots/ 2>/dev/null || true
58
-
59
- # Run pre-commit framework checks (ruff, ty, detect-secrets, etc.) AFTER
60
- # snapshots are captured and staged. Delegates to .pre-commit-config.yaml so
61
- # quality checks stay in one place.
62
- PRECOMMIT="$REPO_ROOT/.venv/bin/pre-commit"
63
- if [ -x "$PRECOMMIT" ]; then
64
- "$PRECOMMIT" run || exit 1
65
- elif command -v pre-commit &>/dev/null; then
66
- pre-commit run || exit 1
67
- fi
68
-
69
- exit 0
70
- """
71
-
72
-
73
- @click.command("install-hooks")
74
- @click.option(
75
- "--repo",
76
- default=".",
77
- type=click.Path(exists=True),
78
- show_default=True,
79
- help="Repository root.",
80
- )
81
- @click.option(
82
- "--force",
83
- is_flag=True,
84
- help="Overwrite an existing pre-commit hook.",
85
- )
86
- def install_hooks(repo: str, force: bool) -> None:
87
- """Install the TypeScriptKG pre-commit git hook.
88
-
89
- After installation, before each commit:
90
-
91
- \b
92
- 1. Rebuilds the local TypeScriptKG index (full wipe)
93
- 2. Captures a metrics snapshot keyed by tree hash
94
- 3. Stages the snapshot directory atomically
95
- 4. Runs the pre-commit framework checks
96
-
97
- This keeps the index in sync and ensures snapshots reflect the state of
98
- the knowledge graph at commit time.
99
-
100
- Example:
101
- tscodekg install-hooks --repo .
102
- """
103
- repo_root = Path(repo).resolve()
104
- git_dir = repo_root / ".git"
105
-
106
- if not git_dir.is_dir():
107
- click.echo(f"Error: {repo_root} is not a git repository.", err=True)
108
- raise SystemExit(1)
109
-
110
- hooks_dir = git_dir / "hooks"
111
- hooks_dir.mkdir(exist_ok=True)
112
- hook_path = hooks_dir / "pre-commit"
113
-
114
- if hook_path.exists() and not force:
115
- click.echo(f"Hook already exists: {hook_path}")
116
- click.echo("Use --force to overwrite.")
117
- raise SystemExit(1)
118
-
119
- hook_path.write_text(_PRE_COMMIT_HOOK)
120
- mode = hook_path.stat().st_mode | stat.S_IXUSR | stat.S_IXGRP | stat.S_IXOTH
121
- hook_path.chmod(mode)
122
-
123
- click.echo(f"OK Installed pre-commit hook: {hook_path}")
124
- click.echo(" Snapshots will be captured automatically before each commit.")
125
- click.echo(" Run 'tscodekg build' first if you haven't built the graph yet.")
File without changes