@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 CHANGED
@@ -1,223 +1,195 @@
1
1
  # Guardian
2
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.
3
+ [![npm version](https://img.shields.io/npm/v/@toolbaux/guardian.svg)](https://www.npmjs.com/package/@toolbaux/guardian)
4
+ [![license](https://img.shields.io/npm/l/@toolbaux/guardian.svg)](./LICENSE)
4
5
 
5
- ## Quick Start
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
- # Initialize a project (creates .specs/, config, pre-commit hook, CLAUDE.md context)
9
+ npm install -g @toolbaux/guardian
9
10
  guardian init
11
+ ```
10
12
 
11
- # Or run extraction manually
12
- guardian extract
13
- guardian generate --ai-context
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 architecture context (between `<!-- guardian:auto-context -->` markers)
19
- - Pre-commit hook that keeps context fresh on every commit
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
- ## What Guardian Solves
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
- **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.
60
+ Guardian auto-injects architecture context into `CLAUDE.md` so your AI tool reads it at session start:
27
61
 
28
- ## Installation
62
+ ```markdown
63
+ # my-project
29
64
 
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
65
+ <!-- guardian:auto-context -->
66
+ ## Codebase Map
67
+ **Backend:** 16 schemas · 35 endpoints · 9 modules
68
+ **Frontend:** 10 components · 8 pages
36
69
 
37
- # Or install from npm
38
- npm install -g @toolbaux/guardian
70
+ ### High-Coupling Files
71
+ - shared/policy/__init__.py (score 1.00)
72
+ - service-conversation/engine.py (score 0.40)
39
73
 
40
- # Initialize a project
41
- cd /path/to/your/project
42
- guardian init
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
- ### VSCode Extension
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
- The extension adds a status bar indicator, background auto-extract on save, and command palette integration.
82
+ ## Key Commands
48
83
 
49
- **Install via symlink (development):**
50
84
  ```bash
51
- ln -sf /path/to/guardian/vscode-extension ~/.vscode/extensions/guardian-vscode
52
- # Reload VSCode: Cmd+Shift+P → "Reload Window"
53
- ```
85
+ # One-time setup — creates config, .specs/, pre-commit hook, CLAUDE.md
86
+ guardian init
54
87
 
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
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
- **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)
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
- **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 |
106
+ All extraction uses Tree-Sitter AST parsing — deterministic, no LLM involved.
77
107
 
78
- ## All Commands (18)
108
+ ## What Guardian Generates
79
109
 
80
- ### Project Setup
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
- # Initialize project: config, .specs dir, pre-commit hook, CLAUDE.md
84
- guardian init
126
+ # Install from npm
127
+ npm install -g @toolbaux/guardian
85
128
 
86
- # Full extraction: architecture + UX snapshots + codebase intelligence + docs
87
- guardian extract
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
- # AI context file only (compact ~3K token summary)
90
- guardian generate --ai-context
137
+ <details>
138
+ <summary><strong>All Commands (18)</strong></summary>
139
+
140
+ ### Project Setup
91
141
 
92
- # Build codebase-intelligence.json from existing snapshots
93
- guardian intel
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
- # Search all artifacts by keyword (models, endpoints, components, modules, tasks)
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
- # 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
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
- # Compute drift metrics (D_t, K_t, delta, entropy, cycles)
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
- # 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
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 (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
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 # print without calling LLM
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
- ### VSCode Commands (Command Palette)
189
+ </details>
162
190
 
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
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
- ## 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
224
+ </details>
271
225
 
272
- <!-- guardian:auto-context -->
273
- ## Codebase Map
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
- ### High-Coupling Files
278
- - shared/policy/__init__.py (score 1.00)
279
- - service-conversation/engine.py (score 0.40)
280
- ...
281
- <!-- /guardian:auto-context -->
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
- 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.
244
+ </details>
318
245
 
319
- ## Key Metrics
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. Same shape + different fingerprint = additive change |
255
+ | **Shape fingerprint** | Change = structural refactor |
328
256
 
329
- ## Automation Flow
257
+ </details>
330
258
 
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
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
- ## Development
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", "specs-out")
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", "specs-out")
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 ?? "specs-out",
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", "specs-out")
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 ?? "specs-out",
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", "specs-out")
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 ?? "specs-out",
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", "specs-out")
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 ?? "specs-out",
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", "specs-out")
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", "specs-out")
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", "specs-out")
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", "specs-out")
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", "specs-out")
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 ?? "specs-out",
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", ".specs")
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 ?? "specs-out",
12
+ output: options.output ?? DEFAULT_SPECS_DIR,
12
13
  includeFileGraph: true,
13
14
  configPath: options.configPath
14
15
  });
@@ -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 || "specs-out");
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),
@@ -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 ?? "specs-out");
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,
@@ -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: "specs-out",
34
+ output: DEFAULT_SPECS_DIR,
34
35
  includeFileGraph: true,
35
36
  configPath: options.configPath
36
37
  });
@@ -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 || ".specs");
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 });
@@ -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 || "specs-out");
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, "specs-out", "machine", "architecture.summary.json"));
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
@@ -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 || "specs-out");
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-out",
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"
@@ -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, "specs-out", ".cache", "file-hashes.json");
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");
@@ -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 || "specs-out");
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
- "specs-out",
14
+ DEFAULT_SPECS_DIR,
15
15
  ".pytest_cache",
16
16
  ".mypy_cache",
17
17
  ".turbo"
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@toolbaux/guardian",
3
- "version": "0.1.1",
3
+ "version": "0.1.3",
4
4
  "type": "module",
5
5
  "description": "Architectural intelligence for codebases. Verify that AI-generated code matches your architectural intent.",
6
6
  "keywords": [