@esneiderbravo/speclaw 0.1.2 → 0.1.4
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/dist/cli/commands/init.js +4 -13
- package/dist/cli/commands/visualize.js +1 -0
- package/dist/modules/compass/visualize.js +20 -3
- package/dist/modules/foundation/assets/docs/standards/base-standards.template.md +10 -5
- package/dist/modules/foundation/assets/docs/standards/conventions.template.md +12 -9
- package/dist/modules/foundation/register.js +4 -6
- package/dist/modules/foundation/scaffold.js +0 -1
- package/dist/modules/lawbook/assets/skills/draft/SKILL.md +1 -1
- package/dist/modules/tools/register.js +1 -1
- package/package.json +2 -2
|
@@ -26,8 +26,7 @@ function detectProjectName(cwd) {
|
|
|
26
26
|
* Interactive setup: pick agents and packs, scaffold, index, and print the handoff prompt.
|
|
27
27
|
*
|
|
28
28
|
* @param flags - Parsed flags; runs interactively on a TTY unless `--agents`, `--yes`, or
|
|
29
|
-
* `-y` is set. Honors `--project-name`, `--
|
|
30
|
-
* and `--no-index`.
|
|
29
|
+
* `-y` is set. Honors `--project-name`, `--packs`, and `--no-index`.
|
|
31
30
|
*/
|
|
32
31
|
export async function runInit(flags) {
|
|
33
32
|
const cwd = process.cwd();
|
|
@@ -35,8 +34,6 @@ export async function runInit(flags) {
|
|
|
35
34
|
const interactive = Boolean(process.stdin.isTTY) && !flags.agents && !flags.yes && !flags.y;
|
|
36
35
|
let agents;
|
|
37
36
|
let packs;
|
|
38
|
-
let ticketPrefix = flags["ticket-prefix"];
|
|
39
|
-
let teamLanguage = flags["team-language"];
|
|
40
37
|
banner();
|
|
41
38
|
if (interactive) {
|
|
42
39
|
const answers = await clack.group({
|
|
@@ -56,13 +53,9 @@ export async function runInit(flags) {
|
|
|
56
53
|
initialValues: ["agents"],
|
|
57
54
|
required: false,
|
|
58
55
|
}),
|
|
59
|
-
ticketPrefix: () => clack.text({ message: "Ticket prefix (optional, e.g. FAR)", defaultValue: "" }),
|
|
60
|
-
teamLanguage: () => clack.text({ message: "Team communication language", defaultValue: "English" }),
|
|
61
56
|
}, { onCancel: () => process.exit(1) });
|
|
62
57
|
agents = answers.agents;
|
|
63
58
|
packs = answers.packs;
|
|
64
|
-
ticketPrefix = answers.ticketPrefix || undefined;
|
|
65
|
-
teamLanguage = answers.teamLanguage || undefined;
|
|
66
59
|
}
|
|
67
60
|
else {
|
|
68
61
|
agents = list(flags.agents).length ? list(flags.agents) : ["claude"];
|
|
@@ -73,11 +66,7 @@ export async function runInit(flags) {
|
|
|
73
66
|
ui.err(`Unknown agent(s): ${unknownAgents.join(", ")}. Known: ${AGENTS.map((a) => a.id).join(", ")}`);
|
|
74
67
|
process.exit(1);
|
|
75
68
|
}
|
|
76
|
-
const profile = {
|
|
77
|
-
project_name: projectName,
|
|
78
|
-
...(ticketPrefix ? { ticket_prefix: ticketPrefix } : {}),
|
|
79
|
-
...(teamLanguage ? { team_language: teamLanguage } : {}),
|
|
80
|
-
};
|
|
69
|
+
const profile = { project_name: projectName };
|
|
81
70
|
// 1. Content + chosen agents, with a check per piece installed
|
|
82
71
|
ui.step(`Setting up ${c.bold(c.cyan(projectName))}`);
|
|
83
72
|
scaffold(cwd, profile, packs, agents);
|
|
@@ -109,6 +98,8 @@ export async function runInit(flags) {
|
|
|
109
98
|
ui.plain();
|
|
110
99
|
console.log(c.cream("Complete speclaw's foundation: analyze this repo and fill LAWS.md and " +
|
|
111
100
|
"docs/standards/* with its real architecture, quality gates and conventions. " +
|
|
101
|
+
"Infer the working language and conventions from the repo itself — docstrings, " +
|
|
102
|
+
"commit messages, branch names, PR and ticket language — don't assume. " +
|
|
112
103
|
"Start with init_project."));
|
|
113
104
|
ui.plain();
|
|
114
105
|
ui.info(`The dev-agents read those standards for your stack — filling them well makes them stack-aware.`);
|
|
@@ -16,6 +16,7 @@ function openInBrowser(file) {
|
|
|
16
16
|
* .speclaw/graph.html and open it.
|
|
17
17
|
*
|
|
18
18
|
* @param flags - `_[0]` optionally focuses on a node; `--depth`, `--limit`, `--no-open`.
|
|
19
|
+
* @throws Exits the process with code 1 if the index is missing or generation fails.
|
|
19
20
|
*/
|
|
20
21
|
export async function runVisualize(flags) {
|
|
21
22
|
const cwd = process.cwd();
|
|
@@ -68,7 +68,15 @@ export function graphData(projectPath, opts = {}) {
|
|
|
68
68
|
db.close();
|
|
69
69
|
}
|
|
70
70
|
}
|
|
71
|
-
/**
|
|
71
|
+
/**
|
|
72
|
+
* Build the graph and write it as an interactive HTML page to
|
|
73
|
+
* `<projectPath>/.speclaw/graph.html` (the gitignored index directory).
|
|
74
|
+
*
|
|
75
|
+
* @param projectPath - Absolute path to the project root.
|
|
76
|
+
* @param opts - Focus/depth/limit controlling which nodes are included.
|
|
77
|
+
* @returns The output path plus the shown/link/total counts for reporting.
|
|
78
|
+
* @throws If no Compass index exists yet.
|
|
79
|
+
*/
|
|
72
80
|
export function visualize(projectPath, opts = {}) {
|
|
73
81
|
const data = graphData(projectPath, opts);
|
|
74
82
|
const dir = path.join(projectPath, ".speclaw");
|
|
@@ -77,7 +85,14 @@ export function visualize(projectPath, opts = {}) {
|
|
|
77
85
|
fs.writeFileSync(out, renderHtml(data));
|
|
78
86
|
return { path: out, shown: data.nodes.length, links: data.links.length, total: data.total };
|
|
79
87
|
}
|
|
80
|
-
/**
|
|
88
|
+
/**
|
|
89
|
+
* Render the graph as a self-contained, offline HTML page: the data is embedded
|
|
90
|
+
* as JSON and drawn by an inline canvas force-directed renderer (no CDN, no
|
|
91
|
+
* dependencies).
|
|
92
|
+
*
|
|
93
|
+
* @param data - The nodes/links/totals to embed.
|
|
94
|
+
* @returns A complete HTML document as a string.
|
|
95
|
+
*/
|
|
81
96
|
export function renderHtml(data) {
|
|
82
97
|
const payload = JSON.stringify(data);
|
|
83
98
|
return `<!doctype html>
|
|
@@ -178,7 +193,7 @@ function draw(){
|
|
|
178
193
|
}
|
|
179
194
|
ctx.globalAlpha=1;
|
|
180
195
|
}
|
|
181
|
-
function loop(){ tick(); draw(); requestAnimationFrame(loop); }
|
|
196
|
+
function loop(){ tick(); draw(); requestAnimationFrame(loop); }
|
|
182
197
|
|
|
183
198
|
// interaction
|
|
184
199
|
let dragging=null, panning=false, lastX=0, lastY=0;
|
|
@@ -199,5 +214,7 @@ addEventListener('mousemove', e=>{
|
|
|
199
214
|
addEventListener('mouseup', ()=>{ dragging=null; panning=false; });
|
|
200
215
|
cv.addEventListener('wheel', e=>{ e.preventDefault(); const f=e.deltaY<0?1.1:0.9;
|
|
201
216
|
const mx=e.clientX, my=e.clientY; ox=mx-(mx-ox)*f; oy=my-(my-oy)*f; scale*=f; }, {passive:false});
|
|
217
|
+
|
|
218
|
+
loop(); // start once every declaration above is initialized (avoids the TDZ on 'dragging')
|
|
202
219
|
</script></body></html>`;
|
|
203
220
|
}
|
|
@@ -5,11 +5,16 @@ of layer or language. This is a law of the project — see [`../../LAWS.md`](../
|
|
|
5
5
|
|
|
6
6
|
## Languages
|
|
7
7
|
|
|
8
|
+
The working language is **inferred from this repo's own conventions** — the
|
|
9
|
+
language already used in docstrings, commit messages, branch names, and PR/ticket
|
|
10
|
+
bodies. Match what the repo does; do not impose a language it doesn't use.
|
|
11
|
+
|
|
8
12
|
- **Code, identifiers, comments, docstrings, commit messages, PR titles/bodies,
|
|
9
|
-
and technical docs**: the repo's artifact language (
|
|
13
|
+
and technical docs**: the repo's artifact language (English unless the repo
|
|
14
|
+
clearly uses another).
|
|
10
15
|
- **User-facing product copy**: as the product requires.
|
|
11
|
-
- **Agent ↔ human communication** (review comments, thread replies
|
|
12
|
-
|
|
16
|
+
- **Agent ↔ human communication** (review comments, thread replies): the same
|
|
17
|
+
language the team already uses in the repo's tickets and PRs. Technical terms
|
|
13
18
|
stay in English within that prose — don't force-translate them.
|
|
14
19
|
|
|
15
20
|
## Commits & branches
|
|
@@ -25,8 +30,8 @@ of layer or language. This is a law of the project — see [`../../LAWS.md`](../
|
|
|
25
30
|
edge cases, "why"). They never narrate history, restate the next line, or
|
|
26
31
|
address the reviewer.
|
|
27
32
|
- **Never** put ticket IDs, ticket text, or changelog narration
|
|
28
|
-
("added for
|
|
29
|
-
|
|
33
|
+
("added for TICKET-123", "fixed as part of…") in code or docstrings.
|
|
34
|
+
Traceability lives in the branch name, PR, and git history.
|
|
30
35
|
|
|
31
36
|
## Dependencies
|
|
32
37
|
|
|
@@ -6,23 +6,26 @@ Naming, branching, PR, and tracking conventions. A law of the project — see
|
|
|
6
6
|
## Branches
|
|
7
7
|
|
|
8
8
|
- Pattern: `{{branch_pattern}}`
|
|
9
|
-
-
|
|
10
|
-
|
|
9
|
+
- Follow the branch/ticket convention this repo already uses (inferred from its
|
|
10
|
+
existing branch names and history) — don't invent a new one.
|
|
11
11
|
|
|
12
12
|
## Pull requests
|
|
13
13
|
|
|
14
14
|
- Title and body in the repo's artifact language, following the PR template.
|
|
15
|
-
-
|
|
16
|
-
|
|
15
|
+
- If the team uses a tracker, the body references its ticket so it links; the
|
|
16
|
+
ticket lives in the PR title/body only — never in the code.
|
|
17
17
|
- CI must be green before requesting review.
|
|
18
18
|
|
|
19
19
|
## Tracker
|
|
20
20
|
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
21
|
+
speclaw does not prescribe a ticket tool — each team configures its own. Follow
|
|
22
|
+
whatever convention this repo already uses (inferred from its branches, PRs, and
|
|
23
|
+
history); if there is none, leave tracker linkage to the team.
|
|
24
|
+
|
|
25
|
+
- New behavior, endpoints, schema changes, or UI flows get a spec change;
|
|
26
|
+
one-line fixes need not.
|
|
27
|
+
- Where a tracker is in use, ticket ↔ PR traceability is expected: closing a
|
|
28
|
+
ticket attaches its PR.
|
|
26
29
|
|
|
27
30
|
## Versioning & releases
|
|
28
31
|
|
|
@@ -16,8 +16,6 @@ const profileShape = {
|
|
|
16
16
|
branch_pattern: z.string().optional().describe("e.g. 'feature/<ticket-id>-<slug>'"),
|
|
17
17
|
commit_style: z.string().optional().describe("e.g. 'conventional commits, imperative, English'"),
|
|
18
18
|
custom_laws: z.string().optional().describe("Extra markdown appended to LAWS.md — project-specific binding rules the analysis surfaced"),
|
|
19
|
-
ticket_prefix: z.string().optional().describe("Ticket prefix in the team's tracker, e.g. 'FAR'"),
|
|
20
|
-
team_language: z.string().optional().describe("The team's working language for posted communication (reviews, replies, tickets), e.g. 'Spanish'. Repo artifacts stay in the repo's own language. Defaults to English."),
|
|
21
19
|
compass_hints: z.string().optional().describe("Markdown bullets with the repo's real entrypoints and common traces, inserted into docs/compass.md"),
|
|
22
20
|
base_standards_extra: z.string().optional().describe("Markdown with any project-specific cross-cutting rules, appended to docs/standards/base-standards.md"),
|
|
23
21
|
modules_table: z.string().optional().describe("Markdown table of the repo's real modules/bounded contexts + one-line responsibility, for docs/standards/architecture.md"),
|
|
@@ -38,9 +36,9 @@ export function registerFoundation(server) {
|
|
|
38
36
|
return text({
|
|
39
37
|
instructions: [
|
|
40
38
|
"1. Analyze the repository at projectPath and fill in every profile field below with REAL values from the codebase (read package.json / pyproject.toml / CI configs / README — do not invent).",
|
|
41
|
-
"2. The foundation is a set of GRANULAR standards under docs/standards/ (base, architecture, backend, frontend, testing, conventions,
|
|
42
|
-
"3. Suggest packs: add stack packs whose 'detect' hints match dependencies you found; offer the rest. Ask the user which packs to install (the
|
|
43
|
-
"4.
|
|
39
|
+
"2. The foundation is a set of GRANULAR standards under docs/standards/ (base, architecture, backend, frontend, testing, conventions, lawbook), bound by LAWS.md and referenced from CLAUDE.md/AGENTS.md. Fill their structured fields from the real repo: modules_table and layering_rules (architecture), backend_layers, frontend_layers, versioning_rules, and any base_standards_extra. Omit a field only when that standard genuinely doesn't apply to this stack.",
|
|
40
|
+
"3. Suggest packs: add stack packs whose 'detect' hints match dependencies you found; offer the rest. Ask the user which packs to install (the lawbook workflow is always installed).",
|
|
41
|
+
"4. Infer the working language and the branch/commit/tracker conventions from the repo itself — the language already used in docstrings, commit messages, branch names, and PR/ticket bodies. Do NOT ask the user or assume English; match what the repo does, and set branch_pattern/commit_style accordingly. speclaw does not prescribe a ticket tool — leave tracker linkage to the team's own convention.",
|
|
44
42
|
"5. Draft any custom_laws (extra binding rules for LAWS.md) from conventions you observed that the standard set doesn't cover.",
|
|
45
43
|
"6. Call the 'scaffold' tool with { projectPath, profile, packs }.",
|
|
46
44
|
"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).",
|
|
@@ -50,7 +48,7 @@ export function registerFoundation(server) {
|
|
|
50
48
|
});
|
|
51
49
|
});
|
|
52
50
|
server.registerTool("scaffold", {
|
|
53
|
-
description: "Write the speclaw setup into a project: the foundation (LAWS.md constitution + granular docs/standards/* + CLAUDE.md + AGENTS.md + docs/compass.md), the
|
|
51
|
+
description: "Write the speclaw setup into a project: the foundation (LAWS.md constitution + granular docs/standards/* + CLAUDE.md + AGENTS.md + docs/compass.md), the lawbook workflow (always), the selected tool packs, multi-IDE symlinks (.claude/.cursor/.codex/.agents), .mcp.json wiring for speclaw, and .gitignore for .speclaw/. Never overwrites existing files. Call init_project first.",
|
|
54
52
|
inputSchema: {
|
|
55
53
|
projectPath: z.string().describe("Absolute path to the project"),
|
|
56
54
|
profile: z.object(profileShape).describe("Project profile gathered by analyzing the repo"),
|
|
@@ -32,7 +32,7 @@ the folder under `lawbook/changes/`.
|
|
|
32
32
|
Create under `lawbook/changes/<name>/`:
|
|
33
33
|
|
|
34
34
|
- **proposal.md** — the why, the what, non-goals, and whether migrations are
|
|
35
|
-
needed. Reference the tracker ticket
|
|
35
|
+
needed. Reference the team's tracker ticket if there is one.
|
|
36
36
|
- **specs/<capability>/spec.md** — the delta spec for each affected capability.
|
|
37
37
|
Use normative language and testable scenarios:
|
|
38
38
|
```markdown
|
|
@@ -12,7 +12,7 @@ export function registerTools(server) {
|
|
|
12
12
|
inputSchema: {},
|
|
13
13
|
}, async () => text(loadPacks()));
|
|
14
14
|
server.registerTool("add_pack", {
|
|
15
|
-
description: "Add a single pack to an already-initialized project, then refresh IDE symlinks. Pass template vars (organization,
|
|
15
|
+
description: "Add a single pack to an already-initialized project, then refresh IDE symlinks. Pass template vars (organization, ...) if the pack needs them.",
|
|
16
16
|
inputSchema: {
|
|
17
17
|
projectPath: z.string().describe("Absolute path to the project"),
|
|
18
18
|
pack: z.string().describe("Pack name (see list_packs)"),
|
package/package.json
CHANGED
|
@@ -1,10 +1,10 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@esneiderbravo/speclaw",
|
|
3
|
-
"version": "0.1.
|
|
3
|
+
"version": "0.1.4",
|
|
4
4
|
"publishConfig": {
|
|
5
5
|
"access": "public"
|
|
6
6
|
},
|
|
7
|
-
"description": "Where specs become law. A self-contained MCP suite that turns any repo into a spec-driven, agent-ready project: its own constitution (foundation), its own local code graph (Compass), its own spec workflow (
|
|
7
|
+
"description": "Where specs become law. A self-contained MCP suite that turns any repo into a spec-driven, agent-ready project: its own constitution (foundation), its own local code graph (Compass), its own spec-driven workflow (Lawbook), and opt-in skill packs (Tools). No external CLIs, no LLM to install.",
|
|
8
8
|
"keywords": [
|
|
9
9
|
"mcp",
|
|
10
10
|
"mcp-server",
|