@esneiderbravo/speclaw 0.3.4 → 0.3.7

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (60) hide show
  1. package/README.md +66 -15
  2. package/dist/cli/commands/budget.js +36 -0
  3. package/dist/cli/commands/doctor.js +53 -11
  4. package/dist/cli/commands/init.js +3 -1
  5. package/dist/cli/commands/telemetry.js +16 -0
  6. package/dist/cli/commands/update.js +36 -3
  7. package/dist/cli/index.js +19 -4
  8. package/dist/modules/compass/indexer.js +10 -0
  9. package/dist/modules/compass/map.js +114 -0
  10. package/dist/modules/compass/register.js +30 -64
  11. package/dist/modules/foundation/assets/docs/compass.template.md +3 -0
  12. package/dist/modules/foundation/context-budget.js +58 -0
  13. package/dist/modules/foundation/doctor.js +626 -161
  14. package/dist/modules/foundation/graph.js +7 -4
  15. package/dist/modules/foundation/hooks.js +12 -0
  16. package/dist/modules/foundation/laws.js +1 -0
  17. package/dist/modules/foundation/register-core.js +108 -0
  18. package/dist/modules/foundation/register.js +22 -159
  19. package/dist/modules/foundation/scaffold.js +1 -1
  20. package/dist/modules/lawbook/assets/skills/archive/SKILL.md +1 -31
  21. package/dist/modules/lawbook/assets/skills/archive/steps/01-confirm-done.md +7 -0
  22. package/dist/modules/lawbook/assets/skills/archive/steps/02-reconcile.md +15 -0
  23. package/dist/modules/lawbook/assets/skills/archive/steps/03-validate-and-sync.md +7 -0
  24. package/dist/modules/lawbook/assets/skills/archive/steps/04-archive.md +9 -0
  25. package/dist/modules/lawbook/assets/skills/archive/steps/05-report.md +7 -0
  26. package/dist/modules/lawbook/assets/skills/build/SKILL.md +2 -100
  27. package/dist/modules/lawbook/assets/skills/build/steps/01-load-change.md +7 -0
  28. package/dist/modules/lawbook/assets/skills/build/steps/02-branch.md +6 -0
  29. package/dist/modules/lawbook/assets/skills/build/steps/03-implement.md +11 -0
  30. package/dist/modules/lawbook/assets/skills/build/steps/04-quality-gates.md +10 -0
  31. package/dist/modules/lawbook/assets/skills/build/steps/05-manual-verification.md +22 -0
  32. package/dist/modules/lawbook/assets/skills/build/steps/06-discipline-reports.md +44 -0
  33. package/dist/modules/lawbook/assets/skills/build/steps/07-hand-off.md +9 -0
  34. package/dist/modules/lawbook/assets/skills/draft/SKILL.md +3 -80
  35. package/dist/modules/lawbook/assets/skills/draft/steps/01-ensure-workspace.md +5 -0
  36. package/dist/modules/lawbook/assets/skills/draft/steps/02-understand.md +12 -0
  37. package/dist/modules/lawbook/assets/skills/draft/steps/03-name-capabilities.md +14 -0
  38. package/dist/modules/lawbook/assets/skills/draft/steps/04-write-artifacts.md +41 -0
  39. package/dist/modules/lawbook/assets/skills/draft/steps/05-validate.md +11 -0
  40. package/dist/modules/lawbook/assets/skills/draft/steps/06-hand-off.md +5 -0
  41. package/dist/modules/lawbook/assets/skills/explore/SKILL.md +6 -22
  42. package/dist/modules/lawbook/assets/skills/explore/steps/01-investigate.md +16 -0
  43. package/dist/modules/lawbook/assets/skills/explore/steps/02-summarize.md +7 -0
  44. package/dist/modules/lawbook/assets/skills/sync/SKILL.md +1 -30
  45. package/dist/modules/lawbook/assets/skills/sync/steps/01-confirm.md +5 -0
  46. package/dist/modules/lawbook/assets/skills/sync/steps/02-reconcile.md +16 -0
  47. package/dist/modules/lawbook/assets/skills/sync/steps/03-validate.md +6 -0
  48. package/dist/modules/lawbook/assets/skills/sync/steps/04-promote.md +10 -0
  49. package/dist/modules/lawbook/assets/skills/sync/steps/05-report.md +7 -0
  50. package/dist/modules/lawbook/register.js +17 -36
  51. package/dist/modules/tools/register.js +15 -15
  52. package/dist/server.js +13 -7
  53. package/dist/shared/budget.js +159 -0
  54. package/dist/shared/exposure.js +110 -0
  55. package/dist/shared/manifest.js +11 -2
  56. package/dist/shared/mcp.js +33 -0
  57. package/dist/shared/redact.js +90 -0
  58. package/dist/shared/schema-tokens.js +86 -0
  59. package/dist/shared/tokens.js +41 -0
  60. package/package.json +2 -1
@@ -1,78 +1,44 @@
1
1
  import { z } from "zod";
2
- import { text } from "../../shared/mcp.js";
2
+ import { defineTool, text } from "../../shared/mcp.js";
3
+ import { shouldExpose } from "../../shared/exposure.js";
3
4
  import { buildIndex } from "./indexer.js";
4
5
  import { explore, search, recall, impact, trace } from "./query.js";
5
6
  import { startWatch, stopWatch, watchStatus } from "./watcher.js";
6
7
  import { visualize } from "./visualize.js";
7
8
  // ─── Compass: speclaw's own code-intelligence engine (no external deps) ───
8
- // A local graph of the codebase (nodes = definitions, edges = calls/imports)
9
- // plus a local vector store for semantic recall. Lives in .speclaw/ (gitignored).
10
9
  /**
11
- * Register all Compass MCP tools (index, explore, search, recall, impact,
12
- * trace, watch) on the given server.
10
+ * Register Compass MCP tools on the given server.
13
11
  *
14
- * @param server - The MCP server to register the Compass tools on.
12
+ * @param server - The MCP server to register on.
13
+ * @param opts - Exposure options (`minimal` omits setup/specialized tools).
15
14
  */
16
- export function registerCompass(server) {
17
- server.registerTool("compass_index", {
18
- description: "Build or incrementally refresh the Compass — speclaw's local code graph (.speclaw/index.db). Parses TS/JS/Python with tree-sitter into nodes (definitions) and edges (calls/imports), and computes a local vector embedding per node for semantic recall. Files unchanged since the last run are skipped by content hash. Run once after init and whenever you want a fresh graph.",
19
- inputSchema: { projectPath: z.string().describe("Absolute path to the project") },
20
- }, async ({ projectPath }) => text(await buildIndex(projectPath)));
21
- server.registerTool("compass_explore", {
22
- description: "Explore a node in the Compass: returns its verbatim source, location, callees, and resolved callers (blast radius). Use this BEFORE grep/read when locating or understanding code. Requires compass_index to have run.",
23
- inputSchema: {
24
- projectPath: z.string().describe("Absolute path to the project"),
25
- node: z.string().describe("Exact node name to explore (function/class/method/type)"),
26
- },
27
- }, async ({ projectPath, node }) => text(explore(projectPath, node)));
28
- server.registerTool("compass_search", {
29
- description: "Structural search of the Compass: find nodes by name or keyword (substring match). Returns name, kind, and file:line per hit. Cheaper and more structural than grep. Requires compass_index to have run.",
30
- inputSchema: {
31
- projectPath: z.string().describe("Absolute path to the project"),
32
- query: z.string().describe("Name or keyword to search for"),
33
- limit: z.number().optional().describe("Max results (default 25)"),
34
- },
35
- }, async ({ projectPath, query, limit }) => text(search(projectPath, query, limit ?? 25)));
36
- server.registerTool("compass_recall", {
37
- description: "Semantic search of the Compass: describe what you're looking for in natural language ('where auth tokens are validated') and get the nodes ranked by meaning, using the local vector store — even when the identifier names don't contain your words. Requires compass_index to have run.",
38
- inputSchema: {
39
- projectPath: z.string().describe("Absolute path to the project"),
40
- query: z.string().describe("Natural-language description of the code you want"),
41
- limit: z.number().optional().describe("Max results (default 15)"),
42
- },
43
- }, async ({ projectPath, query, limit }) => text(await recall(projectPath, query, limit ?? 15)));
44
- server.registerTool("compass_impact", {
45
- description: "Blast radius: every node that transitively calls the target, up to a depth. Answers 'what could break if I change this?' before editing. Includes dynamic-dispatch callers (matched by name). Requires compass_index.",
46
- inputSchema: {
47
- projectPath: z.string().describe("Absolute path to the project"),
48
- node: z.string().describe("Node name whose dependents you want"),
49
- maxDepth: z.number().optional().describe("Max hops to traverse (default 4)"),
50
- },
51
- }, async ({ projectPath, node, maxDepth }) => text(impact(projectPath, node, maxDepth ?? 4)));
52
- server.registerTool("compass_trace", {
53
- description: "Trace a call path from one node to another: returns the chain of calls linking them (or null if none within depth). Useful to understand how an entrypoint reaches a sink. Requires compass_index.",
54
- inputSchema: {
55
- projectPath: z.string().describe("Absolute path to the project"),
56
- from: z.string().describe("Starting node name"),
57
- to: z.string().describe("Target node name"),
58
- maxDepth: z.number().optional().describe("Max hops to search (default 8)"),
59
- },
15
+ export function registerCompass(server, opts = {}) {
16
+ const minimal = Boolean(opts.minimal);
17
+ const add = (name, description, inputSchema, handler) => {
18
+ if (!shouldExpose(name, minimal))
19
+ return;
20
+ defineTool(server, { name, description, inputSchema, handler });
21
+ };
22
+ add("compass_index", "Build or refresh the local code graph index. Run once per project, then on demand.", { projectPath: z.string() }, async ({ projectPath }) => text(await buildIndex(projectPath)));
23
+ add("compass_explore", "Read a symbol's source plus callers and callees. Prefer this before grep or Read.", { projectPath: z.string(), node: z.string() }, async ({ projectPath, node }) => text(explore(projectPath, node)));
24
+ add("compass_search", "Find symbols by name or keyword (substring). Cheaper structural search than grep.", { projectPath: z.string(), query: z.string(), limit: z.number().optional() }, async ({ projectPath, query, limit }) => text(search(projectPath, query, limit ?? 25)));
25
+ add("compass_recall", "Find symbols by meaning via local embeddings. Use when names are unknown.", { projectPath: z.string(), query: z.string(), limit: z.number().optional() }, async ({ projectPath, query, limit }) => text(await recall(projectPath, query, limit ?? 15)));
26
+ add("compass_impact", "List transitive callers of a symbol (blast radius) before editing.", { projectPath: z.string(), node: z.string(), maxDepth: z.number().optional() }, async ({ projectPath, node, maxDepth }) => text(impact(projectPath, node, maxDepth ?? 4)));
27
+ add("compass_trace", "Find a call path between two symbols within a depth limit.", {
28
+ projectPath: z.string(),
29
+ from: z.string(),
30
+ to: z.string(),
31
+ maxDepth: z.number().optional(),
60
32
  }, 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
- },
33
+ add("compass_visualize", "Write an offline HTML graph to .speclaw/graph.html for interactive exploration.", {
34
+ projectPath: z.string(),
35
+ node: z.string().optional(),
36
+ depth: z.number().optional(),
37
+ limit: z.number().optional(),
69
38
  }, async ({ projectPath, node, depth, limit }) => text(visualize(projectPath, { focus: node, depth, limit })));
70
- server.registerTool("compass_watch", {
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.",
72
- inputSchema: {
73
- projectPath: z.string().describe("Absolute path to the project"),
74
- action: z.enum(["start", "stop", "status"]).describe("start, stop, or status"),
75
- },
39
+ add("compass_watch", "Start, stop, or status a debounced file watcher that re-indexes on change.", {
40
+ projectPath: z.string(),
41
+ action: z.enum(["start", "stop", "status"]),
76
42
  }, async ({ projectPath, action }) => {
77
43
  const result = action === "start"
78
44
  ? startWatch(projectPath)
@@ -42,6 +42,9 @@ a missing graph is not license to skip Compass. The only legitimate fallbacks
42
42
  to Grep/Read: a Compass call returned nothing useful for your query, or the
43
43
  target isn't indexed code (stylesheets, JSON/config, markdown, logs).
44
44
 
45
+ <!-- speclaw:map:start -->
46
+ <!-- speclaw:map:end -->
47
+
45
48
  ## Project-specific starting points
46
49
 
47
50
  <!-- Filled in during speclaw init: the project's real entrypoints, core
@@ -0,0 +1,58 @@
1
+ import { z } from "zod";
2
+ import { registerCompass } from "../compass/register.js";
3
+ import { registerSpec } from "../lawbook/register.js";
4
+ import { registerTools } from "../tools/register.js";
5
+ import { registerFoundationCore } from "./register-core.js";
6
+ import { measureBudget } from "../../shared/budget.js";
7
+ import { isMinimalMode, packageRoot, shouldExpose } from "../../shared/exposure.js";
8
+ /** Mirrors the `doctor` tool surface without importing `doctor.ts` (avoids a cycle). */
9
+ const DOCTOR_TOOL_FOR_BUDGET = {
10
+ name: "doctor",
11
+ description: "Verify the speclaw install; returns a versioned DoctorReport (schemaVersion 1).",
12
+ inputSchema: { projectPath: z.string() },
13
+ };
14
+ /**
15
+ * Collect tool definitions as the MCP server would register them for a profile.
16
+ *
17
+ * Uses `registerFoundationCore` plus a static `doctor` stub so budget/doctor
18
+ * measurement never imports the live `doctor` implementation (module cycle).
19
+ *
20
+ * @param minimal - Exposure profile.
21
+ */
22
+ export function collectRegisteredTools(minimal) {
23
+ const tools = [];
24
+ const server = {
25
+ registerTool(name, config) {
26
+ tools.push({
27
+ name,
28
+ description: config.description ?? "",
29
+ inputSchema: config.inputSchema,
30
+ });
31
+ },
32
+ };
33
+ const opts = { minimal };
34
+ const stub = server;
35
+ registerFoundationCore(stub, opts);
36
+ if (shouldExpose("doctor", minimal)) {
37
+ tools.push(DOCTOR_TOOL_FOR_BUDGET);
38
+ }
39
+ registerSpec(stub, opts);
40
+ registerCompass(stub, opts);
41
+ registerTools(stub, opts);
42
+ return tools;
43
+ }
44
+ /**
45
+ * Measure context budget for an install (registered tools + project files).
46
+ *
47
+ * @param projectPath - Project root.
48
+ * @param minimal - Optional forced profile.
49
+ */
50
+ export function measureInstallBudget(projectPath, minimal) {
51
+ const profile = minimal ?? isMinimalMode(projectPath);
52
+ return measureBudget({
53
+ projectPath,
54
+ packagePath: packageRoot(),
55
+ tools: collectRegisteredTools(profile),
56
+ minimal: profile,
57
+ });
58
+ }