@luizsantiago/spec-guardrails 3.6.0 → 3.9.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/README.md CHANGED
@@ -5,207 +5,139 @@
5
5
 
6
6
  **Keep AI coding agents honest — specify the work, prove each step, verify independently.**
7
7
 
8
- | | |
9
- | --- | --- |
10
- | **Problem** | Agents ship “looks good” with thin specs, missing evidence, and the same context that wrote the code declaring victory. |
11
- | **Solution** | One kit, two deliberate modes: **Process** (Node only) for a flexible spec-driven workflow; **Brakes** (Node + Python) for the **full product** — structural gates that exit non-zero when paperwork or evidence is missing. You approve specs/tasks in both. |
12
- | **Result** | Traceable `.specs/` memory, fewer fake finishes, cheaper turns (~70% less skill text on planning). Choose Process for light ceremony; add Python when you want the [Guarantees matrix](#guarantees-matrix) enforced automatically. |
13
-
14
- npm: [`@luizsantiago/spec-guardrails`](https://www.npmjs.com/package/@luizsantiago/spec-guardrails) **3.6.x**
8
+ npm: [`@luizsantiago/spec-guardrails`](https://www.npmjs.com/package/@luizsantiago/spec-guardrails) **3.9.x**
15
9
 
16
10
  ---
17
11
 
18
- ## Install
19
-
20
- ```bash
21
- npx @luizsantiago/spec-guardrails install
22
- ```
12
+ ## What is Spec Guardrails?
23
13
 
24
- ### What you need
14
+ **Spec Guardrails** is a **process kit** for AI-assisted software development. It does not replace your app, framework, or tests. It gives your agent a repeatable way to work:
25
15
 
26
- | Requirement | Role |
27
- | --- | --- |
28
- | **Node.js 18+** | Required runs the CLI and `install` |
29
- | **Python 3.10+** | Activates **Brakes mode** — the **full** kit with Python structural gates (`validate-spec`, `validate-tasks`, …). Gates stay Python by design. Without Python you stay in **Process mode**: same phases and checklists, no exit-code enforcement. Run [`doctor`](#install) to see which mode you are in |
16
+ 1. **Write down the goal** before coding (`spec.md`)
17
+ 2. **Break work into small jobs** (`tasks.md`)
18
+ 3. **Implement in waves** with real checks
19
+ 4. **Verify with fresh eyes** proof, not “trust me”
30
20
 
31
- ### What install does
21
+ Everything important lives in **`.specs/`** in your repo so the project remembers decisions across chats and teammates.
32
22
 
33
- | Lands in your project | Purpose |
34
- | --- | --- |
35
- | `.cursor/skills/` + `.claude/skills/` + `.github/skills/` + `.codex/skills/` | Hub, phase references, sister skills (**shipped adapters** — same content, product-specific paths) |
36
- | `.specs/guardrails/scripts/` | Python gate scripts (Brakes mode) |
37
- | `.specs/STATE.md`, `.specs/features/`, … | Project memory (any agent) |
38
- | `.cursor/rules/engineering-baseline.mdc` | Always-on Cursor rule |
23
+ **You** approve specs and tasks. **The agent** follows phase guides and runs checks. **Gates** (optional Python scripts) can **stop** the workflow when paperwork or evidence is missing.
39
24
 
40
- **Agent environments:** the **core** (`.specs/`, CLI, hub, Python gates) works with any AI agent. **Install** ships adapters for **Cursor, Claude Code, GitHub Copilot, and OpenAI Codex** (plus root `AGENTS.md`). See [Architecture](docs/guide/Architecture.md).
41
-
42
- Re-run `install` anytime to refresh skills; your `.specs/` decisions and `STATE.md` are kept.
43
-
44
- ### Governance focus (3.2+)
45
-
46
- Spec Guardrails is a **single product**: governance, evidence, verification, and controlled execution for agentic software development — not a bundle of companion packages.
47
-
48
- | Capability | What it does |
49
- | --- | --- |
50
- | **Artifact gates** | Structural quality for spec, tasks, and cross-artifact consistency — with **blocking / warning / info** severity |
51
- | **Parallel waves** | `loop-plan` computes safe parallel groups; `workspace-prepare` isolates tasks in git worktrees |
52
- | **Execution policy** | Budgets, path scope, and escalation rules in `.specs/config.yaml` — consult via `execution-policy` |
53
- | **Independent verify** | Fresh-context verification with evidence-or-zero (`validate-state`) |
54
-
55
- | Need | Command |
56
- | --- | --- |
57
- | First time / upgrade | `install` |
58
- | Existing codebase | `project-init` (optional) |
59
- | Something looks wrong | `doctor` |
60
- | Full CLI list | `--help` |
25
+ > **Plain-language guide:** [Overview](docs/guide/Overview.md) · [How it works](docs/guide/How-it-works.md) · [Quick start](docs/guide/Quick-start.md)
61
26
 
62
27
  ---
63
28
 
64
- ## How it works in one screen
29
+ ## What it is not
65
30
 
66
- Four ideas stack full explanation: **[Concepts](docs/guide/concepts.md)**
67
-
68
- | Idea | What it is | What it does |
69
- | --- | --- | --- |
70
- | **Spec-driven** | Written plan before code | `spec.md` + `tasks.md`; evidence before “done” |
71
- | **Brakes / Gates** | Structural stop-gates | Python scripts exit non-zero when paperwork or evidence is missing |
72
- | **Loop** | Execute in waves | `loop-plan` picks the next jobs; sub-agents when files don’t overlap |
73
- | **Graph** | Parallel task map | `task-graph.md` — safe parallelism without file collisions |
74
- | **Memory** | Persistent project state | `.specs/` — specs, decisions, and handoff survive across chats |
75
-
76
- **You** approve specs and tasks. **The agent** runs gates and implements. **Gates** exit non-zero when paperwork or evidence is missing.
77
-
78
- Plain-language tour: [Home](docs/guide/Home.md) · [How it works](docs/guide/How-it-works.md) · [Quick start](docs/guide/Quick-start.md)
31
+ - Not a code generator or a new framework
32
+ - Not a vector database or “AI memory” that reads your whole codebase
33
+ - Not automatic push/merge/deploy you stay in control of git tiers
34
+ - Not a replacement for code review or product judgment
79
35
 
80
36
  ---
81
37
 
82
- ## Operating modes
83
-
84
- Two modes, one package — pick how much rigor you want:
85
-
86
- | Mode | Runtime | What you get | Best for |
87
- | --- | --- | --- | --- |
88
- | **Process** | Node.js 18+ | Spec-driven workflow, `.specs/` memory, progressive loading, independent `/verify` | Flexible ceremony, exploration, teams that enforce by review |
89
- | **Brakes** | Node + **Python 3.10+** | Everything in Process **plus** Python gates from the [Guarantees matrix](#guarantees-matrix) — exit ≠ 0 → stop and fix | The **full Spec Guardrails** — traceability, evidence, and structural guarantees enforced automatically |
90
-
91
- **Gates stay Python.** That is the product: Brakes mode is the complete version with automated enforcement. Process mode is the same loop without exit-code brakes — intentional, not incomplete.
92
-
93
- Install Python when you want gates to fire; run `doctor` to confirm Brakes are available.
38
+ ## Install (once per project)
94
39
 
95
- ---
40
+ ```bash
41
+ npx @luizsantiago/spec-guardrails install
42
+ ```
96
43
 
97
- ## Guarantees matrix
44
+ | Requirement | Purpose |
45
+ | --- | --- |
46
+ | **Node.js 18+** | Required — CLI and install |
47
+ | **Python 3.10+** | Optional — enables **Brakes mode** (automatic gates). Without Python you still get the full workflow in **Process mode** |
98
48
 
99
- **Guarantees are the product.** Commands are implementation.
49
+ Re-run `install` after upgrading the package. Your `.specs/` notes and `STATE.md` are kept.
100
50
 
101
- | Guarantee | Mechanism | Mode | Enforcement |
102
- | --- | --- | --- | --- |
103
- | Intent exists before code | `validate-spec` | Brakes | Hard gate |
104
- | Tasks derive from requirements | `analyze-artifacts` | Brakes | Hard gate |
105
- | Task shape and graph when needed | `validate-tasks` | Brakes | Hard gate |
106
- | Requirements stay traceable | `validate-traceability` | Brakes | Hard gate |
107
- | Quick evidence is complete | `validate-quick` | Brakes | Hard gate |
108
- | Dependencies respected in Execute | `loop-plan` | Brakes | Hard gate |
109
- | Parallel work is file-safe | `task-graph.md` + `validate-tasks` | Process + Brakes | Artifact + gate |
110
- | Completion cites evidence | `validate-state` | Brakes | Hard gate |
111
- | Commits follow policy | `check-commit` | Brakes | Hard gate |
112
- | Lessons grounded after FAIL | `lessons` | Brakes | Hard gate |
113
- | Verification is independent | `/verify` + `validate.md` | Process | Phase skill |
114
- | Knowledge survives chats | `.specs/` + `archive-feature` | Process | Install + CLI |
51
+ Check readiness: `npx @luizsantiago/spec-guardrails doctor`
115
52
 
116
- Full matrix, limits, and phase diagram **[Guarantees matrix](docs/guide/Guarantees-matrix.md)** · [Architecture](docs/guide/Architecture.md) (Core + adapters)
53
+ Works with **Cursor, Claude Code, GitHub Copilot, OpenAI Codex**, and other agents via root `AGENTS.md`. See [Platform parity](docs/guide/Platform-parity.md).
117
54
 
118
55
  ---
119
56
 
120
- ## Token cost
57
+ ## How you use it day to day
121
58
 
122
- Progressive loading is the main cost win: **one working set per turn**, not the entire playbook.
59
+ You talk to the agent in **chat**, not the terminal. These are **agent commands** (phrases the agent understands):
123
60
 
124
- | Profile | Est. tokens | When |
125
- | ---: | ---: | --- |
126
- | Naive full dump (don’t) | ~31k | Loading every skill + reference every message |
127
- | Specify turn | ~9k | `/specify` hub + `specify.md` + standards |
128
- | Tasks turn | ~10k | `/tasks` hub + `tasks.md` + task-graph skill |
129
- | Execute `/loop` (one wave) | ~4k | One implement wave (inline or parallel) |
130
- | Verify turn | ~6k | Independent reviewer stack |
61
+ | Command | When to use |
62
+ | --- | --- |
63
+ | `/specify` | Start a real feature written requirements first |
64
+ | `/tasks` | Shopping list of jobs after you approve the spec |
65
+ | `/loop` | Implementone wave at a time |
66
+ | `/verify` | Proof after the last job use a **fresh** chat when possible |
67
+ | `/quick` | Tiny fix only (≤3 files, no big design decisions) |
131
68
 
132
- Savings vs full dump: **~72%** (Specify), **~86%** (Execute). Numbers from `lib/token-cost.js`; CI guardrails in `test/test_token_cost.test.js`. Order-of-magnitude only — not a billing API.
69
+ **Typical path:** `/specify` approve `/tasks` approve `/loop` `/verify` `/archive`
133
70
 
134
- More: [Token efficiency](docs/guide/Token-efficiency.md)
71
+ You do **not** need to memorize CLI commands. The agent runs gates and helpers for you when **Brakes mode** is available.
135
72
 
136
- ---
73
+ **Full command reference:** [Agent commands](docs/guide/agent-commands.md)
137
74
 
138
- ## Complexity tiers (how work flows)
75
+ ---
139
76
 
140
- The hub **Complexity Router** picks how much ceremony a feature needs — Quick, Simple, Medium, Complex, or Parallel. It is **not** a separate product feature; it is how the agent decides which phases to run.
77
+ ## Two modes (same product)
141
78
 
142
- | Tier | Typical scope | Path |
79
+ | Mode | You need | What changes |
143
80
  | --- | --- | --- |
144
- | **Quick** | ≤3 files, no new deps | `/quick` verify commit |
145
- | **Simple** | Small localized change | `/specify` `/loop` `/verify` |
146
- | **Medium** | New feature, <10 tasks | `/specify` → `/tasks` → `/loop` → `/verify` → `/archive` |
147
- | **Complex** | APIs, architecture, infra | + `/discuss`, `/plan`, optional security/QA on verify |
148
- | **Parallel** | Splittable work | Above + `/task-graph` when 3+ tasks |
149
-
150
- Rules and examples: [Concepts → Complexity tiers](docs/guide/concepts.md#complexity-tiers--how-the-agent-chooses-depth)
151
-
152
- ---
81
+ | **Process** | Node only | Full workflow + `.specs/` memory + phase skills |
82
+ | **Brakes** | Node + Python | Same + scripts that **exit non-zero** when specs, tasks, or evidence are incomplete |
153
83
 
154
- ## Hub and skills (summary)
84
+ Both are intentional. Add Python when you want automatic stop signs, not just checklists.
155
85
 
156
- Install copies a **hub** (`agent-architecture.md`), **phase references** (`references/*.md`), and **sister skills** (security, task-graph, …). The agent loads **one phase file at a time**.
86
+ Details: [FAQ Process vs Brakes](docs/guide/FAQ.md#process-vs-brakes)
157
87
 
158
- | Load order | Layer | Role | Examples |
159
- | ---: | --- | --- | --- |
160
- | 1 | **Hub** | Contract, complexity router, gate schedule | `agent-architecture.md` |
161
- | 2 | **Reference** | One phase procedure per turn | `specify.md`, `implement.md`, `validate.md` |
162
- | 3 | **Sister** (optional) | Cross-cutting depth, on demand | `engineering-standards.md`, `task-graph-engineering.md` |
163
- | 4 | **Gate** | Automatic check at the boundary | `validate-spec`, `loop-plan`, `check-commit` |
88
+ ---
164
89
 
165
- Conditional sisters (`appsec.md`, `qa-strategy.md`, …) load **one at a time** on Verify when risk warrants it.
90
+ ## What lands in your repo
166
91
 
167
- Full map: **[Skills and hub](docs/guide/skills-and-hub.md)**
92
+ | Path | Role |
93
+ | --- | --- |
94
+ | `.cursor/skills/` (and Claude, Copilot, Codex trees) | Instructions the agent reads per phase |
95
+ | `.specs/STATE.md` | Where you are — active feature, next step |
96
+ | `.specs/features/NNN-slug/` | Spec, tasks, validation for each feature |
97
+ | `.specs/guardrails/scripts/` | Python gates (Brakes mode) |
98
+ | `.specs/config.yaml` | Optional project rules and policy |
168
99
 
169
100
  ---
170
101
 
171
- ## Gates (summary)
102
+ ## Optional capabilities (use when you need them)
172
103
 
173
- Commands implement the guarantees above. Scripts in `.specs/guardrails/scripts/`. **Exit 0 stop and fix.**
104
+ Most projects start with Specify Tasks Loop Verify only. Turn these on when the work warrants it:
174
105
 
175
- | When | Gate | What it blocks |
106
+ | Capability | Plain purpose | Learn more |
176
107
  | --- | --- | --- |
177
- | Before approving spec | `validate-spec` | Incomplete or untestable spec |
178
- | Before approving tasks | `analyze-artifacts` | Spec tasks drift |
179
- | Before approving tasks | `validate-tasks` | Bad tasks; missing graph when 3+ tasks |
180
- | After tasks / with validation | `validate-traceability` | REQ missing from tasks or coverage lines |
181
- | End of `/quick` | `validate-quick` | Incomplete Quick TASK/SUMMARY; >3 files; sensitive paths |
182
- | Each `/loop` wave | `loop-plan` | Blocked dependencies; shows parallel groups |
183
- | Each commit | `check-commit` | Non-Conventional commit message |
184
- | Before “done” | `validate-state` | Fake PASS without test evidence |
185
- | After Verify FAIL | `lessons` | Ungrounded “lessons learned” |
186
- | After Verify PASS | `archive-feature` | (CLI) folds feature into domain memory |
187
-
188
- Full reference: **[Gates](docs/guide/gates.md)** · [Guarantees matrix](docs/guide/Guarantees-matrix.md) · [Gates and guarantees](docs/guide/Gates-and-guarantees.md)
108
+ | **Memory search** | Find text in past specs and validations | [Memory guide](docs/guide/Memory.md) |
109
+ | **Context guards** | Check scope before edit or “done” | [Agent commands](docs/guide/agent-commands.md) · **Cursor:** auto via hooks |
110
+ | **Episodic memory** | Session notes episodic lessons | [Memory Episodes](docs/guide/Memory.md#episodic-memory-lifecycle) |
111
+ | **Code index** | Lightweight brownfield file/symbol map | [Brownfield context](docs/guide/brownfield-context.md) |
112
+ | **Sandbox policy** | Block/warn destructive shell commands | [Overview Safety](docs/guide/Overview.md#safety-and-limits) |
113
+ | **Execution policy** | Limit paths, retries, dangerous ops | [Overview → Safety](docs/guide/Overview.md#safety-and-limits) |
114
+ | **Solution exploration** | Compare two+ implementations before committing | [Overview Exploration](docs/guide/Overview.md#optional-exploration-mode) |
115
+ | **Semantic retrieval** | Search by meaning (needs OpenAI or Ollama) | [Memory → Semantic](docs/guide/Memory.md#semantic-search-optional) |
116
+
117
+ **Default:** semantic search is **off**. FTS + graph search work without any API.
189
118
 
190
119
  ---
191
120
 
192
121
  ## Documentation
193
122
 
194
- | Doc | For |
123
+ | Start here | Best for |
195
124
  | --- | --- |
196
- | [Guarantees matrix](docs/guide/Guarantees-matrix.md) | Product promises mechanisms |
197
- | [Architecture](docs/guide/Architecture.md) | Core vs platform adapters |
198
- | [Agent commands](docs/guide/agent-commands.md) | Every `/specify`, `/loop`, `/verify`, … — purpose, when, examples |
125
+ | **[Overview](docs/guide/Overview.md)** | Complete simple picture workflow, layers, team use |
199
126
  | [Quick start](docs/guide/Quick-start.md) | First ten minutes |
200
- | [Concepts](docs/guide/concepts.md) | Spec-driven + guardrails + loop + graph |
201
- | [Skills and hub](docs/guide/skills-and-hub.md) | What each skill file does |
202
- | [Gates](docs/guide/gates.md) | How each gate works |
203
- | [Platform parity](docs/guide/Platform-parity.md) | Shipped adapters (Cursor, Claude, Copilot, Codex) — core works with any agent |
204
- | [Restart PRD seed](docs/guide/Restart-prd-seed.md) | Clean-project PRD template for a single-package restart |
127
+ | [How it works](docs/guide/How-it-works.md) | Story from idea to archive |
128
+ | [Memory](docs/guide/Memory.md) | `.specs/` memory and search when and how much |
205
129
  | [FAQ](docs/guide/FAQ.md) | Common questions |
206
- | [Changelog](docs/CHANGELOG.md) | Full version history |
207
130
 
208
- Start after install: [Quick start](docs/guide/Quick-start.md) · [Agent commands](docs/guide/agent-commands.md)
131
+ | Go deeper | Best for |
132
+ | --- | --- |
133
+ | [Agent commands](docs/guide/agent-commands.md) | Every `/specify`, `/loop`, CLI helper |
134
+ | [Guarantees matrix](docs/guide/Guarantees-matrix.md) | Product promises → mechanisms |
135
+ | [Gates](docs/guide/gates.md) | What each gate checks |
136
+ | [Architecture](docs/guide/Architecture.md) | Core vs platform adapters |
137
+ | [Concepts](docs/guide/concepts.md) | Spec-driven, loop, graph, tiers |
138
+ | [Changelog](docs/CHANGELOG.md) | Version history |
139
+
140
+ Doc index: [docs/guide/README.md](docs/guide/README.md)
209
141
 
210
142
  ---
211
143
 
@@ -215,59 +147,12 @@ Start after install: [Quick start](docs/guide/Quick-start.md) · [Agent commands
215
147
  npx @luizsantiago/spec-guardrails install
216
148
  ```
217
149
 
218
- | Version | What you gain |
219
- | --- | --- |
220
- | **3.6.x** | Hybrid retrieval (`memory-retrieve`); chunk FTS; optional semantic embed |
221
- | **3.5.x** | Solution exploration (`solution-explore`) — compare candidates from approved spec |
222
- | **3.4.x** | Contextual guards (`context-guard`); FTS memory search (`memory-search`) |
223
- | **3.3.x** | Intent/effect policy — `check-path --op read|write|delete` and `effects` config block |
224
- | **3.2.x** | Single-package focus; artifact gate severity labels; git worktree isolation CLI; execution policy (budget/scope/escalation) |
225
- | **3.1.x** | Copilot/Codex/AGENTS.md adapters; doctor Process + Brakes scores; `validate-traceability` / `validate-quick`; `classify-change` / `feature-status` |
226
- | **3.0.x** | Final name Spec Guardrails; `.specs/guardrails/`; no dual-path ([Migration](docs/guide/Migration.md)) |
227
- | **2.2.x** | Seatbelt-era paths & markers; `doctor` Execute hints; docs split from README |
228
- | **2.1.x** | `loop-plan` + parallel `/loop` waves |
229
- | **2.0.x** | Package rename → `@luizsantiago/spec-seatbelt` (superseded by 3.0) |
230
- | **1.1.x** | `project-init` for brownfield repos |
231
- | **0.9.x** | `archive-feature` + domain memory merge |
232
-
233
- Full history: [CHANGELOG](docs/CHANGELOG.md) · [Releases](https://github.com/luizssantiago92/spec-guardrails/releases) · [Stability policy](docs/guide/Stability-policy.md)
234
-
235
- Lineage: `agentic-harness` → `spec-seatbelt` → **`spec-guardrails` (final)**. Run `install` once after switching. See [Migration](docs/guide/Migration.md).
150
+ See [CHANGELOG](docs/CHANGELOG.md) for release notes.
236
151
 
237
152
  ---
238
153
 
239
- ## Contributing
240
-
241
- See [CONTRIBUTING.md](CONTRIBUTING.md) — tests, gate freeze policy, local `npm run guardrails -- install`.
242
-
243
- ---
244
-
245
- ## Credits
246
-
247
- Spec Guardrails adapts open ideas; we did not invent spec-driven phases, loop design, or task-graph rules.
248
-
249
- ### Core lineage
250
-
251
- | Source | License | How we use it |
252
- | --- | --- | --- |
253
- | [tlc-spec-driven](https://github.com/tech-leads-club/agent-skills/tree/main/packages/skills-catalog/skills/(development)/tlc-spec-driven) | CC-BY-4.0 | Phase model, `.specs/` memory, gate lineage |
254
- | [addyosmani/agent-skills](https://github.com/addyosmani/agent-skills) | MIT | Discuss patterns, definition-of-done |
255
- | [graph-engineering](https://github.com/codejunkie99/graph-engineering) | MIT | Task-graph topology, stop rules, parallel merge |
256
-
257
- ### Loop & ecosystem
258
-
259
- | Source | License | How we use it |
260
- | --- | --- | --- |
261
- | [loop-engineering](https://github.com/cobusgreyling/loop-engineering) | MIT | Operational loop patterns; `doctor` score metaphor |
262
- | [Addy Osmani — Loop engineering](https://addyosmani.com/blog/loop-engineering/) | — | Essay lineage |
263
- | [awesome-harness-engineering](https://github.com/ai-boost/awesome-harness-engineering) | CC0 | Ecosystem taxonomy |
264
-
265
- ### Adjacent (not vendored)
266
-
267
- [DeepCode](https://github.com/HKUDS/DeepCode) · [RepoGraph](https://github.com/ozyyshr/RepoGraph)
268
-
269
- Extended attribution: [docs/guide/credits.md](docs/guide/credits.md)
270
-
271
- ## License
154
+ ## Contributing · Credits · License
272
155
 
273
- MIT
156
+ - [CONTRIBUTING.md](CONTRIBUTING.md)
157
+ - [Credits](docs/guide/credits.md)
158
+ - MIT — see [LICENSE](LICENSE)
package/index.js CHANGED
@@ -45,6 +45,11 @@ import {
45
45
  recordExplorationDecision,
46
46
  validateExplorationArtifact,
47
47
  } from "./lib/solution-exploration.js";
48
+ import {
49
+ checkSandboxCommand,
50
+ formatSandboxCheck,
51
+ loadSandboxPolicy,
52
+ } from "./lib/sandbox-policy.js";
48
53
  import {
49
54
  initProjectConfig,
50
55
  listPresets,
@@ -113,6 +118,13 @@ Commands:
113
118
  memory-retrieve "<query>" Hybrid retrieval (FTS + graph + optional semantic)
114
119
  [--mode fts|hybrid|semantic] Strategy (default: hybrid)
115
120
  [--json] Machine-readable output
121
+ episodes record --summary "…" Capture working-session episodic memory
122
+ episodes list|archive|prune|promote Episodic lifecycle (working → episodic → promoted)
123
+ code-index rebuild [--roots src,lib] Lightweight brownfield code map (not full RepoGraph)
124
+ code-index search "<query>" Search indexed files/symbols/imports
125
+ sandbox status Show sandbox policy mode (off|warn|strict)
126
+ sandbox check-command "<cmd>" Soft OS sandbox — block/warn destructive shell commands
127
+ [--json] Machine-readable output
116
128
  context-guard status Execute readiness from STATE + tasks.md
117
129
  [--json] Machine-readable output
118
130
  context-guard check-edit <path> Contextual guard before editing a file
@@ -614,6 +626,58 @@ if (command === "--version" || command === "-v" || command === "version") {
614
626
  console.error(`❌ ${err.message}`);
615
627
  process.exit(1);
616
628
  }
629
+ } else if (command === "sandbox") {
630
+ try {
631
+ const sub = args[0];
632
+ let json = false;
633
+ /** @type {string[]} */
634
+ const rest = [];
635
+
636
+ for (let i = 1; i < args.length; i++) {
637
+ if (args[i] === "--json") {
638
+ json = true;
639
+ } else {
640
+ rest.push(args[i]);
641
+ }
642
+ }
643
+
644
+ const cwd = process.cwd();
645
+ const policy = await loadSandboxPolicy(cwd);
646
+
647
+ if (sub === "status") {
648
+ if (json) {
649
+ console.log(
650
+ JSON.stringify(
651
+ {
652
+ mode: policy.mode,
653
+ deny_rules: policy.deny_patterns.map((rule) => rule.id),
654
+ },
655
+ null,
656
+ 2,
657
+ ),
658
+ );
659
+ } else {
660
+ console.log(
661
+ `Sandbox mode: ${policy.mode} (${policy.deny_patterns.length} deny rule(s))`,
662
+ );
663
+ }
664
+ } else if (sub === "check-command") {
665
+ const shellCommand = rest.join(" ").trim();
666
+ if (!shellCommand) {
667
+ throw new Error('Usage: sandbox check-command "<shell command>" [--json]');
668
+ }
669
+ const result = checkSandboxCommand(shellCommand, policy);
670
+ process.stdout.write(formatSandboxCheck(result, shellCommand, { json }));
671
+ if (!result.allowed) {
672
+ process.exit(1);
673
+ }
674
+ } else {
675
+ throw new Error('Usage: sandbox status | check-command "<cmd>" [--json]');
676
+ }
677
+ } catch (err) {
678
+ console.error(`❌ ${err.message}`);
679
+ process.exit(1);
680
+ }
617
681
  } else if (command === "context-guard") {
618
682
  try {
619
683
  const sub = args[0];
package/lib/constants.js CHANGED
@@ -115,6 +115,8 @@ export const SCRIPT_ASSETS = [
115
115
  { file: "memory_retrieve.py", remotePath: "scripts/memory_retrieve.py" },
116
116
  { file: "_memory_config.py", remotePath: "scripts/_memory_config.py" },
117
117
  { file: "_memory_embed.py", remotePath: "scripts/_memory_embed.py" },
118
+ { file: "episodes.py", remotePath: "scripts/episodes.py" },
119
+ { file: "code_index.py", remotePath: "scripts/code_index.py" },
118
120
  ];
119
121
 
120
122
  /** @type {{ file: string, remotePath: string }[]} */
@@ -0,0 +1,109 @@
1
+ import fs from "node:fs/promises";
2
+ import path from "node:path";
3
+
4
+ import { packagedAssetPath } from "./assets.js";
5
+ import { ensureDir, readFileSafe } from "./fs-utils.js";
6
+
7
+ export const CURSOR_HOOK_EDIT = ".cursor/hooks/context-guard-edit.mjs";
8
+ export const CURSOR_HOOK_SANDBOX = ".cursor/hooks/sandbox-shell.mjs";
9
+ export const CURSOR_HOOKS_JSON = ".cursor/hooks.json";
10
+
11
+ /** @type {readonly { source: string, dest: string }[]} */
12
+ export const CURSOR_HOOK_SCRIPTS = [
13
+ {
14
+ source: "templates/cursor/hooks/context-guard-edit.mjs",
15
+ dest: CURSOR_HOOK_EDIT,
16
+ },
17
+ {
18
+ source: "templates/cursor/hooks/sandbox-shell.mjs",
19
+ dest: CURSOR_HOOK_SANDBOX,
20
+ },
21
+ ];
22
+
23
+ /**
24
+ * Merge shipped hook entries without removing user hooks.
25
+ *
26
+ * @param {Record<string, unknown> | null | undefined} existing
27
+ * @param {Record<string, unknown>} template
28
+ * @returns {Record<string, unknown>}
29
+ */
30
+ export function mergeCursorHooksConfig(existing, template) {
31
+ /** @type {Record<string, unknown>} */
32
+ const result =
33
+ existing && typeof existing === "object"
34
+ ? structuredClone(existing)
35
+ : { version: 1, hooks: {} };
36
+
37
+ result.version = result.version ?? template.version ?? 1;
38
+ /** @type {Record<string, unknown[]>} */
39
+ const hooks =
40
+ result.hooks && typeof result.hooks === "object"
41
+ ? /** @type {Record<string, unknown[]>} */ (result.hooks)
42
+ : {};
43
+ result.hooks = hooks;
44
+
45
+ /** @type {Record<string, unknown[]>} */
46
+ const templateHooks =
47
+ template.hooks && typeof template.hooks === "object"
48
+ ? /** @type {Record<string, unknown[]>} */ (template.hooks)
49
+ : {};
50
+
51
+ for (const [event, entries] of Object.entries(templateHooks)) {
52
+ if (!Array.isArray(entries)) {
53
+ continue;
54
+ }
55
+
56
+ const current = Array.isArray(hooks[event]) ? [...hooks[event]] : [];
57
+ for (const entry of entries) {
58
+ if (!entry || typeof entry !== "object") {
59
+ continue;
60
+ }
61
+ const command = /** @type {{ command?: string }} */ (entry).command;
62
+ if (!command || current.some((item) => item?.command === command)) {
63
+ continue;
64
+ }
65
+ current.push(entry);
66
+ }
67
+ hooks[event] = current;
68
+ }
69
+
70
+ return result;
71
+ }
72
+
73
+ /**
74
+ * Install Cursor hooks for context-guard and sandbox policy.
75
+ *
76
+ * @param {string} cwd
77
+ * @param {{ log?: (message: string) => void }} [options]
78
+ */
79
+ export async function installCursorHooks(cwd, options = {}) {
80
+ const log = options.log ?? (() => {});
81
+ const hooksDir = path.join(cwd, ".cursor/hooks");
82
+ await ensureDir(hooksDir);
83
+
84
+ for (const script of CURSOR_HOOK_SCRIPTS) {
85
+ const scriptDest = path.join(cwd, script.dest);
86
+ await fs.copyFile(packagedAssetPath(script.source), scriptDest);
87
+ try {
88
+ await fs.chmod(scriptDest, 0o755);
89
+ } catch {
90
+ // Windows may ignore chmod; node can still execute the script.
91
+ }
92
+ }
93
+
94
+ const template = JSON.parse(
95
+ await readFileSafe(packagedAssetPath("templates/cursor/hooks.json")),
96
+ );
97
+
98
+ const hooksJsonPath = path.join(cwd, CURSOR_HOOKS_JSON);
99
+ let existing = null;
100
+ try {
101
+ existing = JSON.parse(await readFileSafe(hooksJsonPath));
102
+ } catch {
103
+ existing = null;
104
+ }
105
+
106
+ const merged = mergeCursorHooksConfig(existing, template);
107
+ await fs.writeFile(hooksJsonPath, `${JSON.stringify(merged, null, 2)}\n`, "utf8");
108
+ log(`✅ Cursor hooks → ${CURSOR_HOOKS_JSON} (context-guard + sandbox shell checks)`);
109
+ }
package/lib/doctor.js CHANGED
@@ -11,6 +11,7 @@ import {
11
11
  import { resolvePython, resolveScriptsDir } from "./gates.js";
12
12
  import { readFileSafe } from "./fs-utils.js";
13
13
  import { listFeatureIds, readActiveFeatureFromState } from "./specs-utils.js";
14
+ import { resolveMemoryHint } from "./memory-doctor.js";
14
15
 
15
16
  const execFileAsync = promisify(execFile);
16
17
 
@@ -368,7 +369,7 @@ export function topDoctorSuggestions(checks, limit = 3) {
368
369
  /**
369
370
  * @param {string} cwd
370
371
  * @param {{ suggest?: boolean, json?: boolean }} [options]
371
- * @returns {Promise<{ score: number, modes: ReturnType<typeof scoreDoctorModes>, checks: DoctorCheck[], suggestions: DoctorCheck[], executeHint: string | null, pythonMissing: boolean }>}
372
+ * @returns {Promise<{ score: number, modes: ReturnType<typeof scoreDoctorModes>, checks: DoctorCheck[], suggestions: DoctorCheck[], executeHint: string | null, memoryHint: string | null, pythonMissing: boolean }>}
372
373
  */
373
374
  export async function doctor(cwd, options = {}) {
374
375
  const checks = await runDoctorChecks(cwd);
@@ -377,18 +378,19 @@ export async function doctor(cwd, options = {}) {
377
378
  const suggestions = topDoctorSuggestions(checks);
378
379
  const activeFeature = await readActiveFeature(cwd);
379
380
  const executeHint = await resolveExecuteHint(cwd, activeFeature);
381
+ const memoryHint = await resolveMemoryHint(cwd);
380
382
  const pythonCheck = checks.find((check) => check.id === "python");
381
383
  const pythonMissing = pythonCheck ? !pythonCheck.pass : false;
382
384
 
383
385
  if (options.json) {
384
386
  console.log(
385
387
  JSON.stringify(
386
- { score, modes, checks, suggestions, executeHint, pythonMissing },
388
+ { score, modes, checks, suggestions, executeHint, memoryHint, pythonMissing },
387
389
  null,
388
390
  2,
389
391
  ),
390
392
  );
391
- return { score, modes, checks, suggestions, executeHint, pythonMissing };
393
+ return { score, modes, checks, suggestions, executeHint, memoryHint, pythonMissing };
392
394
  }
393
395
 
394
396
  if (pythonMissing) {
@@ -427,5 +429,9 @@ export async function doctor(cwd, options = {}) {
427
429
  console.log(`\nExecute hint:\n → ${executeHint}`);
428
430
  }
429
431
 
430
- return { score, modes, checks, suggestions, executeHint, pythonMissing };
432
+ if (memoryHint) {
433
+ console.log(`\nMemory hint:\n → ${memoryHint}`);
434
+ }
435
+
436
+ return { score, modes, checks, suggestions, executeHint, memoryHint, pythonMissing };
431
437
  }