@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.
- package/ATTRIBUTION.md +34 -0
- package/LICENSE +21 -0
- package/README.md +134 -0
- package/dist/cli/commands/agent.js +42 -0
- package/dist/cli/commands/doctor.js +21 -0
- package/dist/cli/commands/index-build.js +24 -0
- package/dist/cli/commands/init.js +121 -0
- package/dist/cli/commands/query.js +69 -0
- package/dist/cli/commands/spec.js +71 -0
- package/dist/cli/index.js +77 -0
- package/dist/cli/lib/args.js +44 -0
- package/dist/cli/lib/ui.js +89 -0
- package/dist/modules/compass/db.js +84 -0
- package/dist/modules/compass/embedder.js +87 -0
- package/dist/modules/compass/extract.js +91 -0
- package/dist/modules/compass/indexer.js +158 -0
- package/dist/modules/compass/languages.js +67 -0
- package/dist/modules/compass/parser.js +37 -0
- package/dist/modules/compass/query.js +260 -0
- package/dist/modules/compass/register.js +72 -0
- package/dist/modules/compass/watcher.js +95 -0
- package/dist/modules/foundation/assets/AGENTS.template.md +61 -0
- package/dist/modules/foundation/assets/CLAUDE.template.md +72 -0
- package/dist/modules/foundation/assets/LAWS.template.md +39 -0
- package/dist/modules/foundation/assets/docs/compass.template.md +43 -0
- package/dist/modules/foundation/assets/docs/standards/architecture.template.md +36 -0
- package/dist/modules/foundation/assets/docs/standards/backend-standards.template.md +50 -0
- package/dist/modules/foundation/assets/docs/standards/base-standards.template.md +47 -0
- package/dist/modules/foundation/assets/docs/standards/conventions.template.md +31 -0
- package/dist/modules/foundation/assets/docs/standards/documentation.template.md +50 -0
- package/dist/modules/foundation/assets/docs/standards/frontend-standards.template.md +46 -0
- package/dist/modules/foundation/assets/docs/standards/spec-workflow.template.md +46 -0
- package/dist/modules/foundation/assets/docs/standards/testing-standards.template.md +34 -0
- package/dist/modules/foundation/doctor.js +107 -0
- package/dist/modules/foundation/register.js +86 -0
- package/dist/modules/foundation/scaffold.js +103 -0
- package/dist/modules/spec/assets/commands/archive.md +10 -0
- package/dist/modules/spec/assets/commands/build.md +11 -0
- package/dist/modules/spec/assets/commands/draft.md +12 -0
- package/dist/modules/spec/assets/commands/explore.md +10 -0
- package/dist/modules/spec/assets/commands/sync.md +9 -0
- package/dist/modules/spec/assets/rules/spec-tasks-mandatory-steps.md +37 -0
- package/dist/modules/spec/assets/skills/archive/SKILL.md +22 -0
- package/dist/modules/spec/assets/skills/build/SKILL.md +49 -0
- package/dist/modules/spec/assets/skills/draft/SKILL.md +64 -0
- package/dist/modules/spec/assets/skills/explore/SKILL.md +28 -0
- package/dist/modules/spec/assets/skills/sync/SKILL.md +21 -0
- package/dist/modules/spec/engine.js +227 -0
- package/dist/modules/spec/register.js +53 -0
- package/dist/modules/tools/assets/packs/agents/backend-developer.md +61 -0
- package/dist/modules/tools/assets/packs/agents/frontend-developer.md +62 -0
- package/dist/modules/tools/assets/packs/agents/product-strategy-analyst.md +56 -0
- package/dist/modules/tools/assets/packs.json +6 -0
- package/dist/modules/tools/packs.js +44 -0
- package/dist/modules/tools/register.js +27 -0
- package/dist/server.js +21 -0
- package/dist/shared/agents.js +94 -0
- package/dist/shared/install.js +66 -0
- package/dist/shared/mcp.js +16 -0
- package/dist/shared/paths.js +10 -0
- package/dist/shared/render.js +21 -0
- package/package.json +49 -0
|
@@ -0,0 +1,62 @@
|
|
|
1
|
+
---
|
|
2
|
+
name: frontend-developer
|
|
3
|
+
description: Use this agent for frontend planning, reviews, and refactors in this repository. Focus on the frontend source tree, shared UI, i18n boundaries, and the build/runtime constraints documented in `AGENTS.md` and `LAWS.md`.
|
|
4
|
+
model: sonnet
|
|
5
|
+
color: cyan
|
|
6
|
+
---
|
|
7
|
+
|
|
8
|
+
You are a senior frontend architect for this project. The stack, architecture,
|
|
9
|
+
and layers are defined in the standards below — read them first; they are the
|
|
10
|
+
source of truth for how this repo is built.
|
|
11
|
+
|
|
12
|
+
## Goal
|
|
13
|
+
|
|
14
|
+
Create a concrete implementation plan only (do not implement code directly).
|
|
15
|
+
|
|
16
|
+
Output path:
|
|
17
|
+
|
|
18
|
+
- `docs/agent_outputs/{feature_name}/frontend.md`
|
|
19
|
+
|
|
20
|
+
## Standards to apply (read before planning)
|
|
21
|
+
|
|
22
|
+
- [`docs/standards/frontend-standards.md`](../../docs/standards/frontend-standards.md) — layers, rendering boundaries, state, i18n, UI
|
|
23
|
+
- [`docs/standards/architecture.md`](../../docs/standards/architecture.md) — modules and layer boundaries
|
|
24
|
+
- [`docs/standards/testing-standards.md`](../../docs/standards/testing-standards.md) — quality gates
|
|
25
|
+
- [`docs/standards/spec-workflow.md`](../../docs/standards/spec-workflow.md) — spec-driven workflow
|
|
26
|
+
- [`docs/standards/base-standards.md`](../../docs/standards/base-standards.md) — cross-cutting rules
|
|
27
|
+
|
|
28
|
+
Use `compass_explore` to locate the real entry files, feature routes, shared
|
|
29
|
+
UI, i18n, and API clients. Repo entry points: `AGENTS.md` and `LAWS.md`.
|
|
30
|
+
|
|
31
|
+
## Planning rules
|
|
32
|
+
|
|
33
|
+
1. Plan with small, ordered steps and explicit file paths.
|
|
34
|
+
2. Preserve the framework's rendering and component-boundary patterns (e.g. server/client component boundaries).
|
|
35
|
+
3. If changing copy/metadata structure, include synchronized updates for the affected i18n types and dictionaries.
|
|
36
|
+
4. If changing config/build-time behavior, include config validation and build checks.
|
|
37
|
+
5. Include the repo's real verification commands in the plan — the quality
|
|
38
|
+
gates defined in [`docs/standards/testing-standards.md`](../../docs/standards/testing-standards.md)
|
|
39
|
+
(plus a build when config/runtime is touched).
|
|
40
|
+
|
|
41
|
+
## Review focus
|
|
42
|
+
|
|
43
|
+
- Rendering/component boundaries and hook usage
|
|
44
|
+
- i18n key/type consistency
|
|
45
|
+
- Route-level UX regressions and loading/error states
|
|
46
|
+
- API contract usage in frontend clients
|
|
47
|
+
- Testability and maintainability
|
|
48
|
+
|
|
49
|
+
## Output format requirements
|
|
50
|
+
|
|
51
|
+
- Include a brief context section
|
|
52
|
+
- Include a numbered implementation sequence
|
|
53
|
+
- Include a testing and verification section
|
|
54
|
+
- End with: created plan file path
|
|
55
|
+
|
|
56
|
+
Example closing line:
|
|
57
|
+
`I've created a plan at docs/agent_outputs/{feature_name}/frontend.md.`
|
|
58
|
+
|
|
59
|
+
## Hard constraints
|
|
60
|
+
|
|
61
|
+
- Do not run implementation edits while acting as this planning agent.
|
|
62
|
+
- If context is missing, request it explicitly before finalizing the plan.
|
|
@@ -0,0 +1,56 @@
|
|
|
1
|
+
---
|
|
2
|
+
name: product-strategy-analyst
|
|
3
|
+
description: Use this agent for product discovery, scope definition, and requirement shaping before implementation in this repository.
|
|
4
|
+
model: opus
|
|
5
|
+
color: pink
|
|
6
|
+
---
|
|
7
|
+
|
|
8
|
+
You are an expert product strategist with deep experience in product ideation, market analysis, and value proposition design. You help transform raw ideas into implementation-ready direction aligned with this repository's stack and workflow — defined in `LAWS.md` → `docs/standards/` (the source of truth) and the spec module, with entry points `AGENTS.md` and `CLAUDE.md`. Consult [`docs/standards/architecture.md`](../../docs/standards/architecture.md) for the module map before shaping scope.
|
|
9
|
+
|
|
10
|
+
Your core responsibilities:
|
|
11
|
+
|
|
12
|
+
1. **Idea Analysis**: When presented with a product idea, you systematically break it down to understand its core essence, potential impact, and feasibility. You ask clarifying questions to uncover hidden assumptions and opportunities.
|
|
13
|
+
|
|
14
|
+
2. **Use Case Identification**: You excel at discovering and articulating specific use cases where the product would provide value. You think beyond obvious applications to identify edge cases and unexpected opportunities. Present use cases in a structured format:
|
|
15
|
+
- Scenario description
|
|
16
|
+
- User pain point addressed
|
|
17
|
+
- How the product solves it
|
|
18
|
+
- Expected outcome
|
|
19
|
+
|
|
20
|
+
3. **Target User Definition**: You create detailed user personas based on:
|
|
21
|
+
- Demographics and psychographics
|
|
22
|
+
- Specific needs and pain points
|
|
23
|
+
- Current alternatives they use
|
|
24
|
+
- Willingness to adopt new solutions
|
|
25
|
+
- Potential user segments ranked by market opportunity
|
|
26
|
+
|
|
27
|
+
4. **Value Proposition Development**: You craft compelling value propositions using frameworks like:
|
|
28
|
+
- Jobs-to-be-Done analysis
|
|
29
|
+
- Value Proposition Canvas
|
|
30
|
+
- Unique selling points vs competitors
|
|
31
|
+
- Clear articulation of benefits over features
|
|
32
|
+
|
|
33
|
+
Your methodology:
|
|
34
|
+
|
|
35
|
+
- Start by asking strategic questions to understand the context and constraints
|
|
36
|
+
- Use structured frameworks (SWOT, Porter's Five Forces, Blue Ocean Strategy) when appropriate
|
|
37
|
+
- Provide concrete examples and analogies to illustrate concepts
|
|
38
|
+
- Identify potential risks and mitigation strategies early
|
|
39
|
+
- Suggest MVP approaches to test core assumptions
|
|
40
|
+
- Consider scalability and business model implications
|
|
41
|
+
|
|
42
|
+
Output format:
|
|
43
|
+
|
|
44
|
+
- Use clear headings and bullet points for readability
|
|
45
|
+
- Provide executive summary for key insights
|
|
46
|
+
- Include actionable next steps
|
|
47
|
+
- Highlight critical assumptions that need validation
|
|
48
|
+
- Suggest metrics for measuring success
|
|
49
|
+
|
|
50
|
+
You maintain a balance between optimistic vision and realistic assessment. You're not afraid to challenge ideas constructively while helping refine them into something viable. Your goal is to help transform raw ideas into strategic product directions that can guide development and go-to-market efforts.
|
|
51
|
+
|
|
52
|
+
When you need more information, ask specific, targeted questions that will help you provide more valuable analysis. Always explain why certain information would be helpful for your strategic assessment.
|
|
53
|
+
|
|
54
|
+
At the end of the process, write conclusions in:
|
|
55
|
+
|
|
56
|
+
- `docs/agent_outputs/{feature_name}/product-strategy.md`
|
|
@@ -0,0 +1,6 @@
|
|
|
1
|
+
{
|
|
2
|
+
"agents": {
|
|
3
|
+
"description": "Subagent definitions rendered with the project profile: backend-developer, frontend-developer, product-strategy-analyst. They read the project's standards (docs/standards/*) for the stack, so they stay accurate as the standards are filled.",
|
|
4
|
+
"path": "agents"
|
|
5
|
+
}
|
|
6
|
+
}
|
|
@@ -0,0 +1,44 @@
|
|
|
1
|
+
import fs from "node:fs";
|
|
2
|
+
import path from "node:path";
|
|
3
|
+
import { assetsDir } from "../../shared/paths.js";
|
|
4
|
+
import { copyRendered } from "../../shared/install.js";
|
|
5
|
+
const ASSETS = assetsDir(import.meta.url);
|
|
6
|
+
/**
|
|
7
|
+
* Load the tool-pack manifest from assets/packs.json.
|
|
8
|
+
*
|
|
9
|
+
* @returns A map of pack name to its {@link PackDef}.
|
|
10
|
+
*/
|
|
11
|
+
export function loadPacks() {
|
|
12
|
+
const raw = fs.readFileSync(path.join(ASSETS, "packs.json"), "utf8");
|
|
13
|
+
return JSON.parse(raw);
|
|
14
|
+
}
|
|
15
|
+
/**
|
|
16
|
+
* Install a pack's assets into a project's ai-specs/. A pack contains either
|
|
17
|
+
* subdirectories (skills/, commands/, rules/) mapped to ai-specs/<sub>, or
|
|
18
|
+
* loose agent .md files at its root mapped to ai-specs/agents/.
|
|
19
|
+
*
|
|
20
|
+
* @param projectPath - Absolute path to the project root.
|
|
21
|
+
* @param name - Pack name to install (key in the manifest).
|
|
22
|
+
* @param vars - Template variables applied while copying the pack's assets.
|
|
23
|
+
* @param report - Mutated in place with the copy results.
|
|
24
|
+
* @throws If no pack with the given name exists in the manifest.
|
|
25
|
+
*/
|
|
26
|
+
export function installPack(projectPath, name, vars, report) {
|
|
27
|
+
const packs = loadPacks();
|
|
28
|
+
const def = packs[name];
|
|
29
|
+
if (!def) {
|
|
30
|
+
throw new Error(`Unknown pack "${name}". Available: ${Object.keys(packs).join(", ")}`);
|
|
31
|
+
}
|
|
32
|
+
const packRoot = path.join(ASSETS, "packs", def.path);
|
|
33
|
+
const aiSpecs = path.join(projectPath, "ai-specs");
|
|
34
|
+
for (const sub of fs.readdirSync(packRoot, { withFileTypes: true })) {
|
|
35
|
+
if (sub.isDirectory()) {
|
|
36
|
+
copyRendered(path.join(packRoot, sub.name), path.join(aiSpecs, sub.name), vars, report);
|
|
37
|
+
}
|
|
38
|
+
else if (sub.name.endsWith(".md")) {
|
|
39
|
+
// loose .md at pack root = agent definitions
|
|
40
|
+
copyRendered(packRoot, path.join(aiSpecs, "agents"), vars, report);
|
|
41
|
+
break;
|
|
42
|
+
}
|
|
43
|
+
}
|
|
44
|
+
}
|
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
import { z } from "zod";
|
|
2
|
+
import { text } from "../../shared/mcp.js";
|
|
3
|
+
import { emptyReport } from "../../shared/install.js";
|
|
4
|
+
import { refreshAgents } from "../../shared/agents.js";
|
|
5
|
+
import { loadPacks, installPack } from "./packs.js";
|
|
6
|
+
export { loadPacks, installPack };
|
|
7
|
+
// ─── The tools module: opt-in packs of skills, agents, and commands ───
|
|
8
|
+
/** Register the tools-module MCP tools (list_packs, add_pack). */
|
|
9
|
+
export function registerTools(server) {
|
|
10
|
+
server.registerTool("list_packs", {
|
|
11
|
+
description: "List the available speclaw skill packs and what each contains.",
|
|
12
|
+
inputSchema: {},
|
|
13
|
+
}, async () => text(loadPacks()));
|
|
14
|
+
server.registerTool("add_pack", {
|
|
15
|
+
description: "Add a single pack to an already-initialized project, then refresh IDE symlinks. Pass template vars (organization, ticket_prefix, team_language, ...) if the pack needs them.",
|
|
16
|
+
inputSchema: {
|
|
17
|
+
projectPath: z.string().describe("Absolute path to the project"),
|
|
18
|
+
pack: z.string().describe("Pack name (see list_packs)"),
|
|
19
|
+
vars: z.record(z.string()).optional().describe("Template variables for rendering"),
|
|
20
|
+
},
|
|
21
|
+
}, async ({ projectPath, pack, vars }) => {
|
|
22
|
+
const report = emptyReport();
|
|
23
|
+
installPack(projectPath, pack, vars ?? {}, report);
|
|
24
|
+
refreshAgents(projectPath, report); // link the new content into already-configured agents
|
|
25
|
+
return text(report);
|
|
26
|
+
});
|
|
27
|
+
}
|
package/dist/server.js
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
|
|
2
|
+
import { StdioServerTransport } from "@modelcontextprotocol/sdk/server/stdio.js";
|
|
3
|
+
import { registerCompass } from "./modules/compass/register.js";
|
|
4
|
+
import { registerSpec } from "./modules/spec/register.js";
|
|
5
|
+
import { registerFoundation } from "./modules/foundation/register.js";
|
|
6
|
+
import { registerTools } from "./modules/tools/register.js";
|
|
7
|
+
/** Build the speclaw MCP server with every module's tools registered. */
|
|
8
|
+
export function buildServer() {
|
|
9
|
+
const server = new McpServer({ name: "speclaw", version: "0.1.0" });
|
|
10
|
+
// Each module contributes its own MCP tools. Adding a module = one line here.
|
|
11
|
+
registerFoundation(server); // init_project, scaffold, configure_agent, doctor
|
|
12
|
+
registerSpec(server); // spec_init, spec_validate, spec_sync, spec_archive, spec_list
|
|
13
|
+
registerCompass(server); // compass_index, explore, search, recall, impact, trace, watch
|
|
14
|
+
registerTools(server); // list_packs, add_pack
|
|
15
|
+
return server;
|
|
16
|
+
}
|
|
17
|
+
/** Start the MCP server over stdio (used by `speclaw mcp`). */
|
|
18
|
+
export async function startMcpServer() {
|
|
19
|
+
const server = buildServer();
|
|
20
|
+
await server.connect(new StdioServerTransport());
|
|
21
|
+
}
|
|
@@ -0,0 +1,94 @@
|
|
|
1
|
+
import fs from "node:fs";
|
|
2
|
+
import path from "node:path";
|
|
3
|
+
/** The agents speclaw can configure. Add one here and the CLI picks it up. */
|
|
4
|
+
export const AGENTS = [
|
|
5
|
+
{ id: "claude", label: "Claude Code", ideDir: ".claude", linkTargets: ["skills", "commands", "agents"], mcpFile: ".mcp.json" },
|
|
6
|
+
{ id: "cursor", label: "Cursor", ideDir: ".cursor", linkTargets: ["skills", "commands", "rules"], mcpFile: ".cursor/mcp.json" },
|
|
7
|
+
{ id: "codex", label: "Codex", ideDir: ".codex", linkTargets: ["skills", "commands"], mcpFile: ".codex/mcp.json" },
|
|
8
|
+
{ id: "windsurf", label: "Windsurf", ideDir: ".windsurf", linkTargets: ["skills", "commands"], mcpFile: ".windsurf/mcp.json" },
|
|
9
|
+
{ id: "agents", label: "Generic (AGENTS.md)", ideDir: ".agents", linkTargets: ["skills", "agents"] },
|
|
10
|
+
];
|
|
11
|
+
/**
|
|
12
|
+
* Look up a known agent definition by its id.
|
|
13
|
+
*
|
|
14
|
+
* @param id - The agent identifier (e.g. `"claude"`, `"cursor"`).
|
|
15
|
+
* @returns The matching {@link AgentDef}, or `undefined` if no agent has that id.
|
|
16
|
+
*/
|
|
17
|
+
export function agentById(id) {
|
|
18
|
+
return AGENTS.find((a) => a.id === id);
|
|
19
|
+
}
|
|
20
|
+
const MCP_ENTRY = { type: "stdio", command: "npx", args: ["-y", "@esneiderbravo/speclaw", "mcp"] };
|
|
21
|
+
/** True when `p` exists as a filesystem entry (including a symlink), without following it. */
|
|
22
|
+
function isSymlink(p) {
|
|
23
|
+
try {
|
|
24
|
+
fs.lstatSync(p);
|
|
25
|
+
return true;
|
|
26
|
+
}
|
|
27
|
+
catch {
|
|
28
|
+
return false;
|
|
29
|
+
}
|
|
30
|
+
}
|
|
31
|
+
/** Register the speclaw stdio MCP server in an agent's config file, merging into any existing config and skipping if already present. */
|
|
32
|
+
function writeMcpConfig(projectPath, mcpFile, report) {
|
|
33
|
+
const mcpPath = path.join(projectPath, mcpFile);
|
|
34
|
+
let config = {};
|
|
35
|
+
if (fs.existsSync(mcpPath))
|
|
36
|
+
config = JSON.parse(fs.readFileSync(mcpPath, "utf8"));
|
|
37
|
+
config.mcpServers = config.mcpServers ?? {};
|
|
38
|
+
if (config.mcpServers["speclaw"]) {
|
|
39
|
+
report.skipped.push(`${mcpPath} (speclaw already registered)`);
|
|
40
|
+
return;
|
|
41
|
+
}
|
|
42
|
+
config.mcpServers["speclaw"] = { ...MCP_ENTRY };
|
|
43
|
+
fs.mkdirSync(path.dirname(mcpPath), { recursive: true });
|
|
44
|
+
fs.writeFileSync(mcpPath, JSON.stringify(config, null, 2) + "\n");
|
|
45
|
+
report.written.push(`${mcpPath} (speclaw MCP server)`);
|
|
46
|
+
}
|
|
47
|
+
/**
|
|
48
|
+
* Configure one agent: create its symlinks into ai-specs and register the MCP server.
|
|
49
|
+
*
|
|
50
|
+
* @param projectPath - Project root to configure.
|
|
51
|
+
* @param agentId - Id of a known agent (see {@link AGENTS}).
|
|
52
|
+
* @param report - Report mutated in place with created symlinks and written/skipped paths.
|
|
53
|
+
* @throws Error if `agentId` does not match any known agent.
|
|
54
|
+
*/
|
|
55
|
+
export function configureAgent(projectPath, agentId, report) {
|
|
56
|
+
const agent = agentById(agentId);
|
|
57
|
+
if (!agent)
|
|
58
|
+
throw new Error(`Unknown agent "${agentId}". Known: ${AGENTS.map((a) => a.id).join(", ")}`);
|
|
59
|
+
const ideRoot = path.join(projectPath, agent.ideDir);
|
|
60
|
+
for (const target of agent.linkTargets) {
|
|
61
|
+
const sourceDir = path.join(projectPath, "ai-specs", target);
|
|
62
|
+
if (!fs.existsSync(sourceDir))
|
|
63
|
+
continue;
|
|
64
|
+
fs.mkdirSync(ideRoot, { recursive: true });
|
|
65
|
+
const linkPath = path.join(ideRoot, target);
|
|
66
|
+
if (fs.existsSync(linkPath) || isSymlink(linkPath)) {
|
|
67
|
+
report.skipped.push(linkPath);
|
|
68
|
+
continue;
|
|
69
|
+
}
|
|
70
|
+
fs.symlinkSync(path.join("..", "ai-specs", target), linkPath);
|
|
71
|
+
report.symlinks.push(`${linkPath} -> ../ai-specs/${target}`);
|
|
72
|
+
}
|
|
73
|
+
if (agent.mcpFile)
|
|
74
|
+
writeMcpConfig(projectPath, agent.mcpFile, report);
|
|
75
|
+
}
|
|
76
|
+
/**
|
|
77
|
+
* List the agents already set up in this project (their IDE dir exists).
|
|
78
|
+
*
|
|
79
|
+
* @param projectPath - Project root to inspect.
|
|
80
|
+
* @returns The ids of agents whose IDE directory is present.
|
|
81
|
+
*/
|
|
82
|
+
export function detectConfiguredAgents(projectPath) {
|
|
83
|
+
return AGENTS.filter((a) => fs.existsSync(path.join(projectPath, a.ideDir))).map((a) => a.id);
|
|
84
|
+
}
|
|
85
|
+
/**
|
|
86
|
+
* Re-run configuration for every already-configured agent (e.g. after adding a pack).
|
|
87
|
+
*
|
|
88
|
+
* @param projectPath - Project root whose configured agents should be refreshed.
|
|
89
|
+
* @param report - Report mutated in place across all refreshed agents.
|
|
90
|
+
*/
|
|
91
|
+
export function refreshAgents(projectPath, report) {
|
|
92
|
+
for (const id of detectConfiguredAgents(projectPath))
|
|
93
|
+
configureAgent(projectPath, id, report);
|
|
94
|
+
}
|
|
@@ -0,0 +1,66 @@
|
|
|
1
|
+
import fs from "node:fs";
|
|
2
|
+
import path from "node:path";
|
|
3
|
+
import { render } from "./render.js";
|
|
4
|
+
/** Create a fresh, empty {@link InstallReport} to accumulate results into. */
|
|
5
|
+
export function emptyReport() {
|
|
6
|
+
return { written: [], skipped: [], symlinks: [], unresolvedVars: [] };
|
|
7
|
+
}
|
|
8
|
+
/**
|
|
9
|
+
* Recursively copy a source tree into a destination, rendering {{var}}
|
|
10
|
+
* placeholders in .md/.mdc files and copying everything else verbatim. Existing
|
|
11
|
+
* destination files are never overwritten (recorded as skipped).
|
|
12
|
+
*
|
|
13
|
+
* @param srcDir - Source directory tree to copy from.
|
|
14
|
+
* @param destDir - Destination directory (created if missing).
|
|
15
|
+
* @param vars - Placeholder values used to render `.md`/`.mdc` files.
|
|
16
|
+
* @param report - Report mutated in place with written, skipped, and unresolved-var entries.
|
|
17
|
+
*/
|
|
18
|
+
export function copyRendered(srcDir, destDir, vars, report) {
|
|
19
|
+
fs.mkdirSync(destDir, { recursive: true });
|
|
20
|
+
for (const entry of fs.readdirSync(srcDir, { withFileTypes: true })) {
|
|
21
|
+
const src = path.join(srcDir, entry.name);
|
|
22
|
+
const dest = path.join(destDir, entry.name);
|
|
23
|
+
if (entry.isDirectory()) {
|
|
24
|
+
copyRendered(src, dest, vars, report);
|
|
25
|
+
continue;
|
|
26
|
+
}
|
|
27
|
+
if (fs.existsSync(dest)) {
|
|
28
|
+
report.skipped.push(dest);
|
|
29
|
+
continue;
|
|
30
|
+
}
|
|
31
|
+
if (entry.name.endsWith(".md") || entry.name.endsWith(".mdc")) {
|
|
32
|
+
const { output, unresolved } = render(fs.readFileSync(src, "utf8"), vars);
|
|
33
|
+
unresolved.forEach((v) => {
|
|
34
|
+
if (!report.unresolvedVars.includes(v))
|
|
35
|
+
report.unresolvedVars.push(v);
|
|
36
|
+
});
|
|
37
|
+
fs.writeFileSync(dest, output);
|
|
38
|
+
}
|
|
39
|
+
else {
|
|
40
|
+
fs.copyFileSync(src, dest);
|
|
41
|
+
}
|
|
42
|
+
report.written.push(dest);
|
|
43
|
+
}
|
|
44
|
+
}
|
|
45
|
+
/**
|
|
46
|
+
* Append an entry to the project's .gitignore if not already present.
|
|
47
|
+
*
|
|
48
|
+
* @param projectPath - Project root containing (or receiving) the `.gitignore`.
|
|
49
|
+
* @param entry - The ignore pattern to ensure is present.
|
|
50
|
+
* @param comment - Comment line written above the entry when it is added.
|
|
51
|
+
* @param report - Report mutated in place; the appended entry is recorded under `written`.
|
|
52
|
+
*/
|
|
53
|
+
export function ensureGitignore(projectPath, entry, comment, report) {
|
|
54
|
+
const gitignorePath = path.join(projectPath, ".gitignore");
|
|
55
|
+
let content = "";
|
|
56
|
+
if (fs.existsSync(gitignorePath)) {
|
|
57
|
+
content = fs.readFileSync(gitignorePath, "utf8");
|
|
58
|
+
if (content.split(/\r?\n/).some((l) => l.trim() === entry))
|
|
59
|
+
return;
|
|
60
|
+
if (!content.endsWith("\n"))
|
|
61
|
+
content += "\n";
|
|
62
|
+
}
|
|
63
|
+
content += `\n# ${comment}\n${entry}\n`;
|
|
64
|
+
fs.writeFileSync(gitignorePath, content);
|
|
65
|
+
report.written.push(`${gitignorePath} (${entry})`);
|
|
66
|
+
}
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Wrap a value as an MCP text tool-result.
|
|
3
|
+
*
|
|
4
|
+
* @param value - Payload to return; strings are emitted verbatim, other values are pretty-printed as JSON.
|
|
5
|
+
* @returns An MCP result object with a single text content block.
|
|
6
|
+
*/
|
|
7
|
+
export function text(value) {
|
|
8
|
+
return {
|
|
9
|
+
content: [
|
|
10
|
+
{
|
|
11
|
+
type: "text",
|
|
12
|
+
text: typeof value === "string" ? value : JSON.stringify(value, null, 2),
|
|
13
|
+
},
|
|
14
|
+
],
|
|
15
|
+
};
|
|
16
|
+
}
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
import { fileURLToPath } from "node:url";
|
|
2
|
+
import path from "node:path";
|
|
3
|
+
/**
|
|
4
|
+
* The `assets/` directory that sits next to a compiled module file. Each module
|
|
5
|
+
* calls this with its own `import.meta.url` to locate its bundled markdown/data
|
|
6
|
+
* (copied into dist/ by the build's copy-assets step).
|
|
7
|
+
*/
|
|
8
|
+
export function assetsDir(importMetaUrl) {
|
|
9
|
+
return path.join(path.dirname(fileURLToPath(importMetaUrl)), "assets");
|
|
10
|
+
}
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
const VAR_RE = /\{\{([a-z_]+)\}\}/g;
|
|
2
|
+
/**
|
|
3
|
+
* Replace {{var}} placeholders with values from `vars`. Unknown placeholders
|
|
4
|
+
* are left untouched and reported so the agent can fill them in later.
|
|
5
|
+
*
|
|
6
|
+
* @param content - Template text containing `{{name}}` placeholders.
|
|
7
|
+
* @param vars - Lookup of placeholder names to values; `undefined` values are treated as unresolved.
|
|
8
|
+
* @returns The rendered output alongside the set of placeholder names that could not be resolved.
|
|
9
|
+
*/
|
|
10
|
+
export function render(content, vars) {
|
|
11
|
+
const unresolved = new Set();
|
|
12
|
+
const output = content.replace(VAR_RE, (match, name) => {
|
|
13
|
+
const value = vars[name];
|
|
14
|
+
if (value === undefined) {
|
|
15
|
+
unresolved.add(name);
|
|
16
|
+
return match;
|
|
17
|
+
}
|
|
18
|
+
return value;
|
|
19
|
+
});
|
|
20
|
+
return { output, unresolved };
|
|
21
|
+
}
|
package/package.json
ADDED
|
@@ -0,0 +1,49 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@esneiderbravo/speclaw",
|
|
3
|
+
"version": "0.1.0",
|
|
4
|
+
"publishConfig": {
|
|
5
|
+
"access": "public"
|
|
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 (Spec), and opt-in skill packs (Tools). No external CLIs, no LLM to install.",
|
|
8
|
+
"keywords": [
|
|
9
|
+
"mcp",
|
|
10
|
+
"mcp-server",
|
|
11
|
+
"spec-driven-development",
|
|
12
|
+
"ai-agents",
|
|
13
|
+
"claude-code",
|
|
14
|
+
"cursor",
|
|
15
|
+
"code-graph",
|
|
16
|
+
"skills"
|
|
17
|
+
],
|
|
18
|
+
"author": "Esneider Bravo",
|
|
19
|
+
"license": "MIT",
|
|
20
|
+
"type": "module",
|
|
21
|
+
"bin": {
|
|
22
|
+
"speclaw": "dist/cli/index.js"
|
|
23
|
+
},
|
|
24
|
+
"main": "dist/server.js",
|
|
25
|
+
"files": [
|
|
26
|
+
"dist",
|
|
27
|
+
"ATTRIBUTION.md"
|
|
28
|
+
],
|
|
29
|
+
"scripts": {
|
|
30
|
+
"build": "tsc && node scripts/copy-assets.mjs",
|
|
31
|
+
"start": "node dist/cli/index.js",
|
|
32
|
+
"prepublishOnly": "npm run build"
|
|
33
|
+
},
|
|
34
|
+
"engines": {
|
|
35
|
+
"node": ">=22"
|
|
36
|
+
},
|
|
37
|
+
"dependencies": {
|
|
38
|
+
"@clack/prompts": "^1.7.0",
|
|
39
|
+
"@modelcontextprotocol/sdk": "^1.0.0",
|
|
40
|
+
"picocolors": "^1.1.1",
|
|
41
|
+
"tree-sitter-wasms": "^0.1.13",
|
|
42
|
+
"web-tree-sitter": "^0.25.10",
|
|
43
|
+
"zod": "^3.23.0"
|
|
44
|
+
},
|
|
45
|
+
"devDependencies": {
|
|
46
|
+
"@types/node": "^22.0.0",
|
|
47
|
+
"typescript": "^5.6.0"
|
|
48
|
+
}
|
|
49
|
+
}
|