@luizsantiago/spec-guardrails 3.0.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (63) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +206 -0
  3. package/index.js +335 -0
  4. package/lib/archive.js +208 -0
  5. package/lib/assets.js +145 -0
  6. package/lib/brownfield.js +446 -0
  7. package/lib/config.js +293 -0
  8. package/lib/constants.js +262 -0
  9. package/lib/cursorrules.js +92 -0
  10. package/lib/delta-merge.js +248 -0
  11. package/lib/doctor.js +343 -0
  12. package/lib/download.js +133 -0
  13. package/lib/feature.js +272 -0
  14. package/lib/fs-utils.js +114 -0
  15. package/lib/gates.js +138 -0
  16. package/lib/install.js +140 -0
  17. package/lib/memory.js +34 -0
  18. package/lib/next-steps.js +50 -0
  19. package/lib/presets.js +176 -0
  20. package/lib/project-rules.js +210 -0
  21. package/lib/specs-utils.js +117 -0
  22. package/lib/token-cost.js +124 -0
  23. package/package.json +46 -0
  24. package/rules/engineering-baseline.mdc +56 -0
  25. package/scripts/_common.py +356 -0
  26. package/scripts/analyze_artifacts.py +187 -0
  27. package/scripts/check_commit.py +140 -0
  28. package/scripts/lessons.py +447 -0
  29. package/scripts/loop_plan.py +217 -0
  30. package/scripts/validate_spec.py +345 -0
  31. package/scripts/validate_state.py +385 -0
  32. package/scripts/validate_tasks.py +379 -0
  33. package/skills/agent-architecture.md +221 -0
  34. package/skills/appsec.md +83 -0
  35. package/skills/code-simplify.md +49 -0
  36. package/skills/engineering-standards.md +98 -0
  37. package/skills/git-handoff.md +213 -0
  38. package/skills/qa-strategy.md +83 -0
  39. package/skills/references/analyze.md +56 -0
  40. package/skills/references/archive.md +60 -0
  41. package/skills/references/constitution.md +66 -0
  42. package/skills/references/context-limits.md +73 -0
  43. package/skills/references/converge.md +47 -0
  44. package/skills/references/design.md +88 -0
  45. package/skills/references/discuss.md +68 -0
  46. package/skills/references/explore.md +61 -0
  47. package/skills/references/implement.md +175 -0
  48. package/skills/references/lessons.md +71 -0
  49. package/skills/references/memory.md +98 -0
  50. package/skills/references/project-init.md +62 -0
  51. package/skills/references/quick-mode.md +84 -0
  52. package/skills/references/specify.md +144 -0
  53. package/skills/references/sub-agents.md +117 -0
  54. package/skills/references/tasks.md +178 -0
  55. package/skills/references/validate.md +210 -0
  56. package/skills/security-review.md +120 -0
  57. package/skills/ship-ready.md +50 -0
  58. package/skills/task-graph-engineering.md +180 -0
  59. package/templates/GETTING_STARTED.md +61 -0
  60. package/templates/config.yaml.example +28 -0
  61. package/templates/presets/default.yaml +16 -0
  62. package/templates/presets/node-ts.yaml +22 -0
  63. package/templates/presets/python.yaml +22 -0
package/LICENSE ADDED
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026 Luiz Santiago
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
package/README.md ADDED
@@ -0,0 +1,206 @@
1
+ # Spec Guardrails
2
+
3
+ [![npm version](https://img.shields.io/npm/v/@luizsantiago/spec-guardrails.svg)](https://www.npmjs.com/package/@luizsantiago/spec-guardrails)
4
+ [![license: MIT](https://img.shields.io/badge/license-MIT-blue.svg)](LICENSE)
5
+
6
+ **Guardrails for AI coding agents** β€” agree on the goal in writing, break work into provable steps, run automatic checks before calling anything β€œdone”, and verify with a fresh context that did not write the code.
7
+
8
+ **Token-efficient by design:** the agent loads **one phase guide per turn** (~9k est. tokens on Specify) instead of dumping the full skill library (~31k). Measured savings: **~72%** on planning, **~86%** on Execute vs a naive full reload ([details below](#token-cost)).
9
+
10
+ npm: [`@luizsantiago/spec-guardrails`](https://www.npmjs.com/package/@luizsantiago/spec-guardrails) **3.0.x**
11
+
12
+ ---
13
+
14
+ ## Install
15
+
16
+ ```bash
17
+ npx @luizsantiago/spec-guardrails install
18
+ ```
19
+
20
+ ### What you need
21
+
22
+ | Requirement | Role |
23
+ | --- | --- |
24
+ | **Node.js 18+** | Required β€” runs the CLI and `install` |
25
+ | **Python 3.10+** | Recommended β€” runs automatic **gates** (`validate-spec`, `validate-tasks`, …). Without Python the agent still follows the same checklists manually |
26
+
27
+ ### What install does
28
+
29
+ | Lands in your project | Purpose |
30
+ | --- | --- |
31
+ | `.cursor/skills/` + `.claude/skills/` | Hub, phase references, sister skills |
32
+ | `.specs/guardrails/scripts/` | Python gate scripts |
33
+ | `.specs/STATE.md`, `.specs/features/`, … | Project memory |
34
+ | `.cursor/rules/engineering-baseline.mdc` | Always-on Cursor rule |
35
+
36
+ Re-run `install` anytime to refresh skills; your `.specs/` decisions and `STATE.md` are kept.
37
+
38
+ | Need | Command |
39
+ | --- | --- |
40
+ | First time / upgrade | `install` |
41
+ | Existing codebase | `project-init` (optional) |
42
+ | Something looks wrong | `doctor` |
43
+ | Full CLI list | `--help` |
44
+
45
+ ---
46
+
47
+ ## Token cost
48
+
49
+ Progressive loading is the main cost win: **one working set per turn**, not the entire playbook.
50
+
51
+ | Profile | Est. tokens | When |
52
+ | ---: | ---: | --- |
53
+ | Naive full dump (don’t) | ~31k | Loading every skill + reference every message |
54
+ | Specify turn | ~9k | `/specify` β€” hub + `specify.md` + standards |
55
+ | Tasks turn | ~10k | `/tasks` β€” hub + `tasks.md` + task-graph skill |
56
+ | Execute `/loop` (one wave) | ~4k | One implement wave (inline or parallel) |
57
+ | Verify turn | ~6k | Independent reviewer stack |
58
+
59
+ 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.
60
+
61
+ More: [Token efficiency](docs/guide/Token-efficiency.md)
62
+
63
+ ---
64
+
65
+ ## How the pieces fit together
66
+
67
+ Four ideas stack β€” full explanation: **[Concepts](docs/guide/concepts.md)**
68
+
69
+ | Idea | What it is | What it does |
70
+ | --- | --- | --- |
71
+ | **Spec-driven** | Written plan before code | `spec.md` + `tasks.md`; evidence before β€œdone” |
72
+ | **Guardrails** | This package | Skills + Python gates that stop incomplete work |
73
+ | **Loop** | Execute in waves | `loop-plan` picks the next jobs; sub-agents when files don’t overlap |
74
+ | **Graph** | Parallel task map | `task-graph.md` β€” safe parallelism without file collisions |
75
+ | **Memory** | Repo-local state | `.specs/` β€” specs, decisions, and handoff survive across chats |
76
+
77
+ **You** approve specs and tasks. **The agent** runs gates and implements. **Gates** exit non-zero when paperwork or evidence is missing.
78
+
79
+ Plain-language tour: [Home](docs/guide/Home.md) Β· [How it works](docs/guide/How-it-works.md) Β· [Quick start](docs/guide/Quick-start.md)
80
+
81
+ ---
82
+
83
+ ## Complexity tiers (how work flows)
84
+
85
+ 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.
86
+
87
+ | Tier | Typical scope | Path |
88
+ | --- | --- | --- |
89
+ | **Quick** | ≀3 files, no new deps | `/quick` β†’ verify β†’ commit |
90
+ | **Simple** | Small localized change | `/specify` β†’ `/loop` β†’ `/verify` |
91
+ | **Medium** | New feature, <10 tasks | `/specify` β†’ `/tasks` β†’ `/loop` β†’ `/verify` β†’ `/archive` |
92
+ | **Complex** | APIs, architecture, infra | + `/discuss`, `/plan`, optional security/QA on verify |
93
+ | **Parallel** | Splittable work | Above + `/task-graph` when 3+ tasks |
94
+
95
+ Rules and examples: [Concepts β†’ Complexity tiers](docs/guide/concepts.md#complexity-tiers--how-the-agent-chooses-depth)
96
+
97
+ ---
98
+
99
+ ## Hub and skills (summary)
100
+
101
+ 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**.
102
+
103
+ | Load order | Layer | Role | Examples |
104
+ | ---: | --- | --- | --- |
105
+ | 1 | **Hub** | Contract, complexity router, gate schedule | `agent-architecture.md` |
106
+ | 2 | **Reference** | One phase procedure per turn | `specify.md`, `implement.md`, `validate.md` |
107
+ | 3 | **Sister** (optional) | Cross-cutting depth, on demand | `engineering-standards.md`, `task-graph-engineering.md` |
108
+ | 4 | **Gate** | Automatic check at the boundary | `validate-spec`, `loop-plan`, `check-commit` |
109
+
110
+ Conditional sisters (`appsec.md`, `qa-strategy.md`, …) load **one at a time** on Verify when risk warrants it.
111
+
112
+ Full map: **[Skills and hub](docs/guide/skills-and-hub.md)**
113
+
114
+ ---
115
+
116
+ ## Gates (summary)
117
+
118
+ Scripts in `.specs/guardrails/scripts/`. **Exit β‰  0 β†’ stop and fix.**
119
+
120
+ | When | Gate | What it blocks |
121
+ | --- | --- | --- |
122
+ | Before approving spec | `validate-spec` | Incomplete or untestable spec |
123
+ | Before approving tasks | `analyze-artifacts` | Spec ↔ tasks drift |
124
+ | Before approving tasks | `validate-tasks` | Bad tasks; missing graph when 3+ tasks |
125
+ | Each `/loop` wave | `loop-plan` | Blocked dependencies; shows parallel groups |
126
+ | Each commit | `check-commit` | Non-Conventional commit message |
127
+ | Before β€œdone” | `validate-state` | Fake PASS without test evidence |
128
+ | After Verify FAIL | `lessons` | Ungrounded β€œlessons learned” |
129
+ | After Verify PASS | `archive-feature` | (CLI) folds feature into domain memory |
130
+
131
+ Full reference: **[Gates](docs/guide/gates.md)** Β· [Gates and guarantees](docs/guide/Gates-and-guarantees.md)
132
+
133
+ ---
134
+
135
+ ## Documentation
136
+
137
+ | Doc | For |
138
+ | --- | --- |
139
+ | [Agent commands](docs/guide/agent-commands.md) | Every `/specify`, `/loop`, `/verify`, … β€” purpose, when, examples |
140
+ | [Quick start](docs/guide/Quick-start.md) | First ten minutes |
141
+ | [Concepts](docs/guide/concepts.md) | Spec-driven + guardrails + loop + graph |
142
+ | [Skills and hub](docs/guide/skills-and-hub.md) | What each skill file does |
143
+ | [Gates](docs/guide/gates.md) | How each gate works |
144
+ | [FAQ](docs/guide/FAQ.md) | Common questions |
145
+ | [Changelog](docs/CHANGELOG.md) | Full version history |
146
+
147
+ Start after install: [Quick start](docs/guide/Quick-start.md) Β· [Agent commands](docs/guide/agent-commands.md)
148
+
149
+ ---
150
+
151
+ ## Upgrading
152
+
153
+ ```bash
154
+ npx @luizsantiago/spec-guardrails install
155
+ ```
156
+
157
+ | Version | What you gain |
158
+ | --- | --- |
159
+ | **3.0.x** | Final name Spec Guardrails; `.specs/guardrails/`; no dual-path ([Migration](docs/guide/Migration.md)) |
160
+ | **2.2.x** | Seatbelt-era paths & markers; `doctor` Execute hints; docs split from README |
161
+ | **2.1.x** | `loop-plan` + parallel `/loop` waves |
162
+ | **2.0.x** | Package rename β†’ `@luizsantiago/spec-seatbelt` (superseded by 3.0) |
163
+ | **1.1.x** | `project-init` for brownfield repos |
164
+ | **0.9.x** | `archive-feature` + domain memory merge |
165
+
166
+ Full history: [CHANGELOG](docs/CHANGELOG.md) Β· [Releases](https://github.com/luizssantiago92/spec-guardrails/releases) Β· [Stability policy](docs/guide/Stability-policy.md)
167
+
168
+ Lineage: `agentic-harness` β†’ `spec-seatbelt` β†’ **`spec-guardrails` (final)**. Run `install` once after switching. See [Migration](docs/guide/Migration.md).
169
+
170
+ ---
171
+
172
+ ## Contributing
173
+
174
+ See [CONTRIBUTING.md](CONTRIBUTING.md) β€” tests, gate freeze policy, local `npm run guardrails -- install`.
175
+
176
+ ---
177
+
178
+ ## Credits
179
+
180
+ Spec Guardrails adapts open ideas; we did not invent spec-driven phases, loop design, or task-graph rules.
181
+
182
+ ### Core lineage
183
+
184
+ | Source | License | How we use it |
185
+ | --- | --- | --- |
186
+ | [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 |
187
+ | [addyosmani/agent-skills](https://github.com/addyosmani/agent-skills) | MIT | Discuss patterns, definition-of-done |
188
+ | [graph-engineering](https://github.com/codejunkie99/graph-engineering) | MIT | Task-graph topology, stop rules, parallel merge |
189
+
190
+ ### Loop & ecosystem
191
+
192
+ | Source | License | How we use it |
193
+ | --- | --- | --- |
194
+ | [loop-engineering](https://github.com/cobusgreyling/loop-engineering) | MIT | Operational loop patterns; `doctor` score metaphor |
195
+ | [Addy Osmani β€” Loop engineering](https://addyosmani.com/blog/loop-engineering/) | β€” | Essay lineage |
196
+ | [awesome-harness-engineering](https://github.com/ai-boost/awesome-harness-engineering) | CC0 | Ecosystem taxonomy |
197
+
198
+ ### Adjacent (not vendored)
199
+
200
+ [DeepCode](https://github.com/HKUDS/DeepCode) Β· [RepoGraph](https://github.com/ozyyshr/RepoGraph)
201
+
202
+ Extended attribution: [docs/guide/credits.md](docs/guide/credits.md)
203
+
204
+ ## License
205
+
206
+ MIT
package/index.js ADDED
@@ -0,0 +1,335 @@
1
+ #!/usr/bin/env node
2
+
3
+ import path from "node:path";
4
+
5
+ import { archiveFeature } from "./lib/archive.js";
6
+ import { projectInit } from "./lib/brownfield.js";
7
+ import { PACKAGE_VERSION, CLI_NAME } from "./lib/constants.js";
8
+ import { phaseContext } from "./lib/config.js";
9
+ import { doctor } from "./lib/doctor.js";
10
+ import { featureInit } from "./lib/feature.js";
11
+ import { GATE_COMMANDS, AUX_COMMANDS, runGate, runGuardrailsScript } from "./lib/gates.js";
12
+ import { install } from "./lib/install.js";
13
+ import {
14
+ initProjectConfig,
15
+ listPresets,
16
+ loadPresetText,
17
+ } from "./lib/presets.js";
18
+
19
+ const USAGE = `Usage: ${CLI_NAME} <command> [args]
20
+
21
+ Commands:
22
+ install Install skills, references, gates and .specs/ memory
23
+ [--preset <name>] Seed .specs/config.yaml from a built-in preset
24
+ [--force-config] Replace existing config.yaml when using --preset
25
+ init-config [--preset <name>] Create .specs/config.yaml (default preset: default)
26
+ [--force] Replace existing config.yaml
27
+ preset list List built-in config presets
28
+ preset show <name> Print a preset YAML file
29
+ project-init Map an existing repo into .specs/ project memory (brownfield)
30
+ [--preset <name>] Config preset (auto-detected when omitted)
31
+ [--domains a,b,c] Explicit domain slugs (overrides auto-detect)
32
+ [--no-domains] Skip .specs/domains/ scaffolding
33
+ [--no-project] Skip PROJECT.md generation
34
+ [--force] Overwrite generated project/domain/config files
35
+ [--dry-run] Print scan results without writing files
36
+ feature-init "<description>" Allocate NNN-slug feature, STATE, local branch (Tier 0)
37
+ [--no-branch] Skip git checkout -b
38
+ [--no-spec] Skip spec.md stub
39
+ archive-feature [feature] Fold verified feature into ROADMAP + domain spec; reset STATE
40
+ [--domain <slug>] Domain folder under .specs/domains/ (default: feature slug)
41
+ [--skip-verify] Skip validate-state (tests / recovery only)
42
+ [--no-roadmap] Skip ROADMAP update
43
+ [--no-domain] Skip domain spec merge
44
+ [--no-state] Skip STATE reset
45
+ phase-context <phase> Print .specs/config.yaml context + rules for a phase
46
+ doctor [path] Audit guardrails readiness (score + next actions)
47
+ [--json] Machine-readable output
48
+ [--no-suggest] Hide per-check remediation hints
49
+ validate-spec [spec.md|feature] Closure gate for a feature spec
50
+ analyze-artifacts [feature] Cross-artifact consistency before task approval
51
+ validate-tasks [tasks.md|feature] Granularity gate for a task breakdown
52
+ loop-plan [tasks.md|feature] Next Execute wave β€” parallel groups + sub-agent hints
53
+ [--json] Machine-readable plan for agents
54
+ validate-state [feature] Completion gate before declaring a feature done
55
+ check-commit --message "<msg>" Conventional Commits gate
56
+ lessons <add|list|penalize|prune|status> Lessons engine
57
+ --help Show this message
58
+ --version Print the package version
59
+ `;
60
+
61
+ const [, , command, ...args] = process.argv;
62
+
63
+ if (command === "--version" || command === "-v" || command === "version") {
64
+ console.log(PACKAGE_VERSION);
65
+ process.exit(0);
66
+ } else if (!command || command === "--help" || command === "-h" || command === "help") {
67
+ const out = command ? console.log : console.error;
68
+ out(USAGE);
69
+ process.exit(command ? 0 : 1);
70
+ } else if (command === "install") {
71
+ try {
72
+ const installOptions = {};
73
+ for (let i = 0; i < args.length; i++) {
74
+ const arg = args[i];
75
+ if (arg === "--preset") {
76
+ installOptions.preset = args[++i];
77
+ if (!installOptions.preset) {
78
+ throw new Error("--preset requires a name. Run preset list.");
79
+ }
80
+ } else if (arg === "--force-config") {
81
+ installOptions.forceConfig = true;
82
+ } else {
83
+ throw new Error(`Unknown install flag: ${arg}`);
84
+ }
85
+ }
86
+
87
+ await install(installOptions);
88
+ } catch (err) {
89
+ console.error(`❌ ${err.message}`);
90
+ process.exit(1);
91
+ }
92
+ } else if (command === "init-config") {
93
+ try {
94
+ let preset = "default";
95
+ let force = false;
96
+
97
+ for (let i = 0; i < args.length; i++) {
98
+ const arg = args[i];
99
+ if (arg === "--preset") {
100
+ preset = args[++i];
101
+ if (!preset) {
102
+ throw new Error("--preset requires a name. Run preset list.");
103
+ }
104
+ } else if (arg === "--force") {
105
+ force = true;
106
+ } else {
107
+ throw new Error(`Unknown init-config flag: ${arg}`);
108
+ }
109
+ }
110
+
111
+ const result = await initProjectConfig({ preset, force });
112
+ if (result.skipped) {
113
+ console.log(`ℹ️ ${result.path} already exists β€” kept your file (use --force to replace)`);
114
+ } else if (result.updated) {
115
+ console.log(`βœ… ${result.path} replaced from preset: ${result.preset}`);
116
+ } else {
117
+ console.log(`βœ… ${result.path} created from preset: ${result.preset}`);
118
+ }
119
+ } catch (err) {
120
+ console.error(`❌ ${err.message}`);
121
+ process.exit(1);
122
+ }
123
+ } else if (command === "preset") {
124
+ try {
125
+ const sub = args[0];
126
+ if (sub === "list") {
127
+ const presets = await listPresets();
128
+ console.log("Built-in presets:");
129
+ for (const name of presets) {
130
+ console.log(` ${name}`);
131
+ }
132
+ } else if (sub === "show") {
133
+ const name = args[1];
134
+ if (!name) {
135
+ throw new Error("Preset name required. Example: preset show node-ts");
136
+ }
137
+ process.stdout.write(await loadPresetText(name));
138
+ } else {
139
+ throw new Error("Usage: preset list | preset show <name>");
140
+ }
141
+ } catch (err) {
142
+ console.error(`❌ ${err.message}`);
143
+ process.exit(1);
144
+ }
145
+ } else if (command === "project-init") {
146
+ try {
147
+ const initOptions = {
148
+ skipDomains: false,
149
+ skipProject: false,
150
+ force: false,
151
+ dryRun: false,
152
+ };
153
+
154
+ for (let i = 0; i < args.length; i++) {
155
+ const arg = args[i];
156
+ if (arg === "--preset") {
157
+ initOptions.preset = args[++i];
158
+ if (!initOptions.preset) {
159
+ throw new Error("--preset requires a name. Run preset list.");
160
+ }
161
+ } else if (arg === "--domains") {
162
+ const raw = args[++i];
163
+ if (!raw) {
164
+ throw new Error("--domains requires a comma-separated list.");
165
+ }
166
+ initOptions.domains = raw.split(",").map((item) => item.trim()).filter(Boolean);
167
+ } else if (arg === "--no-domains") {
168
+ initOptions.skipDomains = true;
169
+ } else if (arg === "--no-project") {
170
+ initOptions.skipProject = true;
171
+ } else if (arg === "--force") {
172
+ initOptions.force = true;
173
+ } else if (arg === "--dry-run") {
174
+ initOptions.dryRun = true;
175
+ } else {
176
+ throw new Error(`Unknown project-init flag: ${arg}`);
177
+ }
178
+ }
179
+
180
+ const result = await projectInit(initOptions);
181
+
182
+ if (result.dryRun) {
183
+ console.log(`πŸ” Brownfield scan: ${result.repoName}`);
184
+ console.log(` Stack: ${result.stack.stack}`);
185
+ console.log(` Preset: ${result.preset}`);
186
+ if (result.domains.length) {
187
+ console.log(` Domains: ${result.domains.map((d) => d.domain).join(", ")}`);
188
+ } else {
189
+ console.log(" Domains: (none detected β€” use --domains or add code layout)");
190
+ }
191
+ console.log(" Dry run β€” no files written.");
192
+ process.exit(0);
193
+ }
194
+
195
+ console.log(`βœ… Brownfield project memory initialized for ${result.repoName}`);
196
+ console.log(` Stack: ${result.stack.stack}`);
197
+ console.log(` Preset: ${result.preset}`);
198
+ for (const line of result.planned) {
199
+ console.log(` ${line}`);
200
+ }
201
+ console.log(" Tier 0 β€” review PROJECT.md and domain stubs, then run feature-init.");
202
+ } catch (err) {
203
+ console.error(`❌ ${err.message}`);
204
+ process.exit(1);
205
+ }
206
+ } else if (command === "feature-init") {
207
+ try {
208
+ const descriptionParts = [];
209
+ const initOptions = { skipBranch: false, skipSpec: false };
210
+
211
+ for (const arg of args) {
212
+ if (arg === "--no-branch") {
213
+ initOptions.skipBranch = true;
214
+ } else if (arg === "--no-spec") {
215
+ initOptions.skipSpec = true;
216
+ } else {
217
+ descriptionParts.push(arg);
218
+ }
219
+ }
220
+
221
+ const description = descriptionParts.join(" ").trim();
222
+ const result = await featureInit(description, initOptions);
223
+
224
+ console.log(`βœ… Feature ${result.featureId}`);
225
+ console.log(` Directory: ${result.featureDir}`);
226
+ console.log(` Branch: ${result.branchName}`);
227
+ console.log(` Git: ${result.branchMessage}`);
228
+ console.log(" Tier 0 complete β€” draft spec.md, then run validate-spec.");
229
+ } catch (err) {
230
+ console.error(`❌ ${err.message}`);
231
+ process.exit(1);
232
+ }
233
+ } else if (command === "archive-feature") {
234
+ try {
235
+ const archiveOptions = {
236
+ skipVerify: false,
237
+ skipRoadmap: false,
238
+ skipDomainMerge: false,
239
+ skipState: false,
240
+ };
241
+ const positional = [];
242
+
243
+ for (let i = 0; i < args.length; i++) {
244
+ const arg = args[i];
245
+ if (arg === "--skip-verify") {
246
+ archiveOptions.skipVerify = true;
247
+ } else if (arg === "--no-roadmap") {
248
+ archiveOptions.skipRoadmap = true;
249
+ } else if (arg === "--no-domain") {
250
+ archiveOptions.skipDomainMerge = true;
251
+ } else if (arg === "--no-state") {
252
+ archiveOptions.skipState = true;
253
+ } else if (arg === "--domain") {
254
+ archiveOptions.domain = args[++i];
255
+ if (!archiveOptions.domain) {
256
+ throw new Error("--domain requires a slug argument.");
257
+ }
258
+ } else {
259
+ positional.push(arg);
260
+ }
261
+ }
262
+
263
+ const result = await archiveFeature(positional[0], archiveOptions);
264
+
265
+ console.log(`βœ… Archived ${result.featureId}`);
266
+ if (result.roadmapPath) {
267
+ console.log(` ROADMAP: ${result.roadmapPath}${result.roadmapUpdated ? " (updated)" : ""}`);
268
+ }
269
+ if (result.domainPath) {
270
+ console.log(` Domain: ${result.domainPath}`);
271
+ if (result.mergeSummary.length) {
272
+ console.log(` Merge: ${result.mergeSummary.join(", ")}`);
273
+ }
274
+ }
275
+ if (result.stateReset) {
276
+ console.log(" STATE: reset for next feature");
277
+ }
278
+ console.log(" Tier 0 β€” commit archive updates locally; push needs owner go-ahead.");
279
+ } catch (err) {
280
+ console.error(`❌ ${err.message}`);
281
+ process.exit(1);
282
+ }
283
+ } else if (command === "phase-context") {
284
+ try {
285
+ const phase = args[0];
286
+ if (!phase) {
287
+ throw new Error("Phase is required. Example: phase-context specify");
288
+ }
289
+ const output = await phaseContext(phase);
290
+ process.stdout.write(output);
291
+ } catch (err) {
292
+ console.error(`❌ ${err.message}`);
293
+ process.exit(1);
294
+ }
295
+ } else if (command === "doctor") {
296
+ try {
297
+ const doctorOptions = { json: false, suggest: true };
298
+ const positional = [];
299
+
300
+ for (const arg of args) {
301
+ if (arg === "--json") {
302
+ doctorOptions.json = true;
303
+ } else if (arg === "--no-suggest") {
304
+ doctorOptions.suggest = false;
305
+ } else {
306
+ positional.push(arg);
307
+ }
308
+ }
309
+
310
+ const target = positional[0] ? path.resolve(positional[0]) : process.cwd();
311
+ await doctor(target, doctorOptions);
312
+ } catch (err) {
313
+ console.error(`❌ ${err.message}`);
314
+ process.exit(1);
315
+ }
316
+ } else if (AUX_COMMANDS.includes(command)) {
317
+ try {
318
+ const code = await runGuardrailsScript(command, args);
319
+ process.exit(code);
320
+ } catch (err) {
321
+ console.error(`❌ ${err.message}`);
322
+ process.exit(2);
323
+ }
324
+ } else if (GATE_COMMANDS.includes(command)) {
325
+ try {
326
+ const code = await runGate(command, args);
327
+ process.exit(code);
328
+ } catch (err) {
329
+ console.error(`❌ ${err.message}`);
330
+ process.exit(2);
331
+ }
332
+ } else {
333
+ console.error(USAGE);
334
+ process.exit(1);
335
+ }