@esneiderbravo/speclaw 0.1.0 → 0.1.2
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 +18 -14
- package/dist/cli/commands/init.js +12 -13
- package/dist/cli/commands/{spec.js → lawbook.js} +5 -5
- package/dist/cli/commands/visualize.js +43 -0
- package/dist/cli/index.js +11 -8
- package/dist/modules/compass/register.js +10 -0
- package/dist/modules/compass/visualize.js +203 -0
- package/dist/modules/foundation/assets/AGENTS.template.md +4 -4
- package/dist/modules/foundation/assets/CLAUDE.template.md +3 -3
- package/dist/modules/foundation/assets/LAWS.template.md +3 -3
- package/dist/modules/foundation/assets/docs/standards/{spec-workflow.template.md → lawbook.template.md} +8 -8
- package/dist/modules/foundation/assets/docs/standards/testing-standards.template.md +1 -1
- package/dist/modules/foundation/doctor.js +7 -7
- package/dist/modules/foundation/register.js +2 -2
- package/dist/modules/foundation/scaffold.js +2 -2
- package/dist/modules/{spec → lawbook}/assets/commands/archive.md +2 -2
- package/dist/modules/{spec → lawbook}/assets/commands/draft.md +4 -4
- package/dist/modules/{spec → lawbook}/assets/commands/explore.md +1 -1
- package/dist/modules/{spec → lawbook}/assets/commands/sync.md +2 -2
- package/dist/modules/{spec → lawbook}/assets/rules/spec-tasks-mandatory-steps.md +6 -6
- package/dist/modules/{spec → lawbook}/assets/skills/archive/SKILL.md +7 -7
- package/dist/modules/{spec → lawbook}/assets/skills/build/SKILL.md +3 -3
- package/dist/modules/{spec → lawbook}/assets/skills/draft/SKILL.md +7 -7
- package/dist/modules/{spec → lawbook}/assets/skills/explore/SKILL.md +2 -2
- package/dist/modules/lawbook/assets/skills/sync/SKILL.md +21 -0
- package/dist/modules/{spec → lawbook}/engine.js +17 -17
- package/dist/modules/{spec → lawbook}/register.js +12 -12
- package/dist/modules/tools/assets/packs/agents/backend-developer.md +1 -1
- package/dist/modules/tools/assets/packs/agents/frontend-developer.md +1 -1
- package/dist/server.js +2 -2
- package/package.json +11 -1
- package/dist/modules/spec/assets/skills/sync/SKILL.md +0 -21
- /package/dist/modules/{spec → lawbook}/assets/commands/build.md +0 -0
package/README.md
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
<div align="center">
|
|
2
2
|
|
|
3
|
-
<img src="brand/speclaw-banner.
|
|
3
|
+
<img src="https://raw.githubusercontent.com/esneiderbravo/speclaw/main/brand/speclaw-banner.png" width="820" alt="speclaw — where specs become law">
|
|
4
4
|
|
|
5
5
|
<br/>
|
|
6
6
|
|
|
@@ -37,17 +37,21 @@ One command. No cloud, no LLM, no API keys — <b>everything runs on your machin
|
|
|
37
37
|
|
|
38
38
|
## ◆ Quick start
|
|
39
39
|
|
|
40
|
+
In your project root:
|
|
41
|
+
|
|
42
|
+
```bash
|
|
43
|
+
npx @esneiderbravo/speclaw init
|
|
44
|
+
```
|
|
45
|
+
|
|
40
46
|
<p align="center">
|
|
41
|
-
<img src="brand/terminal-quickstart.
|
|
47
|
+
<img src="https://raw.githubusercontent.com/esneiderbravo/speclaw/main/brand/terminal-quickstart.png" width="800" alt="npx @esneiderbravo/speclaw init">
|
|
42
48
|
</p>
|
|
43
49
|
|
|
44
|
-
<p align="center">Copy & run in your project root: <code>npx @esneiderbravo/speclaw init</code></p>
|
|
45
|
-
|
|
46
50
|
`init` will:
|
|
47
51
|
|
|
48
52
|
1. **Ask which agents you use** (Claude Code, Cursor, Codex, …) — and configure
|
|
49
53
|
only those. Add more later; nothing is forced on you.
|
|
50
|
-
2. Write the **foundation** (constitution + standards) and the **
|
|
54
|
+
2. Write the **foundation** (constitution + standards) and the **lawbook workflow**.
|
|
51
55
|
3. **Index your code** with a live progress bar and a summary of what it found.
|
|
52
56
|
4. Register the speclaw **MCP server** in each chosen agent's config.
|
|
53
57
|
5. Print a prompt to paste into your agent so it fills the constitution with your
|
|
@@ -60,7 +64,7 @@ Works with `npm`, `pnpm` (`pnpm dlx @esneiderbravo/speclaw init`), and `yarn`.
|
|
|
60
64
|
## ◆ It looks like this
|
|
61
65
|
|
|
62
66
|
<p align="center">
|
|
63
|
-
<img src="brand/terminal-init.
|
|
67
|
+
<img src="https://raw.githubusercontent.com/esneiderbravo/speclaw/main/brand/terminal-init.png" width="800" alt="speclaw init — terminal output">
|
|
64
68
|
</p>
|
|
65
69
|
|
|
66
70
|
<p align="center"><i>Teal steps, green checks, a live progress bar — themed with the speclaw palette.</i></p>
|
|
@@ -71,12 +75,12 @@ Works with `npm`, `pnpm` (`pnpm dlx @esneiderbravo/speclaw init`), and `yarn`.
|
|
|
71
75
|
|
|
72
76
|
| Module | What it does |
|
|
73
77
|
| :-- | :-- |
|
|
74
|
-
| **Foundation** | The project's constitution: `LAWS.md` binding a set of granular standards under `docs/standards/` (base, architecture, backend, frontend, testing, documentation, conventions,
|
|
78
|
+
| **Foundation** | The project's constitution: `LAWS.md` binding a set of granular standards under `docs/standards/` (base, architecture, backend, frontend, testing, documentation, conventions, lawbook), plus strict `CLAUDE.md` / `AGENTS.md` agent contracts — filled from your real codebase. |
|
|
75
79
|
| **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
|
-
| **
|
|
80
|
+
| **Lawbook** | speclaw's own spec-driven workflow: `draft → build → sync → archive` (and `explore`), backed by `lawbook_*` engine tools. No external CLI. |
|
|
77
81
|
| **Tools** | Opt-in packs of skills and subagents (currently the dev-agents) that agents use for specific tasks. |
|
|
78
82
|
|
|
79
|
-
Compass is inspired by [CodeGraph](https://github.com/colbymchenry/codegraph) and the
|
|
83
|
+
Compass is inspired by [CodeGraph](https://github.com/colbymchenry/codegraph) and the Lawbook 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
84
|
|
|
81
85
|
<br/>
|
|
82
86
|
|
|
@@ -85,20 +89,20 @@ Compass is inspired by [CodeGraph](https://github.com/colbymchenry/codegraph) an
|
|
|
85
89
|
speclaw meets you where you are. Everything works through the **CLI** — so no one
|
|
86
90
|
is blocked by MCP setup — and the same capabilities are exposed as **MCP tools**
|
|
87
91
|
for a smoother, integrated experience once configured. An agent without MCP can
|
|
88
|
-
still use Compass and the
|
|
92
|
+
still use Compass and the lawbook engine by calling the CLI from its shell.
|
|
89
93
|
|
|
90
94
|
<p align="center"><b>CLI</b> — the installer & operator, runs anywhere <code>node</code> does</p>
|
|
91
|
-
<p align="center"><img src="brand/terminal-cli.
|
|
95
|
+
<p align="center"><img src="https://raw.githubusercontent.com/esneiderbravo/speclaw/main/brand/terminal-cli.png" width="800" alt="speclaw CLI commands"></p>
|
|
92
96
|
|
|
93
97
|
<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.
|
|
98
|
+
<p align="center"><img src="https://raw.githubusercontent.com/esneiderbravo/speclaw/main/brand/terminal-mcp.png" width="800" alt="speclaw MCP tools"></p>
|
|
95
99
|
|
|
96
100
|
<br/>
|
|
97
101
|
|
|
98
102
|
## ◆ What lands in your project
|
|
99
103
|
|
|
100
104
|
<p align="center">
|
|
101
|
-
<img src="brand/terminal-tree.
|
|
105
|
+
<img src="https://raw.githubusercontent.com/esneiderbravo/speclaw/main/brand/terminal-tree.png" width="800" alt="what speclaw writes into your project">
|
|
102
106
|
</p>
|
|
103
107
|
|
|
104
108
|
<br/>
|
|
@@ -110,7 +114,7 @@ still use Compass and the spec engine by calling the CLI from its shell.
|
|
|
110
114
|
> of AI coding agents isn't lack of capability — it's working without the project's
|
|
111
115
|
> tacit knowledge: the rules the team actually lives by. speclaw makes that
|
|
112
116
|
> knowledge explicit, executable, and binding, and gives agents a local map
|
|
113
|
-
> (Compass) and a disciplined workflow (
|
|
117
|
+
> (Compass) and a disciplined workflow (Lawbook) to act on it — without burning tokens.
|
|
114
118
|
|
|
115
119
|
<br/>
|
|
116
120
|
|
|
@@ -2,12 +2,12 @@ import fs from "node:fs";
|
|
|
2
2
|
import path from "node:path";
|
|
3
3
|
import * as clack from "@clack/prompts";
|
|
4
4
|
import { scaffold } from "../../modules/foundation/scaffold.js";
|
|
5
|
-
import { specInit } from "../../modules/
|
|
5
|
+
import { specInit } from "../../modules/lawbook/engine.js";
|
|
6
6
|
import { buildIndex } from "../../modules/compass/indexer.js";
|
|
7
7
|
import { AGENTS, agentById } from "../../shared/agents.js";
|
|
8
8
|
import { loadPacks } from "../../modules/tools/packs.js";
|
|
9
9
|
import { list } from "../lib/args.js";
|
|
10
|
-
import { ui, c, banner, renderProgress, clearProgress
|
|
10
|
+
import { ui, c, banner, renderProgress, clearProgress } from "../lib/ui.js";
|
|
11
11
|
const PACK_LABELS = {
|
|
12
12
|
agents: "dev-agents (backend · frontend · product)",
|
|
13
13
|
};
|
|
@@ -82,11 +82,11 @@ export async function runInit(flags) {
|
|
|
82
82
|
ui.step(`Setting up ${c.bold(c.cyan(projectName))}`);
|
|
83
83
|
scaffold(cwd, profile, packs, agents);
|
|
84
84
|
ui.ok(`Foundation ${c.muted("— LAWS.md + 8 standards + CLAUDE.md/AGENTS.md")}`);
|
|
85
|
-
ui.ok(`
|
|
85
|
+
ui.ok(`Lawbook workflow ${c.muted("— draft · build · sync · archive · explore")}`);
|
|
86
86
|
for (const p of packs)
|
|
87
87
|
ui.ok(`${PACK_LABELS[p] ?? p + " pack"}`);
|
|
88
88
|
specInit(cwd);
|
|
89
|
-
ui.ok(`
|
|
89
|
+
ui.ok(`Lawbook workspace ${c.muted("— lawbook/")}`);
|
|
90
90
|
ui.step("Configuring agents");
|
|
91
91
|
for (const id of agents)
|
|
92
92
|
ui.ok(`${agentById(id).label} ${c.muted("— symlinks + MCP")}`);
|
|
@@ -100,19 +100,18 @@ export async function runInit(flags) {
|
|
|
100
100
|
c.bold(c.cream(String(stats.edges))) + c.muted(" edges · ") +
|
|
101
101
|
c.bold(c.cream(String(stats.embeddings))) + c.muted(" embeddings"));
|
|
102
102
|
}
|
|
103
|
-
// 3. Handoff prompt for the chosen agent
|
|
103
|
+
// 3. Handoff prompt for the chosen agent — printed as a single flush-left
|
|
104
|
+
// line so it copy-pastes cleanly (no borders, no wrapping artifacts).
|
|
104
105
|
const primary = agentById(agents[0]);
|
|
105
106
|
ui.step("You're set — one last step");
|
|
106
|
-
ui.info(`Paste this into ${c.cyan(primary.label)} to build your constitution:`);
|
|
107
107
|
ui.plain();
|
|
108
|
-
|
|
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");
|
|
108
|
+
ui.info(`Copy this and paste it into ${c.cyan(primary.label)}:`);
|
|
113
109
|
ui.plain();
|
|
114
|
-
|
|
115
|
-
|
|
110
|
+
console.log(c.cream("Complete speclaw's foundation: analyze this repo and fill LAWS.md and " +
|
|
111
|
+
"docs/standards/* with its real architecture, quality gates and conventions. " +
|
|
112
|
+
"Start with init_project."));
|
|
113
|
+
ui.plain();
|
|
114
|
+
ui.info(`The dev-agents read those standards for your stack — filling them well makes them stack-aware.`);
|
|
116
115
|
ui.plain();
|
|
117
116
|
ui.info(`Add an agent: ${ui.code("speclaw agent add cursor")}`);
|
|
118
117
|
ui.info(`Refresh index: ${ui.code("speclaw index")}`);
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { specInit, specValidate, specSync, specArchive, specList } from "../../modules/
|
|
1
|
+
import { specInit, specValidate, specSync, specArchive, specList } from "../../modules/lawbook/engine.js";
|
|
2
2
|
import { ui } from "../lib/ui.js";
|
|
3
3
|
function today() {
|
|
4
4
|
// The MCP path passes the date in; the CLL runs on a real machine, so read it here.
|
|
@@ -17,14 +17,14 @@ export async function runSpec(flags) {
|
|
|
17
17
|
switch (sub) {
|
|
18
18
|
case "init": {
|
|
19
19
|
const r = specInit(cwd);
|
|
20
|
-
ui.ok(r.alreadyExisted ? "
|
|
20
|
+
ui.ok(r.alreadyExisted ? "lawbook/ already present" : `lawbook/ created (${r.created.length} entries)`);
|
|
21
21
|
return;
|
|
22
22
|
}
|
|
23
23
|
case "list": {
|
|
24
24
|
const r = specList(cwd);
|
|
25
25
|
if (!r.initialized)
|
|
26
|
-
return ui.warn("No
|
|
27
|
-
ui.heading("
|
|
26
|
+
return ui.warn("No lawbook/ — run `speclaw lawbook init`.");
|
|
27
|
+
ui.heading("Lawbook workspace");
|
|
28
28
|
ui.info(`active changes: ${r.activeChanges.join(", ") || "none"}`);
|
|
29
29
|
ui.info(`archived: ${r.archivedChanges.join(", ") || "none"}`);
|
|
30
30
|
ui.info(`capabilities: ${r.capabilities.join(", ") || "none"}`);
|
|
@@ -52,7 +52,7 @@ export async function runSpec(flags) {
|
|
|
52
52
|
return;
|
|
53
53
|
}
|
|
54
54
|
default:
|
|
55
|
-
ui.err("Usage: speclaw
|
|
55
|
+
ui.err("Usage: speclaw lawbook <init|list|validate|sync|archive> [change]");
|
|
56
56
|
process.exit(1);
|
|
57
57
|
}
|
|
58
58
|
}
|
|
@@ -0,0 +1,43 @@
|
|
|
1
|
+
import { spawn } from "node:child_process";
|
|
2
|
+
import { visualize } from "../../modules/compass/visualize.js";
|
|
3
|
+
import { ui, c } from "../lib/ui.js";
|
|
4
|
+
/** Open a file in the OS default application (browser for .html). */
|
|
5
|
+
function openInBrowser(file) {
|
|
6
|
+
const cmd = process.platform === "darwin" ? "open" : process.platform === "win32" ? "start" : "xdg-open";
|
|
7
|
+
try {
|
|
8
|
+
spawn(cmd, [file], { stdio: "ignore", detached: true, shell: process.platform === "win32" }).unref();
|
|
9
|
+
}
|
|
10
|
+
catch {
|
|
11
|
+
/* best-effort */
|
|
12
|
+
}
|
|
13
|
+
}
|
|
14
|
+
/**
|
|
15
|
+
* Build an interactive HTML visualization of the Compass graph into
|
|
16
|
+
* .speclaw/graph.html and open it.
|
|
17
|
+
*
|
|
18
|
+
* @param flags - `_[0]` optionally focuses on a node; `--depth`, `--limit`, `--no-open`.
|
|
19
|
+
*/
|
|
20
|
+
export async function runVisualize(flags) {
|
|
21
|
+
const cwd = process.cwd();
|
|
22
|
+
const focus = flags._[0];
|
|
23
|
+
try {
|
|
24
|
+
const r = visualize(cwd, {
|
|
25
|
+
focus,
|
|
26
|
+
depth: flags.depth ? Number(flags.depth) : undefined,
|
|
27
|
+
limit: flags.limit ? Number(flags.limit) : undefined,
|
|
28
|
+
});
|
|
29
|
+
ui.step("Compass graph");
|
|
30
|
+
ui.ok(c.bold(c.cream(String(r.shown))) + c.muted(" nodes · ") +
|
|
31
|
+
c.bold(c.cream(String(r.links))) + c.muted(" edges") +
|
|
32
|
+
(focus ? c.muted(` · focused on ${focus}`) : c.muted(` · top of ${r.total}`)));
|
|
33
|
+
ui.info(`→ ${ui.code(".speclaw/graph.html")}`);
|
|
34
|
+
if (!flags["no-open"]) {
|
|
35
|
+
openInBrowser(r.path);
|
|
36
|
+
ui.info("Opening in your browser…");
|
|
37
|
+
}
|
|
38
|
+
}
|
|
39
|
+
catch (err) {
|
|
40
|
+
ui.err(err.message);
|
|
41
|
+
process.exit(1);
|
|
42
|
+
}
|
|
43
|
+
}
|
package/dist/cli/index.js
CHANGED
|
@@ -18,13 +18,14 @@ Compass (code intelligence — the same surface agents use via MCP)
|
|
|
18
18
|
recall "<query>" Find code by meaning (semantic)
|
|
19
19
|
impact <node> Blast radius: everything that (transitively) calls it
|
|
20
20
|
trace <from> <to> A call path between two nodes
|
|
21
|
+
visualize [node] Interactive HTML graph → .speclaw/graph.html
|
|
21
22
|
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
23
|
+
Lawbook (spec-driven workflow)
|
|
24
|
+
lawbook init Create the lawbook/ workspace
|
|
25
|
+
lawbook list Active/archived changes and capabilities
|
|
26
|
+
lawbook validate <c> Validate a change's artifacts
|
|
27
|
+
lawbook sync <c> Promote delta specs to canonical
|
|
28
|
+
lawbook archive <c> Finalize and archive a change
|
|
28
29
|
|
|
29
30
|
Other
|
|
30
31
|
doctor Verify the installation
|
|
@@ -61,8 +62,10 @@ async function main() {
|
|
|
61
62
|
case "impact":
|
|
62
63
|
case "trace":
|
|
63
64
|
return (await import("./commands/query.js")).runQuery(cmd, flags);
|
|
64
|
-
case "
|
|
65
|
-
return (await import("./commands/
|
|
65
|
+
case "visualize":
|
|
66
|
+
return (await import("./commands/visualize.js")).runVisualize(flags);
|
|
67
|
+
case "lawbook":
|
|
68
|
+
return (await import("./commands/lawbook.js")).runSpec(flags);
|
|
66
69
|
case "doctor":
|
|
67
70
|
return (await import("./commands/doctor.js")).runDoctor(flags);
|
|
68
71
|
default:
|
|
@@ -3,6 +3,7 @@ import { text } from "../../shared/mcp.js";
|
|
|
3
3
|
import { buildIndex } from "./indexer.js";
|
|
4
4
|
import { explore, search, recall, impact, trace } from "./query.js";
|
|
5
5
|
import { startWatch, stopWatch, watchStatus } from "./watcher.js";
|
|
6
|
+
import { visualize } from "./visualize.js";
|
|
6
7
|
// ─── Compass: speclaw's own code-intelligence engine (no external deps) ───
|
|
7
8
|
// A local graph of the codebase (nodes = definitions, edges = calls/imports)
|
|
8
9
|
// plus a local vector store for semantic recall. Lives in .speclaw/ (gitignored).
|
|
@@ -57,6 +58,15 @@ export function registerCompass(server) {
|
|
|
57
58
|
maxDepth: z.number().optional().describe("Max hops to search (default 8)"),
|
|
58
59
|
},
|
|
59
60
|
}, async ({ projectPath, from, to, maxDepth }) => text(trace(projectPath, from, to, maxDepth ?? 8)));
|
|
61
|
+
server.registerTool("compass_visualize", {
|
|
62
|
+
description: "Generate an interactive, offline HTML visualization of the code graph into .speclaw/graph.html (gitignored). Nodes are definitions, edges are calls; drag/zoom/hover to explore. Pass a node to focus on its neighborhood, else the most-connected nodes are shown. Requires compass_index.",
|
|
63
|
+
inputSchema: {
|
|
64
|
+
projectPath: z.string().describe("Absolute path to the project"),
|
|
65
|
+
node: z.string().optional().describe("Focus on this node's neighborhood (optional)"),
|
|
66
|
+
depth: z.number().optional().describe("BFS depth around the focus node (default 2)"),
|
|
67
|
+
limit: z.number().optional().describe("Max nodes for the whole-graph view (default 300)"),
|
|
68
|
+
},
|
|
69
|
+
}, async ({ projectPath, node, depth, limit }) => text(visualize(projectPath, { focus: node, depth, limit })));
|
|
60
70
|
server.registerTool("compass_watch", {
|
|
61
71
|
description: "Keep the Compass index fresh automatically: start/stop a file watcher that incrementally re-indexes on change (debounced). action=start|stop|status. Optional — the index is also refreshed on demand by compass_index.",
|
|
62
72
|
inputSchema: {
|
|
@@ -0,0 +1,203 @@
|
|
|
1
|
+
import fs from "node:fs";
|
|
2
|
+
import path from "node:path";
|
|
3
|
+
import { openDb, indexExists } from "./db.js";
|
|
4
|
+
/**
|
|
5
|
+
* Build the graph payload from the Compass index: nodes (definitions) and the
|
|
6
|
+
* resolved call links between them. With a focus node, returns its neighborhood
|
|
7
|
+
* (BFS both directions); otherwise the most-connected nodes up to `limit`.
|
|
8
|
+
*
|
|
9
|
+
* @throws If no index exists yet (run `compass_index` first).
|
|
10
|
+
*/
|
|
11
|
+
export function graphData(projectPath, opts = {}) {
|
|
12
|
+
if (!indexExists(projectPath)) {
|
|
13
|
+
throw new Error("No index found. Run the compass_index tool (or `speclaw index`) first.");
|
|
14
|
+
}
|
|
15
|
+
const { focus, depth = 2, limit = 300 } = opts;
|
|
16
|
+
const db = openDb(projectPath);
|
|
17
|
+
try {
|
|
18
|
+
const allNodes = db
|
|
19
|
+
.prepare(`SELECT n.id, n.name, n.kind, f.path AS file, n.start_line AS line
|
|
20
|
+
FROM nodes n JOIN files f ON f.id = n.file_id`)
|
|
21
|
+
.all();
|
|
22
|
+
const allEdges = db
|
|
23
|
+
.prepare(`SELECT src_node_id AS s, dst_node_id AS t FROM edges
|
|
24
|
+
WHERE kind = 'call' AND dst_node_id IS NOT NULL AND src_node_id IS NOT NULL`)
|
|
25
|
+
.all();
|
|
26
|
+
// adjacency (undirected) + degree
|
|
27
|
+
const adj = new Map();
|
|
28
|
+
const deg = new Map();
|
|
29
|
+
for (const e of allEdges) {
|
|
30
|
+
if (e.s === e.t)
|
|
31
|
+
continue;
|
|
32
|
+
(adj.get(e.s) ?? adj.set(e.s, new Set()).get(e.s)).add(e.t);
|
|
33
|
+
(adj.get(e.t) ?? adj.set(e.t, new Set()).get(e.t)).add(e.s);
|
|
34
|
+
deg.set(e.s, (deg.get(e.s) ?? 0) + 1);
|
|
35
|
+
deg.set(e.t, (deg.get(e.t) ?? 0) + 1);
|
|
36
|
+
}
|
|
37
|
+
let included;
|
|
38
|
+
if (focus) {
|
|
39
|
+
const starts = allNodes.filter((n) => n.name === focus).map((n) => n.id);
|
|
40
|
+
included = new Set(starts);
|
|
41
|
+
let frontier = starts;
|
|
42
|
+
for (let d = 0; d < depth && frontier.length; d++) {
|
|
43
|
+
const next = [];
|
|
44
|
+
for (const id of frontier) {
|
|
45
|
+
for (const nb of adj.get(id) ?? []) {
|
|
46
|
+
if (!included.has(nb)) {
|
|
47
|
+
included.add(nb);
|
|
48
|
+
next.push(nb);
|
|
49
|
+
}
|
|
50
|
+
}
|
|
51
|
+
}
|
|
52
|
+
frontier = next;
|
|
53
|
+
}
|
|
54
|
+
}
|
|
55
|
+
else {
|
|
56
|
+
included = new Set([...allNodes]
|
|
57
|
+
.sort((a, b) => (deg.get(b.id) ?? 0) - (deg.get(a.id) ?? 0))
|
|
58
|
+
.slice(0, limit)
|
|
59
|
+
.map((n) => n.id));
|
|
60
|
+
}
|
|
61
|
+
const nodes = allNodes
|
|
62
|
+
.filter((n) => included.has(n.id))
|
|
63
|
+
.map((n) => ({ ...n, deg: deg.get(n.id) ?? 0 }));
|
|
64
|
+
const links = allEdges.filter((e) => included.has(e.s) && included.has(e.t));
|
|
65
|
+
return { nodes, links, total: allNodes.length, focus: focus ?? null };
|
|
66
|
+
}
|
|
67
|
+
finally {
|
|
68
|
+
db.close();
|
|
69
|
+
}
|
|
70
|
+
}
|
|
71
|
+
/** Write the interactive graph to <projectPath>/.speclaw/graph.html and return its path. */
|
|
72
|
+
export function visualize(projectPath, opts = {}) {
|
|
73
|
+
const data = graphData(projectPath, opts);
|
|
74
|
+
const dir = path.join(projectPath, ".speclaw");
|
|
75
|
+
fs.mkdirSync(dir, { recursive: true });
|
|
76
|
+
const out = path.join(dir, "graph.html");
|
|
77
|
+
fs.writeFileSync(out, renderHtml(data));
|
|
78
|
+
return { path: out, shown: data.nodes.length, links: data.links.length, total: data.total };
|
|
79
|
+
}
|
|
80
|
+
/** Render a self-contained, offline HTML page with an inline force-directed renderer. */
|
|
81
|
+
export function renderHtml(data) {
|
|
82
|
+
const payload = JSON.stringify(data);
|
|
83
|
+
return `<!doctype html>
|
|
84
|
+
<html lang="en"><head>
|
|
85
|
+
<meta charset="utf-8"><meta name="viewport" content="width=device-width, initial-scale=1">
|
|
86
|
+
<title>speclaw · Compass graph</title>
|
|
87
|
+
<style>
|
|
88
|
+
:root { --cy:#17C1C1; --cr:#F4F1EA; --mu:#6E7B80; --bg:#0B0F10; }
|
|
89
|
+
* { box-sizing:border-box; }
|
|
90
|
+
html,body { margin:0; height:100%; background:var(--bg); color:var(--cr);
|
|
91
|
+
font-family:'SF Mono','JetBrains Mono',Menlo,Consolas,monospace; overflow:hidden; }
|
|
92
|
+
canvas { display:block; cursor:grab; }
|
|
93
|
+
canvas:active { cursor:grabbing; }
|
|
94
|
+
.panel { position:fixed; top:16px; left:16px; padding:12px 14px; border-radius:10px;
|
|
95
|
+
background:rgba(12,17,19,.82); border:1px solid #232A2D; font-size:12px; line-height:1.5; }
|
|
96
|
+
.panel b { color:var(--cr); } .panel .sub { color:var(--mu); }
|
|
97
|
+
.legend { position:fixed; bottom:16px; left:16px; font-size:11px; color:var(--mu);
|
|
98
|
+
background:rgba(12,17,19,.82); border:1px solid #232A2D; border-radius:10px; padding:10px 12px; }
|
|
99
|
+
.legend span { display:inline-flex; align-items:center; margin-right:12px; }
|
|
100
|
+
.legend i { width:9px; height:9px; border-radius:50%; display:inline-block; margin-right:5px; }
|
|
101
|
+
.tip { position:fixed; padding:6px 9px; border-radius:7px; background:#0E1517; border:1px solid #17C1C1;
|
|
102
|
+
color:var(--cr); font-size:12px; pointer-events:none; opacity:0; transition:opacity .1s; white-space:nowrap; }
|
|
103
|
+
.brand { color:var(--cy); font-weight:700; }
|
|
104
|
+
</style></head><body>
|
|
105
|
+
<canvas id="c"></canvas>
|
|
106
|
+
<div class="panel">
|
|
107
|
+
<div><span class="brand">speclaw</span> · Compass graph</div>
|
|
108
|
+
<div class="sub" id="meta"></div>
|
|
109
|
+
<div class="sub">drag to pan · wheel to zoom · drag a node · hover to inspect</div>
|
|
110
|
+
</div>
|
|
111
|
+
<div class="legend" id="legend"></div>
|
|
112
|
+
<div class="tip" id="tip"></div>
|
|
113
|
+
<script>
|
|
114
|
+
const DATA = ${payload};
|
|
115
|
+
const KIND_COLORS = { function:'#17C1C1', method:'#3FB950', class:'#E3B341',
|
|
116
|
+
interface:'#8B989E', type:'#8B989E', enum:'#8B989E' };
|
|
117
|
+
const colorOf = k => KIND_COLORS[k] || '#6E7B80';
|
|
118
|
+
|
|
119
|
+
const cv = document.getElementById('c'), ctx = cv.getContext('2d');
|
|
120
|
+
const tip = document.getElementById('tip');
|
|
121
|
+
document.getElementById('meta').textContent =
|
|
122
|
+
DATA.nodes.length + ' nodes · ' + DATA.links.length + ' edges' +
|
|
123
|
+
(DATA.focus ? ' · focus: ' + DATA.focus : ' · top ' + DATA.nodes.length + ' of ' + DATA.total);
|
|
124
|
+
{
|
|
125
|
+
const kinds = [...new Set(DATA.nodes.map(n=>n.kind))];
|
|
126
|
+
document.getElementById('legend').innerHTML = kinds.map(k =>
|
|
127
|
+
'<span><i style="background:'+colorOf(k)+'"></i>'+k+'</span>').join('');
|
|
128
|
+
}
|
|
129
|
+
|
|
130
|
+
const idx = new Map(DATA.nodes.map((n,i)=>[n.id,i]));
|
|
131
|
+
const N = DATA.nodes.map(n => ({...n, x:(Math.random()-.5)*800, y:(Math.random()-.5)*600, vx:0, vy:0,
|
|
132
|
+
r: 4 + Math.min(10, Math.sqrt(n.deg||0)*2) }));
|
|
133
|
+
const L = DATA.links.map(l => ({s:idx.get(l.s), t:idx.get(l.t)})).filter(l=>l.s!=null&&l.t!=null);
|
|
134
|
+
const neigh = N.map(()=>new Set());
|
|
135
|
+
L.forEach(l => { neigh[l.s].add(l.t); neigh[l.t].add(l.s); });
|
|
136
|
+
|
|
137
|
+
let scale=1, ox=0, oy=0, W=0, H=0, DPR=Math.min(2,devicePixelRatio||1);
|
|
138
|
+
function resize(){ W=innerWidth; H=innerHeight; cv.width=W*DPR; cv.height=H*DPR; cv.style.width=W+'px'; cv.style.height=H+'px'; ctx.setTransform(DPR,0,0,DPR,0,0); }
|
|
139
|
+
addEventListener('resize', resize); resize(); ox=W/2; oy=H/2;
|
|
140
|
+
|
|
141
|
+
// force simulation
|
|
142
|
+
function tick(){
|
|
143
|
+
for(let i=0;i<N.length;i++){ const a=N[i];
|
|
144
|
+
for(let j=i+1;j<N.length;j++){ const b=N[j];
|
|
145
|
+
let dx=a.x-b.x, dy=a.y-b.y, d2=dx*dx+dy*dy||0.01, d=Math.sqrt(d2);
|
|
146
|
+
const f=Math.min(2200/d2, 40); const ux=dx/d, uy=dy/d;
|
|
147
|
+
a.vx+=ux*f; a.vy+=uy*f; b.vx-=ux*f; b.vy-=uy*f;
|
|
148
|
+
}
|
|
149
|
+
a.vx += -a.x*0.0016; a.vy += -a.y*0.0016; // gravity to center
|
|
150
|
+
}
|
|
151
|
+
for(const l of L){ const a=N[l.s], b=N[l.t];
|
|
152
|
+
let dx=b.x-a.x, dy=b.y-a.y, d=Math.sqrt(dx*dx+dy*dy)||0.01;
|
|
153
|
+
const f=(d-90)*0.02, ux=dx/d, uy=dy/d;
|
|
154
|
+
a.vx+=ux*f; a.vy+=uy*f; b.vx-=ux*f; b.vy-=uy*f;
|
|
155
|
+
}
|
|
156
|
+
for(const n of N){ if(n===dragging) continue; n.vx*=0.82; n.vy*=0.82; n.x+=n.vx; n.y+=n.vy; }
|
|
157
|
+
}
|
|
158
|
+
function toScreen(n){ return { x: n.x*scale+ox, y: n.y*scale+oy }; }
|
|
159
|
+
|
|
160
|
+
let hover=-1;
|
|
161
|
+
function draw(){
|
|
162
|
+
ctx.clearRect(0,0,W,H);
|
|
163
|
+
const hoverSet = hover>=0 ? neigh[hover] : null;
|
|
164
|
+
ctx.lineWidth = 1;
|
|
165
|
+
for(const l of L){ const a=toScreen(N[l.s]), b=toScreen(N[l.t]);
|
|
166
|
+
const on = hover>=0 && (l.s===hover||l.t===hover);
|
|
167
|
+
ctx.strokeStyle = on ? 'rgba(23,193,193,.7)' : 'rgba(110,123,128,.16)';
|
|
168
|
+
ctx.beginPath(); ctx.moveTo(a.x,a.y); ctx.lineTo(b.x,b.y); ctx.stroke();
|
|
169
|
+
}
|
|
170
|
+
for(let i=0;i<N.length;i++){ const n=N[i], p=toScreen(n);
|
|
171
|
+
const dim = hover>=0 && i!==hover && !(hoverSet&&hoverSet.has(i));
|
|
172
|
+
ctx.globalAlpha = dim ? 0.28 : 1;
|
|
173
|
+
ctx.beginPath(); ctx.arc(p.x,p.y,n.r*Math.sqrt(scale),0,7); ctx.fillStyle=colorOf(n.kind); ctx.fill();
|
|
174
|
+
if((n.r>7 || i===hover || (hoverSet&&hoverSet.has(i))) && scale>0.5){
|
|
175
|
+
ctx.globalAlpha = dim?0.4:0.9; ctx.fillStyle='#F4F1EA'; ctx.font='11px monospace';
|
|
176
|
+
ctx.fillText(n.name, p.x+n.r+3, p.y+3);
|
|
177
|
+
}
|
|
178
|
+
}
|
|
179
|
+
ctx.globalAlpha=1;
|
|
180
|
+
}
|
|
181
|
+
function loop(){ tick(); draw(); requestAnimationFrame(loop); } loop();
|
|
182
|
+
|
|
183
|
+
// interaction
|
|
184
|
+
let dragging=null, panning=false, lastX=0, lastY=0;
|
|
185
|
+
function pick(mx,my){ let best=-1, bd=1e9;
|
|
186
|
+
for(let i=0;i<N.length;i++){ const p=toScreen(N[i]); const d=Math.hypot(p.x-mx,p.y-my);
|
|
187
|
+
if(d < N[i].r*Math.sqrt(scale)+6 && d<bd){ bd=d; best=i; } } return best; }
|
|
188
|
+
cv.addEventListener('mousedown', e=>{ const i=pick(e.clientX,e.clientY);
|
|
189
|
+
if(i>=0) dragging=N[i]; else { panning=true; } lastX=e.clientX; lastY=e.clientY; });
|
|
190
|
+
addEventListener('mousemove', e=>{
|
|
191
|
+
if(dragging){ dragging.x += (e.clientX-lastX)/scale; dragging.y += (e.clientY-lastY)/scale; dragging.vx=dragging.vy=0; }
|
|
192
|
+
else if(panning){ ox+=e.clientX-lastX; oy+=e.clientY-lastY; }
|
|
193
|
+
lastX=e.clientX; lastY=e.clientY;
|
|
194
|
+
const i=pick(e.clientX,e.clientY); hover=i;
|
|
195
|
+
if(i>=0){ const n=N[i]; tip.style.opacity=1; tip.style.left=(e.clientX+12)+'px'; tip.style.top=(e.clientY+12)+'px';
|
|
196
|
+
tip.innerHTML='<b>'+n.name+'</b> <span style="color:#6E7B80">'+n.kind+' · '+n.file+':'+n.line+'</span>'; }
|
|
197
|
+
else tip.style.opacity=0;
|
|
198
|
+
});
|
|
199
|
+
addEventListener('mouseup', ()=>{ dragging=null; panning=false; });
|
|
200
|
+
cv.addEventListener('wheel', e=>{ e.preventDefault(); const f=e.deltaY<0?1.1:0.9;
|
|
201
|
+
const mx=e.clientX, my=e.clientY; ox=mx-(mx-ox)*f; oy=my-(my-oy)*f; scale*=f; }, {passive:false});
|
|
202
|
+
</script></body></html>`;
|
|
203
|
+
}
|
|
@@ -19,9 +19,9 @@ Claude-specific notes: [`CLAUDE.md`](CLAUDE.md). The law: [`LAWS.md`](LAWS.md).
|
|
|
19
19
|
2. **Use Compass before file scanning** — the `compass_explore` /
|
|
20
20
|
`compass_search` / `compass_recall` tools (run `compass_index` first if the
|
|
21
21
|
graph is missing). Cheat sheet: [`docs/compass.md`](docs/compass.md).
|
|
22
|
-
3. **Follow the
|
|
22
|
+
3. **Follow the lawbook workflow** for every non-trivial change; archive
|
|
23
23
|
within the same PR. Rules:
|
|
24
|
-
[`docs/standards/
|
|
24
|
+
[`docs/standards/lawbook.md`](docs/standards/lawbook.md).
|
|
25
25
|
4. **Run the quality gates yourself** before declaring anything done — see
|
|
26
26
|
[`docs/standards/testing-standards.md`](docs/standards/testing-standards.md):
|
|
27
27
|
- Tests: `{{test_commands}}`
|
|
@@ -45,7 +45,7 @@ Claude-specific notes: [`CLAUDE.md`](CLAUDE.md). The law: [`LAWS.md`](LAWS.md).
|
|
|
45
45
|
| [`docs/standards/testing-standards.md`](docs/standards/testing-standards.md) | Quality gates, testing rules |
|
|
46
46
|
| [`docs/standards/documentation.md`](docs/standards/documentation.md) | Docstring/API-comment convention per language |
|
|
47
47
|
| [`docs/standards/conventions.md`](docs/standards/conventions.md) | Branches, PRs, tracker, versioning |
|
|
48
|
-
| [`docs/standards/
|
|
48
|
+
| [`docs/standards/lawbook.md`](docs/standards/lawbook.md) | Spec-driven workflow, archiving |
|
|
49
49
|
| [`docs/compass.md`](docs/compass.md) | Compass usage |
|
|
50
50
|
|
|
51
51
|
## Directory map for agents
|
|
@@ -57,5 +57,5 @@ Claude-specific notes: [`CLAUDE.md`](CLAUDE.md). The law: [`LAWS.md`](LAWS.md).
|
|
|
57
57
|
| `AGENTS.md` / `CLAUDE.md` | Agent entry points (this contract) |
|
|
58
58
|
| `ai-specs/` | Canonical skills, commands, rules, agents |
|
|
59
59
|
| `.claude/` `.cursor/` `.codex/` `.agents/` | IDE mirrors (symlinks into `ai-specs/`) |
|
|
60
|
-
| `
|
|
60
|
+
| `lawbook/` | Spec-driven workflow: specs, changes, archive |
|
|
61
61
|
| `.mcp.json` | MCP wiring (speclaw) |
|
|
@@ -21,7 +21,7 @@ governs the area you're touching **before** touching it:
|
|
|
21
21
|
| Tests / gates | [`docs/standards/testing-standards.md`](docs/standards/testing-standards.md) |
|
|
22
22
|
| Docstrings / API comments | [`docs/standards/documentation.md`](docs/standards/documentation.md) |
|
|
23
23
|
| Branches / PRs / tickets | [`docs/standards/conventions.md`](docs/standards/conventions.md) |
|
|
24
|
-
| Any non-trivial change | [`docs/standards/
|
|
24
|
+
| Any non-trivial change | [`docs/standards/lawbook.md`](docs/standards/lawbook.md) |
|
|
25
25
|
|
|
26
26
|
When any instruction conflicts with a standard, **the standard wins** — and if
|
|
27
27
|
you believe it is wrong, propose an amendment via a spec change; never silently
|
|
@@ -39,8 +39,8 @@ only when the graph doesn't cover what you need.
|
|
|
39
39
|
|
|
40
40
|
No non-trivial change lands without a spec change (propose → implement →
|
|
41
41
|
verify → archive). The rules are in
|
|
42
|
-
[`docs/standards/
|
|
43
|
-
the workflow skills live in `ai-specs/skills/` and the `/
|
|
42
|
+
[`docs/standards/lawbook.md`](docs/standards/lawbook.md);
|
|
43
|
+
the workflow skills live in `ai-specs/skills/` and the `/lawbook` commands wrap
|
|
44
44
|
them. A change is not done until it is archived — archiving belongs in the PR.
|
|
45
45
|
|
|
46
46
|
## Rule 3 — Quality gates are non-negotiable
|
|
@@ -21,7 +21,7 @@
|
|
|
21
21
|
| Testing | [`docs/standards/testing-standards.md`](docs/standards/testing-standards.md) | Quality gates, what must be tested, verification |
|
|
22
22
|
| Documentation | [`docs/standards/documentation.md`](docs/standards/documentation.md) | Docstring/API-comment convention per language |
|
|
23
23
|
| Conventions | [`docs/standards/conventions.md`](docs/standards/conventions.md) | Branches, PRs, tracker, versioning |
|
|
24
|
-
|
|
|
24
|
+
| Lawbook | [`docs/standards/lawbook.md`](docs/standards/lawbook.md) | Spec-driven workflow, mandatory task steps, archiving |
|
|
25
25
|
| Compass | [`docs/compass.md`](docs/compass.md) | Using the code knowledge graph before grep |
|
|
26
26
|
|
|
27
27
|
## Binding rules
|
|
@@ -30,8 +30,8 @@
|
|
|
30
30
|
the map. Agents open the standard that governs the code they're changing.
|
|
31
31
|
2. **The standards are enforced, not advisory.** A violation is a blocking
|
|
32
32
|
finding in review.
|
|
33
|
-
3. **Amendments go through the
|
|
34
|
-
reviewed change (see the
|
|
33
|
+
3. **Amendments go through the lawbook workflow.** A standard is changed like code — via a
|
|
34
|
+
reviewed change (see the lawbook law). An agent may propose an amendment;
|
|
35
35
|
it may never silently ignore a standard.
|
|
36
36
|
4. **Entry points reference the law.** [`CLAUDE.md`](CLAUDE.md) and
|
|
37
37
|
[`AGENTS.md`](AGENTS.md) point every agent here first.
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
#
|
|
1
|
+
# Lawbook — {{project_name}}
|
|
2
2
|
|
|
3
3
|
The process law of the project — see [`../../LAWS.md`](../../LAWS.md). This
|
|
4
4
|
repo is spec-driven through speclaw's **spec** module (no external CLI; the
|
|
@@ -9,18 +9,18 @@ mechanical steps are speclaw MCP tools).
|
|
|
9
9
|
No non-trivial change lands without a spec change:
|
|
10
10
|
|
|
11
11
|
1. **explore** — think an idea through before committing (writes nothing).
|
|
12
|
-
2. **draft** — create `
|
|
12
|
+
2. **draft** — create `lawbook/changes/<name>/`: `proposal.md`, delta specs under
|
|
13
13
|
`specs/<capability>/spec.md`, optional `design.md`, and `tasks.md`.
|
|
14
14
|
3. **build** — implement the tasks in order, keeping code and spec in
|
|
15
15
|
agreement.
|
|
16
16
|
4. **sync** — promote the change's delta specs into the canonical
|
|
17
|
-
`
|
|
18
|
-
5. **archive** — finalize: sync + move the change to `
|
|
19
|
-
(`
|
|
17
|
+
`lawbook/specs/` (`lawbook_sync`).
|
|
18
|
+
5. **archive** — finalize: sync + move the change to `lawbook/changes/archive/`
|
|
19
|
+
(`lawbook_archive`), **within the same PR** — never a post-merge chore.
|
|
20
20
|
|
|
21
21
|
## Mandatory task steps
|
|
22
22
|
|
|
23
|
-
`tasks.md` MUST include the steps defined in `
|
|
23
|
+
`tasks.md` MUST include the steps defined in `lawbook/config.yaml` and the
|
|
24
24
|
`spec-tasks-mandatory-steps` rule: feature branch first, tests reviewed and
|
|
25
25
|
run, manual verification executed by the agent, docs updated, archive within
|
|
26
26
|
the PR. The agent performs the manual testing itself — never delegates it.
|
|
@@ -32,11 +32,11 @@ the PR. The agent performs the manual testing itself — never delegates it.
|
|
|
32
32
|
- Scenario headers use exactly `#### Scenario:`.
|
|
33
33
|
- Acceptance criteria are testable without production integrations.
|
|
34
34
|
- The implemented code must match what the delta spec promises. Validate with
|
|
35
|
-
the `
|
|
35
|
+
the `lawbook_validate` tool before syncing or archiving.
|
|
36
36
|
|
|
37
37
|
## Archiving discipline
|
|
38
38
|
|
|
39
|
-
Always archive with the `archive` command / `
|
|
39
|
+
Always archive with the `archive` command / `lawbook_archive` tool, never a manual
|
|
40
40
|
`mv` — the tool performs the spec promotion and validation a manual move skips.
|
|
41
41
|
|
|
42
42
|
## Amendments to the law
|
|
@@ -30,5 +30,5 @@ suppressing a linter or deleting a test.
|
|
|
30
30
|
- When a change affects runtime behavior and it's feasible, verify it works by
|
|
31
31
|
exercising the endpoint/UI — don't assume green CI covers everything.
|
|
32
32
|
- The mandatory spec task steps
|
|
33
|
-
([`
|
|
33
|
+
([`lawbook.md`](lawbook.md)) define which manual checks
|
|
34
34
|
the agent must execute itself.
|
|
@@ -4,7 +4,7 @@ import { AGENTS, detectConfiguredAgents } from "../../shared/agents.js";
|
|
|
4
4
|
/**
|
|
5
5
|
* Run the speclaw installation health checks against a project: ai-specs and
|
|
6
6
|
* LAWS.md presence, agent contracts, the docs/standards set, per-agent IDE
|
|
7
|
-
* symlink health, the
|
|
7
|
+
* symlink health, the lawbook/ workflow, the Compass index, and .mcp.json wiring.
|
|
8
8
|
*
|
|
9
9
|
* @param projectPath - Absolute path to the project root.
|
|
10
10
|
* @returns One {@link Check} per verified item, each carrying a remediation hint.
|
|
@@ -39,7 +39,7 @@ export function doctor(projectPath) {
|
|
|
39
39
|
"testing-standards",
|
|
40
40
|
"documentation",
|
|
41
41
|
"conventions",
|
|
42
|
-
"
|
|
42
|
+
"lawbook",
|
|
43
43
|
];
|
|
44
44
|
const missingStandards = standards.filter((s) => !has(path.join("docs/standards", `${s}.md`)));
|
|
45
45
|
checks.push({
|
|
@@ -85,11 +85,11 @@ export function doctor(projectPath) {
|
|
|
85
85
|
}
|
|
86
86
|
}
|
|
87
87
|
checks.push({
|
|
88
|
-
name: "
|
|
89
|
-
ok: has("
|
|
90
|
-
detail: has("
|
|
91
|
-
? "
|
|
92
|
-
: "missing — run the `
|
|
88
|
+
name: "lawbook workflow",
|
|
89
|
+
ok: has("lawbook"),
|
|
90
|
+
detail: has("lawbook")
|
|
91
|
+
? "lawbook/ present"
|
|
92
|
+
: "missing — run the `lawbook_init` tool",
|
|
93
93
|
});
|
|
94
94
|
checks.push({
|
|
95
95
|
name: "Compass index",
|
|
@@ -43,7 +43,7 @@ export function registerFoundation(server) {
|
|
|
43
43
|
"4. If the 'workflow' pack is selected, ask the user for their tracker's ticket prefix and team working language.",
|
|
44
44
|
"5. Draft any custom_laws (extra binding rules for LAWS.md) from conventions you observed that the standard set doesn't cover.",
|
|
45
45
|
"6. Call the 'scaffold' tool with { projectPath, profile, packs }.",
|
|
46
|
-
"7. Follow the nextSteps returned by scaffold: complete the HTML-comment sections still left in docs/standards/*, then run the
|
|
46
|
+
"7. Follow the nextSteps returned by scaffold: complete the HTML-comment sections still left in docs/standards/*, then run the lawbook_init and compass_index tools (both built into speclaw — no external installs).",
|
|
47
47
|
],
|
|
48
48
|
profileFields: Object.fromEntries(Object.entries(profileShape).map(([key, schema]) => [key, schema.description ?? ""])),
|
|
49
49
|
packs,
|
|
@@ -70,7 +70,7 @@ export function registerFoundation(server) {
|
|
|
70
70
|
return text(report);
|
|
71
71
|
});
|
|
72
72
|
server.registerTool("doctor", {
|
|
73
|
-
description: "Verify a speclaw installation: ai-specs presence, the foundation (LAWS.md + standards + agent contracts), IDE symlinks health, the
|
|
73
|
+
description: "Verify a speclaw installation: ai-specs presence, the foundation (LAWS.md + standards + agent contracts), IDE symlinks health, the lawbook/ workflow, the Compass index, and .mcp.json wiring. Returns a checklist with remediation hints.",
|
|
74
74
|
inputSchema: { projectPath: z.string().describe("Absolute path to the project") },
|
|
75
75
|
}, async ({ projectPath }) => {
|
|
76
76
|
const checks = doctor(projectPath);
|
|
@@ -4,7 +4,7 @@ import { assetsDir } from "../../shared/paths.js";
|
|
|
4
4
|
import { render } from "../../shared/render.js";
|
|
5
5
|
import { emptyReport, ensureGitignore } from "../../shared/install.js";
|
|
6
6
|
import { configureAgent } from "../../shared/agents.js";
|
|
7
|
-
import { installWorkflow } from "../
|
|
7
|
+
import { installWorkflow } from "../lawbook/register.js";
|
|
8
8
|
import { installPack, loadPacks } from "../tools/packs.js";
|
|
9
9
|
const ASSETS = assetsDir(import.meta.url);
|
|
10
10
|
// Every {{var}} the foundation templates may reference. Ones the agent didn't
|
|
@@ -90,7 +90,7 @@ export function scaffold(projectPath, profile, packNames, agents = []) {
|
|
|
90
90
|
for (const id of agents)
|
|
91
91
|
configureAgent(projectPath, id, report); // only the chosen agents
|
|
92
92
|
report.nextSteps = [
|
|
93
|
-
"Run the `
|
|
93
|
+
"Run the `lawbook_init` tool to set up the spec-driven workflow (creates lawbook/). No external CLI needed — it's built into speclaw.",
|
|
94
94
|
"Run the `compass_index` tool to build the local code graph (.speclaw/). No install, no LLM — it's built into speclaw. Re-run it after significant edits.",
|
|
95
95
|
"Analyze the repo's real entrypoints and core flows, then fill in the 'Project-specific starting points' section of docs/compass.md (or pass compass_hints in the profile) so agents know where to start querying.",
|
|
96
96
|
"Fill in the per-standard sections left as HTML comments in docs/standards/* (architecture module table, backend/frontend layer tables, versioning) by analyzing the real repo. These are the granular laws CLAUDE.md, AGENTS.md and the dev agents reference.",
|
|
@@ -5,6 +5,6 @@ description: Finalize a completed change — sync specs into canonical, then arc
|
|
|
5
5
|
Archive the completed change: $ARGUMENTS
|
|
6
6
|
|
|
7
7
|
Follow the `archive` skill: confirm every task is done and gates are green,
|
|
8
|
-
run `
|
|
9
|
-
syncs the specs and moves the change to `
|
|
8
|
+
run `lawbook_validate`, then `lawbook_archive` with today's date (YYYY-MM-DD). It
|
|
9
|
+
syncs the specs and moves the change to `lawbook/changes/archive/`. Never move
|
|
10
10
|
the folder by hand.
|
|
@@ -2,11 +2,11 @@
|
|
|
2
2
|
description: Draft a new spec-driven change (proposal, delta specs, tasks) before coding.
|
|
3
3
|
---
|
|
4
4
|
|
|
5
|
-
Draft a new change under `
|
|
5
|
+
Draft a new change under `lawbook/changes/<name>/` for: $ARGUMENTS
|
|
6
6
|
|
|
7
|
-
Follow the `draft` skill: ensure `
|
|
7
|
+
Follow the `draft` skill: ensure `lawbook/` exists (`lawbook_init`), investigate the
|
|
8
8
|
code with `compass_explore`/`compass_recall`, read the governing
|
|
9
9
|
`docs/standards/`, then write `proposal.md`, `specs/<capability>/spec.md`
|
|
10
10
|
(normative `SHALL`/`MUST` + `#### Scenario:`), optional `design.md`, and
|
|
11
|
-
`tasks.md` (with the mandatory steps from `
|
|
12
|
-
running `
|
|
11
|
+
`tasks.md` (with the mandatory steps from `lawbook/config.yaml`). Finish by
|
|
12
|
+
running `lawbook_validate` and fixing every issue.
|
|
@@ -7,4 +7,4 @@ Explore: $ARGUMENTS
|
|
|
7
7
|
Follow the `explore` skill: use `compass_recall`/`compass_explore` to understand
|
|
8
8
|
the code, ask sharp questions, check the relevant `docs/standards/`, weigh
|
|
9
9
|
approaches with trade-offs, and give a recommendation. Write nothing to
|
|
10
|
-
`
|
|
10
|
+
`lawbook/`; when the direction is clear, offer to `draft` the change.
|
|
@@ -4,6 +4,6 @@ description: Promote a change's delta specs into the canonical specs, without ar
|
|
|
4
4
|
|
|
5
5
|
Sync the change's specs into canonical: $ARGUMENTS
|
|
6
6
|
|
|
7
|
-
Follow the `sync` skill: validate the change (`
|
|
8
|
-
`
|
|
7
|
+
Follow the `sync` skill: validate the change (`lawbook_validate`), then run
|
|
8
|
+
`lawbook_sync` to promote each delta spec into `lawbook/specs/`. Report what was
|
|
9
9
|
promoted; leave the change active.
|
|
@@ -1,15 +1,15 @@
|
|
|
1
1
|
---
|
|
2
|
-
description: Enforce the mandatory steps from
|
|
2
|
+
description: Enforce the mandatory steps from lawbook/config.yaml when creating tasks.md, and ensure the agent executes all manual verification itself.
|
|
3
3
|
alwaysApply: true
|
|
4
4
|
---
|
|
5
5
|
|
|
6
6
|
# Spec Tasks: Mandatory Steps
|
|
7
7
|
|
|
8
|
-
When creating or updating a `tasks.md` inside a `
|
|
8
|
+
When creating or updating a `tasks.md` inside a `lawbook/changes/<name>/`, you MUST:
|
|
9
9
|
|
|
10
|
-
## 1. Read
|
|
10
|
+
## 1. Read lawbook/config.yaml first
|
|
11
11
|
|
|
12
|
-
Before writing tasks, read `
|
|
12
|
+
Before writing tasks, read `lawbook/config.yaml` for the project's mandatory task
|
|
13
13
|
steps, branch convention, and testing/documentation requirements.
|
|
14
14
|
|
|
15
15
|
## 2. Include the mandatory steps, in order
|
|
@@ -22,7 +22,7 @@ steps, branch convention, and testing/documentation requirements.
|
|
|
22
22
|
- Perform manual verification of the behavior — **the agent executes this
|
|
23
23
|
itself, never the user.**
|
|
24
24
|
- Update the technical documentation the change touches.
|
|
25
|
-
- Archive the change within the same PR (the `archive` command / `
|
|
25
|
+
- Archive the change within the same PR (the `archive` command / `lawbook_archive`
|
|
26
26
|
tool).
|
|
27
27
|
|
|
28
28
|
## 3. Manual verification — the agent must execute it
|
|
@@ -33,5 +33,5 @@ not complete until the agent has verified it.
|
|
|
33
33
|
|
|
34
34
|
## 4. Archiving is part of the change
|
|
35
35
|
|
|
36
|
-
A change is not done until it is archived with `
|
|
36
|
+
A change is not done until it is archived with `lawbook_archive` (never a manual
|
|
37
37
|
`mv`). The archive lands in the same PR that implements the change.
|
|
@@ -1,22 +1,22 @@
|
|
|
1
1
|
---
|
|
2
2
|
name: archive
|
|
3
|
-
description: Finalize a completed change — sync its specs into canonical, then move it to the archive. Use when a change is done and merged-ready: "archive the change", "finalize X", "close out the change". Part of speclaw's
|
|
3
|
+
description: Finalize a completed change — sync its specs into canonical, then move it to the archive. Use when a change is done and merged-ready: "archive the change", "finalize X", "close out the change". Part of speclaw's lawbook module (draft → build → sync → archive). Archiving belongs in the same PR that implements the change.
|
|
4
4
|
---
|
|
5
5
|
|
|
6
6
|
# archive — Finalize and archive a change
|
|
7
7
|
|
|
8
8
|
Close out a completed change: its delta specs become canonical and the change
|
|
9
|
-
folder moves to `
|
|
9
|
+
folder moves to `lawbook/changes/archive/`. This is part of the PR that
|
|
10
10
|
implements the change, not a post-merge chore.
|
|
11
11
|
|
|
12
12
|
## Steps
|
|
13
13
|
|
|
14
14
|
1. Confirm the change is truly done: every task in `tasks.md` checked, quality
|
|
15
15
|
gates green, behavior verified.
|
|
16
|
-
2. Run `
|
|
17
|
-
3. Run the `
|
|
18
|
-
(`YYYY-MM-DD`). It syncs the delta specs into `
|
|
19
|
-
`
|
|
16
|
+
2. Run `lawbook_validate` for the change; resolve any issues first.
|
|
17
|
+
3. Run the `lawbook_archive` tool with the change name and today's date
|
|
18
|
+
(`YYYY-MM-DD`). It syncs the delta specs into `lawbook/specs/` and then moves
|
|
19
|
+
`lawbook/changes/<name>/` to `lawbook/changes/archive/<date>-<name>/`.
|
|
20
20
|
4. Report the archive path and the promoted specs. Never move the folder by
|
|
21
|
-
hand — always use `
|
|
21
|
+
hand — always use `lawbook_archive`, which performs the sync and validation a
|
|
22
22
|
manual move would skip.
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
---
|
|
2
2
|
name: build
|
|
3
|
-
description: Implement the tasks of a drafted change, following its spec and the project's standards. Use when the user wants to start or continue implementing a change: "build X", "implement the change", "work through the tasks", "continue X". Part of speclaw's
|
|
3
|
+
description: Implement the tasks of a drafted change, following its spec and the project's standards. Use when the user wants to start or continue implementing a change: "build X", "implement the change", "work through the tasks", "continue X". Part of speclaw's lawbook module (draft → build → sync → archive).
|
|
4
4
|
---
|
|
5
5
|
|
|
6
6
|
# build — Implement a change
|
|
@@ -10,8 +10,8 @@ in lockstep.
|
|
|
10
10
|
|
|
11
11
|
## Step 0 — Load the change
|
|
12
12
|
|
|
13
|
-
- Read `
|
|
14
|
-
`specs/`. If unsure which change, run `
|
|
13
|
+
- Read `lawbook/changes/<name>/proposal.md`, `tasks.md`, and the delta specs under
|
|
14
|
+
`specs/`. If unsure which change, run `lawbook_list`.
|
|
15
15
|
- Read the governing standards in `docs/standards/` for the areas you'll touch.
|
|
16
16
|
|
|
17
17
|
## Step 1 — Branch first
|
|
@@ -1,17 +1,17 @@
|
|
|
1
1
|
---
|
|
2
2
|
name: draft
|
|
3
|
-
description: Draft a new spec-driven change — proposal, delta specs, and tasks — before writing any code. Use when the user wants to start, plan, or propose a new feature, fix, or refactor: "draft a change for X", "propose X", "let's plan X", "spec out X", "new change". Part of speclaw's
|
|
3
|
+
description: Draft a new spec-driven change — proposal, delta specs, and tasks — before writing any code. Use when the user wants to start, plan, or propose a new feature, fix, or refactor: "draft a change for X", "propose X", "let's plan X", "spec out X", "new change". Part of speclaw's lawbook module (draft → build → sync → archive).
|
|
4
4
|
---
|
|
5
5
|
|
|
6
6
|
# draft — Draft a new change
|
|
7
7
|
|
|
8
|
-
Turn a request into a complete, reviewable change under `
|
|
8
|
+
Turn a request into a complete, reviewable change under `lawbook/changes/<name>/`
|
|
9
9
|
before any implementation. This is speclaw's own spec-driven workflow — no
|
|
10
10
|
external CLI; the mechanical steps are speclaw MCP tools.
|
|
11
11
|
|
|
12
12
|
## Step 0 — Ensure the workspace exists
|
|
13
13
|
|
|
14
|
-
If `
|
|
14
|
+
If `lawbook/` is missing, run the `lawbook_init` tool once to create it.
|
|
15
15
|
|
|
16
16
|
## Step 1 — Understand the request and the code
|
|
17
17
|
|
|
@@ -25,11 +25,11 @@ If `spec/` is missing, run the `spec_init` tool once to create it.
|
|
|
25
25
|
## Step 2 — Pick a change name
|
|
26
26
|
|
|
27
27
|
Kebab-case, action-oriented (e.g. `add-login`, `fix-shift-overlap`). This is
|
|
28
|
-
the folder under `
|
|
28
|
+
the folder under `lawbook/changes/`.
|
|
29
29
|
|
|
30
30
|
## Step 3 — Write the artifacts
|
|
31
31
|
|
|
32
|
-
Create under `
|
|
32
|
+
Create under `lawbook/changes/<name>/`:
|
|
33
33
|
|
|
34
34
|
- **proposal.md** — the why, the what, non-goals, and whether migrations are
|
|
35
35
|
needed. Reference the tracker ticket ({{ticket_prefix}}-N) if there is one.
|
|
@@ -49,13 +49,13 @@ Create under `spec/changes/<name>/`:
|
|
|
49
49
|
- **design.md** (optional) — for non-trivial changes: approach, alternatives,
|
|
50
50
|
trade-offs.
|
|
51
51
|
- **tasks.md** — ordered, checkable steps. MUST include the mandatory steps
|
|
52
|
-
from `
|
|
52
|
+
from `lawbook/config.yaml` (feature branch first; tests reviewed and run;
|
|
53
53
|
manual verification executed by the agent; docs updated; archive within the
|
|
54
54
|
PR).
|
|
55
55
|
|
|
56
56
|
## Step 4 — Validate
|
|
57
57
|
|
|
58
|
-
Run the `
|
|
58
|
+
Run the `lawbook_validate` tool for the change and fix every issue it reports
|
|
59
59
|
(missing artifacts, non-normative specs, missing scenarios) before handing off
|
|
60
60
|
to implementation.
|
|
61
61
|
|
|
@@ -1,12 +1,12 @@
|
|
|
1
1
|
---
|
|
2
2
|
name: explore
|
|
3
|
-
description: Enter explore mode — a thinking partner for investigating an idea, a problem, or the codebase before or during a change. Use when the user wants to think something through, understand how something works, or clarify requirements before committing to a spec: "help me think through X", "how does X work", "explore X", "I'm not sure how to approach X". Part of speclaw's
|
|
3
|
+
description: Enter explore mode — a thinking partner for investigating an idea, a problem, or the codebase before or during a change. Use when the user wants to think something through, understand how something works, or clarify requirements before committing to a spec: "help me think through X", "how does X work", "explore X", "I'm not sure how to approach X". Part of speclaw's lawbook module.
|
|
4
4
|
---
|
|
5
5
|
|
|
6
6
|
# explore — Think it through
|
|
7
7
|
|
|
8
8
|
A low-commitment mode for investigating ideas and the codebase before (or
|
|
9
|
-
during) a change. Nothing is written to `
|
|
9
|
+
during) a change. Nothing is written to `lawbook/` here — the output is shared
|
|
10
10
|
understanding and a recommended direction.
|
|
11
11
|
|
|
12
12
|
## How to explore
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
---
|
|
2
|
+
name: sync
|
|
3
|
+
description: Promote a change's delta specs into the canonical specs, without archiving. Use when the user wants to update the source-of-truth specs from a change: "sync the specs", "update the canonical specs", "promote the specs". Part of speclaw's lawbook module (draft → build → sync → archive).
|
|
4
|
+
---
|
|
5
|
+
|
|
6
|
+
# sync — Promote delta specs to canonical
|
|
7
|
+
|
|
8
|
+
Update the project's canonical specifications (`lawbook/specs/`) with a change's
|
|
9
|
+
delta specs, without archiving the change. Use this when the specs should
|
|
10
|
+
become the source of truth but the change isn't finished (e.g. multi-PR work).
|
|
11
|
+
|
|
12
|
+
## Steps
|
|
13
|
+
|
|
14
|
+
1. Confirm which change to sync (run `lawbook_list` if unsure).
|
|
15
|
+
2. Run `lawbook_validate` for the change; do not sync a change whose specs are
|
|
16
|
+
invalid.
|
|
17
|
+
3. Run the `lawbook_sync` tool for the change. It copies each
|
|
18
|
+
`lawbook/changes/<name>/specs/<capability>/spec.md` over the canonical
|
|
19
|
+
`lawbook/specs/<capability>/spec.md` and reports what it promoted.
|
|
20
|
+
4. Report the promoted files to the user. The change stays active — `archive`
|
|
21
|
+
it when it's fully done.
|
|
@@ -4,20 +4,20 @@ import path from "node:path";
|
|
|
4
4
|
// (proposals, delta specs, changes, archive) but implemented from scratch and
|
|
5
5
|
// deliberately simpler: a change's specs/ holds the full intended spec for each
|
|
6
6
|
// affected capability, and sync promotes those into the canonical specs/.
|
|
7
|
-
const SPEC_DIR = "
|
|
7
|
+
const SPEC_DIR = "lawbook";
|
|
8
8
|
function specRoot(projectPath) {
|
|
9
9
|
return path.join(projectPath, SPEC_DIR);
|
|
10
10
|
}
|
|
11
11
|
/**
|
|
12
|
-
* Report whether the
|
|
12
|
+
* Report whether the lawbook workspace (lawbook/) has been initialized for a project.
|
|
13
13
|
*
|
|
14
14
|
* @param projectPath - Absolute path to the project root.
|
|
15
|
-
* @returns True if the
|
|
15
|
+
* @returns True if the lawbook/ directory exists.
|
|
16
16
|
*/
|
|
17
17
|
export function specExists(projectPath) {
|
|
18
18
|
return fs.existsSync(specRoot(projectPath));
|
|
19
19
|
}
|
|
20
|
-
const CONFIG_YAML = `# speclaw
|
|
20
|
+
const CONFIG_YAML = `# speclaw lawbook module configuration
|
|
21
21
|
# The spec-driven workflow: draft -> build -> sync -> archive (explore anytime).
|
|
22
22
|
|
|
23
23
|
# Mandatory steps every change's tasks.md must include, in order.
|
|
@@ -27,14 +27,14 @@ mandatory_task_steps:
|
|
|
27
27
|
- "Run the quality gates and verify they pass (see docs/standards/testing-standards.md)."
|
|
28
28
|
- "Perform manual verification of the behavior — the agent executes this itself, never the user."
|
|
29
29
|
- "Update the technical documentation touched by the change."
|
|
30
|
-
- "Archive the change within the same PR (
|
|
30
|
+
- "Archive the change within the same PR (lawbook:archive)."
|
|
31
31
|
|
|
32
32
|
# A change is required for new behavior, endpoints, schema changes, or UI flows;
|
|
33
33
|
# one-line fixes need not have one.
|
|
34
34
|
`;
|
|
35
|
-
const README_MD = `#
|
|
35
|
+
const README_MD = `# lawbook/ — the spec-driven workflow (speclaw)
|
|
36
36
|
|
|
37
|
-
This directory is managed by speclaw's **
|
|
37
|
+
This directory is managed by speclaw's **lawbook** module.
|
|
38
38
|
|
|
39
39
|
- \`specs/\` — the canonical specifications (the current source of truth).
|
|
40
40
|
- \`changes/<name>/\` — an in-flight change: \`proposal.md\`, \`tasks.md\`,
|
|
@@ -44,11 +44,11 @@ This directory is managed by speclaw's **spec** module.
|
|
|
44
44
|
|
|
45
45
|
## Workflow
|
|
46
46
|
|
|
47
|
-
1. \`
|
|
48
|
-
2. \`
|
|
49
|
-
3. \`
|
|
50
|
-
4. \`
|
|
51
|
-
5. \`
|
|
47
|
+
1. \`lawbook:draft\` — describe the change; generates proposal, delta specs, tasks.
|
|
48
|
+
2. \`lawbook:build\` — implement the tasks.
|
|
49
|
+
3. \`lawbook:sync\` — promote the change's delta specs into \`specs/\`.
|
|
50
|
+
4. \`lawbook:archive\` — sync + move the change to \`changes/archive/\`.
|
|
51
|
+
5. \`lawbook:explore\` — think through an idea before or during a change.
|
|
52
52
|
`;
|
|
53
53
|
/**
|
|
54
54
|
* Initialize the spec/ workspace, creating the specs/, changes/, and archive
|
|
@@ -107,7 +107,7 @@ function deltaSpecFiles(changeDir) {
|
|
|
107
107
|
* header, and a "#### Scenario:" acceptance criterion.
|
|
108
108
|
*
|
|
109
109
|
* @param projectPath - Absolute path to the project root.
|
|
110
|
-
* @param change - Change name (folder under
|
|
110
|
+
* @param change - Change name (folder under lawbook/changes/).
|
|
111
111
|
* @returns The validation verdict and the list of issues to fix; never throws
|
|
112
112
|
* for a missing change — it is reported as an issue with `valid: false`.
|
|
113
113
|
*/
|
|
@@ -115,7 +115,7 @@ export function specValidate(projectPath, change) {
|
|
|
115
115
|
const changeDir = path.join(specRoot(projectPath), "changes", change);
|
|
116
116
|
const issues = [];
|
|
117
117
|
if (!fs.existsSync(changeDir)) {
|
|
118
|
-
return { change, valid: false, issues: [`change "${change}" not found under
|
|
118
|
+
return { change, valid: false, issues: [`change "${change}" not found under lawbook/changes/`], deltaSpecs: [] };
|
|
119
119
|
}
|
|
120
120
|
if (!fs.existsSync(path.join(changeDir, "proposal.md")))
|
|
121
121
|
issues.push("missing proposal.md");
|
|
@@ -145,7 +145,7 @@ export function specValidate(projectPath, change) {
|
|
|
145
145
|
* file for each affected capability.
|
|
146
146
|
*
|
|
147
147
|
* @param projectPath - Absolute path to the project root.
|
|
148
|
-
* @param change - Change name (folder under
|
|
148
|
+
* @param change - Change name (folder under lawbook/changes/).
|
|
149
149
|
* @returns The change name and the list of promoted spec paths.
|
|
150
150
|
* @throws If the change directory does not exist.
|
|
151
151
|
*/
|
|
@@ -168,7 +168,7 @@ export function specSync(projectPath, change) {
|
|
|
168
168
|
const dest = path.join(root, "specs", rel);
|
|
169
169
|
fs.mkdirSync(path.dirname(dest), { recursive: true });
|
|
170
170
|
fs.copyFileSync(full, dest);
|
|
171
|
-
promoted.push(path.join("
|
|
171
|
+
promoted.push(path.join("lawbook/specs", rel));
|
|
172
172
|
}
|
|
173
173
|
}
|
|
174
174
|
};
|
|
@@ -180,7 +180,7 @@ export function specSync(projectPath, change) {
|
|
|
180
180
|
* it to changes/archive/<date>-<name>/.
|
|
181
181
|
*
|
|
182
182
|
* @param projectPath - Absolute path to the project root.
|
|
183
|
-
* @param change - Change name (folder under
|
|
183
|
+
* @param change - Change name (folder under lawbook/changes/).
|
|
184
184
|
* @param date - Archive date prefix, formatted YYYY-MM-DD.
|
|
185
185
|
* @returns The promoted specs and the archive destination path.
|
|
186
186
|
* @throws If the change does not exist, or the archive target already exists.
|
|
@@ -13,40 +13,40 @@ const ASSETS = assetsDir(import.meta.url);
|
|
|
13
13
|
export function installWorkflow(projectPath, vars, report) {
|
|
14
14
|
const aiSpecs = path.join(projectPath, "ai-specs");
|
|
15
15
|
copyRendered(path.join(ASSETS, "skills"), path.join(aiSpecs, "skills"), vars, report);
|
|
16
|
-
copyRendered(path.join(ASSETS, "commands"), path.join(aiSpecs, "commands", "
|
|
16
|
+
copyRendered(path.join(ASSETS, "commands"), path.join(aiSpecs, "commands", "lawbook"), vars, report);
|
|
17
17
|
copyRendered(path.join(ASSETS, "rules"), path.join(aiSpecs, "rules"), vars, report);
|
|
18
18
|
}
|
|
19
19
|
// ─── The spec module: speclaw's own spec-driven workflow (no external OpenSpec) ───
|
|
20
20
|
// Mechanical operations behind the draft/build/sync/archive/explore commands.
|
|
21
21
|
/** Register the spec workflow MCP tools (init, list, validate, sync, archive). */
|
|
22
22
|
export function registerSpec(server) {
|
|
23
|
-
server.registerTool("
|
|
24
|
-
description: "Initialize speclaw's spec-driven workflow in a project: creates
|
|
23
|
+
server.registerTool("lawbook_init", {
|
|
24
|
+
description: "Initialize speclaw's spec-driven workflow in a project: creates lawbook/ (specs/, changes/, changes/archive/, config.yaml, README). Idempotent — never overwrites existing files.",
|
|
25
25
|
inputSchema: { projectPath: z.string().describe("Absolute path to the project") },
|
|
26
26
|
}, async ({ projectPath }) => text(specInit(projectPath)));
|
|
27
|
-
server.registerTool("
|
|
27
|
+
server.registerTool("lawbook_list", {
|
|
28
28
|
description: "List the spec workspace: active changes, archived changes, and canonical capabilities under spec/.",
|
|
29
29
|
inputSchema: { projectPath: z.string().describe("Absolute path to the project") },
|
|
30
30
|
}, async ({ projectPath }) => text(specList(projectPath)));
|
|
31
|
-
server.registerTool("
|
|
31
|
+
server.registerTool("lawbook_validate", {
|
|
32
32
|
description: "Validate a change's artifacts: proposal.md and tasks.md present, and delta specs use normative language (SHALL/MUST), '### Requirement:' headers, and '#### Scenario:' acceptance criteria. Returns the issues to fix. Used by the draft/build commands before proceeding.",
|
|
33
33
|
inputSchema: {
|
|
34
34
|
projectPath: z.string().describe("Absolute path to the project"),
|
|
35
|
-
change: z.string().describe("Change name (folder under
|
|
35
|
+
change: z.string().describe("Change name (folder under lawbook/changes/)"),
|
|
36
36
|
},
|
|
37
37
|
}, async ({ projectPath, change }) => text(specValidate(projectPath, change)));
|
|
38
|
-
server.registerTool("
|
|
39
|
-
description: "Promote a change's delta specs into the canonical
|
|
38
|
+
server.registerTool("lawbook_sync", {
|
|
39
|
+
description: "Promote a change's delta specs into the canonical lawbook/specs/ (per capability), without archiving. Backs the `sync` command.",
|
|
40
40
|
inputSchema: {
|
|
41
41
|
projectPath: z.string().describe("Absolute path to the project"),
|
|
42
|
-
change: z.string().describe("Change name (folder under
|
|
42
|
+
change: z.string().describe("Change name (folder under lawbook/changes/)"),
|
|
43
43
|
},
|
|
44
44
|
}, async ({ projectPath, change }) => text(specSync(projectPath, change)));
|
|
45
|
-
server.registerTool("
|
|
46
|
-
description: "Finalize a change: sync its delta specs into
|
|
45
|
+
server.registerTool("lawbook_archive", {
|
|
46
|
+
description: "Finalize a change: sync its delta specs into lawbook/specs/, then move it to lawbook/changes/archive/<date>-<name>/. Backs the `archive` command. Pass today's date as YYYY-MM-DD.",
|
|
47
47
|
inputSchema: {
|
|
48
48
|
projectPath: z.string().describe("Absolute path to the project"),
|
|
49
|
-
change: z.string().describe("Change name (folder under
|
|
49
|
+
change: z.string().describe("Change name (folder under lawbook/changes/)"),
|
|
50
50
|
date: z.string().regex(/^\d{4}-\d{2}-\d{2}$/).describe("Today's date, YYYY-MM-DD"),
|
|
51
51
|
},
|
|
52
52
|
}, async ({ projectPath, change, date }) => text(specArchive(projectPath, change, date)));
|
|
@@ -22,7 +22,7 @@ Output path:
|
|
|
22
22
|
- [`docs/standards/backend-standards.md`](../../docs/standards/backend-standards.md) — layers, docstrings, typing, tests, migrations
|
|
23
23
|
- [`docs/standards/architecture.md`](../../docs/standards/architecture.md) — modules and layer boundaries
|
|
24
24
|
- [`docs/standards/testing-standards.md`](../../docs/standards/testing-standards.md) — quality gates
|
|
25
|
-
- [`docs/standards/
|
|
25
|
+
- [`docs/standards/lawbook.md`](../../docs/standards/lawbook.md) — spec-driven workflow
|
|
26
26
|
- [`docs/standards/base-standards.md`](../../docs/standards/base-standards.md) — cross-cutting rules
|
|
27
27
|
|
|
28
28
|
Use `compass_explore` to locate the real entrypoints, routers, schemas,
|
|
@@ -22,7 +22,7 @@ Output path:
|
|
|
22
22
|
- [`docs/standards/frontend-standards.md`](../../docs/standards/frontend-standards.md) — layers, rendering boundaries, state, i18n, UI
|
|
23
23
|
- [`docs/standards/architecture.md`](../../docs/standards/architecture.md) — modules and layer boundaries
|
|
24
24
|
- [`docs/standards/testing-standards.md`](../../docs/standards/testing-standards.md) — quality gates
|
|
25
|
-
- [`docs/standards/
|
|
25
|
+
- [`docs/standards/lawbook.md`](../../docs/standards/lawbook.md) — spec-driven workflow
|
|
26
26
|
- [`docs/standards/base-standards.md`](../../docs/standards/base-standards.md) — cross-cutting rules
|
|
27
27
|
|
|
28
28
|
Use `compass_explore` to locate the real entry files, feature routes, shared
|
package/dist/server.js
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
|
|
2
2
|
import { StdioServerTransport } from "@modelcontextprotocol/sdk/server/stdio.js";
|
|
3
3
|
import { registerCompass } from "./modules/compass/register.js";
|
|
4
|
-
import { registerSpec } from "./modules/
|
|
4
|
+
import { registerSpec } from "./modules/lawbook/register.js";
|
|
5
5
|
import { registerFoundation } from "./modules/foundation/register.js";
|
|
6
6
|
import { registerTools } from "./modules/tools/register.js";
|
|
7
7
|
/** Build the speclaw MCP server with every module's tools registered. */
|
|
@@ -9,7 +9,7 @@ export function buildServer() {
|
|
|
9
9
|
const server = new McpServer({ name: "speclaw", version: "0.1.0" });
|
|
10
10
|
// Each module contributes its own MCP tools. Adding a module = one line here.
|
|
11
11
|
registerFoundation(server); // init_project, scaffold, configure_agent, doctor
|
|
12
|
-
registerSpec(server); //
|
|
12
|
+
registerSpec(server); // lawbook_init, lawbook_validate, lawbook_sync, lawbook_archive, lawbook_list
|
|
13
13
|
registerCompass(server); // compass_index, explore, search, recall, impact, trace, watch
|
|
14
14
|
registerTools(server); // list_packs, add_pack
|
|
15
15
|
return server;
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@esneiderbravo/speclaw",
|
|
3
|
-
"version": "0.1.
|
|
3
|
+
"version": "0.1.2",
|
|
4
4
|
"publishConfig": {
|
|
5
5
|
"access": "public"
|
|
6
6
|
},
|
|
@@ -17,6 +17,14 @@
|
|
|
17
17
|
],
|
|
18
18
|
"author": "Esneider Bravo",
|
|
19
19
|
"license": "MIT",
|
|
20
|
+
"repository": {
|
|
21
|
+
"type": "git",
|
|
22
|
+
"url": "git+https://github.com/esneiderbravo/speclaw.git"
|
|
23
|
+
},
|
|
24
|
+
"homepage": "https://github.com/esneiderbravo/speclaw#readme",
|
|
25
|
+
"bugs": {
|
|
26
|
+
"url": "https://github.com/esneiderbravo/speclaw/issues"
|
|
27
|
+
},
|
|
20
28
|
"type": "module",
|
|
21
29
|
"bin": {
|
|
22
30
|
"speclaw": "dist/cli/index.js"
|
|
@@ -28,6 +36,7 @@
|
|
|
28
36
|
],
|
|
29
37
|
"scripts": {
|
|
30
38
|
"build": "tsc && node scripts/copy-assets.mjs",
|
|
39
|
+
"brand": "node scripts/render-brand.mjs",
|
|
31
40
|
"start": "node dist/cli/index.js",
|
|
32
41
|
"prepublishOnly": "npm run build"
|
|
33
42
|
},
|
|
@@ -43,6 +52,7 @@
|
|
|
43
52
|
"zod": "^3.23.0"
|
|
44
53
|
},
|
|
45
54
|
"devDependencies": {
|
|
55
|
+
"@resvg/resvg-js": "^2.6.2",
|
|
46
56
|
"@types/node": "^22.0.0",
|
|
47
57
|
"typescript": "^5.6.0"
|
|
48
58
|
}
|
|
@@ -1,21 +0,0 @@
|
|
|
1
|
-
---
|
|
2
|
-
name: sync
|
|
3
|
-
description: Promote a change's delta specs into the canonical specs, without archiving. Use when the user wants to update the source-of-truth specs from a change: "sync the specs", "update the canonical specs", "promote the specs". Part of speclaw's spec module (draft → build → sync → archive).
|
|
4
|
-
---
|
|
5
|
-
|
|
6
|
-
# sync — Promote delta specs to canonical
|
|
7
|
-
|
|
8
|
-
Update the project's canonical specifications (`spec/specs/`) with a change's
|
|
9
|
-
delta specs, without archiving the change. Use this when the specs should
|
|
10
|
-
become the source of truth but the change isn't finished (e.g. multi-PR work).
|
|
11
|
-
|
|
12
|
-
## Steps
|
|
13
|
-
|
|
14
|
-
1. Confirm which change to sync (run `spec_list` if unsure).
|
|
15
|
-
2. Run `spec_validate` for the change; do not sync a change whose specs are
|
|
16
|
-
invalid.
|
|
17
|
-
3. Run the `spec_sync` tool for the change. It copies each
|
|
18
|
-
`spec/changes/<name>/specs/<capability>/spec.md` over the canonical
|
|
19
|
-
`spec/specs/<capability>/spec.md` and reports what it promoted.
|
|
20
|
-
4. Report the promoted files to the user. The change stays active — `archive`
|
|
21
|
-
it when it's fully done.
|
|
File without changes
|