@toolbaux/guardian 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.
- package/LICENSE +21 -0
- package/README.md +366 -0
- package/dist/adapters/csharp-adapter.js +149 -0
- package/dist/adapters/go-adapter.js +96 -0
- package/dist/adapters/index.js +16 -0
- package/dist/adapters/java-adapter.js +122 -0
- package/dist/adapters/python-adapter.js +183 -0
- package/dist/adapters/runner.js +69 -0
- package/dist/adapters/types.js +1 -0
- package/dist/adapters/typescript-adapter.js +179 -0
- package/dist/benchmarking/framework.js +91 -0
- package/dist/cli.js +343 -0
- package/dist/commands/analyze-depth.js +43 -0
- package/dist/commands/api-spec-extractor.js +52 -0
- package/dist/commands/breaking-change-analyzer.js +334 -0
- package/dist/commands/config-compliance.js +219 -0
- package/dist/commands/constraints.js +221 -0
- package/dist/commands/context.js +101 -0
- package/dist/commands/data-flow-tracer.js +291 -0
- package/dist/commands/dependency-impact-analyzer.js +27 -0
- package/dist/commands/diff.js +146 -0
- package/dist/commands/discrepancy.js +71 -0
- package/dist/commands/doc-generate.js +163 -0
- package/dist/commands/doc-html.js +120 -0
- package/dist/commands/drift.js +88 -0
- package/dist/commands/extract.js +16 -0
- package/dist/commands/feature-context.js +116 -0
- package/dist/commands/generate.js +339 -0
- package/dist/commands/guard.js +182 -0
- package/dist/commands/init.js +209 -0
- package/dist/commands/intel.js +20 -0
- package/dist/commands/license-dependency-auditor.js +33 -0
- package/dist/commands/performance-hotspot-profiler.js +42 -0
- package/dist/commands/search.js +314 -0
- package/dist/commands/security-boundary-auditor.js +359 -0
- package/dist/commands/simulate.js +294 -0
- package/dist/commands/summary.js +27 -0
- package/dist/commands/test-coverage-mapper.js +264 -0
- package/dist/commands/verify-drift.js +62 -0
- package/dist/config.js +441 -0
- package/dist/extract/ai-context-hints.js +107 -0
- package/dist/extract/analyzers/backend.js +1704 -0
- package/dist/extract/analyzers/depth.js +264 -0
- package/dist/extract/analyzers/frontend.js +2221 -0
- package/dist/extract/api-usage-tracker.js +19 -0
- package/dist/extract/cache.js +53 -0
- package/dist/extract/codebase-intel.js +190 -0
- package/dist/extract/compress.js +452 -0
- package/dist/extract/context-block.js +356 -0
- package/dist/extract/contracts.js +183 -0
- package/dist/extract/discrepancies.js +233 -0
- package/dist/extract/docs-loader.js +110 -0
- package/dist/extract/docs.js +2379 -0
- package/dist/extract/drift.js +1578 -0
- package/dist/extract/duplicates.js +435 -0
- package/dist/extract/feature-arcs.js +138 -0
- package/dist/extract/graph.js +76 -0
- package/dist/extract/html-doc.js +1409 -0
- package/dist/extract/ignore.js +45 -0
- package/dist/extract/index.js +455 -0
- package/dist/extract/llm-client.js +159 -0
- package/dist/extract/pattern-registry.js +141 -0
- package/dist/extract/product-doc.js +497 -0
- package/dist/extract/python.js +1202 -0
- package/dist/extract/runtime.js +193 -0
- package/dist/extract/schema-evolution-validator.js +35 -0
- package/dist/extract/test-gap-analyzer.js +20 -0
- package/dist/extract/tests.js +74 -0
- package/dist/extract/types.js +1 -0
- package/dist/extract/validate-backend.js +30 -0
- package/dist/extract/writer.js +11 -0
- package/dist/output-layout.js +37 -0
- package/dist/project-discovery.js +309 -0
- package/dist/schema/architecture.js +350 -0
- package/dist/schema/feature-spec.js +89 -0
- package/dist/schema/index.js +8 -0
- package/dist/schema/ux.js +46 -0
- package/package.json +75 -0
package/LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 Harish Kumar
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
package/README.md
ADDED
|
@@ -0,0 +1,366 @@
|
|
|
1
|
+
# Guardian
|
|
2
|
+
|
|
3
|
+
Architectural intelligence for codebases. One command turns your repo into compact, machine-readable context that AI coding tools can reason about without hallucinating.
|
|
4
|
+
|
|
5
|
+
## Quick Start
|
|
6
|
+
|
|
7
|
+
```bash
|
|
8
|
+
# Initialize a project (creates .specs/, config, pre-commit hook, CLAUDE.md context)
|
|
9
|
+
guardian init
|
|
10
|
+
|
|
11
|
+
# Or run extraction manually
|
|
12
|
+
guardian extract
|
|
13
|
+
guardian generate --ai-context
|
|
14
|
+
```
|
|
15
|
+
|
|
16
|
+
After `guardian init`, your project gets:
|
|
17
|
+
- `.specs/` directory with architecture snapshots
|
|
18
|
+
- `CLAUDE.md` with auto-injected architecture context (between `<!-- guardian:auto-context -->` markers)
|
|
19
|
+
- Pre-commit hook that keeps context fresh on every commit
|
|
20
|
+
- `guardian.config.json` with auto-detected backend/frontend roots
|
|
21
|
+
|
|
22
|
+
## What Guardian Solves
|
|
23
|
+
|
|
24
|
+
**Without Guardian:** AI invents fake schemas, imports wrong components, edits high-coupling files blindly, guesses at endpoint paths.
|
|
25
|
+
|
|
26
|
+
**With Guardian:** AI gets a deterministic map of your repo — exact boundaries, coupling hotspots, model-to-endpoint relationships, page routes, and data flows — in ~3,000 tokens.
|
|
27
|
+
|
|
28
|
+
## Installation
|
|
29
|
+
|
|
30
|
+
```bash
|
|
31
|
+
# From source
|
|
32
|
+
git clone <repo>
|
|
33
|
+
cd guardian
|
|
34
|
+
npm install && npm run build
|
|
35
|
+
npm link # makes `guardian` available globally
|
|
36
|
+
|
|
37
|
+
# Or install from npm
|
|
38
|
+
npm install -g @toolbaux/guardian
|
|
39
|
+
|
|
40
|
+
# Initialize a project
|
|
41
|
+
cd /path/to/your/project
|
|
42
|
+
guardian init
|
|
43
|
+
```
|
|
44
|
+
|
|
45
|
+
### VSCode Extension
|
|
46
|
+
|
|
47
|
+
The extension adds a status bar indicator, background auto-extract on save, and command palette integration.
|
|
48
|
+
|
|
49
|
+
**Install via symlink (development):**
|
|
50
|
+
```bash
|
|
51
|
+
ln -sf /path/to/guardian/vscode-extension ~/.vscode/extensions/guardian-vscode
|
|
52
|
+
# Reload VSCode: Cmd+Shift+P → "Reload Window"
|
|
53
|
+
```
|
|
54
|
+
|
|
55
|
+
**Install via .vsix (packaged):**
|
|
56
|
+
```bash
|
|
57
|
+
cd guardian/vscode-extension
|
|
58
|
+
npx vsce package --allow-missing-repository
|
|
59
|
+
# In VSCode: Cmd+Shift+P → "Extensions: Install from VSIX" → select the .vsix file
|
|
60
|
+
```
|
|
61
|
+
|
|
62
|
+
**What the extension provides:**
|
|
63
|
+
- Status bar: `✓ Guardian: stable · 35 ep · 8 pg` — click to run drift check
|
|
64
|
+
- Background extract on file save (5s debounce, code files only)
|
|
65
|
+
- Auto-injects fresh context into CLAUDE.md on each save
|
|
66
|
+
- Command palette: 8 commands (see below)
|
|
67
|
+
|
|
68
|
+
**Extension settings:**
|
|
69
|
+
| Setting | Default | Description |
|
|
70
|
+
|---------|---------|-------------|
|
|
71
|
+
| `guardian.autoExtract` | `true` | Auto-run extract + context on file save |
|
|
72
|
+
| `guardian.runOnSave` | `false` | Also run drift check on save (heavier) |
|
|
73
|
+
| `guardian.backendRoot` | `"backend"` | Backend root relative to workspace |
|
|
74
|
+
| `guardian.frontendRoot` | `"frontend"` | Frontend root relative to workspace |
|
|
75
|
+
| `guardian.configPath` | `""` | Path to guardian.config.json |
|
|
76
|
+
| `guardian.debounceMs` | `750` | Debounce for drift-on-save |
|
|
77
|
+
|
|
78
|
+
## All Commands (18)
|
|
79
|
+
|
|
80
|
+
### Project Setup
|
|
81
|
+
|
|
82
|
+
```bash
|
|
83
|
+
# Initialize project: config, .specs dir, pre-commit hook, CLAUDE.md
|
|
84
|
+
guardian init
|
|
85
|
+
|
|
86
|
+
# Full extraction: architecture + UX snapshots + codebase intelligence + docs
|
|
87
|
+
guardian extract
|
|
88
|
+
|
|
89
|
+
# AI context file only (compact ~3K token summary)
|
|
90
|
+
guardian generate --ai-context
|
|
91
|
+
|
|
92
|
+
# Build codebase-intelligence.json from existing snapshots
|
|
93
|
+
guardian intel
|
|
94
|
+
```
|
|
95
|
+
|
|
96
|
+
### Search & Context
|
|
97
|
+
|
|
98
|
+
```bash
|
|
99
|
+
# Search all artifacts by keyword (models, endpoints, components, modules, tasks)
|
|
100
|
+
guardian search --query "session"
|
|
101
|
+
guardian search --query "auth" --types models,endpoints
|
|
102
|
+
|
|
103
|
+
# Render focused AI context block (stdout or append to file)
|
|
104
|
+
guardian context --focus "auth"
|
|
105
|
+
guardian context --output CLAUDE.md # injects between auto-context markers
|
|
106
|
+
guardian context --focus "session" --output CLAUDE.md
|
|
107
|
+
|
|
108
|
+
# Executive summary from existing snapshots
|
|
109
|
+
guardian summary
|
|
110
|
+
```
|
|
111
|
+
|
|
112
|
+
### Architectural Metrics
|
|
113
|
+
|
|
114
|
+
```bash
|
|
115
|
+
# Compute drift metrics (D_t, K_t, delta, entropy, cycles)
|
|
116
|
+
guardian drift
|
|
117
|
+
guardian drift --baseline # save baseline for future comparison
|
|
118
|
+
|
|
119
|
+
# Verify drift stays within threshold (for CI gates)
|
|
120
|
+
guardian verify-drift --baseline specs-out/machine/drift.baseline.json
|
|
121
|
+
|
|
122
|
+
# Generate constraints JSON (duplicates, cycles, similar endpoints)
|
|
123
|
+
guardian constraints
|
|
124
|
+
|
|
125
|
+
# Structural complexity analysis for a feature area
|
|
126
|
+
guardian analyze-depth --query "session"
|
|
127
|
+
guardian analyze-depth --query "payment" --ci # exit 1 on HIGH complexity
|
|
128
|
+
|
|
129
|
+
# Diff between two snapshots
|
|
130
|
+
guardian diff --baseline old.yaml --current new.yaml
|
|
131
|
+
```
|
|
132
|
+
|
|
133
|
+
### Documentation
|
|
134
|
+
|
|
135
|
+
```bash
|
|
136
|
+
# LLM-powered product document (uses Ollama or Anthropic)
|
|
137
|
+
guardian doc-generate
|
|
138
|
+
guardian doc-generate --update-baseline # freeze for discrepancy tracking
|
|
139
|
+
|
|
140
|
+
# HTML Javadoc-style viewer (no server needed, open in browser)
|
|
141
|
+
guardian doc-html
|
|
142
|
+
|
|
143
|
+
# Discrepancy report: code vs baseline (JSON + Markdown)
|
|
144
|
+
guardian discrepancy
|
|
145
|
+
```
|
|
146
|
+
|
|
147
|
+
### Simulation & LLM Guardrails
|
|
148
|
+
|
|
149
|
+
```bash
|
|
150
|
+
# Simulate drift impact of a patch before merging
|
|
151
|
+
guardian simulate --patch changes.patch
|
|
152
|
+
|
|
153
|
+
# LLM-guided code generation with drift guardrails
|
|
154
|
+
guardian guard --task "add payment endpoint"
|
|
155
|
+
guardian guard --task "refactor auth" --print-context # print without calling LLM
|
|
156
|
+
|
|
157
|
+
# Generate filtered context packet for implementing a single feature
|
|
158
|
+
guardian feature-context --spec feature-specs/billing.yaml
|
|
159
|
+
```
|
|
160
|
+
|
|
161
|
+
### VSCode Commands (Command Palette)
|
|
162
|
+
|
|
163
|
+
| Command | Description |
|
|
164
|
+
|---------|-------------|
|
|
165
|
+
| `Guardian: Initialize Project` | Run `guardian init` on workspace |
|
|
166
|
+
| `Guardian: Generate AI Context` | Generate architecture-context.md |
|
|
167
|
+
| `Guardian: Drift Check` | Compute drift metrics |
|
|
168
|
+
| `Guardian: Generate Constraints` | Find duplicates, cycles, similar endpoints |
|
|
169
|
+
| `Guardian: Copy Constraints Prompt` | Copy LLM guardrail prompt to clipboard |
|
|
170
|
+
| `Guardian: Simulate Drift` | Simulate drift without a patch |
|
|
171
|
+
| `Guardian: Guard Patch (Simulate)` | Pick a .patch file and simulate its impact |
|
|
172
|
+
| `Guardian: Guarded Run` | Constraints + simulation in one step |
|
|
173
|
+
|
|
174
|
+
## Output Structure
|
|
175
|
+
|
|
176
|
+
```
|
|
177
|
+
.specs/ (or specs-out/)
|
|
178
|
+
├── machine/
|
|
179
|
+
│ ├── architecture-context.md ← AI context (~3K tokens, injected into CLAUDE.md)
|
|
180
|
+
│ ├── architecture.snapshot.yaml ← full architecture snapshot
|
|
181
|
+
│ ├── ux.snapshot.yaml ← frontend components + pages
|
|
182
|
+
│ ├── codebase-intelligence.json ← unified registry for all downstream commands
|
|
183
|
+
│ ├── structural-intelligence.json ← per-module complexity analysis
|
|
184
|
+
│ ├── drift.heatmap.json ← coupling scores per module
|
|
185
|
+
│ ├── drift.report.json ← drift metrics
|
|
186
|
+
│ ├── constraints.json ← duplicates, cycles, similar endpoints
|
|
187
|
+
│ ├── discrepancies.json ← code vs baseline diff
|
|
188
|
+
│ └── docs/ ← generated markdown docs
|
|
189
|
+
│ ├── summary.md ← product overview with quality signals
|
|
190
|
+
│ ├── hld.md ← system diagrams, coupling heatmap, subsystems
|
|
191
|
+
│ ├── integration.md ← all API endpoints grouped by domain
|
|
192
|
+
│ ├── data.md ← data models and schemas
|
|
193
|
+
│ ├── ux.md ← pages, components, interaction maps
|
|
194
|
+
│ ├── diff.md ← changelog between snapshots
|
|
195
|
+
│ ├── runtime.md ← Docker services, background tasks
|
|
196
|
+
│ ├── infra.md ← manifests, scripts, Makefiles
|
|
197
|
+
│ ├── tests.md ← behavioral test specs
|
|
198
|
+
│ ├── stakeholder.md ← one-page executive view
|
|
199
|
+
│ └── index.md ← table of contents
|
|
200
|
+
├── human/
|
|
201
|
+
│ ├── product-document.md ← LLM-powered comprehensive product doc
|
|
202
|
+
│ ├── discrepancies.md ← human-readable drift report
|
|
203
|
+
│ ├── start-here.md ← onboarding guide
|
|
204
|
+
│ ├── system-overview.md ← boundaries, risk zones
|
|
205
|
+
│ ├── backend-overview.md ← modules by layer
|
|
206
|
+
│ ├── frontend-overview.md ← page/component inventory
|
|
207
|
+
│ ├── data-and-flows.md ← models and cross-stack contracts
|
|
208
|
+
│ ├── change-guide.md ← what changed, what's risky
|
|
209
|
+
│ └── docs/ ← HTML viewer (open index.html in browser)
|
|
210
|
+
│ ├── index.html ← overview with product context from README
|
|
211
|
+
│ ├── architecture.html ← system diagram, workflow sequences, coupling
|
|
212
|
+
│ ├── api-surface.html ← all endpoints by domain
|
|
213
|
+
│ ├── data-models.html ← models with role badges
|
|
214
|
+
│ ├── quality.html ← patterns, duplicates, orphans
|
|
215
|
+
│ ├── frontend.html ← pages with component trees
|
|
216
|
+
│ ├── tasks.html ← background tasks
|
|
217
|
+
│ └── discrepancies.html ← code vs spec drift
|
|
218
|
+
```
|
|
219
|
+
|
|
220
|
+
## Configuration
|
|
221
|
+
|
|
222
|
+
`guardian.config.json` at project root (auto-created by `guardian init`):
|
|
223
|
+
|
|
224
|
+
```json
|
|
225
|
+
{
|
|
226
|
+
"project": {
|
|
227
|
+
"backendRoot": "./backend",
|
|
228
|
+
"frontendRoot": "./frontend",
|
|
229
|
+
"description": "Short product description for generated docs"
|
|
230
|
+
},
|
|
231
|
+
"frontend": {
|
|
232
|
+
"routeDirs": ["app"],
|
|
233
|
+
"aliases": { "@": "./frontend" }
|
|
234
|
+
},
|
|
235
|
+
"python": {
|
|
236
|
+
"absoluteImportRoots": ["backend"]
|
|
237
|
+
},
|
|
238
|
+
"drift": {
|
|
239
|
+
"layers": {
|
|
240
|
+
"core": ["shared"],
|
|
241
|
+
"top": ["service-conversation"],
|
|
242
|
+
"isolated": ["service-auth", "service-content"]
|
|
243
|
+
},
|
|
244
|
+
"domains": {
|
|
245
|
+
"session": ["service-conversation", "shared"],
|
|
246
|
+
"auth": ["service-auth"]
|
|
247
|
+
}
|
|
248
|
+
},
|
|
249
|
+
"llm": {
|
|
250
|
+
"command": "ollama",
|
|
251
|
+
"args": ["run", "llama3"]
|
|
252
|
+
},
|
|
253
|
+
"docs": {
|
|
254
|
+
"mode": "full"
|
|
255
|
+
},
|
|
256
|
+
"ignore": {
|
|
257
|
+
"directories": ["venv", "node_modules", "__pycache__"],
|
|
258
|
+
"paths": ["backend/alembic/versions"]
|
|
259
|
+
}
|
|
260
|
+
}
|
|
261
|
+
```
|
|
262
|
+
|
|
263
|
+
## Claude Code Integration
|
|
264
|
+
|
|
265
|
+
Guardian auto-injects architecture context into `CLAUDE.md` so Claude Code reads it at session start:
|
|
266
|
+
|
|
267
|
+
```markdown
|
|
268
|
+
# my-project
|
|
269
|
+
|
|
270
|
+
## Guardian Architecture Context
|
|
271
|
+
|
|
272
|
+
<!-- guardian:auto-context -->
|
|
273
|
+
## Codebase Map
|
|
274
|
+
**Backend:** 16 schemas · 35 endpoints · 9 modules
|
|
275
|
+
**Frontend:** 10 components · 8 pages
|
|
276
|
+
|
|
277
|
+
### High-Coupling Files
|
|
278
|
+
- shared/policy/__init__.py (score 1.00)
|
|
279
|
+
- service-conversation/engine.py (score 0.40)
|
|
280
|
+
...
|
|
281
|
+
<!-- /guardian:auto-context -->
|
|
282
|
+
```
|
|
283
|
+
|
|
284
|
+
The block between `<!-- guardian:auto-context -->` markers is replaced on every save (via VSCode extension) and every commit (via pre-commit hook). Your manual content in CLAUDE.md outside the markers is never touched.
|
|
285
|
+
|
|
286
|
+
## Key Architectural Outputs
|
|
287
|
+
|
|
288
|
+
### Workflow Sequence Diagrams
|
|
289
|
+
Auto-generated Mermaid sequence diagrams for your most complex endpoints, showing the full call chain from client through handler to services and data stores.
|
|
290
|
+
|
|
291
|
+
### System Architecture Diagram
|
|
292
|
+
Full system view: frontend → backend services → data stores → external APIs, with actual endpoint paths shown per service.
|
|
293
|
+
|
|
294
|
+
### Service Communication Map
|
|
295
|
+
Cross-service dependency flowchart showing which modules import from which, proxy patterns, and external API calls.
|
|
296
|
+
|
|
297
|
+
### Model Role Badges
|
|
298
|
+
Each data model gets an inferred role badge: API Request, API Response, Configuration, Safety Policy, Entity Profile, Content Entity, etc.
|
|
299
|
+
|
|
300
|
+
### Subsystem Diagrams with Entity Names
|
|
301
|
+
Backend module diagrams show actual class names (e.g., `ConversationEngine`, `ContentRetriever`, `SessionStateMachine`) instead of generic file counts.
|
|
302
|
+
|
|
303
|
+
## Framework Support
|
|
304
|
+
|
|
305
|
+
### Frontend Frameworks
|
|
306
|
+
- **Expo Router** — auto-detected from `package.json`. Every `.tsx` in `app/` is a page (except `_layout`, `_error`). Route derived from filename.
|
|
307
|
+
- **Next.js** — `page.tsx` convention in `app/` directory.
|
|
308
|
+
- **React Router** — route definitions parsed from JSX `<Route>` elements and `createBrowserRouter()`.
|
|
309
|
+
|
|
310
|
+
### Backend Frameworks
|
|
311
|
+
- **Python**: FastAPI, Django, Pydantic, SQLAlchemy
|
|
312
|
+
- **TypeScript/JavaScript**: Express, React, Next.js
|
|
313
|
+
- **Java**: Spring Boot (`@RestController`, JPA)
|
|
314
|
+
- **Go**: Gin routing, struct models
|
|
315
|
+
- **C#**: ASP.NET Core HTTP endpoints, POCO schemas
|
|
316
|
+
|
|
317
|
+
All extraction uses Tree-Sitter AST parsing — deterministic, no LLM involved.
|
|
318
|
+
|
|
319
|
+
## Key Metrics
|
|
320
|
+
|
|
321
|
+
| Metric | Meaning |
|
|
322
|
+
|--------|---------|
|
|
323
|
+
| **D_t** | Coupling delta (lower = less entangled) |
|
|
324
|
+
| **K_t** | Architectural complexity |
|
|
325
|
+
| **Delta** | Overall drift score |
|
|
326
|
+
| **Coupling score** | Per-module dependency pressure (0-1) |
|
|
327
|
+
| **Shape fingerprint** | Change = structural refactor. Same shape + different fingerprint = additive change |
|
|
328
|
+
|
|
329
|
+
## Automation Flow
|
|
330
|
+
|
|
331
|
+
```
|
|
332
|
+
Developer writes code
|
|
333
|
+
↓ (save)
|
|
334
|
+
VSCode extension (5s debounce)
|
|
335
|
+
↓
|
|
336
|
+
guardian extract → .specs/
|
|
337
|
+
guardian generate --ai-context → .specs/
|
|
338
|
+
guardian context → CLAUDE.md (between markers)
|
|
339
|
+
Status bar: "✓ Guardian: stable · 35 ep · 8 pg"
|
|
340
|
+
↓ (git commit)
|
|
341
|
+
Pre-commit hook: extract + context → auto-staged
|
|
342
|
+
↓
|
|
343
|
+
Claude Code reads CLAUDE.md → fresh architecture context
|
|
344
|
+
```
|
|
345
|
+
|
|
346
|
+
## GitHub Action
|
|
347
|
+
|
|
348
|
+
```yaml
|
|
349
|
+
uses: ./
|
|
350
|
+
with:
|
|
351
|
+
project-root: .
|
|
352
|
+
output: specs-out
|
|
353
|
+
```
|
|
354
|
+
|
|
355
|
+
See [`.github/workflows/guardian-example.yml`](./.github/workflows/guardian-example.yml).
|
|
356
|
+
|
|
357
|
+
## Development
|
|
358
|
+
|
|
359
|
+
```bash
|
|
360
|
+
npm install
|
|
361
|
+
npm run dev -- extract . # run from source
|
|
362
|
+
npm run build # compile to dist/
|
|
363
|
+
npm run start -- extract . # run compiled
|
|
364
|
+
npm run typecheck # type check only
|
|
365
|
+
npm test # run tests
|
|
366
|
+
```
|
|
@@ -0,0 +1,149 @@
|
|
|
1
|
+
import CSharp from "tree-sitter-c-sharp";
|
|
2
|
+
import Parser from "tree-sitter";
|
|
3
|
+
function text(node) {
|
|
4
|
+
return node ? node.text : "";
|
|
5
|
+
}
|
|
6
|
+
export const CSharpAdapter = {
|
|
7
|
+
name: "C# ASP.NET Core Adapter",
|
|
8
|
+
language: CSharp,
|
|
9
|
+
fileExtensions: [".cs"],
|
|
10
|
+
queries: {
|
|
11
|
+
// Endpoints: Methods with an attribute [HttpXxx("...")]
|
|
12
|
+
endpoints: "(method_declaration name: (identifier) @handler) @endpoint",
|
|
13
|
+
// Models: C# classes without Controller or Service suffixes usually.
|
|
14
|
+
models: "(class_declaration name: (identifier) @name) @model",
|
|
15
|
+
// Tests: Methods with [Fact], [Test], [TestMethod], [Theory]
|
|
16
|
+
tests: `
|
|
17
|
+
(method_declaration
|
|
18
|
+
(attribute_list (attribute name: (identifier) @test_attr (#match? @test_attr "^Fact$|^Test$|^TestMethod$|^Theory$")))
|
|
19
|
+
name: (identifier) @test_name
|
|
20
|
+
)
|
|
21
|
+
`
|
|
22
|
+
},
|
|
23
|
+
extract(file, source, root) {
|
|
24
|
+
const endpoints = [];
|
|
25
|
+
const models = [];
|
|
26
|
+
const components = []; // N/A
|
|
27
|
+
const tests = [];
|
|
28
|
+
const epQuery = new Parser.Query(this.language, this.queries.endpoints);
|
|
29
|
+
const mdQuery = new Parser.Query(this.language, this.queries.models);
|
|
30
|
+
// Extract C# Endpoints
|
|
31
|
+
const epMatches = epQuery.matches(root);
|
|
32
|
+
for (const match of epMatches) {
|
|
33
|
+
const handlerNode = match.captures.find(c => c.name === "handler")?.node;
|
|
34
|
+
const endpointNode = match.captures.find(c => c.name === "endpoint")?.node;
|
|
35
|
+
if (handlerNode && endpointNode) {
|
|
36
|
+
let isEndpoint = false;
|
|
37
|
+
let method = "ANY";
|
|
38
|
+
let routePath = "/";
|
|
39
|
+
// Scan the attributes of this method dynamically
|
|
40
|
+
for (let i = 0; i < endpointNode.childCount; i++) {
|
|
41
|
+
const child = endpointNode.child(i);
|
|
42
|
+
if (!child)
|
|
43
|
+
continue;
|
|
44
|
+
if (child.type === "attribute_list") {
|
|
45
|
+
const attrText = text(child);
|
|
46
|
+
if (attrText.includes("HttpGet")) {
|
|
47
|
+
isEndpoint = true;
|
|
48
|
+
method = "GET";
|
|
49
|
+
}
|
|
50
|
+
else if (attrText.includes("HttpPost")) {
|
|
51
|
+
isEndpoint = true;
|
|
52
|
+
method = "POST";
|
|
53
|
+
}
|
|
54
|
+
else if (attrText.includes("HttpPut")) {
|
|
55
|
+
isEndpoint = true;
|
|
56
|
+
method = "PUT";
|
|
57
|
+
}
|
|
58
|
+
else if (attrText.includes("HttpDelete")) {
|
|
59
|
+
isEndpoint = true;
|
|
60
|
+
method = "DELETE";
|
|
61
|
+
}
|
|
62
|
+
else if (attrText.includes("HttpPatch")) {
|
|
63
|
+
isEndpoint = true;
|
|
64
|
+
method = "PATCH";
|
|
65
|
+
}
|
|
66
|
+
// Extract path if present
|
|
67
|
+
const strMatch = attrText.match(/\\"([^\\"]+)\\"/);
|
|
68
|
+
if (strMatch)
|
|
69
|
+
routePath = strMatch[1];
|
|
70
|
+
}
|
|
71
|
+
}
|
|
72
|
+
if (isEndpoint) {
|
|
73
|
+
let requestSchema = null;
|
|
74
|
+
let responseSchema = null;
|
|
75
|
+
const typeNode = endpointNode.childForFieldName("type");
|
|
76
|
+
if (typeNode)
|
|
77
|
+
responseSchema = text(typeNode);
|
|
78
|
+
const paramsNode = endpointNode.childForFieldName("parameters");
|
|
79
|
+
if (paramsNode) {
|
|
80
|
+
for (let i = 0; i < paramsNode.childCount; i++) {
|
|
81
|
+
const pChild = paramsNode.child(i);
|
|
82
|
+
if (!pChild)
|
|
83
|
+
continue;
|
|
84
|
+
if (text(pChild).includes("[FromBody]")) {
|
|
85
|
+
const typeChild = pChild.childForFieldName("type");
|
|
86
|
+
if (typeChild)
|
|
87
|
+
requestSchema = text(typeChild);
|
|
88
|
+
}
|
|
89
|
+
else if (pChild.type === "parameter") {
|
|
90
|
+
// Default ASP.NET Core Model Binding implicit body binding
|
|
91
|
+
const typeChild = pChild.childForFieldName("type");
|
|
92
|
+
if (typeChild && !["string", "int", "long", "boolean"].includes(text(typeChild).toLowerCase())) {
|
|
93
|
+
if (!requestSchema)
|
|
94
|
+
requestSchema = text(typeChild);
|
|
95
|
+
}
|
|
96
|
+
}
|
|
97
|
+
}
|
|
98
|
+
}
|
|
99
|
+
endpoints.push({
|
|
100
|
+
method,
|
|
101
|
+
path: routePath,
|
|
102
|
+
handler: text(handlerNode),
|
|
103
|
+
file,
|
|
104
|
+
request_schema: requestSchema,
|
|
105
|
+
response_schema: responseSchema,
|
|
106
|
+
service_calls: []
|
|
107
|
+
});
|
|
108
|
+
}
|
|
109
|
+
}
|
|
110
|
+
}
|
|
111
|
+
// Extract C# Models
|
|
112
|
+
const mdMatches = mdQuery.matches(root);
|
|
113
|
+
for (const match of mdMatches) {
|
|
114
|
+
const nameNode = match.captures.find(c => c.name === "name")?.node;
|
|
115
|
+
const modelNode = match.captures.find(c => c.name === "model")?.node;
|
|
116
|
+
if (nameNode && modelNode) {
|
|
117
|
+
const name = text(nameNode);
|
|
118
|
+
// Skip controllers, services, interfaces (which start with I usually), and unmapped classes
|
|
119
|
+
if (name.endsWith("Controller") || name.endsWith("Service") || name.startsWith("I")) {
|
|
120
|
+
continue;
|
|
121
|
+
}
|
|
122
|
+
const fields = [];
|
|
123
|
+
const bodyNode = modelNode.childForFieldName("body");
|
|
124
|
+
if (bodyNode) {
|
|
125
|
+
for (let i = 0; i < bodyNode.childCount; i++) {
|
|
126
|
+
const child = bodyNode.child(i);
|
|
127
|
+
if (!child)
|
|
128
|
+
continue;
|
|
129
|
+
if (child.type === "property_declaration") {
|
|
130
|
+
const fieldNameNode = child.childForFieldName("name");
|
|
131
|
+
if (fieldNameNode)
|
|
132
|
+
fields.push(text(fieldNameNode));
|
|
133
|
+
}
|
|
134
|
+
}
|
|
135
|
+
}
|
|
136
|
+
if (fields.length > 0) {
|
|
137
|
+
models.push({
|
|
138
|
+
name,
|
|
139
|
+
file,
|
|
140
|
+
framework: "csharp-poco",
|
|
141
|
+
fields,
|
|
142
|
+
relationships: []
|
|
143
|
+
});
|
|
144
|
+
}
|
|
145
|
+
}
|
|
146
|
+
}
|
|
147
|
+
return { endpoints, models, components, tests };
|
|
148
|
+
}
|
|
149
|
+
};
|
|
@@ -0,0 +1,96 @@
|
|
|
1
|
+
import { createRequire } from "node:module";
|
|
2
|
+
import Parser from "tree-sitter";
|
|
3
|
+
const require = createRequire(import.meta.url);
|
|
4
|
+
const Go = require("tree-sitter-go");
|
|
5
|
+
function text(node) {
|
|
6
|
+
return node ? node.text : "";
|
|
7
|
+
}
|
|
8
|
+
export const GoAdapter = {
|
|
9
|
+
name: "Go Gin Adapter",
|
|
10
|
+
language: Go,
|
|
11
|
+
fileExtensions: [".go"],
|
|
12
|
+
queries: {
|
|
13
|
+
// Endpoints: r.GET("/route", handler)
|
|
14
|
+
endpoints: "(call_expression function: (selector_expression field: (field_identifier) @method) arguments: (argument_list (interpreted_string_literal) @path)) @endpoint",
|
|
15
|
+
// Models: type User struct { ... }
|
|
16
|
+
models: "(type_declaration (type_spec name: (type_identifier) @name type: (struct_type))) @model",
|
|
17
|
+
// Tests: func TestXxx(t *testing.T)
|
|
18
|
+
tests: `(function_declaration name: (identifier) @test_name (#match? @test_name "^Test"))`
|
|
19
|
+
},
|
|
20
|
+
extract(file, source, root) {
|
|
21
|
+
const endpoints = [];
|
|
22
|
+
const models = [];
|
|
23
|
+
const components = [];
|
|
24
|
+
const tests = [];
|
|
25
|
+
const epQuery = new Parser.Query(this.language, this.queries.endpoints);
|
|
26
|
+
const mdQuery = new Parser.Query(this.language, this.queries.models);
|
|
27
|
+
// Extract Gin Endpoints
|
|
28
|
+
const epMatches = epQuery.matches(root);
|
|
29
|
+
for (const match of epMatches) {
|
|
30
|
+
const methodNode = match.captures.find(c => c.name === "method")?.node;
|
|
31
|
+
const pathNode = match.captures.find(c => c.name === "path")?.node;
|
|
32
|
+
const endpointNode = match.captures.find(c => c.name === "endpoint")?.node;
|
|
33
|
+
if (methodNode && pathNode && endpointNode) {
|
|
34
|
+
let routePath = text(pathNode).replace(/\\"/g, "");
|
|
35
|
+
const method = text(methodNode).toUpperCase();
|
|
36
|
+
// Filter out non-http methods dynamically
|
|
37
|
+
if (!["GET", "POST", "PUT", "DELETE", "PATCH", "OPTIONS", "HEAD", "ANY"].includes(method))
|
|
38
|
+
continue;
|
|
39
|
+
// Find handler: It's the last argument in the call.
|
|
40
|
+
let handler = "anonymous";
|
|
41
|
+
const argsNode = endpointNode.childForFieldName("arguments");
|
|
42
|
+
if (argsNode && argsNode.childCount > 0) {
|
|
43
|
+
const lastChild = argsNode.child(argsNode.childCount - 2); // -1 is ')', -2 is the last arg
|
|
44
|
+
if (lastChild)
|
|
45
|
+
handler = text(lastChild);
|
|
46
|
+
}
|
|
47
|
+
endpoints.push({
|
|
48
|
+
method,
|
|
49
|
+
path: routePath,
|
|
50
|
+
handler,
|
|
51
|
+
file,
|
|
52
|
+
request_schema: null,
|
|
53
|
+
response_schema: null,
|
|
54
|
+
service_calls: []
|
|
55
|
+
});
|
|
56
|
+
}
|
|
57
|
+
}
|
|
58
|
+
// Extract Struct Models
|
|
59
|
+
const mdMatches = mdQuery.matches(root);
|
|
60
|
+
for (const match of mdMatches) {
|
|
61
|
+
const nameNode = match.captures.find(c => c.name === "name")?.node;
|
|
62
|
+
const modelNode = match.captures.find(c => c.name === "model")?.node;
|
|
63
|
+
if (nameNode && modelNode) {
|
|
64
|
+
const name = text(nameNode);
|
|
65
|
+
const fields = [];
|
|
66
|
+
const typeSpec = modelNode.child(0);
|
|
67
|
+
if (typeSpec) {
|
|
68
|
+
const structType = typeSpec.childForFieldName("type");
|
|
69
|
+
if (structType && structType.type === "struct_type") {
|
|
70
|
+
const fieldList = structType.childForFieldName("field_declaration_list");
|
|
71
|
+
if (fieldList) {
|
|
72
|
+
for (let i = 0; i < fieldList.childCount; i++) {
|
|
73
|
+
const child = fieldList.child(i);
|
|
74
|
+
if (!child)
|
|
75
|
+
continue;
|
|
76
|
+
if (child.type === "field_declaration") {
|
|
77
|
+
const fieldNameNode = child.childForFieldName("name");
|
|
78
|
+
if (fieldNameNode)
|
|
79
|
+
fields.push(text(fieldNameNode));
|
|
80
|
+
}
|
|
81
|
+
}
|
|
82
|
+
}
|
|
83
|
+
}
|
|
84
|
+
}
|
|
85
|
+
models.push({
|
|
86
|
+
name,
|
|
87
|
+
file,
|
|
88
|
+
framework: "go-struct",
|
|
89
|
+
fields,
|
|
90
|
+
relationships: []
|
|
91
|
+
});
|
|
92
|
+
}
|
|
93
|
+
}
|
|
94
|
+
return { endpoints, models, components, tests };
|
|
95
|
+
}
|
|
96
|
+
};
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
import { PythonAdapter } from "./python-adapter.js";
|
|
2
|
+
import { TypeScriptAdapter } from "./typescript-adapter.js";
|
|
3
|
+
import { JavaAdapter } from "./java-adapter.js";
|
|
4
|
+
import { GoAdapter } from "./go-adapter.js";
|
|
5
|
+
import { CSharpAdapter } from "./csharp-adapter.js";
|
|
6
|
+
import { runAdapter } from "./runner.js";
|
|
7
|
+
export { PythonAdapter, TypeScriptAdapter, JavaAdapter, GoAdapter, CSharpAdapter, runAdapter };
|
|
8
|
+
export const ADAPTERS = [PythonAdapter, TypeScriptAdapter, JavaAdapter, GoAdapter, CSharpAdapter];
|
|
9
|
+
export function getAdapterForFile(file) {
|
|
10
|
+
for (const adapter of ADAPTERS) {
|
|
11
|
+
if (adapter.fileExtensions.some(ext => file.endsWith(ext))) {
|
|
12
|
+
return adapter;
|
|
13
|
+
}
|
|
14
|
+
}
|
|
15
|
+
return null;
|
|
16
|
+
}
|