@toolbaux/guardian 0.1.1 → 0.1.3
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/README.md +162 -240
- package/dist/cli.js +17 -16
- package/dist/commands/analyze-depth.js +2 -1
- package/dist/commands/context.js +2 -1
- package/dist/commands/extract.js +25 -0
- package/dist/commands/generate.js +2 -1
- package/dist/commands/guard.js +2 -1
- package/dist/commands/init.js +2 -1
- package/dist/commands/search.js +2 -1
- package/dist/commands/simulate.js +2 -1
- package/dist/commands/summary.js +2 -1
- package/dist/config.js +15 -2
- package/dist/extract/cache.js +2 -1
- package/dist/output-layout.js +2 -1
- package/dist/project-discovery.js +2 -2
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -1,223 +1,195 @@
|
|
|
1
1
|
# Guardian
|
|
2
2
|
|
|
3
|
-
|
|
3
|
+
[](https://www.npmjs.com/package/@toolbaux/guardian)
|
|
4
|
+
[](./LICENSE)
|
|
4
5
|
|
|
5
|
-
|
|
6
|
+
Architectural intelligence for codebases. One command turns your repo into compact, machine-readable context that AI coding tools can reason about without hallucinating.
|
|
6
7
|
|
|
7
8
|
```bash
|
|
8
|
-
|
|
9
|
+
npm install -g @toolbaux/guardian
|
|
9
10
|
guardian init
|
|
11
|
+
```
|
|
10
12
|
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
13
|
+
## The Problem
|
|
14
|
+
|
|
15
|
+
AI coding tools hallucinate when they don't understand your architecture. They guess at imports, invent schemas that don't exist, and edit high-coupling files without understanding the blast radius.
|
|
16
|
+
|
|
17
|
+
**Without Guardian** — Cursor generates this:
|
|
18
|
+
|
|
19
|
+
```python
|
|
20
|
+
from app.schemas import UpdateCaseRequest # ← doesn't exist
|
|
21
|
+
from app.models import User # ← it's actually UserProfile
|
|
22
|
+
from shared.utils import validate # ← wrong module, it's in shared.policy
|
|
23
|
+
```
|
|
24
|
+
|
|
25
|
+
**With Guardian** — the AI reads your architecture context and generates:
|
|
26
|
+
|
|
27
|
+
```python
|
|
28
|
+
from shared.policy.persona import ChildProfile # ✓ exact import path
|
|
29
|
+
from service_conversation.engine import ConversationEngine # ✓ correct module
|
|
30
|
+
from shared.content.retriever import ContentRetriever # ✓ verified by AST
|
|
31
|
+
```
|
|
32
|
+
|
|
33
|
+
Guardian extracts exact boundaries, coupling hotspots, model-to-endpoint relationships, page routes, and data flows from your source code using Tree-Sitter AST parsing. No LLM involved in extraction — deterministic, reproducible, fast.
|
|
34
|
+
|
|
35
|
+
## How It Works
|
|
36
|
+
|
|
37
|
+
```
|
|
38
|
+
Developer writes code
|
|
39
|
+
↓ (save)
|
|
40
|
+
VSCode extension (5s debounce)
|
|
41
|
+
↓
|
|
42
|
+
guardian extract → .specs/
|
|
43
|
+
guardian generate --ai-context → .specs/
|
|
44
|
+
guardian context → CLAUDE.md (between markers)
|
|
45
|
+
Status bar: "✓ Guardian: stable · 35 ep · 8 pg"
|
|
46
|
+
↓ (git commit)
|
|
47
|
+
Pre-commit hook: extract + context → auto-staged
|
|
48
|
+
↓
|
|
49
|
+
Claude Code / Cursor reads CLAUDE.md → fresh architecture context
|
|
14
50
|
```
|
|
15
51
|
|
|
16
52
|
After `guardian init`, your project gets:
|
|
17
53
|
- `.specs/` directory with architecture snapshots
|
|
18
|
-
- `CLAUDE.md` with auto-injected
|
|
19
|
-
- Pre-commit hook that keeps context fresh
|
|
54
|
+
- `CLAUDE.md` with auto-injected context (refreshed on every save and commit)
|
|
55
|
+
- Pre-commit hook that keeps context fresh automatically
|
|
20
56
|
- `guardian.config.json` with auto-detected backend/frontend roots
|
|
21
57
|
|
|
22
|
-
##
|
|
23
|
-
|
|
24
|
-
**Without Guardian:** AI invents fake schemas, imports wrong components, edits high-coupling files blindly, guesses at endpoint paths.
|
|
58
|
+
## Claude Code / Cursor Integration
|
|
25
59
|
|
|
26
|
-
|
|
60
|
+
Guardian auto-injects architecture context into `CLAUDE.md` so your AI tool reads it at session start:
|
|
27
61
|
|
|
28
|
-
|
|
62
|
+
```markdown
|
|
63
|
+
# my-project
|
|
29
64
|
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
npm install && npm run build
|
|
35
|
-
npm link # makes `guardian` available globally
|
|
65
|
+
<!-- guardian:auto-context -->
|
|
66
|
+
## Codebase Map
|
|
67
|
+
**Backend:** 16 schemas · 35 endpoints · 9 modules
|
|
68
|
+
**Frontend:** 10 components · 8 pages
|
|
36
69
|
|
|
37
|
-
|
|
38
|
-
|
|
70
|
+
### High-Coupling Files
|
|
71
|
+
- shared/policy/__init__.py (score 1.00)
|
|
72
|
+
- service-conversation/engine.py (score 0.40)
|
|
39
73
|
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
74
|
+
### Key Model → Endpoint Map
|
|
75
|
+
- ChildProfile (1 endpoints) → POST /sessions/start
|
|
76
|
+
- StartSessionResponse (1 endpoints) → POST /sessions/start
|
|
77
|
+
<!-- /guardian:auto-context -->
|
|
43
78
|
```
|
|
44
79
|
|
|
45
|
-
|
|
80
|
+
The block between markers is replaced on every save (VSCode extension) and every commit (pre-commit hook). Your manual content outside the markers is never touched.
|
|
46
81
|
|
|
47
|
-
|
|
82
|
+
## Key Commands
|
|
48
83
|
|
|
49
|
-
**Install via symlink (development):**
|
|
50
84
|
```bash
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
```
|
|
85
|
+
# One-time setup — creates config, .specs/, pre-commit hook, CLAUDE.md
|
|
86
|
+
guardian init
|
|
54
87
|
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
88
|
+
# Extract architecture (run after major changes, or let the hook do it)
|
|
89
|
+
guardian extract
|
|
90
|
+
|
|
91
|
+
# Search your codebase by concept
|
|
92
|
+
guardian search --query "session"
|
|
93
|
+
|
|
94
|
+
# Compute architectural drift
|
|
95
|
+
guardian drift
|
|
96
|
+
|
|
97
|
+
# Generate HTML docs (open in browser, no server needed)
|
|
98
|
+
guardian doc-html
|
|
60
99
|
```
|
|
61
100
|
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
- Command palette: 8 commands (see below)
|
|
101
|
+
## Framework Support
|
|
102
|
+
|
|
103
|
+
**Frontend:** Expo Router, Next.js, React Router — auto-detected from `package.json`
|
|
104
|
+
**Backend:** FastAPI, Django, Express, Spring Boot, Gin, ASP.NET Core
|
|
67
105
|
|
|
68
|
-
|
|
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 |
|
|
106
|
+
All extraction uses Tree-Sitter AST parsing — deterministic, no LLM involved.
|
|
77
107
|
|
|
78
|
-
##
|
|
108
|
+
## What Guardian Generates
|
|
79
109
|
|
|
80
|
-
|
|
110
|
+
**Workflow sequence diagrams** — Mermaid diagrams for your most complex endpoints, showing the full call chain from client through handler to services and data stores.
|
|
111
|
+
|
|
112
|
+
**System architecture diagram** — Full system view: frontend → backend services → data stores → external APIs, with actual endpoint paths per service.
|
|
113
|
+
|
|
114
|
+
**Model role badges** — Each data model gets an inferred role: API Request, API Response, Configuration, Safety Policy, Entity Profile, Content Entity.
|
|
115
|
+
|
|
116
|
+
**Subsystem diagrams with real names** — Backend modules show `ConversationEngine`, `ContentRetriever`, `SessionStateMachine` instead of generic file counts.
|
|
117
|
+
|
|
118
|
+
---
|
|
119
|
+
|
|
120
|
+
## Full Reference
|
|
121
|
+
|
|
122
|
+
<details>
|
|
123
|
+
<summary><strong>Installation</strong></summary>
|
|
81
124
|
|
|
82
125
|
```bash
|
|
83
|
-
#
|
|
84
|
-
guardian
|
|
126
|
+
# Install from npm
|
|
127
|
+
npm install -g @toolbaux/guardian
|
|
85
128
|
|
|
86
|
-
#
|
|
87
|
-
guardian
|
|
129
|
+
# Or from source
|
|
130
|
+
git clone https://github.com/idocoding/guardian
|
|
131
|
+
cd guardian
|
|
132
|
+
npm install && npm run build && npm link
|
|
133
|
+
```
|
|
134
|
+
|
|
135
|
+
</details>
|
|
88
136
|
|
|
89
|
-
|
|
90
|
-
|
|
137
|
+
<details>
|
|
138
|
+
<summary><strong>All Commands (18)</strong></summary>
|
|
139
|
+
|
|
140
|
+
### Project Setup
|
|
91
141
|
|
|
92
|
-
|
|
93
|
-
guardian
|
|
142
|
+
```bash
|
|
143
|
+
guardian init # config, .specs dir, pre-commit hook, CLAUDE.md
|
|
144
|
+
guardian extract # full architecture + UX snapshots + docs
|
|
145
|
+
guardian generate --ai-context # compact ~3K token AI context only
|
|
146
|
+
guardian intel # build codebase-intelligence.json
|
|
94
147
|
```
|
|
95
148
|
|
|
96
149
|
### Search & Context
|
|
97
150
|
|
|
98
151
|
```bash
|
|
99
|
-
|
|
100
|
-
guardian search --query "session"
|
|
152
|
+
guardian search --query "session" # search models, endpoints, components
|
|
101
153
|
guardian search --query "auth" --types models,endpoints
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
guardian
|
|
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
|
|
154
|
+
guardian context --focus "auth" # focused AI context block
|
|
155
|
+
guardian context --output CLAUDE.md # inject between auto-context markers
|
|
156
|
+
guardian summary # executive summary
|
|
110
157
|
```
|
|
111
158
|
|
|
112
159
|
### Architectural Metrics
|
|
113
160
|
|
|
114
161
|
```bash
|
|
115
|
-
|
|
116
|
-
guardian drift
|
|
162
|
+
guardian drift # compute D_t, K_t, delta, entropy, cycles
|
|
117
163
|
guardian drift --baseline # save baseline for future comparison
|
|
118
|
-
|
|
119
|
-
#
|
|
120
|
-
guardian
|
|
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
|
|
164
|
+
guardian verify-drift --baseline drift.baseline.json # CI gate
|
|
165
|
+
guardian constraints # duplicates, cycles, similar endpoints
|
|
166
|
+
guardian analyze-depth --query "session" # structural complexity
|
|
167
|
+
guardian analyze-depth --query "payment" --ci # exit 1 on HIGH complexity
|
|
130
168
|
guardian diff --baseline old.yaml --current new.yaml
|
|
131
169
|
```
|
|
132
170
|
|
|
133
171
|
### Documentation
|
|
134
172
|
|
|
135
173
|
```bash
|
|
136
|
-
# LLM-powered product document
|
|
137
|
-
guardian doc-generate
|
|
138
|
-
guardian doc-
|
|
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
|
|
174
|
+
guardian doc-generate # LLM-powered product document
|
|
175
|
+
guardian doc-generate --update-baseline
|
|
176
|
+
guardian doc-html # HTML viewer (open in browser)
|
|
177
|
+
guardian discrepancy # code vs baseline drift report
|
|
145
178
|
```
|
|
146
179
|
|
|
147
180
|
### Simulation & LLM Guardrails
|
|
148
181
|
|
|
149
182
|
```bash
|
|
150
|
-
# Simulate drift impact of a patch before merging
|
|
151
183
|
guardian simulate --patch changes.patch
|
|
152
|
-
|
|
153
|
-
# LLM-guided code generation with drift guardrails
|
|
154
184
|
guardian guard --task "add payment endpoint"
|
|
155
|
-
guardian guard --task "refactor auth" --print-context
|
|
156
|
-
|
|
157
|
-
# Generate filtered context packet for implementing a single feature
|
|
185
|
+
guardian guard --task "refactor auth" --print-context
|
|
158
186
|
guardian feature-context --spec feature-specs/billing.yaml
|
|
159
187
|
```
|
|
160
188
|
|
|
161
|
-
|
|
189
|
+
</details>
|
|
162
190
|
|
|
163
|
-
|
|
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
|
|
191
|
+
<details>
|
|
192
|
+
<summary><strong>Configuration</strong></summary>
|
|
221
193
|
|
|
222
194
|
`guardian.config.json` at project root (auto-created by `guardian init`):
|
|
223
195
|
|
|
@@ -240,83 +212,39 @@ guardian feature-context --spec feature-specs/billing.yaml
|
|
|
240
212
|
"core": ["shared"],
|
|
241
213
|
"top": ["service-conversation"],
|
|
242
214
|
"isolated": ["service-auth", "service-content"]
|
|
243
|
-
},
|
|
244
|
-
"domains": {
|
|
245
|
-
"session": ["service-conversation", "shared"],
|
|
246
|
-
"auth": ["service-auth"]
|
|
247
215
|
}
|
|
248
216
|
},
|
|
249
217
|
"llm": {
|
|
250
218
|
"command": "ollama",
|
|
251
219
|
"args": ["run", "llama3"]
|
|
252
|
-
},
|
|
253
|
-
"docs": {
|
|
254
|
-
"mode": "full"
|
|
255
|
-
},
|
|
256
|
-
"ignore": {
|
|
257
|
-
"directories": ["venv", "node_modules", "__pycache__"],
|
|
258
|
-
"paths": ["backend/alembic/versions"]
|
|
259
220
|
}
|
|
260
221
|
}
|
|
261
222
|
```
|
|
262
223
|
|
|
263
|
-
|
|
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
|
|
224
|
+
</details>
|
|
271
225
|
|
|
272
|
-
|
|
273
|
-
|
|
274
|
-
**Backend:** 16 schemas · 35 endpoints · 9 modules
|
|
275
|
-
**Frontend:** 10 components · 8 pages
|
|
226
|
+
<details>
|
|
227
|
+
<summary><strong>Output Structure</strong></summary>
|
|
276
228
|
|
|
277
|
-
|
|
278
|
-
|
|
279
|
-
|
|
280
|
-
|
|
281
|
-
|
|
229
|
+
```
|
|
230
|
+
.specs/
|
|
231
|
+
├── machine/
|
|
232
|
+
│ ├── architecture-context.md ← AI context (~3K tokens)
|
|
233
|
+
│ ├── architecture.snapshot.yaml ← full architecture snapshot
|
|
234
|
+
│ ├── ux.snapshot.yaml ← frontend components + pages
|
|
235
|
+
│ ├── codebase-intelligence.json ← unified registry
|
|
236
|
+
│ ├── drift.report.json ← drift metrics
|
|
237
|
+
│ ├── constraints.json ← duplicates, cycles
|
|
238
|
+
│ └── docs/ ← generated markdown docs
|
|
239
|
+
├── human/
|
|
240
|
+
│ ├── product-document.md ← LLM-powered product doc
|
|
241
|
+
│ └── docs/ ← HTML viewer (open index.html)
|
|
282
242
|
```
|
|
283
243
|
|
|
284
|
-
|
|
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.
|
|
244
|
+
</details>
|
|
318
245
|
|
|
319
|
-
|
|
246
|
+
<details>
|
|
247
|
+
<summary><strong>Key Metrics</strong></summary>
|
|
320
248
|
|
|
321
249
|
| Metric | Meaning |
|
|
322
250
|
|--------|---------|
|
|
@@ -324,26 +252,12 @@ All extraction uses Tree-Sitter AST parsing — deterministic, no LLM involved.
|
|
|
324
252
|
| **K_t** | Architectural complexity |
|
|
325
253
|
| **Delta** | Overall drift score |
|
|
326
254
|
| **Coupling score** | Per-module dependency pressure (0-1) |
|
|
327
|
-
| **Shape fingerprint** | Change = structural refactor
|
|
255
|
+
| **Shape fingerprint** | Change = structural refactor |
|
|
328
256
|
|
|
329
|
-
|
|
257
|
+
</details>
|
|
330
258
|
|
|
331
|
-
|
|
332
|
-
|
|
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
|
|
259
|
+
<details>
|
|
260
|
+
<summary><strong>GitHub Action</strong></summary>
|
|
347
261
|
|
|
348
262
|
```yaml
|
|
349
263
|
- name: Install Guardian
|
|
@@ -358,13 +272,21 @@ Claude Code reads CLAUDE.md → fresh architecture context
|
|
|
358
272
|
|
|
359
273
|
See [`.github/workflows/guardian.yml`](./.github/workflows/guardian.yml).
|
|
360
274
|
|
|
361
|
-
|
|
275
|
+
</details>
|
|
276
|
+
|
|
277
|
+
<details>
|
|
278
|
+
<summary><strong>Development</strong></summary>
|
|
362
279
|
|
|
363
280
|
```bash
|
|
364
281
|
npm install
|
|
365
282
|
npm run dev -- extract . # run from source
|
|
366
283
|
npm run build # compile to dist/
|
|
367
|
-
npm run start -- extract . # run compiled
|
|
368
284
|
npm run typecheck # type check only
|
|
369
285
|
npm test # run tests
|
|
370
286
|
```
|
|
287
|
+
|
|
288
|
+
</details>
|
|
289
|
+
|
|
290
|
+
---
|
|
291
|
+
|
|
292
|
+
Built by [ToolBaux](https://github.com/idocoding). If Guardian helps you ship with confidence, [star the repo](https://github.com/idocoding/guardian).
|
package/dist/cli.js
CHANGED
|
@@ -18,6 +18,7 @@ import { runDocGenerate } from "./commands/doc-generate.js";
|
|
|
18
18
|
import { runDiscrepancy } from "./commands/discrepancy.js";
|
|
19
19
|
import { runDocHtml } from "./commands/doc-html.js";
|
|
20
20
|
import { runInit } from "./commands/init.js";
|
|
21
|
+
import { DEFAULT_SPECS_DIR } from "./config.js";
|
|
21
22
|
const program = new Command();
|
|
22
23
|
program
|
|
23
24
|
.name("guardian")
|
|
@@ -30,7 +31,7 @@ program
|
|
|
30
31
|
.option("--backend-root <path>", "Path to backend root")
|
|
31
32
|
.option("--frontend-root <path>", "Path to frontend root")
|
|
32
33
|
.option("--config <path>", "Path to specguard.config.json")
|
|
33
|
-
.option("--output <path>", "Output directory",
|
|
34
|
+
.option("--output <path>", "Output directory", DEFAULT_SPECS_DIR)
|
|
34
35
|
.option("--focus <text>", "Focus the generated AI context on a feature area")
|
|
35
36
|
.option("--max-lines <count>", "Maximum lines for the generated context")
|
|
36
37
|
.option("--ai-context", "Generate architecture-context.md for AI tools", false)
|
|
@@ -52,7 +53,7 @@ program
|
|
|
52
53
|
.argument("[projectRoot]", "Repo or project root", process.cwd())
|
|
53
54
|
.option("--backend-root <path>", "Path to backend root")
|
|
54
55
|
.option("--frontend-root <path>", "Path to frontend root")
|
|
55
|
-
.option("--output <path>", "Output directory",
|
|
56
|
+
.option("--output <path>", "Output directory", DEFAULT_SPECS_DIR)
|
|
56
57
|
.option("--include-file-graph", "Include file-level dependency graph", false)
|
|
57
58
|
.option("--config <path>", "Path to specguard.config.json")
|
|
58
59
|
.option("--docs-mode <mode>", "Docs mode (lean|full)")
|
|
@@ -61,7 +62,7 @@ program
|
|
|
61
62
|
projectRoot,
|
|
62
63
|
backendRoot: options.backendRoot,
|
|
63
64
|
frontendRoot: options.frontendRoot,
|
|
64
|
-
output: options.output ??
|
|
65
|
+
output: options.output ?? DEFAULT_SPECS_DIR,
|
|
65
66
|
includeFileGraph: options.includeFileGraph ?? false,
|
|
66
67
|
configPath: options.config,
|
|
67
68
|
docsMode: options.docsMode
|
|
@@ -194,24 +195,24 @@ program
|
|
|
194
195
|
program
|
|
195
196
|
.command("summary")
|
|
196
197
|
.description("Generate a plain-language project summary from existing snapshots")
|
|
197
|
-
.option("--input <path>", "Snapshot output directory",
|
|
198
|
+
.option("--input <path>", "Snapshot output directory", DEFAULT_SPECS_DIR)
|
|
198
199
|
.option("--output <path>", "Summary output path")
|
|
199
200
|
.action(async (options) => {
|
|
200
201
|
await runSummary({
|
|
201
|
-
input: options.input ??
|
|
202
|
+
input: options.input ?? DEFAULT_SPECS_DIR,
|
|
202
203
|
output: options.output
|
|
203
204
|
});
|
|
204
205
|
});
|
|
205
206
|
program
|
|
206
207
|
.command("search")
|
|
207
208
|
.description("Search existing snapshots for models, endpoints, components, modules, and tasks")
|
|
208
|
-
.option("--input <path>", "Snapshot output directory",
|
|
209
|
+
.option("--input <path>", "Snapshot output directory", DEFAULT_SPECS_DIR)
|
|
209
210
|
.requiredOption("--query <text>", "Search query")
|
|
210
211
|
.option("--output <path>", "Write search results to a file")
|
|
211
212
|
.option("--types <items>", "Comma-separated filters: models,endpoints,components,modules,tasks")
|
|
212
213
|
.action(async (options) => {
|
|
213
214
|
await runSearch({
|
|
214
|
-
input: options.input ??
|
|
215
|
+
input: options.input ?? DEFAULT_SPECS_DIR,
|
|
215
216
|
query: options.query,
|
|
216
217
|
output: options.output,
|
|
217
218
|
types: options.types ? [options.types] : undefined
|
|
@@ -220,13 +221,13 @@ program
|
|
|
220
221
|
program
|
|
221
222
|
.command("context")
|
|
222
223
|
.description("Render an AI-ready context block from existing snapshots")
|
|
223
|
-
.option("--input <path>", "Snapshot output directory",
|
|
224
|
+
.option("--input <path>", "Snapshot output directory", DEFAULT_SPECS_DIR)
|
|
224
225
|
.option("--output <path>", "Append the context block to a file")
|
|
225
226
|
.option("--focus <text>", "Focus the context on a feature area")
|
|
226
227
|
.option("--max-lines <count>", "Maximum number of lines to include")
|
|
227
228
|
.action(async (options) => {
|
|
228
229
|
await runContext({
|
|
229
|
-
input: options.input ??
|
|
230
|
+
input: options.input ?? DEFAULT_SPECS_DIR,
|
|
230
231
|
output: options.output,
|
|
231
232
|
focus: options.focus,
|
|
232
233
|
maxLines: options.maxLines
|
|
@@ -258,7 +259,7 @@ program
|
|
|
258
259
|
program
|
|
259
260
|
.command("intel")
|
|
260
261
|
.description("Build codebase-intelligence.json from existing snapshots")
|
|
261
|
-
.option("--specs <dir>", "Snapshot output directory",
|
|
262
|
+
.option("--specs <dir>", "Snapshot output directory", DEFAULT_SPECS_DIR)
|
|
262
263
|
.option("--output <path>", "Output path for codebase-intelligence.json")
|
|
263
264
|
.action(async (options) => {
|
|
264
265
|
await runIntel({
|
|
@@ -270,7 +271,7 @@ program
|
|
|
270
271
|
.command("feature-context")
|
|
271
272
|
.description("Generate a filtered context packet for implementing a single feature")
|
|
272
273
|
.requiredOption("--spec <file>", "Path to feature spec YAML")
|
|
273
|
-
.option("--specs <dir>", "Snapshot output directory",
|
|
274
|
+
.option("--specs <dir>", "Snapshot output directory", DEFAULT_SPECS_DIR)
|
|
274
275
|
.option("--output <path>", "Output path for feature context JSON")
|
|
275
276
|
.action(async (options) => {
|
|
276
277
|
await runFeatureContext({
|
|
@@ -282,7 +283,7 @@ program
|
|
|
282
283
|
program
|
|
283
284
|
.command("doc-generate")
|
|
284
285
|
.description("Generate a human-readable product document from codebase intelligence")
|
|
285
|
-
.option("--specs <dir>", "Snapshot output directory",
|
|
286
|
+
.option("--specs <dir>", "Snapshot output directory", DEFAULT_SPECS_DIR)
|
|
286
287
|
.option("--feature-specs <dir>", "Directory of feature spec YAML files")
|
|
287
288
|
.option("--output <path>", "Output path for product-document.md")
|
|
288
289
|
.option("--update-baseline", "Freeze current state as new baseline for discrepancy tracking", false)
|
|
@@ -297,7 +298,7 @@ program
|
|
|
297
298
|
program
|
|
298
299
|
.command("discrepancy")
|
|
299
300
|
.description("Diff current codebase intelligence against a committed baseline")
|
|
300
|
-
.option("--specs <dir>", "Snapshot output directory",
|
|
301
|
+
.option("--specs <dir>", "Snapshot output directory", DEFAULT_SPECS_DIR)
|
|
301
302
|
.option("--feature-specs <dir>", "Directory of feature spec YAML files")
|
|
302
303
|
.option("--output <path>", "Output path (used when --format is json or md)")
|
|
303
304
|
.option("--format <fmt>", "Output format: json, md, or both (default: both)", "both")
|
|
@@ -312,11 +313,11 @@ program
|
|
|
312
313
|
program
|
|
313
314
|
.command("doc-html")
|
|
314
315
|
.description("Generate a self-contained Javadoc-style HTML viewer from codebase intelligence")
|
|
315
|
-
.option("--specs <dir>", "Snapshot output directory",
|
|
316
|
+
.option("--specs <dir>", "Snapshot output directory", DEFAULT_SPECS_DIR)
|
|
316
317
|
.option("--output <path>", "Output path for index.html")
|
|
317
318
|
.action(async (options) => {
|
|
318
319
|
await runDocHtml({
|
|
319
|
-
specs: options.specs ??
|
|
320
|
+
specs: options.specs ?? DEFAULT_SPECS_DIR,
|
|
320
321
|
output: options.output,
|
|
321
322
|
});
|
|
322
323
|
});
|
|
@@ -326,7 +327,7 @@ program
|
|
|
326
327
|
.argument("[projectRoot]", "Repo or project root", process.cwd())
|
|
327
328
|
.option("--backend-root <path>", "Path to backend root")
|
|
328
329
|
.option("--frontend-root <path>", "Path to frontend root")
|
|
329
|
-
.option("--output <path>", "Output directory",
|
|
330
|
+
.option("--output <path>", "Output directory", DEFAULT_SPECS_DIR)
|
|
330
331
|
.option("--skip-hook", "Skip pre-commit hook installation", false)
|
|
331
332
|
.action(async (projectRoot, options) => {
|
|
332
333
|
await runInit({
|
|
@@ -3,12 +3,13 @@ import fs from "node:fs/promises";
|
|
|
3
3
|
import yaml from "js-yaml";
|
|
4
4
|
import { buildSnapshots } from "../extract/index.js";
|
|
5
5
|
import { analyzeDepth } from "../extract/analyzers/depth.js";
|
|
6
|
+
import { DEFAULT_SPECS_DIR } from "../config.js";
|
|
6
7
|
export async function runAnalyzeDepth(options) {
|
|
7
8
|
const { architecture } = await buildSnapshots({
|
|
8
9
|
projectRoot: options.projectRoot,
|
|
9
10
|
backendRoot: options.backendRoot,
|
|
10
11
|
frontendRoot: options.frontendRoot,
|
|
11
|
-
output: options.output ??
|
|
12
|
+
output: options.output ?? DEFAULT_SPECS_DIR,
|
|
12
13
|
includeFileGraph: true,
|
|
13
14
|
configPath: options.configPath
|
|
14
15
|
});
|
package/dist/commands/context.js
CHANGED
|
@@ -4,8 +4,9 @@ import yaml from "js-yaml";
|
|
|
4
4
|
import { loadArchitectureDiff, loadHeatmap } from "../extract/compress.js";
|
|
5
5
|
import { renderContextBlock } from "../extract/context-block.js";
|
|
6
6
|
import { resolveMachineInputDir } from "../output-layout.js";
|
|
7
|
+
import { DEFAULT_SPECS_DIR } from "../config.js";
|
|
7
8
|
export async function runContext(options) {
|
|
8
|
-
const inputDir = await resolveMachineInputDir(options.input ||
|
|
9
|
+
const inputDir = await resolveMachineInputDir(options.input || DEFAULT_SPECS_DIR);
|
|
9
10
|
const { architecture, ux } = await loadSnapshots(inputDir);
|
|
10
11
|
const [diff, heatmap] = await Promise.all([
|
|
11
12
|
loadArchitectureDiff(inputDir),
|
package/dist/commands/extract.js
CHANGED
|
@@ -1,6 +1,9 @@
|
|
|
1
|
+
import fs from "node:fs/promises";
|
|
1
2
|
import path from "node:path";
|
|
2
3
|
import { extractProject } from "../extract/index.js";
|
|
3
4
|
import { runIntel } from "./intel.js";
|
|
5
|
+
import { runGenerate } from "./generate.js";
|
|
6
|
+
import { runContext } from "./context.js";
|
|
4
7
|
export async function runExtract(options) {
|
|
5
8
|
const { architecturePath, uxPath } = await extractProject(options);
|
|
6
9
|
console.log(`Wrote ${architecturePath}`);
|
|
@@ -13,4 +16,26 @@ export async function runExtract(options) {
|
|
|
13
16
|
catch {
|
|
14
17
|
// Non-fatal — intel build failure should not break extract
|
|
15
18
|
}
|
|
19
|
+
// Auto-generate AI context + inject into CLAUDE.md
|
|
20
|
+
const projectRoot = path.resolve(options.projectRoot || process.cwd());
|
|
21
|
+
try {
|
|
22
|
+
await runGenerate({
|
|
23
|
+
projectRoot,
|
|
24
|
+
backendRoot: options.backendRoot,
|
|
25
|
+
frontendRoot: options.frontendRoot,
|
|
26
|
+
output: specsDir,
|
|
27
|
+
aiContext: true,
|
|
28
|
+
});
|
|
29
|
+
const claudeMdPath = path.join(projectRoot, "CLAUDE.md");
|
|
30
|
+
try {
|
|
31
|
+
await fs.stat(claudeMdPath);
|
|
32
|
+
await runContext({ input: specsDir, output: claudeMdPath });
|
|
33
|
+
}
|
|
34
|
+
catch {
|
|
35
|
+
// No CLAUDE.md — skip context injection
|
|
36
|
+
}
|
|
37
|
+
}
|
|
38
|
+
catch {
|
|
39
|
+
// Non-fatal — context generation failure should not break extract
|
|
40
|
+
}
|
|
16
41
|
}
|
|
@@ -3,12 +3,13 @@ import path from "node:path";
|
|
|
3
3
|
import { buildSnapshots } from "../extract/index.js";
|
|
4
4
|
import { renderContextBlock } from "../extract/context-block.js";
|
|
5
5
|
import { getOutputLayout } from "../output-layout.js";
|
|
6
|
+
import { DEFAULT_SPECS_DIR } from "../config.js";
|
|
6
7
|
import { analyzeDepth } from "../extract/analyzers/depth.js";
|
|
7
8
|
export async function runGenerate(options) {
|
|
8
9
|
if (!options.aiContext) {
|
|
9
10
|
throw new Error("`specguard generate` currently supports `--ai-context` only.");
|
|
10
11
|
}
|
|
11
|
-
const outputRoot = path.resolve(options.output ??
|
|
12
|
+
const outputRoot = path.resolve(options.output ?? DEFAULT_SPECS_DIR);
|
|
12
13
|
const layout = getOutputLayout(outputRoot);
|
|
13
14
|
const { architecture, ux } = await buildSnapshots({
|
|
14
15
|
projectRoot: options.projectRoot,
|
package/dist/commands/guard.js
CHANGED
|
@@ -6,6 +6,7 @@ import { runSimulate } from "./simulate.js";
|
|
|
6
6
|
import { buildSnapshots } from "../extract/index.js";
|
|
7
7
|
import { renderContextBlock } from "../extract/context-block.js";
|
|
8
8
|
import { logResolvedProjectPaths, resolveProjectPaths } from "../project-discovery.js";
|
|
9
|
+
import { DEFAULT_SPECS_DIR } from "../config.js";
|
|
9
10
|
export async function runGuard(options) {
|
|
10
11
|
const resolved = await resolveProjectPaths({
|
|
11
12
|
projectRoot: options.projectRoot,
|
|
@@ -30,7 +31,7 @@ export async function runGuard(options) {
|
|
|
30
31
|
projectRoot: resolved.workspaceRoot,
|
|
31
32
|
backendRoot: resolved.backendRoot,
|
|
32
33
|
frontendRoot: resolved.frontendRoot,
|
|
33
|
-
output:
|
|
34
|
+
output: DEFAULT_SPECS_DIR,
|
|
34
35
|
includeFileGraph: true,
|
|
35
36
|
configPath: options.configPath
|
|
36
37
|
});
|
package/dist/commands/init.js
CHANGED
|
@@ -12,6 +12,7 @@
|
|
|
12
12
|
*/
|
|
13
13
|
import fs from "node:fs/promises";
|
|
14
14
|
import path from "node:path";
|
|
15
|
+
import { DEFAULT_SPECS_DIR } from "../config.js";
|
|
15
16
|
const DEFAULT_CONFIG = {
|
|
16
17
|
project: {
|
|
17
18
|
backendRoot: "./backend",
|
|
@@ -47,7 +48,7 @@ exit 0
|
|
|
47
48
|
`;
|
|
48
49
|
export async function runInit(options) {
|
|
49
50
|
const root = path.resolve(options.projectRoot || process.cwd());
|
|
50
|
-
const specsDir = path.join(root, options.output ||
|
|
51
|
+
const specsDir = path.join(root, options.output || DEFAULT_SPECS_DIR);
|
|
51
52
|
console.log(`Initializing Guardian in ${root}\n`);
|
|
52
53
|
// 1. Create .specs/ directory
|
|
53
54
|
await fs.mkdir(path.join(specsDir, "machine", "docs"), { recursive: true });
|
package/dist/commands/search.js
CHANGED
|
@@ -3,8 +3,9 @@ import path from "node:path";
|
|
|
3
3
|
import yaml from "js-yaml";
|
|
4
4
|
import { loadHeatmap } from "../extract/compress.js";
|
|
5
5
|
import { resolveMachineInputDir } from "../output-layout.js";
|
|
6
|
+
import { DEFAULT_SPECS_DIR } from "../config.js";
|
|
6
7
|
export async function runSearch(options) {
|
|
7
|
-
const inputDir = await resolveMachineInputDir(options.input ||
|
|
8
|
+
const inputDir = await resolveMachineInputDir(options.input || DEFAULT_SPECS_DIR);
|
|
8
9
|
const { architecture, ux } = await loadSnapshots(inputDir);
|
|
9
10
|
const heatmap = await loadHeatmap(inputDir);
|
|
10
11
|
const types = normalizeTypes(options.types);
|
|
@@ -6,6 +6,7 @@ import { spawn } from "node:child_process";
|
|
|
6
6
|
import yaml from "js-yaml";
|
|
7
7
|
import { buildSnapshots } from "../extract/index.js";
|
|
8
8
|
import { buildArchitectureSummary, loadArchitectureSummary } from "../extract/compress.js";
|
|
9
|
+
import { DEFAULT_SPECS_DIR } from "../config.js";
|
|
9
10
|
import { createIgnoreMatcher } from "../extract/ignore.js";
|
|
10
11
|
import { logResolvedProjectPaths, resolveProjectPaths } from "../project-discovery.js";
|
|
11
12
|
export async function runSimulate(options) {
|
|
@@ -106,7 +107,7 @@ async function resolveBaselineSummaryPath(params) {
|
|
|
106
107
|
if (params.override) {
|
|
107
108
|
candidates.push(params.override);
|
|
108
109
|
}
|
|
109
|
-
candidates.push(path.join(params.projectRoot,
|
|
110
|
+
candidates.push(path.join(params.projectRoot, DEFAULT_SPECS_DIR, "machine", "architecture.summary.json"));
|
|
110
111
|
for (const candidate of candidates) {
|
|
111
112
|
const resolved = path.isAbsolute(candidate)
|
|
112
113
|
? candidate
|
package/dist/commands/summary.js
CHANGED
|
@@ -4,8 +4,9 @@ import yaml from "js-yaml";
|
|
|
4
4
|
import { renderExecutiveSummary } from "../extract/docs.js";
|
|
5
5
|
import { loadArchitectureSummary, loadArchitectureDiff, loadHeatmap } from "../extract/compress.js";
|
|
6
6
|
import { resolveMachineInputDir } from "../output-layout.js";
|
|
7
|
+
import { DEFAULT_SPECS_DIR } from "../config.js";
|
|
7
8
|
export async function runSummary(options) {
|
|
8
|
-
const inputDir = await resolveMachineInputDir(options.input ||
|
|
9
|
+
const inputDir = await resolveMachineInputDir(options.input || DEFAULT_SPECS_DIR);
|
|
9
10
|
const architecturePath = path.join(inputDir, "architecture.snapshot.yaml");
|
|
10
11
|
const uxPath = path.join(inputDir, "ux.snapshot.yaml");
|
|
11
12
|
const [architectureRaw, uxRaw] = await Promise.all([
|
package/dist/config.js
CHANGED
|
@@ -1,5 +1,7 @@
|
|
|
1
1
|
import fs from "node:fs/promises";
|
|
2
2
|
import path from "node:path";
|
|
3
|
+
/** Single source of truth for the default specs output directory */
|
|
4
|
+
export const DEFAULT_SPECS_DIR = ".specs";
|
|
3
5
|
const DEFAULT_CONFIG = {
|
|
4
6
|
project: {
|
|
5
7
|
root: "",
|
|
@@ -29,8 +31,13 @@ const DEFAULT_CONFIG = {
|
|
|
29
31
|
"log",
|
|
30
32
|
"tmp",
|
|
31
33
|
"cache",
|
|
32
|
-
"specs
|
|
33
|
-
"ghost-out"
|
|
34
|
+
".specs",
|
|
35
|
+
"ghost-out",
|
|
36
|
+
"ios",
|
|
37
|
+
"android",
|
|
38
|
+
".expo",
|
|
39
|
+
".turbo",
|
|
40
|
+
"web-build"
|
|
34
41
|
],
|
|
35
42
|
paths: []
|
|
36
43
|
},
|
|
@@ -77,6 +84,9 @@ const DEFAULT_CONFIG = {
|
|
|
77
84
|
timeoutMs: 120000,
|
|
78
85
|
promptTemplate: ""
|
|
79
86
|
},
|
|
87
|
+
output: {
|
|
88
|
+
specsDir: DEFAULT_SPECS_DIR
|
|
89
|
+
},
|
|
80
90
|
docs: {
|
|
81
91
|
mode: "lean",
|
|
82
92
|
internalDir: "internal"
|
|
@@ -338,6 +348,9 @@ function mergeConfig(base, override) {
|
|
|
338
348
|
timeoutMs: override.llm?.timeoutMs ?? base.llm?.timeoutMs ?? 120000,
|
|
339
349
|
promptTemplate: override.llm?.promptTemplate ?? base.llm?.promptTemplate ?? ""
|
|
340
350
|
},
|
|
351
|
+
output: {
|
|
352
|
+
specsDir: override.output?.specsDir ?? base.output?.specsDir ?? DEFAULT_SPECS_DIR
|
|
353
|
+
},
|
|
341
354
|
docs: {
|
|
342
355
|
mode: override.docs?.mode ?? base.docs?.mode ?? "lean",
|
|
343
356
|
internalDir: override.docs?.internalDir ?? base.docs?.internalDir ?? "internal"
|
package/dist/extract/cache.js
CHANGED
|
@@ -1,9 +1,10 @@
|
|
|
1
1
|
import crypto from "node:crypto";
|
|
2
2
|
import fs from "node:fs/promises";
|
|
3
3
|
import path from "node:path";
|
|
4
|
+
import { DEFAULT_SPECS_DIR } from "../config.js";
|
|
4
5
|
const BACKEND_CACHE_VERSION = "specguard-backend-cache-v4";
|
|
5
6
|
export async function loadBackendExtractionCache(params) {
|
|
6
|
-
const cachePath = path.join(params.projectRoot,
|
|
7
|
+
const cachePath = path.join(params.projectRoot, DEFAULT_SPECS_DIR, ".cache", "file-hashes.json");
|
|
7
8
|
const configHash = hashObject(params.config);
|
|
8
9
|
try {
|
|
9
10
|
const raw = await fs.readFile(cachePath, "utf8");
|
package/dist/output-layout.js
CHANGED
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import path from "node:path";
|
|
2
2
|
import fs from "node:fs/promises";
|
|
3
|
+
import { DEFAULT_SPECS_DIR } from "./config.js";
|
|
3
4
|
export function getOutputLayout(outputRoot, internalDir = "internal") {
|
|
4
5
|
const rootDir = path.resolve(outputRoot);
|
|
5
6
|
const machineDir = path.join(rootDir, "machine");
|
|
@@ -12,7 +13,7 @@ export function getOutputLayout(outputRoot, internalDir = "internal") {
|
|
|
12
13
|
};
|
|
13
14
|
}
|
|
14
15
|
export async function resolveMachineInputDir(input) {
|
|
15
|
-
const resolved = path.resolve(input ||
|
|
16
|
+
const resolved = path.resolve(input || DEFAULT_SPECS_DIR);
|
|
16
17
|
const directSnapshot = await hasMachineSnapshots(resolved);
|
|
17
18
|
if (directSnapshot) {
|
|
18
19
|
return resolved;
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import fs from "node:fs/promises";
|
|
2
2
|
import path from "node:path";
|
|
3
|
-
import { loadSpecGuardConfig } from "./config.js";
|
|
3
|
+
import { DEFAULT_SPECS_DIR, loadSpecGuardConfig } from "./config.js";
|
|
4
4
|
const IGNORE_DIRS = new Set([
|
|
5
5
|
".git",
|
|
6
6
|
"node_modules",
|
|
@@ -11,7 +11,7 @@ const IGNORE_DIRS = new Set([
|
|
|
11
11
|
"__pycache__",
|
|
12
12
|
".venv",
|
|
13
13
|
"venv",
|
|
14
|
-
|
|
14
|
+
DEFAULT_SPECS_DIR,
|
|
15
15
|
".pytest_cache",
|
|
16
16
|
".mypy_cache",
|
|
17
17
|
".turbo"
|
package/package.json
CHANGED