@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.
- package/LICENSE +21 -0
- package/README.md +206 -0
- package/index.js +335 -0
- package/lib/archive.js +208 -0
- package/lib/assets.js +145 -0
- package/lib/brownfield.js +446 -0
- package/lib/config.js +293 -0
- package/lib/constants.js +262 -0
- package/lib/cursorrules.js +92 -0
- package/lib/delta-merge.js +248 -0
- package/lib/doctor.js +343 -0
- package/lib/download.js +133 -0
- package/lib/feature.js +272 -0
- package/lib/fs-utils.js +114 -0
- package/lib/gates.js +138 -0
- package/lib/install.js +140 -0
- package/lib/memory.js +34 -0
- package/lib/next-steps.js +50 -0
- package/lib/presets.js +176 -0
- package/lib/project-rules.js +210 -0
- package/lib/specs-utils.js +117 -0
- package/lib/token-cost.js +124 -0
- package/package.json +46 -0
- package/rules/engineering-baseline.mdc +56 -0
- package/scripts/_common.py +356 -0
- package/scripts/analyze_artifacts.py +187 -0
- package/scripts/check_commit.py +140 -0
- package/scripts/lessons.py +447 -0
- package/scripts/loop_plan.py +217 -0
- package/scripts/validate_spec.py +345 -0
- package/scripts/validate_state.py +385 -0
- package/scripts/validate_tasks.py +379 -0
- package/skills/agent-architecture.md +221 -0
- package/skills/appsec.md +83 -0
- package/skills/code-simplify.md +49 -0
- package/skills/engineering-standards.md +98 -0
- package/skills/git-handoff.md +213 -0
- package/skills/qa-strategy.md +83 -0
- package/skills/references/analyze.md +56 -0
- package/skills/references/archive.md +60 -0
- package/skills/references/constitution.md +66 -0
- package/skills/references/context-limits.md +73 -0
- package/skills/references/converge.md +47 -0
- package/skills/references/design.md +88 -0
- package/skills/references/discuss.md +68 -0
- package/skills/references/explore.md +61 -0
- package/skills/references/implement.md +175 -0
- package/skills/references/lessons.md +71 -0
- package/skills/references/memory.md +98 -0
- package/skills/references/project-init.md +62 -0
- package/skills/references/quick-mode.md +84 -0
- package/skills/references/specify.md +144 -0
- package/skills/references/sub-agents.md +117 -0
- package/skills/references/tasks.md +178 -0
- package/skills/references/validate.md +210 -0
- package/skills/security-review.md +120 -0
- package/skills/ship-ready.md +50 -0
- package/skills/task-graph-engineering.md +180 -0
- package/templates/GETTING_STARTED.md +61 -0
- package/templates/config.yaml.example +28 -0
- package/templates/presets/default.yaml +16 -0
- package/templates/presets/node-ts.yaml +22 -0
- 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
|
+
[](https://www.npmjs.com/package/@luizsantiago/spec-guardrails)
|
|
4
|
+
[](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
|
+
}
|