@esneiderbravo/speclaw 0.1.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.
Files changed (62) hide show
  1. package/ATTRIBUTION.md +34 -0
  2. package/LICENSE +21 -0
  3. package/README.md +134 -0
  4. package/dist/cli/commands/agent.js +42 -0
  5. package/dist/cli/commands/doctor.js +21 -0
  6. package/dist/cli/commands/index-build.js +24 -0
  7. package/dist/cli/commands/init.js +121 -0
  8. package/dist/cli/commands/query.js +69 -0
  9. package/dist/cli/commands/spec.js +71 -0
  10. package/dist/cli/index.js +77 -0
  11. package/dist/cli/lib/args.js +44 -0
  12. package/dist/cli/lib/ui.js +89 -0
  13. package/dist/modules/compass/db.js +84 -0
  14. package/dist/modules/compass/embedder.js +87 -0
  15. package/dist/modules/compass/extract.js +91 -0
  16. package/dist/modules/compass/indexer.js +158 -0
  17. package/dist/modules/compass/languages.js +67 -0
  18. package/dist/modules/compass/parser.js +37 -0
  19. package/dist/modules/compass/query.js +260 -0
  20. package/dist/modules/compass/register.js +72 -0
  21. package/dist/modules/compass/watcher.js +95 -0
  22. package/dist/modules/foundation/assets/AGENTS.template.md +61 -0
  23. package/dist/modules/foundation/assets/CLAUDE.template.md +72 -0
  24. package/dist/modules/foundation/assets/LAWS.template.md +39 -0
  25. package/dist/modules/foundation/assets/docs/compass.template.md +43 -0
  26. package/dist/modules/foundation/assets/docs/standards/architecture.template.md +36 -0
  27. package/dist/modules/foundation/assets/docs/standards/backend-standards.template.md +50 -0
  28. package/dist/modules/foundation/assets/docs/standards/base-standards.template.md +47 -0
  29. package/dist/modules/foundation/assets/docs/standards/conventions.template.md +31 -0
  30. package/dist/modules/foundation/assets/docs/standards/documentation.template.md +50 -0
  31. package/dist/modules/foundation/assets/docs/standards/frontend-standards.template.md +46 -0
  32. package/dist/modules/foundation/assets/docs/standards/spec-workflow.template.md +46 -0
  33. package/dist/modules/foundation/assets/docs/standards/testing-standards.template.md +34 -0
  34. package/dist/modules/foundation/doctor.js +107 -0
  35. package/dist/modules/foundation/register.js +86 -0
  36. package/dist/modules/foundation/scaffold.js +103 -0
  37. package/dist/modules/spec/assets/commands/archive.md +10 -0
  38. package/dist/modules/spec/assets/commands/build.md +11 -0
  39. package/dist/modules/spec/assets/commands/draft.md +12 -0
  40. package/dist/modules/spec/assets/commands/explore.md +10 -0
  41. package/dist/modules/spec/assets/commands/sync.md +9 -0
  42. package/dist/modules/spec/assets/rules/spec-tasks-mandatory-steps.md +37 -0
  43. package/dist/modules/spec/assets/skills/archive/SKILL.md +22 -0
  44. package/dist/modules/spec/assets/skills/build/SKILL.md +49 -0
  45. package/dist/modules/spec/assets/skills/draft/SKILL.md +64 -0
  46. package/dist/modules/spec/assets/skills/explore/SKILL.md +28 -0
  47. package/dist/modules/spec/assets/skills/sync/SKILL.md +21 -0
  48. package/dist/modules/spec/engine.js +227 -0
  49. package/dist/modules/spec/register.js +53 -0
  50. package/dist/modules/tools/assets/packs/agents/backend-developer.md +61 -0
  51. package/dist/modules/tools/assets/packs/agents/frontend-developer.md +62 -0
  52. package/dist/modules/tools/assets/packs/agents/product-strategy-analyst.md +56 -0
  53. package/dist/modules/tools/assets/packs.json +6 -0
  54. package/dist/modules/tools/packs.js +44 -0
  55. package/dist/modules/tools/register.js +27 -0
  56. package/dist/server.js +21 -0
  57. package/dist/shared/agents.js +94 -0
  58. package/dist/shared/install.js +66 -0
  59. package/dist/shared/mcp.js +16 -0
  60. package/dist/shared/paths.js +10 -0
  61. package/dist/shared/render.js +21 -0
  62. package/package.json +49 -0
package/ATTRIBUTION.md ADDED
@@ -0,0 +1,34 @@
1
+ # Attribution
2
+
3
+ speclaw is a self-contained suite. It does **not** bundle, fork, or depend on
4
+ OpenSpec or CodeGraph at runtime — its spec workflow (the **spec** module) and
5
+ its code graph (the **Compass** module) are original implementations written
6
+ from scratch. But the ideas came from two excellent open-source projects, and
7
+ they deserve the credit. Please star and support the originals:
8
+
9
+ ## OpenSpec — inspiration for the spec module
10
+
11
+ - **Repo**: https://github.com/Fission-AI/openspec
12
+ - **License**: MIT — Copyright (c) Fission AI
13
+ - **What we took**: the *idea* of spec-driven development for AI agents —
14
+ proposals, delta specs, a `changes/` workflow, and archiving. speclaw's spec
15
+ module (`draft → build → sync → archive → explore`, the `spec_*` tools, and
16
+ the `spec/` layout) is our own implementation of that idea, deliberately
17
+ simpler, with no external CLI. No OpenSpec code is included.
18
+
19
+ ## CodeGraph — inspiration for the Compass module
20
+
21
+ - **Repo**: https://github.com/colbymchenry/codegraph
22
+ - **License**: MIT — Copyright (c) Colby McHenry
23
+ - **What we took**: the *idea* of a local, pre-indexed code knowledge graph
24
+ that saves an agent the tokens of grep/read loops, with semantic search over
25
+ nodes. speclaw's Compass module (tree-sitter parsing, a `node:sqlite` graph
26
+ of nodes/edges, a local vector store, and the `compass_*` tools) is our own
27
+ implementation. No CodeGraph code is included.
28
+
29
+ ## Everything else
30
+
31
+ The foundation (the LAWS constitution and the granular `docs/standards/`), the
32
+ `ai-specs/` multi-IDE distribution standard, the skill/agent packs, and the MCP
33
+ server are original work by Esneider Bravo, distilled from real production
34
+ projects, and released under the MIT license of this repository.
package/LICENSE ADDED
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026 Esneider Bravo
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,134 @@
1
+ <div align="center">
2
+
3
+ <img src="brand/speclaw-banner.svg" width="820" alt="speclaw — where specs become law">
4
+
5
+ <br/>
6
+
7
+ <a href="https://www.npmjs.com/package/@esneiderbravo/speclaw"><img src="https://img.shields.io/npm/v/@esneiderbravo/speclaw?color=0E8E8E&labelColor=0B0F10&style=flat-square&label=npm" alt="npm"></a>
8
+ &nbsp;<a href="LICENSE"><img src="https://img.shields.io/badge/license-MIT-0E8E8E?labelColor=0B0F10&style=flat-square" alt="MIT"></a>
9
+ &nbsp;<img src="https://img.shields.io/badge/node-%E2%89%A522-0E8E8E?labelColor=0B0F10&style=flat-square" alt="Node >= 22">
10
+
11
+ <br/><br/>
12
+
13
+ <p align="center">
14
+ <b>AI agents are brilliant and blind</b> — brilliant at writing code, blind to <i>your</i>
15
+ project's rules. <b>speclaw</b> hands them what they're missing: the codebase's
16
+ <b>written laws</b> (a constitution built from your real code), a <b>local map</b> to
17
+ navigate it without burning tokens, and a <b>disciplined workflow</b> for every change.
18
+ <br/>
19
+ One command. No cloud, no LLM, no API keys — <b>everything runs on your machine.</b>
20
+ </p>
21
+
22
+ <img src="https://img.shields.io/badge/100%25_local-0E8E8E?labelColor=0B0F10&style=flat-square" alt="100% local">
23
+ &nbsp;<img src="https://img.shields.io/badge/no_LLM_·_no_cloud-0E8E8E?labelColor=0B0F10&style=flat-square" alt="no LLM">
24
+ &nbsp;<img src="https://img.shields.io/badge/CLI_+_MCP-0E8E8E?labelColor=0B0F10&style=flat-square" alt="CLI + MCP">
25
+ &nbsp;<img src="https://img.shields.io/badge/any_agent-0E8E8E?labelColor=0B0F10&style=flat-square" alt="any agent">
26
+
27
+ </div>
28
+
29
+ <br/>
30
+
31
+ > [!TIP]
32
+ > **One command sets everything up.** Run `npx @esneiderbravo/speclaw init`, pick the agents you
33
+ > use, and speclaw scaffolds the project, indexes your code, and hands your agent
34
+ > a ready-to-paste prompt to finish the setup.
35
+
36
+ <br/>
37
+
38
+ ## ◆ Quick start
39
+
40
+ <p align="center">
41
+ <img src="brand/terminal-quickstart.svg" width="800" alt="npx @esneiderbravo/speclaw init">
42
+ </p>
43
+
44
+ <p align="center">Copy &amp; run in your project root: <code>npx @esneiderbravo/speclaw init</code></p>
45
+
46
+ `init` will:
47
+
48
+ 1. **Ask which agents you use** (Claude Code, Cursor, Codex, …) — and configure
49
+ only those. Add more later; nothing is forced on you.
50
+ 2. Write the **foundation** (constitution + standards) and the **spec workflow**.
51
+ 3. **Index your code** with a live progress bar and a summary of what it found.
52
+ 4. Register the speclaw **MCP server** in each chosen agent's config.
53
+ 5. Print a prompt to paste into your agent so it fills the constitution with your
54
+ project's real architecture and conventions.
55
+
56
+ Works with `npm`, `pnpm` (`pnpm dlx @esneiderbravo/speclaw init`), and `yarn`.
57
+
58
+ <br/>
59
+
60
+ ## ◆ It looks like this
61
+
62
+ <p align="center">
63
+ <img src="brand/terminal-init.svg" width="800" alt="speclaw init — terminal output">
64
+ </p>
65
+
66
+ <p align="center"><i>Teal steps, green checks, a live progress bar — themed with the speclaw palette.</i></p>
67
+
68
+ <br/>
69
+
70
+ ## ◆ The suite — four modules
71
+
72
+ | Module | What it does |
73
+ | :-- | :-- |
74
+ | **Foundation** | The project's constitution: `LAWS.md` binding a set of granular standards under `docs/standards/` (base, architecture, backend, frontend, testing, documentation, conventions, spec-workflow), plus strict `CLAUDE.md` / `AGENTS.md` agent contracts — filled from your real codebase. |
75
+ | **Compass** | speclaw's own local code graph. Parses your code (tree-sitter) into nodes + edges plus a local vector store, so an agent finds and understands code with a fraction of the tokens a grep/read loop would cost. No LLM, 100% local, lives in `.speclaw/` (gitignored). |
76
+ | **Spec** | speclaw's own spec-driven workflow: `draft → build → sync → archive` (and `explore`), backed by `spec_*` engine tools. No external CLI. |
77
+ | **Tools** | Opt-in packs of skills and subagents (currently the dev-agents) that agents use for specific tasks. |
78
+
79
+ Compass is inspired by [CodeGraph](https://github.com/colbymchenry/codegraph) and the Spec module by [OpenSpec](https://github.com/Fission-AI/openspec) — both MIT. speclaw reimplements the ideas as its own code and gives full credit; see [ATTRIBUTION.md](ATTRIBUTION.md).
80
+
81
+ <br/>
82
+
83
+ ## ◆ Two ways to use it
84
+
85
+ speclaw meets you where you are. Everything works through the **CLI** — so no one
86
+ is blocked by MCP setup — and the same capabilities are exposed as **MCP tools**
87
+ for a smoother, integrated experience once configured. An agent without MCP can
88
+ still use Compass and the spec engine by calling the CLI from its shell.
89
+
90
+ <p align="center"><b>CLI</b> — the installer &amp; operator, runs anywhere <code>node</code> does</p>
91
+ <p align="center"><img src="brand/terminal-cli.svg" width="800" alt="speclaw CLI commands"></p>
92
+
93
+ <p align="center"><b>MCP</b> — the integrated agent surface, auto-registered by <code>init</code></p>
94
+ <p align="center"><img src="brand/terminal-mcp.svg" width="800" alt="speclaw MCP tools"></p>
95
+
96
+ <br/>
97
+
98
+ ## ◆ What lands in your project
99
+
100
+ <p align="center">
101
+ <img src="brand/terminal-tree.svg" width="800" alt="what speclaw writes into your project">
102
+ </p>
103
+
104
+ <br/>
105
+
106
+ ## ◆ Philosophy — why "laws"?
107
+
108
+ > [!NOTE]
109
+ > A guideline is a suggestion. A **law** is enforced. The most common failure mode
110
+ > of AI coding agents isn't lack of capability — it's working without the project's
111
+ > tacit knowledge: the rules the team actually lives by. speclaw makes that
112
+ > knowledge explicit, executable, and binding, and gives agents a local map
113
+ > (Compass) and a disciplined workflow (Spec) to act on it — without burning tokens.
114
+
115
+ <br/>
116
+
117
+ ## ◆ Requirements
118
+
119
+ - **Node.js ≥ 22** — uses the built-in `node:sqlite`.
120
+ - **No native builds, no services, no API keys, no LLM download.** Tree-sitter
121
+ parsers ship as WASM; the vector store is local.
122
+
123
+ <br/>
124
+
125
+ <div align="center">
126
+
127
+ **[MIT](LICENSE)** &nbsp;·&nbsp; built on ideas from
128
+ [OpenSpec](https://github.com/Fission-AI/openspec) &&nbsp;
129
+ [CodeGraph](https://github.com/colbymchenry/codegraph) &nbsp;·&nbsp;
130
+ see [ATTRIBUTION.md](ATTRIBUTION.md)
131
+
132
+ <i>speclaw · where specs become law</i>
133
+
134
+ </div>
@@ -0,0 +1,42 @@
1
+ import { AGENTS, agentById, configureAgent, detectConfiguredAgents } from "../../shared/agents.js";
2
+ import { emptyReport } from "../../shared/install.js";
3
+ import { ui } from "../lib/ui.js";
4
+ /**
5
+ * List configured agents or configure a new one (symlinks + MCP).
6
+ *
7
+ * @param flags - Parsed flags; `_[0]` is the subcommand (`list`/`add`) and `_[1]` the agent id.
8
+ */
9
+ export async function runAgent(flags) {
10
+ const cwd = process.cwd();
11
+ const [sub, id] = flags._;
12
+ if (sub === "list" || !sub) {
13
+ const configured = new Set(detectConfiguredAgents(cwd));
14
+ ui.heading("Agents");
15
+ for (const a of AGENTS) {
16
+ const mark = configured.has(a.id) ? ui.code("configured") : "—";
17
+ console.log(` ${a.id.padEnd(10)} ${a.label.padEnd(22)} ${mark}`);
18
+ }
19
+ ui.plain();
20
+ ui.info("Add one: speclaw agent add <id>");
21
+ return;
22
+ }
23
+ if (sub === "add") {
24
+ if (!id || !agentById(id)) {
25
+ ui.err(`Usage: speclaw agent add <${AGENTS.map((a) => a.id).join("|")}>`);
26
+ process.exit(1);
27
+ }
28
+ const report = emptyReport();
29
+ configureAgent(cwd, id, report);
30
+ ui.ok(`${agentById(id).label} configured`);
31
+ for (const s of report.symlinks)
32
+ ui.info(s);
33
+ for (const w of report.written)
34
+ ui.info(w);
35
+ if (!report.symlinks.length && !report.written.length) {
36
+ ui.warn("Nothing to do (already configured, or run `speclaw init` first).");
37
+ }
38
+ return;
39
+ }
40
+ ui.err(`Unknown: agent ${sub}. Use: agent list | agent add <id>`);
41
+ process.exit(1);
42
+ }
@@ -0,0 +1,21 @@
1
+ import { doctor } from "../../modules/foundation/doctor.js";
2
+ import { ui } from "../lib/ui.js";
3
+ /** Verify the installation, printing each check and exiting non-zero on failure. */
4
+ export async function runDoctor(_flags) {
5
+ const checks = doctor(process.cwd());
6
+ ui.heading("speclaw doctor");
7
+ for (const c of checks) {
8
+ if (c.ok)
9
+ ui.ok(`${c.name} — ${c.detail}`);
10
+ else
11
+ ui.err(`${c.name} — ${c.detail}`);
12
+ }
13
+ const failed = checks.filter((c) => !c.ok).length;
14
+ ui.plain();
15
+ if (failed === 0)
16
+ ui.ok("Everything is within the law.");
17
+ else {
18
+ ui.warn(`${failed} check(s) failed.`);
19
+ process.exit(1);
20
+ }
21
+ }
@@ -0,0 +1,24 @@
1
+ import { buildIndex } from "../../modules/compass/indexer.js";
2
+ import { startWatch } from "../../modules/compass/watcher.js";
3
+ import { ui, renderProgress, clearProgress } from "../lib/ui.js";
4
+ /** (Re)build the Compass code graph for the cwd, showing progress and final stats. */
5
+ export async function runIndex(_flags) {
6
+ const cwd = process.cwd();
7
+ ui.step("Indexing with Compass");
8
+ const start = Date.now();
9
+ const stats = await buildIndex(cwd, (e) => renderProgress(e.done, e.total, e.file));
10
+ clearProgress();
11
+ const secs = ((Date.now() - start) / 1000).toFixed(1);
12
+ ui.ok(`${stats.files} files · ${stats.nodes} nodes · ${stats.edges} edges · ` +
13
+ `${stats.embeddings} embeddings · ${stats.unchanged} unchanged (${secs}s)`);
14
+ }
15
+ /** Build the initial index, then watch for file changes to keep it fresh. */
16
+ export async function runWatch(_flags) {
17
+ const cwd = process.cwd();
18
+ ui.step("Building initial index");
19
+ await buildIndex(cwd);
20
+ const status = startWatch(cwd);
21
+ ui.ok(`Watching for changes (${status.mode}). Press Ctrl+C to stop.`);
22
+ // keep the process alive
23
+ await new Promise(() => { });
24
+ }
@@ -0,0 +1,121 @@
1
+ import fs from "node:fs";
2
+ import path from "node:path";
3
+ import * as clack from "@clack/prompts";
4
+ import { scaffold } from "../../modules/foundation/scaffold.js";
5
+ import { specInit } from "../../modules/spec/engine.js";
6
+ import { buildIndex } from "../../modules/compass/indexer.js";
7
+ import { AGENTS, agentById } from "../../shared/agents.js";
8
+ import { loadPacks } from "../../modules/tools/packs.js";
9
+ import { list } from "../lib/args.js";
10
+ import { ui, c, banner, renderProgress, clearProgress, box } from "../lib/ui.js";
11
+ const PACK_LABELS = {
12
+ agents: "dev-agents (backend · frontend · product)",
13
+ };
14
+ function detectProjectName(cwd) {
15
+ try {
16
+ const pkg = JSON.parse(fs.readFileSync(path.join(cwd, "package.json"), "utf8"));
17
+ if (pkg.name)
18
+ return String(pkg.name).replace(/^@[^/]+\//, "");
19
+ }
20
+ catch {
21
+ /* ignore */
22
+ }
23
+ return path.basename(cwd);
24
+ }
25
+ /**
26
+ * Interactive setup: pick agents and packs, scaffold, index, and print the handoff prompt.
27
+ *
28
+ * @param flags - Parsed flags; runs interactively on a TTY unless `--agents`, `--yes`, or
29
+ * `-y` is set. Honors `--project-name`, `--ticket-prefix`, `--team-language`, `--packs`,
30
+ * and `--no-index`.
31
+ */
32
+ export async function runInit(flags) {
33
+ const cwd = process.cwd();
34
+ const projectName = flags["project-name"] || detectProjectName(cwd);
35
+ const interactive = Boolean(process.stdin.isTTY) && !flags.agents && !flags.yes && !flags.y;
36
+ let agents;
37
+ let packs;
38
+ let ticketPrefix = flags["ticket-prefix"];
39
+ let teamLanguage = flags["team-language"];
40
+ banner();
41
+ if (interactive) {
42
+ const answers = await clack.group({
43
+ agents: () => clack.multiselect({
44
+ message: "Which agents do you use? (space to select)",
45
+ options: AGENTS.map((a) => ({ value: a.id, label: a.label })),
46
+ initialValues: ["claude"],
47
+ required: true,
48
+ }),
49
+ packs: () => clack.multiselect({
50
+ message: "Which tool packs to install?",
51
+ options: Object.entries(loadPacks()).map(([id, def]) => ({
52
+ value: id,
53
+ label: id,
54
+ hint: def.description.slice(0, 50),
55
+ })),
56
+ initialValues: ["agents"],
57
+ required: false,
58
+ }),
59
+ ticketPrefix: () => clack.text({ message: "Ticket prefix (optional, e.g. FAR)", defaultValue: "" }),
60
+ teamLanguage: () => clack.text({ message: "Team communication language", defaultValue: "English" }),
61
+ }, { onCancel: () => process.exit(1) });
62
+ agents = answers.agents;
63
+ packs = answers.packs;
64
+ ticketPrefix = answers.ticketPrefix || undefined;
65
+ teamLanguage = answers.teamLanguage || undefined;
66
+ }
67
+ else {
68
+ agents = list(flags.agents).length ? list(flags.agents) : ["claude"];
69
+ packs = list(flags.packs).length ? list(flags.packs) : ["agents"];
70
+ }
71
+ const unknownAgents = agents.filter((a) => !agentById(a));
72
+ if (unknownAgents.length) {
73
+ ui.err(`Unknown agent(s): ${unknownAgents.join(", ")}. Known: ${AGENTS.map((a) => a.id).join(", ")}`);
74
+ process.exit(1);
75
+ }
76
+ const profile = {
77
+ project_name: projectName,
78
+ ...(ticketPrefix ? { ticket_prefix: ticketPrefix } : {}),
79
+ ...(teamLanguage ? { team_language: teamLanguage } : {}),
80
+ };
81
+ // 1. Content + chosen agents, with a check per piece installed
82
+ ui.step(`Setting up ${c.bold(c.cyan(projectName))}`);
83
+ scaffold(cwd, profile, packs, agents);
84
+ ui.ok(`Foundation ${c.muted("— LAWS.md + 8 standards + CLAUDE.md/AGENTS.md")}`);
85
+ ui.ok(`Spec workflow ${c.muted("— draft · build · sync · archive · explore")}`);
86
+ for (const p of packs)
87
+ ui.ok(`${PACK_LABELS[p] ?? p + " pack"}`);
88
+ specInit(cwd);
89
+ ui.ok(`Spec workspace ${c.muted("— spec/")}`);
90
+ ui.step("Configuring agents");
91
+ for (const id of agents)
92
+ ui.ok(`${agentById(id).label} ${c.muted("— symlinks + MCP")}`);
93
+ // 2. Compass index with progress
94
+ if (!flags["no-index"]) {
95
+ ui.step("Indexing your code with Compass");
96
+ const stats = await buildIndex(cwd, (e) => renderProgress(e.done, e.total, e.file));
97
+ clearProgress();
98
+ ui.ok(c.bold(c.cream(String(stats.files))) + c.muted(" files · ") +
99
+ c.bold(c.cream(String(stats.nodes))) + c.muted(" nodes · ") +
100
+ c.bold(c.cream(String(stats.edges))) + c.muted(" edges · ") +
101
+ c.bold(c.cream(String(stats.embeddings))) + c.muted(" embeddings"));
102
+ }
103
+ // 3. Handoff prompt for the chosen agent
104
+ const primary = agentById(agents[0]);
105
+ ui.step("You're set — one last step");
106
+ ui.info(`Paste this into ${c.cyan(primary.label)} to build your constitution:`);
107
+ ui.plain();
108
+ box([
109
+ "Complete speclaw's foundation: analyze this repo and fill",
110
+ "LAWS.md and docs/standards/* with the real architecture,",
111
+ "quality gates and conventions. Use init_project to start.",
112
+ ], "prompt");
113
+ ui.plain();
114
+ ui.info(`The dev-agents read those standards for your stack — filling them well`);
115
+ ui.info(`makes them stack-aware.`);
116
+ ui.plain();
117
+ ui.info(`Add an agent: ${ui.code("speclaw agent add cursor")}`);
118
+ ui.info(`Refresh index: ${ui.code("speclaw index")}`);
119
+ ui.info(`Health check: ${ui.code("speclaw doctor")}`);
120
+ ui.plain();
121
+ }
@@ -0,0 +1,69 @@
1
+ import { explore, search, recall, impact, trace } from "../../modules/compass/query.js";
2
+ import { ui } from "../lib/ui.js";
3
+ /**
4
+ * Run a Compass query from the shell — the same surface agents call via MCP.
5
+ *
6
+ * @param cmd - The query verb: `explore`, `search`, `recall`, `impact`, or `trace`.
7
+ * @param flags - Parsed flags supplying the positional query arguments in `_`.
8
+ * @throws Exits the process with code 1 on missing arguments or query errors.
9
+ */
10
+ export async function runQuery(cmd, flags) {
11
+ const cwd = process.cwd();
12
+ const args = flags._;
13
+ try {
14
+ switch (cmd) {
15
+ case "explore": {
16
+ const r = explore(cwd, need(args[0], "explore <node>"));
17
+ if (!r.found) {
18
+ ui.warn(r.message ?? "not found");
19
+ r.otherMatches?.forEach((m) => ui.info(`${m.name} (${m.kind}) ${m.file}:${m.line}`));
20
+ return;
21
+ }
22
+ const s = r.symbol;
23
+ ui.heading(`${s.kind} ${s.name} ${s.file}:${s.startLine}-${s.endLine}`);
24
+ console.log(s.source);
25
+ ui.heading("Callees");
26
+ r.callees?.forEach((c) => ui.info(`${c.name}${c.file ? ` (${c.file}:${c.line})` : ""}`));
27
+ ui.heading("Callers");
28
+ r.callers?.forEach((c) => ui.info(`${c.name} (${c.file}:${c.line})`));
29
+ return;
30
+ }
31
+ case "search": {
32
+ const hits = search(cwd, need(args[0], "search <query>"));
33
+ ui.heading(`${hits.length} result(s)`);
34
+ hits.forEach((h) => ui.info(`${h.name} (${h.kind}) ${h.file}:${h.line}`));
35
+ return;
36
+ }
37
+ case "recall": {
38
+ const hits = await recall(cwd, need(args[0], 'recall "<query>"'));
39
+ ui.heading(`${hits.length} result(s) by meaning`);
40
+ hits.forEach((h) => ui.info(`${h.score.toFixed(3)} ${h.name} (${h.kind}) ${h.file}:${h.line}`));
41
+ return;
42
+ }
43
+ case "impact": {
44
+ const nodes = impact(cwd, need(args[0], "impact <node>"));
45
+ ui.heading(`Blast radius: ${nodes.length} dependent(s)`);
46
+ nodes.forEach((n) => ui.info(`depth ${n.depth}: ${n.name} (${n.file}:${n.line})`));
47
+ return;
48
+ }
49
+ case "trace": {
50
+ const r = trace(cwd, need(args[0], "trace <from> <to>"), need(args[1], "trace <from> <to>"));
51
+ ui.heading(`Trace ${r.from} → ${r.to}`);
52
+ console.log(r.path ? " " + r.path.join(" → ") + ` (${r.hops} hops)` : " no path found");
53
+ return;
54
+ }
55
+ }
56
+ }
57
+ catch (err) {
58
+ ui.err(err.message);
59
+ process.exit(1);
60
+ }
61
+ }
62
+ /** Return the value or print a usage error and exit if it is missing. */
63
+ function need(value, usage) {
64
+ if (!value) {
65
+ ui.err(`Usage: speclaw ${usage}`);
66
+ process.exit(1);
67
+ }
68
+ return value;
69
+ }
@@ -0,0 +1,71 @@
1
+ import { specInit, specValidate, specSync, specArchive, specList } from "../../modules/spec/engine.js";
2
+ import { ui } from "../lib/ui.js";
3
+ function today() {
4
+ // The MCP path passes the date in; the CLL runs on a real machine, so read it here.
5
+ return new Date().toISOString().slice(0, 10);
6
+ }
7
+ /**
8
+ * Run a spec-workflow subcommand: init, list, validate, sync, or archive.
9
+ *
10
+ * @param flags - Parsed flags; `_[0]` is the subcommand and `_[1]` the change name where required.
11
+ * @throws Exits the process with code 1 on unknown subcommands, missing arguments, or engine errors.
12
+ */
13
+ export async function runSpec(flags) {
14
+ const cwd = process.cwd();
15
+ const [sub, change] = flags._;
16
+ try {
17
+ switch (sub) {
18
+ case "init": {
19
+ const r = specInit(cwd);
20
+ ui.ok(r.alreadyExisted ? "spec/ already present" : `spec/ created (${r.created.length} entries)`);
21
+ return;
22
+ }
23
+ case "list": {
24
+ const r = specList(cwd);
25
+ if (!r.initialized)
26
+ return ui.warn("No spec/ — run `speclaw spec init`.");
27
+ ui.heading("Spec workspace");
28
+ ui.info(`active changes: ${r.activeChanges.join(", ") || "none"}`);
29
+ ui.info(`archived: ${r.archivedChanges.join(", ") || "none"}`);
30
+ ui.info(`capabilities: ${r.capabilities.join(", ") || "none"}`);
31
+ return;
32
+ }
33
+ case "validate": {
34
+ const r = specValidate(cwd, req(change, "spec validate <change>"));
35
+ if (r.valid)
36
+ ui.ok(`${r.change} is valid (${r.deltaSpecs.length} delta spec(s))`);
37
+ else {
38
+ ui.warn(`${r.change} has ${r.issues.length} issue(s):`);
39
+ r.issues.forEach((i) => ui.info(i));
40
+ }
41
+ return;
42
+ }
43
+ case "sync": {
44
+ const r = specSync(cwd, req(change, "spec sync <change>"));
45
+ ui.ok(`promoted ${r.promoted.length} spec(s)`);
46
+ r.promoted.forEach((p) => ui.info(p));
47
+ return;
48
+ }
49
+ case "archive": {
50
+ const r = specArchive(cwd, req(change, "spec archive <change>"), today());
51
+ ui.ok(`archived to ${r.archivedTo} (${r.promoted.length} spec(s) promoted)`);
52
+ return;
53
+ }
54
+ default:
55
+ ui.err("Usage: speclaw spec <init|list|validate|sync|archive> [change]");
56
+ process.exit(1);
57
+ }
58
+ }
59
+ catch (err) {
60
+ ui.err(err.message);
61
+ process.exit(1);
62
+ }
63
+ }
64
+ /** Return the value or print a usage error and exit if it is missing. */
65
+ function req(value, usage) {
66
+ if (!value) {
67
+ ui.err(`Usage: speclaw ${usage}`);
68
+ process.exit(1);
69
+ }
70
+ return value;
71
+ }
@@ -0,0 +1,77 @@
1
+ #!/usr/bin/env node
2
+ import { parseFlags } from "./lib/args.js";
3
+ import { ui } from "./lib/ui.js";
4
+ const HELP = `speclaw — spec-driven, agent-ready projects (foundation + Compass + Spec)
5
+
6
+ Usage: speclaw <command> [options]
7
+
8
+ Setup
9
+ init Interactive setup: pick agents, scaffold, index, get the prompt
10
+ agent list Show which agents are configured
11
+ agent add <id> Configure another agent later (symlinks + MCP)
12
+
13
+ Compass (code intelligence — the same surface agents use via MCP)
14
+ index (Re)build the local code graph, with progress
15
+ watch Keep the index fresh on file changes
16
+ explore <node> A node's source + callers/callees
17
+ search <query> Find nodes by name/keyword
18
+ recall "<query>" Find code by meaning (semantic)
19
+ impact <node> Blast radius: everything that (transitively) calls it
20
+ trace <from> <to> A call path between two nodes
21
+
22
+ Spec workflow
23
+ spec init Create the spec/ workspace
24
+ spec list Active/archived changes and capabilities
25
+ spec validate <change> Validate a change's artifacts
26
+ spec sync <change> Promote delta specs to canonical
27
+ spec archive <change> Finalize and archive a change
28
+
29
+ Other
30
+ doctor Verify the installation
31
+ mcp Start the MCP server (used by your agent's config)
32
+ help Show this help
33
+ `;
34
+ /** Parse argv and dispatch to the matching command handler. */
35
+ async function main() {
36
+ const [cmd, ...rest] = process.argv.slice(2);
37
+ const flags = parseFlags(rest);
38
+ switch (cmd) {
39
+ case undefined:
40
+ case "help":
41
+ case "--help":
42
+ case "-h":
43
+ console.log(HELP);
44
+ return;
45
+ case "mcp": {
46
+ const { startMcpServer } = await import("../server.js");
47
+ await startMcpServer();
48
+ return;
49
+ }
50
+ case "init":
51
+ return (await import("./commands/init.js")).runInit(flags);
52
+ case "agent":
53
+ return (await import("./commands/agent.js")).runAgent(flags);
54
+ case "index":
55
+ return (await import("./commands/index-build.js")).runIndex(flags);
56
+ case "watch":
57
+ return (await import("./commands/index-build.js")).runWatch(flags);
58
+ case "explore":
59
+ case "search":
60
+ case "recall":
61
+ case "impact":
62
+ case "trace":
63
+ return (await import("./commands/query.js")).runQuery(cmd, flags);
64
+ case "spec":
65
+ return (await import("./commands/spec.js")).runSpec(flags);
66
+ case "doctor":
67
+ return (await import("./commands/doctor.js")).runDoctor(flags);
68
+ default:
69
+ ui.err(`Unknown command: ${cmd}`);
70
+ console.log(HELP);
71
+ process.exit(1);
72
+ }
73
+ }
74
+ main().catch((err) => {
75
+ ui.err(err.message);
76
+ process.exit(1);
77
+ });
@@ -0,0 +1,44 @@
1
+ /**
2
+ * Minimal flag parser: `--key value`, `--key=value`, `--bool`, `-x`.
3
+ *
4
+ * @param argv - Raw argument tokens (already stripped of the command name).
5
+ * @returns Flags with named options set and non-flag tokens collected in `_`.
6
+ */
7
+ export function parseFlags(argv) {
8
+ const flags = { _: [] };
9
+ for (let i = 0; i < argv.length; i++) {
10
+ const arg = argv[i];
11
+ if (arg.startsWith("--")) {
12
+ const [key, inlineVal] = arg.slice(2).split("=", 2);
13
+ if (inlineVal !== undefined) {
14
+ flags[key] = inlineVal;
15
+ }
16
+ else if (i + 1 < argv.length && !argv[i + 1].startsWith("-")) {
17
+ flags[key] = argv[++i];
18
+ }
19
+ else {
20
+ flags[key] = true;
21
+ }
22
+ }
23
+ else if (arg.startsWith("-")) {
24
+ flags[arg.slice(1)] = true;
25
+ }
26
+ else {
27
+ flags._.push(arg);
28
+ }
29
+ }
30
+ return flags;
31
+ }
32
+ /**
33
+ * Normalize a flag value into a list, splitting comma-separated strings.
34
+ *
35
+ * @param value - A flag value that may be an array, a comma-separated string, or absent.
36
+ * @returns The trimmed, non-empty entries; empty when the value is missing or a boolean.
37
+ */
38
+ export function list(value) {
39
+ if (Array.isArray(value))
40
+ return value;
41
+ if (typeof value === "string")
42
+ return value.split(",").map((s) => s.trim()).filter(Boolean);
43
+ return [];
44
+ }