MertCapkin-GraphStack 4.5.1__py3-none-any.whl

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 (57) hide show
  1. graphstack/__init__.py +12 -0
  2. graphstack/__main__.py +10 -0
  3. graphstack/assets/docs/CURSOR_PROMPTS.md +215 -0
  4. graphstack/assets/handoff/BOOTSTRAP.md +73 -0
  5. graphstack/assets/handoff/BRIEF.md +66 -0
  6. graphstack/assets/handoff/REVIEW.md +7 -0
  7. graphstack/assets/handoff/board/README.md +60 -0
  8. graphstack/assets/orchestrator/ORCHESTRATOR.md +416 -0
  9. graphstack/assets/orchestrator/TOKEN_OPTIMIZER.md +319 -0
  10. graphstack/assets/scripts/board.ps1 +37 -0
  11. graphstack/assets/scripts/board.sh +22 -0
  12. graphstack/assets/scripts/gate-hook.ps1 +41 -0
  13. graphstack/assets/scripts/gate-hook.sh +26 -0
  14. graphstack/assets/scripts/post-commit +20 -0
  15. graphstack/assets/scripts/post-commit.ps1 +44 -0
  16. graphstack/board.py +361 -0
  17. graphstack/bootstrap.py +50 -0
  18. graphstack/cli.py +99 -0
  19. graphstack/compact/__init__.py +9 -0
  20. graphstack/compact/__pycache__/__init__.cpython-311.pyc +0 -0
  21. graphstack/compact/__pycache__/base.cpython-311.pyc +0 -0
  22. graphstack/compact/__pycache__/generic.cpython-311.pyc +0 -0
  23. graphstack/compact/__pycache__/git.cpython-311.pyc +0 -0
  24. graphstack/compact/__pycache__/registry.cpython-311.pyc +0 -0
  25. graphstack/compact/base.py +115 -0
  26. graphstack/compact/generic.py +90 -0
  27. graphstack/compact/git.py +167 -0
  28. graphstack/compact/registry.py +47 -0
  29. graphstack/constants.py +38 -0
  30. graphstack/gate.py +429 -0
  31. graphstack/graph.py +143 -0
  32. graphstack/hook.py +144 -0
  33. graphstack/init_cmd.py +113 -0
  34. graphstack/installer.py +366 -0
  35. graphstack/platform_utils.py +127 -0
  36. graphstack/run.py +103 -0
  37. graphstack/state.py +117 -0
  38. graphstack/tests/__init__.py +0 -0
  39. graphstack/tests/conftest.py +30 -0
  40. graphstack/tests/test_assets.py +35 -0
  41. graphstack/tests/test_board.py +166 -0
  42. graphstack/tests/test_compact.py +93 -0
  43. graphstack/tests/test_gate.py +406 -0
  44. graphstack/tests/test_graph.py +60 -0
  45. graphstack/tests/test_hook.py +57 -0
  46. graphstack/tests/test_init.py +58 -0
  47. graphstack/tests/test_installer.py +73 -0
  48. graphstack/tests/test_platform_utils.py +69 -0
  49. graphstack/tests/test_state.py +56 -0
  50. graphstack/tests/test_validate.py +204 -0
  51. graphstack/validate.py +469 -0
  52. mertcapkin_graphstack-4.5.1.dist-info/METADATA +720 -0
  53. mertcapkin_graphstack-4.5.1.dist-info/RECORD +57 -0
  54. mertcapkin_graphstack-4.5.1.dist-info/WHEEL +5 -0
  55. mertcapkin_graphstack-4.5.1.dist-info/entry_points.txt +2 -0
  56. mertcapkin_graphstack-4.5.1.dist-info/licenses/LICENSE +21 -0
  57. mertcapkin_graphstack-4.5.1.dist-info/top_level.txt +1 -0
@@ -0,0 +1,319 @@
1
+ # Token Optimizer
2
+
3
+ GraphStack's token optimization layer. These rules are enforced across ALL roles.
4
+
5
+ ---
6
+
7
+ ## The Core Principle
8
+
9
+ > The graph is a compression of your codebase.
10
+ > Reading the graph = reading the codebase, at 1/10th the cost.
11
+ > Every raw file read that the graph could have answered is waste.
12
+
13
+ ---
14
+
15
+ ## Session Budget Tracker
16
+
17
+ At session start, initialize mentally:
18
+
19
+ ```
20
+ SESSION BUDGET
21
+ ──────────────
22
+ Graph reads: 1 allowed (GRAPH_REPORT.md) — FREE
23
+ Brief reads: 1 allowed (BRIEF.md) — FREE
24
+ Raw file reads: track each one
25
+ Re-reads: 0 allowed
26
+ Speculative: 0 allowed
27
+ ```
28
+
29
+ ---
30
+
31
+ ## Decision Tree (Run Before Every Tool Call)
32
+
33
+ ```
34
+ Before reading a file:
35
+ ├─ Is it GRAPH_REPORT.md?
36
+ │ ├─ Already read this session? → SKIP (use context)
37
+ │ └─ Not yet read? → READ (Tier 1, free)
38
+
39
+ ├─ Is it in handoff/BRIEF.md's file list?
40
+ │ └─ YES → READ (Tier 2, justified)
41
+
42
+ ├─ Can graph.json answer this question?
43
+ │ ├─ YES → QUERY GRAPH (Tier 1, free)
44
+ │ └─ NO → continue...
45
+
46
+ ├─ Is this file already in my context window?
47
+ │ └─ YES → SKIP (use context — re-read is banned)
48
+
49
+ ├─ Do I need the WHOLE file or just one function?
50
+ │ ├─ One function → READ TARGETED SECTION ONLY (Tier 2)
51
+ │ └─ Whole file → JUSTIFY FIRST
52
+ │ └─ Can I get away with the graph summary? → try graph first
53
+
54
+ └─ Is this a "just to be sure" read?
55
+ └─ YES → CANCEL (speculative reads banned)
56
+ ```
57
+
58
+ ---
59
+
60
+ ## Shell Output (Mandatory for Agents)
61
+
62
+ Every **Shell / terminal** tool call that runs git, tests, linters, or package managers MUST use GraphStack compaction — not raw commands.
63
+
64
+ **Default:**
65
+ ```bash
66
+ python -m graphstack run -- git status
67
+ python -m graphstack run -- git diff
68
+ python -m graphstack run -- git log -n 20 --oneline
69
+ python -m graphstack run -- pytest -q
70
+ ```
71
+
72
+ **Quality escape hatch** (debug only — wastes tokens):
73
+ ```bash
74
+ python -m graphstack run --raw -- git diff
75
+ ```
76
+
77
+ ### What compactors preserve (never sacrifice for size)
78
+
79
+ - File paths, branch names, commit hashes
80
+ - Diff hunk headers (`@@`) and changed lines (`+` / `-`)
81
+ - Test failures, tracebacks, `FAILED` / `ERROR` lines
82
+ - stderr (always appended verbatim)
83
+
84
+ If compaction would drop critical signal, the tool **falls back to raw stdout** automatically.
85
+
86
+ ### Tier placement
87
+
88
+ | Action | Tier |
89
+ |--------|------|
90
+ | `graphstack run -- git status` | Tier 2 — cheap, preferred |
91
+ | Raw `git status` in shell | Tier 3 — avoid unless `run` unavailable |
92
+ | `graphstack run --raw` | Tier 3 — justified for deep debug |
93
+
94
+ ---
95
+
96
+ ## Graph Query Patterns
97
+
98
+ **Default:** use the GraphStack wrapper (same graphify engine, consistent argv):
99
+
100
+ ```bash
101
+ python -m graphstack graph query "who calls login"
102
+ python -m graphstack graph query "blast radius of crypto.ts" --budget 1500
103
+ python -m graphstack graph path src/auth/login.ts src/utils/crypto.ts
104
+ python -m graphstack graph explain "login()"
105
+ python -m graphstack graph update . # AST-only refresh after code changes
106
+ ```
107
+
108
+ Only read `GRAPH_REPORT.md` once per session for broad architecture overview.
109
+ For targeted questions, **always query first** — never grep or read raw files speculatively.
110
+
111
+ ### Manual graph.json patterns (fallback only)
112
+
113
+ These answer common questions WITHOUT reading files when `graph query` is unavailable:
114
+
115
+ ### "What does this module import?"
116
+ ```
117
+ graph.json → node["src/auth/login.ts"].edges.filter(e => e.type === "imports")
118
+ ```
119
+
120
+ ### "Who calls this function?"
121
+ ```
122
+ graph.json → nodes.filter(n => n.edges.some(e => e.target === "login" && e.type === "calls"))
123
+ ```
124
+
125
+ ### "What modules are in this cluster?"
126
+ ```
127
+ GRAPH_REPORT.md → cluster section → find cluster containing target
128
+ ```
129
+
130
+ ### "What's the blast radius of changing X?"
131
+ ```
132
+ graph.json → BFS from node X, depth 2, outgoing edges only
133
+ ```
134
+
135
+ ### "Are there tests for this?"
136
+ ```
137
+ graph.json → node["src/auth/login.ts"].edges.filter(e => e.type === "tested_by")
138
+ ```
139
+
140
+ ### "What pattern does the codebase use for Y?"
141
+ ```
142
+ GRAPH_REPORT.md → patterns section
143
+ graph.json → find 3 nodes in same cluster as Y → read ONE as example
144
+ ```
145
+
146
+ ---
147
+
148
+ ## Parallel Read Protocol
149
+
150
+ When 2+ files must be read, NEVER read sequentially.
151
+
152
+ **Wrong (2x cost):**
153
+ ```
154
+ read(src/auth/login.ts)
155
+ read(src/auth/session.ts)
156
+ ```
157
+
158
+ **Right (1x cost):**
159
+ ```
160
+ read([src/auth/login.ts, src/auth/session.ts]) // one tool call
161
+ ```
162
+
163
+ Rule: If you know you need N files, request all N in one call.
164
+
165
+ ---
166
+
167
+ ## Output Compression Rules
168
+
169
+ ### Never produce these:
170
+ - Restatement of user's request ("You want me to add a login feature...")
171
+ - Transition announcements over 2 lines ("Now I will switch to the Builder role and begin implementing...")
172
+ - Ellipsis thinking ("Let me think about this...")
173
+ - Excessive role headers (one short `[ROLE MODE]` tag is enough)
174
+ - Full file contents when a diff is sufficient
175
+ - Full function when a summary is sufficient
176
+
177
+ ### Always prefer:
178
+ - Diffs over full files
179
+ - Summaries over full reads
180
+ - One-line status over paragraphs
181
+ - Tables over bullet lists for comparisons
182
+ - "✓" / "✗" over "passed" / "failed"
183
+
184
+ ---
185
+
186
+ ## Context Window Rules
187
+
188
+ ### What stays in context (never re-read):
189
+ - GRAPH_REPORT.md content
190
+ - BRIEF.md content
191
+ - Any file read this session
192
+
193
+ ### What gets summarized out (to free context):
194
+ - Intermediate reasoning
195
+ - Rejected alternatives
196
+ - Verbose error messages (summarize to one line)
197
+
198
+ ### When context is getting full:
199
+ ```
200
+ "Context at ~80% capacity. Summarizing intermediate state to handoff/STATE.md.
201
+ Continuing from current role."
202
+ ```
203
+
204
+ ---
205
+
206
+ ## Estimated Token Savings by Pattern
207
+
208
+ | Old Pattern | New Pattern | Savings |
209
+ |-------------|-------------|---------|
210
+ | Read 10 files to understand architecture | Read GRAPH_REPORT.md | ~85% |
211
+ | Re-read file already in context | Use existing context | 100% |
212
+ | Sequential file reads | Parallel reads | ~50% |
213
+ | Full file read for one function | Targeted section read | ~70% |
214
+ | Speculative read "just in case" | Query graph first | ~90% |
215
+ | Chat history for state | STATE.md file | ~60% per new session |
216
+
217
+ ---
218
+
219
+ ## Graph Update Strategy
220
+
221
+ ### When to Update (Smart Triggers)
222
+
223
+ The post-commit hook enforces these rules automatically. Manually apply the same logic:
224
+
225
+ ```
226
+ TRIGGER 1 — Structural change (highest priority)
227
+ Any file added or deleted (outside graphify-out/ and handoff/)
228
+ → Always update, immediately
229
+ → Reason: new nodes or missing nodes break graph queries
230
+
231
+ TRIGGER 2 — Ship completed
232
+ A cycle or feature just shipped
233
+ → Always update after Ship role completes
234
+ → Reason: the next Architect/Bootstrapper reads this graph
235
+
236
+ TRIGGER 3 — Staleness
237
+ graph.json is >24 hours old
238
+ → Update at start of next session
239
+ → Reason: accumulated small changes may have shifted architecture
240
+
241
+ NOT A TRIGGER — Content edits only
242
+ Modifying existing functions, fixing bugs, updating tests
243
+ → Do NOT update graph unless 24h threshold reached
244
+ → Reason: graph topology unchanged; existing queries still valid
245
+ ```
246
+
247
+ ### Why Not Update Every Commit?
248
+
249
+ Graphify runs locally (tree-sitter, no API calls) so it has no token cost. But:
250
+
251
+ 1. **Time cost** — update takes 5-30 seconds on large repos
252
+ 2. **Noise cost** — frequent graph commits clutter git history
253
+ 3. **Diminishing returns** — a bug fix in an existing function doesn't change who imports what
254
+
255
+ **The graph represents structure, not content.** Content changes don't require a new graph.
256
+
257
+ ### Manual Update Commands
258
+
259
+ ```bash
260
+ # Force update now (run in Cursor or terminal)
261
+ /graphify --update
262
+
263
+ # Check graph age
264
+ ls -la graphify-out/GRAPH_REPORT.md
265
+
266
+ # See what changed since last graph update
267
+ git diff $(git log --all --oneline -- graphify-out/graph.json | head -1 | cut -d' ' -f1) -- src/
268
+ ```
269
+
270
+ ### Bootstrap Mode Graph Schedule
271
+
272
+ During Bootstrap cycles, the Orchestrator enforces this manually:
273
+
274
+ ```
275
+ Cycle 1 → Ship → STOP → "Run /graphify . now" → wait for confirmation → Cycle 2 brief
276
+ Cycle N → Ship → STOP → "Run /graphify --update now" → wait → Cycle N+1 brief
277
+ ```
278
+
279
+ Never skip this step in bootstrap mode. The next brief depends on the real graph.
280
+
281
+ ---
282
+
283
+ ## Project Type Suitability
284
+
285
+ GraphStack token savings scale with **codebase complexity and query frequency**.
286
+
287
+ ```
288
+ High value (>60% savings):
289
+ ✅ REST/GraphQL APIs ✅ Data pipelines
290
+ ✅ Monolithic web apps ✅ Libraries/SDKs
291
+ ✅ Game backends ✅ CLI tools (medium+)
292
+ ✅ Microservices ✅ Admin panels
293
+
294
+ Medium value (30-60% savings):
295
+ 🟡 React/Vue SPAs 🟡 Mobile apps (React Native)
296
+ 🟡 Unity games (C#) 🟡 TypeScript monorepos
297
+ 🟡 Serverless functions 🟡 E-commerce backends
298
+
299
+ Low value (<30% savings):
300
+ 🔴 Static sites 🔴 Single-file scripts
301
+ 🔴 Jupyter notebooks 🔴 Rapid prototypes (<20 files)
302
+ 🔴 Godot (GDScript) 🔴 Flutter (widget-heavy)
303
+
304
+ The threshold: GraphStack pays off when your codebase exceeds ~20 files
305
+ and queries regularly cross module boundaries.
306
+ ```
307
+
308
+ ```
309
+ ❌ Reading GRAPH_REPORT.md more than once per session
310
+ ❌ Reading a file to "explore" without a specific question
311
+ ❌ Asking the user a question you could answer from the graph
312
+ ❌ Reading all files in a directory to find the right one
313
+ (use graph.json node lookup instead)
314
+ ❌ Producing a plan before reading the graph
315
+ ❌ Re-reading a file because you forgot its contents
316
+ (summarize key facts to STATE.md before losing context)
317
+ ❌ Writing code before checking graph dependencies
318
+ ❌ Reviewing code without checking graph neighbors
319
+ ```
@@ -0,0 +1,37 @@
1
+ # GraphStack GNAP board — thin PowerShell shim that delegates to the Python core.
2
+ # Real logic lives in scripts/graphstack/board.py.
3
+ #
4
+ # Usage: .\scripts\board.ps1 <command> [args]
5
+
6
+ $ErrorActionPreference = 'Stop'
7
+
8
+ $scriptDir = Split-Path -Parent $MyInvocation.MyCommand.Path
9
+ $env:PYTHONPATH = if ($env:PYTHONPATH) {
10
+ "$scriptDir;$env:PYTHONPATH"
11
+ } else {
12
+ $scriptDir
13
+ }
14
+
15
+ function Resolve-Python {
16
+ # On Windows, prefer `py -3` because `python.exe` is often the Microsoft
17
+ # Store redirect stub which prints a localized error and exits 9009.
18
+ $py = Get-Command 'py' -ErrorAction SilentlyContinue
19
+ if ($py) { return [pscustomobject]@{ Exe = $py.Source; PreArgs = @('-3') } }
20
+ foreach ($name in @('python3', 'python')) {
21
+ $cmd = Get-Command $name -ErrorAction SilentlyContinue
22
+ if (-not $cmd) { continue }
23
+ # Skip the Windows Store stub (lives under WindowsApps).
24
+ if ($cmd.Source -match 'WindowsApps') { continue }
25
+ return [pscustomobject]@{ Exe = $cmd.Source; PreArgs = @() }
26
+ }
27
+ return $null
28
+ }
29
+
30
+ $python = Resolve-Python
31
+ if (-not $python) {
32
+ Write-Error 'GraphStack: Python not found. Install Python 3.8+ and retry.'
33
+ exit 127
34
+ }
35
+
36
+ & $python.Exe @($python.PreArgs) -m graphstack board @args
37
+ exit $LASTEXITCODE
@@ -0,0 +1,22 @@
1
+ #!/usr/bin/env bash
2
+ # GraphStack GNAP board — thin shim that delegates to the Python core.
3
+ # Real logic lives in scripts/graphstack/board.py.
4
+ #
5
+ # Usage: bash scripts/board.sh <command> [args]
6
+ # commands: status | new | claim | complete | log | help
7
+
8
+ set -uo pipefail
9
+
10
+ SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
11
+ export PYTHONPATH="${SCRIPT_DIR}${PYTHONPATH:+:$PYTHONPATH}"
12
+
13
+ if command -v python3 >/dev/null 2>&1; then
14
+ PY=python3
15
+ elif command -v python >/dev/null 2>&1; then
16
+ PY=python
17
+ else
18
+ echo "❌ GraphStack: Python not found. Install Python 3.8+ and retry." >&2
19
+ exit 127
20
+ fi
21
+
22
+ exec "$PY" -m graphstack board "$@"
@@ -0,0 +1,41 @@
1
+ # GraphStack process gate — thin PowerShell shim for Cursor / Claude Code hooks.
2
+ # Real logic lives in scripts/graphstack/gate.py.
3
+ #
4
+ # Usage: .\scripts\gate-hook.ps1 <cursor|claude>
5
+
6
+ $ErrorActionPreference = 'Stop'
7
+
8
+ $scriptDir = Split-Path -Parent $MyInvocation.MyCommand.Path
9
+ $env:PYTHONPATH = if ($env:PYTHONPATH) {
10
+ "$scriptDir;$env:PYTHONPATH"
11
+ } else {
12
+ $scriptDir
13
+ }
14
+
15
+ function Resolve-Python {
16
+ $py = Get-Command 'py' -ErrorAction SilentlyContinue
17
+ if ($py) { return [pscustomobject]@{ Exe = $py.Source; PreArgs = @('-3') } }
18
+ foreach ($name in @('python3', 'python')) {
19
+ $cmd = Get-Command $name -ErrorAction SilentlyContinue
20
+ if (-not $cmd) { continue }
21
+ if ($cmd.Source -match 'WindowsApps') { continue }
22
+ return [pscustomobject]@{ Exe = $cmd.Source; PreArgs = @() }
23
+ }
24
+ return $null
25
+ }
26
+
27
+ $platform = if ($args.Count -gt 0) { $args[0] } else { 'cursor' }
28
+
29
+ $python = Resolve-Python
30
+ if (-not $python) {
31
+ Write-Host 'graphstack gate-hook: Python not found — failing open' -ForegroundColor Yellow
32
+ if ($platform -eq 'cursor') {
33
+ Write-Output '{"continue": true, "permission": "allow"}'
34
+ } else {
35
+ Write-Output '{}'
36
+ }
37
+ exit 0
38
+ }
39
+
40
+ & $python.Exe @($python.PreArgs) -m graphstack gate hook @args
41
+ exit $LASTEXITCODE
@@ -0,0 +1,26 @@
1
+ #!/usr/bin/env bash
2
+ # GraphStack process gate — thin shim for Cursor / Claude Code hooks.
3
+ # Real logic lives in scripts/graphstack/gate.py.
4
+ #
5
+ # Usage: bash scripts/gate-hook.sh <cursor|claude>
6
+
7
+ set -uo pipefail
8
+
9
+ SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
10
+ export PYTHONPATH="${SCRIPT_DIR}${PYTHONPATH:+:$PYTHONPATH}"
11
+
12
+ if command -v python3 >/dev/null 2>&1; then
13
+ exec python3 -m graphstack gate hook "$@"
14
+ elif command -v python >/dev/null 2>&1; then
15
+ exec python -m graphstack gate hook "$@"
16
+ elif command -v py >/dev/null 2>&1; then
17
+ exec py -3 -m graphstack gate hook "$@"
18
+ fi
19
+
20
+ echo "graphstack gate-hook: Python not found — failing open" >&2
21
+ if [ "${1:-}" = "cursor" ]; then
22
+ echo '{"continue": true, "permission": "allow"}'
23
+ else
24
+ echo '{}'
25
+ fi
26
+ exit 0
@@ -0,0 +1,20 @@
1
+ #!/usr/bin/env bash
2
+ # GraphStack — Smart Graph Update Hook (thin shim).
3
+ # Real logic lives in scripts/graphstack/hook.py.
4
+
5
+ set -uo pipefail
6
+
7
+ REPO_ROOT="$(git rev-parse --show-toplevel 2>/dev/null || pwd)"
8
+ export PYTHONPATH="${REPO_ROOT}/scripts${PYTHONPATH:+:$PYTHONPATH}"
9
+
10
+ if command -v python3 >/dev/null 2>&1; then
11
+ PY=python3
12
+ elif command -v python >/dev/null 2>&1; then
13
+ PY=python
14
+ else
15
+ echo "⚠️ GraphStack: Python not found — skipping graph update."
16
+ exit 0
17
+ fi
18
+
19
+ cd "$REPO_ROOT" || exit 0
20
+ exec "$PY" -m graphstack hook
@@ -0,0 +1,44 @@
1
+ # GraphStack — Smart Graph Update Hook (thin PowerShell shim).
2
+ # Real logic lives in scripts/graphstack/hook.py.
3
+
4
+ $ErrorActionPreference = 'Stop'
5
+
6
+ try {
7
+ $repoRoot = (& git rev-parse --show-toplevel 2>$null).Trim()
8
+ if (-not $repoRoot) { $repoRoot = (Get-Location).Path }
9
+ } catch {
10
+ $repoRoot = (Get-Location).Path
11
+ }
12
+
13
+ $packageParent = Join-Path $repoRoot 'scripts'
14
+ $env:PYTHONPATH = if ($env:PYTHONPATH) {
15
+ "$packageParent;$env:PYTHONPATH"
16
+ } else {
17
+ $packageParent
18
+ }
19
+
20
+ function Resolve-Python {
21
+ $py = Get-Command 'py' -ErrorAction SilentlyContinue
22
+ if ($py) { return [pscustomobject]@{ Exe = $py.Source; PreArgs = @('-3') } }
23
+ foreach ($name in @('python3', 'python')) {
24
+ $cmd = Get-Command $name -ErrorAction SilentlyContinue
25
+ if (-not $cmd) { continue }
26
+ if ($cmd.Source -match 'WindowsApps') { continue }
27
+ return [pscustomobject]@{ Exe = $cmd.Source; PreArgs = @() }
28
+ }
29
+ return $null
30
+ }
31
+
32
+ $python = Resolve-Python
33
+ if (-not $python) {
34
+ Write-Host 'GraphStack: Python not found - skipping graph update.'
35
+ exit 0
36
+ }
37
+
38
+ Push-Location $repoRoot
39
+ try {
40
+ & $python.Exe @($python.PreArgs) -m graphstack hook
41
+ exit $LASTEXITCODE
42
+ } finally {
43
+ Pop-Location
44
+ }