buildanything 2.0.0 → 2.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.
Files changed (115) hide show
  1. package/.claude-plugin/marketplace.json +1 -1
  2. package/.claude-plugin/plugin.json +9 -1
  3. package/README.md +57 -61
  4. package/agents/a11y-architect.md +2 -0
  5. package/agents/briefing-officer.md +172 -0
  6. package/agents/business-model.md +14 -12
  7. package/agents/code-architect.md +6 -1
  8. package/agents/code-reviewer.md +3 -2
  9. package/agents/code-simplifier.md +12 -4
  10. package/agents/design-brand-guardian.md +19 -0
  11. package/agents/design-critic.md +16 -11
  12. package/agents/design-inclusive-visuals-specialist.md +2 -0
  13. package/agents/design-ui-designer.md +17 -0
  14. package/agents/design-ux-architect.md +15 -0
  15. package/agents/design-ux-researcher.md +102 -7
  16. package/agents/engineering-ai-engineer.md +2 -0
  17. package/agents/engineering-backend-architect.md +2 -0
  18. package/agents/engineering-data-engineer.md +2 -0
  19. package/agents/engineering-devops-automator.md +2 -0
  20. package/agents/engineering-frontend-developer.md +13 -0
  21. package/agents/engineering-mobile-app-builder.md +2 -0
  22. package/agents/engineering-rapid-prototyper.md +15 -2
  23. package/agents/engineering-security-engineer.md +2 -0
  24. package/agents/engineering-senior-developer.md +13 -0
  25. package/agents/engineering-sre.md +2 -0
  26. package/agents/engineering-technical-writer.md +2 -0
  27. package/agents/feature-intel.md +8 -7
  28. package/agents/ios-app-review-guardian.md +2 -0
  29. package/agents/ios-foundation-models-specialist.md +2 -0
  30. package/agents/ios-product-reality-auditor.md +292 -0
  31. package/agents/ios-storekit-specialist.md +2 -0
  32. package/agents/ios-swift-architect.md +1 -0
  33. package/agents/ios-swift-search.md +1 -0
  34. package/agents/ios-swift-ui-design.md +7 -4
  35. package/agents/marketing-app-store-optimizer.md +2 -0
  36. package/agents/planner.md +6 -1
  37. package/agents/pr-test-analyzer.md +3 -2
  38. package/agents/product-feedback-synthesizer.md +62 -0
  39. package/agents/product-owner.md +163 -0
  40. package/agents/product-reality-auditor.md +216 -0
  41. package/agents/product-spec-writer.md +176 -0
  42. package/agents/refactor-cleaner.md +9 -1
  43. package/agents/security-reviewer.md +2 -1
  44. package/agents/silent-failure-hunter.md +2 -1
  45. package/agents/swift-build-resolver.md +2 -0
  46. package/agents/swift-reviewer.md +2 -1
  47. package/agents/tech-feasibility.md +5 -3
  48. package/agents/testing-api-tester.md +2 -0
  49. package/agents/testing-evidence-collector.md +24 -0
  50. package/agents/testing-performance-benchmarker.md +2 -0
  51. package/agents/testing-reality-checker.md +2 -1
  52. package/agents/visual-research.md +7 -5
  53. package/bin/adapters/scribe-tool.ts +4 -2
  54. package/bin/adapters/write-lease-tool.ts +1 -1
  55. package/bin/buildanything-runtime.ts +20 -107
  56. package/bin/graph-index.js +24 -0
  57. package/bin/graph-index.ts +340 -0
  58. package/bin/mcp-servers/graph-mcp.js +26 -0
  59. package/bin/mcp-servers/graph-mcp.ts +481 -0
  60. package/bin/mcp-servers/orchestrator-mcp.js +26 -0
  61. package/bin/mcp-servers/orchestrator-mcp.ts +361 -0
  62. package/bin/setup.js +272 -111
  63. package/commands/build.md +424 -177
  64. package/commands/idea-sweep.md +2 -2
  65. package/commands/setup.md +15 -4
  66. package/commands/ux-review.md +3 -3
  67. package/commands/verify.md +3 -0
  68. package/docs/migration/phase-graph.yaml +573 -157
  69. package/hooks/design-md-lint +4 -0
  70. package/hooks/design-md-lint.ts +295 -0
  71. package/hooks/pre-tool-use.ts +37 -6
  72. package/hooks/record-mode-transitions.ts +63 -6
  73. package/hooks/subagent-start.ts +3 -2
  74. package/package.json +3 -1
  75. package/protocols/agent-prompt-authoring.md +165 -0
  76. package/protocols/architecture-schema.md +10 -3
  77. package/protocols/cleanup.md +4 -0
  78. package/protocols/decision-log.md +8 -4
  79. package/protocols/design-md-authoring.md +520 -0
  80. package/protocols/design-md-spec.md +362 -0
  81. package/protocols/fake-data-detector.md +1 -1
  82. package/protocols/ios-fake-data-detector.md +65 -0
  83. package/protocols/ios-phase-branches.md +128 -43
  84. package/protocols/launch-readiness.md +9 -5
  85. package/protocols/metric-loop.md +1 -1
  86. package/protocols/page-spec-schema.md +234 -0
  87. package/protocols/product-spec-schema.md +354 -0
  88. package/protocols/sprint-tasks-schema.md +53 -0
  89. package/protocols/state-schema.json +38 -3
  90. package/protocols/state-schema.md +32 -2
  91. package/protocols/verify.md +29 -1
  92. package/protocols/web-phase-branches.md +246 -76
  93. package/skills/ios/ios-bootstrap/SKILL.md +1 -1
  94. package/src/graph/ids.ts +86 -0
  95. package/src/graph/index.ts +32 -0
  96. package/src/graph/parser/architecture.ts +603 -0
  97. package/src/graph/parser/component-manifest.ts +268 -0
  98. package/src/graph/parser/decisions-jsonl.ts +407 -0
  99. package/src/graph/parser/design-md-pass2.ts +253 -0
  100. package/src/graph/parser/design-md.ts +477 -0
  101. package/src/graph/parser/page-spec.ts +496 -0
  102. package/src/graph/parser/product-spec.ts +930 -0
  103. package/src/graph/parser/screenshot.ts +342 -0
  104. package/src/graph/parser/sprint-tasks.ts +317 -0
  105. package/src/graph/storage/index.ts +1154 -0
  106. package/src/graph/types.ts +432 -0
  107. package/src/graph/util/dhash.ts +84 -0
  108. package/src/lrr/aggregator.ts +105 -10
  109. package/src/orchestrator/hooks/context-header.ts +34 -10
  110. package/src/orchestrator/hooks/token-accounting.ts +25 -14
  111. package/src/orchestrator/mcp/cycle-counter.ts +2 -1
  112. package/src/orchestrator/mcp/scribe.ts +27 -16
  113. package/src/orchestrator/mcp/write-lease.ts +30 -13
  114. package/src/orchestrator/phase4-shared-context.ts +20 -4
  115. package/protocols/visual-dna.md +0 -185
@@ -0,0 +1,26 @@
1
+ #!/usr/bin/env node
2
+ 'use strict';
3
+
4
+ // Thin shim: delegates to the TypeScript entry via tsx.
5
+ // Mirrors bin/buildanything-runtime.js and the hooks pattern so Claude Code
6
+ // can launch this stdio MCP server without tsx installed globally.
7
+ const { spawn } = require('child_process');
8
+ const path = require('path');
9
+
10
+ const tsEntry = path.join(__dirname, 'graph-mcp.ts');
11
+ const child = spawn('npx', ['--no-install', 'tsx', tsEntry, ...process.argv.slice(2)], {
12
+ stdio: 'inherit',
13
+ });
14
+
15
+ child.on('exit', (code, signal) => {
16
+ if (signal) {
17
+ process.kill(process.pid, signal);
18
+ return;
19
+ }
20
+ process.exit(code ?? 0);
21
+ });
22
+
23
+ child.on('error', (err) => {
24
+ process.stderr.write(`[graph-mcp shim] failed to spawn tsx: ${err.message}\n`);
25
+ process.exit(1);
26
+ });
@@ -0,0 +1,481 @@
1
+ #!/usr/bin/env tsx
2
+ /**
3
+ * Graph stdio MCP server (Slice 1).
4
+ *
5
+ * Exposes the three structured-query tools that Briefing Officers use to
6
+ * pull feature/screen/acceptance slices out of the Slice 1 graph fragment
7
+ * (`.buildanything/graph/slice-1.json`). The fragment is produced at end of
8
+ * Step 1.6 by the product-spec extractor.
9
+ *
10
+ * Tools exposed:
11
+ * graph_query_feature, graph_query_screen, graph_query_acceptance
12
+ *
13
+ * Tool inputs/outputs follow docs/graph/04-slice1-schema.md §5.
14
+ *
15
+ * Fallback contract: when no graph fragment exists (pre-Step-1.6, or extractor
16
+ * failure), each tool returns an isError result whose message starts with
17
+ * "No graph fragment at <path>." — Briefing Officers detect that prefix and
18
+ * fall back to direct reads of docs/plans/product-spec.md.
19
+ */
20
+
21
+ import { z } from "zod";
22
+ import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
23
+ import { StdioServerTransport } from "@modelcontextprotocol/sdk/server/stdio.js";
24
+
25
+ import {
26
+ loadGraph,
27
+ loadAllGraphs,
28
+ queryFeature,
29
+ queryFeatureList,
30
+ queryScreen,
31
+ queryAcceptance,
32
+ queryDna,
33
+ queryManifest,
34
+ queryToken,
35
+ queryDependencies,
36
+ queryCrossContracts,
37
+ queryDecisions,
38
+ queryScreenshot,
39
+ queryScreenshotSimilar,
40
+ queryBrandDrift,
41
+ graphPath,
42
+ } from "../../src/graph/storage/index.js";
43
+ import type { GraphFragment } from "../../src/graph/types.js";
44
+
45
+ // ---------------------------------------------------------------------------
46
+ // Helpers (mirror orchestrator-mcp.ts)
47
+ // ---------------------------------------------------------------------------
48
+
49
+ function okResult(payload: unknown) {
50
+ return {
51
+ content: [{ type: "text" as const, text: JSON.stringify(payload) }],
52
+ };
53
+ }
54
+
55
+ function errResult(toolName: string, err: unknown) {
56
+ const message = err instanceof Error ? err.message : String(err);
57
+ return {
58
+ isError: true,
59
+ content: [
60
+ { type: "text" as const, text: `${toolName} error: ${message}` },
61
+ ],
62
+ };
63
+ }
64
+
65
+ // ---------------------------------------------------------------------------
66
+ // Zod input shapes
67
+ // ---------------------------------------------------------------------------
68
+
69
+ const queryFeatureShape = {
70
+ feature_id: z.string().min(1),
71
+ };
72
+
73
+ const queryScreenShape = {
74
+ screen_id: z.string().min(1),
75
+ full: z.boolean().optional(),
76
+ };
77
+
78
+ const queryAcceptanceShape = {
79
+ feature_id: z.string().min(1),
80
+ };
81
+
82
+ const queryManifestShape = {
83
+ slot: z.string().optional(),
84
+ };
85
+
86
+ const queryTokenShape = {
87
+ name: z.string().min(1),
88
+ };
89
+
90
+ const queryDependenciesShape = {
91
+ feature_id: z.string().min(1),
92
+ };
93
+
94
+ const queryCrossContractsShape = {
95
+ endpoint: z.string().min(1),
96
+ };
97
+
98
+ const queryDecisionsShape = {
99
+ status: z.enum(["open", "triggered", "resolved"]).optional(),
100
+ phase: z.string().optional(),
101
+ decided_by: z.string().optional(),
102
+ };
103
+
104
+ const queryScreenshotShape = {
105
+ id: z.string().min(1),
106
+ };
107
+
108
+ const queryScreenshotSimilarShape = {
109
+ screenshot_id: z.string().min(1),
110
+ threshold: z.number().int().min(0).max(64).optional(),
111
+ };
112
+
113
+ const queryBrandDriftShape = {};
114
+
115
+ // ---------------------------------------------------------------------------
116
+ // Graph load helper
117
+ // ---------------------------------------------------------------------------
118
+
119
+ interface LoadedGraph {
120
+ fragment: GraphFragment;
121
+ path: string;
122
+ }
123
+
124
+ function loadOrError(toolName: string, cwd: string): LoadedGraph | ReturnType<typeof errResult> {
125
+ const path = graphPath(cwd);
126
+ const fragment = loadAllGraphs(cwd);
127
+ if (!fragment) {
128
+ return {
129
+ isError: true as const,
130
+ content: [
131
+ {
132
+ type: "text" as const,
133
+ text: `${toolName} error: No graph fragment at ${path}. Run the indexer at end of Step 1.6 or fall back to direct file reads of product-spec.md.`,
134
+ },
135
+ ],
136
+ };
137
+ }
138
+ return { fragment, path };
139
+ }
140
+
141
+ function isErrPayload(x: unknown): x is ReturnType<typeof errResult> {
142
+ return typeof x === "object" && x !== null && "isError" in x && (x as { isError: unknown }).isError === true;
143
+ }
144
+
145
+ // ---------------------------------------------------------------------------
146
+ // Server registration
147
+ // ---------------------------------------------------------------------------
148
+
149
+ function registerTools(server: McpServer): void {
150
+ server.registerTool(
151
+ "graph_query_feature",
152
+ {
153
+ description:
154
+ "Returns full structured spec slice for one feature (screens, states, transitions, business rules, failure modes, persona constraints, acceptance criteria, depends_on), sourced from product-spec.md via the Slice 1 indexer.",
155
+ inputSchema: queryFeatureShape,
156
+ },
157
+ async ({ feature_id }) => {
158
+ try {
159
+ const loaded = loadOrError("graph_query_feature", process.cwd());
160
+ if (isErrPayload(loaded)) return loaded;
161
+ const result = queryFeature(loaded.fragment, feature_id);
162
+ if (!result) {
163
+ return errResult(
164
+ "graph_query_feature",
165
+ `feature_id "${feature_id}" not found in graph at ${loaded.path}`,
166
+ );
167
+ }
168
+ return okResult(result);
169
+ } catch (err) {
170
+ return errResult("graph_query_feature", err);
171
+ }
172
+ },
173
+ );
174
+
175
+ server.registerTool(
176
+ "graph_list_features",
177
+ {
178
+ description:
179
+ "Returns all feature IDs, labels, and kebab anchors from the indexed product-spec. No arguments. Used by the orchestrator at Step 5.2 to enumerate features for Track B dispatch.",
180
+ inputSchema: {},
181
+ },
182
+ async () => {
183
+ try {
184
+ const loaded = loadOrError("graph_list_features", process.cwd());
185
+ if (isErrPayload(loaded)) return loaded;
186
+ const result = queryFeatureList(loaded.fragment);
187
+ return okResult(result);
188
+ } catch (err) {
189
+ return errResult("graph_list_features", err);
190
+ }
191
+ },
192
+ );
193
+
194
+ server.registerTool(
195
+ "graph_query_screen",
196
+ {
197
+ description:
198
+ "Returns one screen inventory row plus its owning features and the states from those features that surface here, sourced from product-spec.md via the Slice 1 indexer. Set full: true to receive Slice 3 enrichment (wireframe text + sections + states + component uses with manifest joined inline + key copy + tokens used).",
199
+ inputSchema: queryScreenShape,
200
+ },
201
+ async ({ screen_id, full }) => {
202
+ try {
203
+ const loaded = loadOrError("graph_query_screen", process.cwd());
204
+ if (isErrPayload(loaded)) return loaded;
205
+ const result = full
206
+ ? queryScreen(loaded.fragment, screen_id, { full: true })
207
+ : queryScreen(loaded.fragment, screen_id);
208
+ if (!result) {
209
+ return errResult(
210
+ "graph_query_screen",
211
+ `screen_id "${screen_id}" not found in graph at ${loaded.path}`,
212
+ );
213
+ }
214
+ return okResult(result);
215
+ } catch (err) {
216
+ return errResult("graph_query_screen", err);
217
+ }
218
+ },
219
+ );
220
+
221
+ server.registerTool(
222
+ "graph_query_acceptance",
223
+ {
224
+ description:
225
+ "Returns the acceptance criteria, in-scope business rules, and persona constraints for one feature, sourced from product-spec.md via the Slice 1 indexer.",
226
+ inputSchema: queryAcceptanceShape,
227
+ },
228
+ async ({ feature_id }) => {
229
+ try {
230
+ const loaded = loadOrError("graph_query_acceptance", process.cwd());
231
+ if (isErrPayload(loaded)) return loaded;
232
+ const result = queryAcceptance(loaded.fragment, feature_id);
233
+ if (!result) {
234
+ return errResult(
235
+ "graph_query_acceptance",
236
+ `feature_id "${feature_id}" not found in graph at ${loaded.path}`,
237
+ );
238
+ }
239
+ return okResult(result);
240
+ } catch (err) {
241
+ return errResult("graph_query_acceptance", err);
242
+ }
243
+ },
244
+ );
245
+
246
+ server.registerTool(
247
+ "graph_query_dna",
248
+ {
249
+ description:
250
+ "Returns the 7-axis Visual DNA card + Do's/Don'ts + references + lint status. Sourced from DESIGN.md via the Slice 2 indexer (Step 3.0).",
251
+ inputSchema: {},
252
+ },
253
+ async () => {
254
+ try {
255
+ const loaded = loadOrError("graph_query_dna", process.cwd());
256
+ if (isErrPayload(loaded)) return loaded;
257
+ const result = queryDna(loaded.fragment, process.cwd());
258
+ if (!result) {
259
+ return errResult(
260
+ "graph_query_dna",
261
+ `DESIGN.md not yet indexed at ${process.cwd()}/.buildanything/graph/. Run the indexer at end of Step 3.0 or fall back to direct file read of DESIGN.md.`,
262
+ );
263
+ }
264
+ return okResult(result);
265
+ } catch (err) {
266
+ return errResult("graph_query_dna", err);
267
+ }
268
+ },
269
+ );
270
+
271
+ server.registerTool(
272
+ "graph_query_manifest",
273
+ {
274
+ description:
275
+ "Returns component manifest entries (slot → library + variant + HARD-GATE flag). Optional slot arg returns single entry; without slot returns all. Sourced from component-manifest.md via the Slice 2 indexer (Step 3.2).",
276
+ inputSchema: queryManifestShape,
277
+ },
278
+ async ({ slot }) => {
279
+ try {
280
+ const loaded = loadOrError("graph_query_manifest", process.cwd());
281
+ if (isErrPayload(loaded)) return loaded;
282
+ const result = queryManifest(loaded.fragment, slot);
283
+ if (result === null) {
284
+ return errResult(
285
+ "graph_query_manifest",
286
+ "component-manifest.md not yet indexed. Run the indexer at end of Step 3.2 or fall back to direct file read.",
287
+ );
288
+ }
289
+ return okResult(result);
290
+ } catch (err) {
291
+ return errResult("graph_query_manifest", err);
292
+ }
293
+ },
294
+ );
295
+
296
+ server.registerTool(
297
+ "graph_query_token",
298
+ {
299
+ description:
300
+ "Returns a design token by exact dot-path name (e.g. 'colors.primary'). Sourced from DESIGN.md Pass 2 via the Slice 3 indexer (Step 3.4).",
301
+ inputSchema: queryTokenShape,
302
+ },
303
+ async ({ name }) => {
304
+ try {
305
+ const loaded = loadOrError("graph_query_token", process.cwd());
306
+ if (isErrPayload(loaded)) return loaded;
307
+ const result = queryToken(loaded.fragment, name);
308
+ if (!result) {
309
+ return errResult(
310
+ "graph_query_token",
311
+ `Token '${name}' not found. Either DESIGN.md Pass 2 has not been indexed at ${loaded.path}, or the token name doesn't exist in the design system.`,
312
+ );
313
+ }
314
+ return okResult(result);
315
+ } catch (err) {
316
+ return errResult("graph_query_token", err);
317
+ }
318
+ },
319
+ );
320
+
321
+ server.registerTool(
322
+ "graph_query_dependencies",
323
+ {
324
+ description:
325
+ "Returns the full dependency closure for one feature: API endpoints provided + consumed (with module + auth_required), feature-level depends_on / depended_on_by, and the per-feature task DAG (each task with size, depends_on, behavioral_test, assigned_phase). Sourced from architecture.md + sprint-tasks.md via the Slice 4 indexer (Steps 2.3.1, 2.3.2). Primary call for the Product Owner's wave grouping.",
326
+ inputSchema: queryDependenciesShape,
327
+ },
328
+ async ({ feature_id }) => {
329
+ try {
330
+ const loaded = loadOrError("graph_query_dependencies", process.cwd());
331
+ if (isErrPayload(loaded)) return loaded;
332
+ const result = queryDependencies(loaded.fragment, feature_id);
333
+ if (!result) {
334
+ return errResult(
335
+ "graph_query_dependencies",
336
+ `Feature '${feature_id}' not found, OR Slice 4 (architecture/sprint-tasks) not yet indexed at ${process.cwd()}/.buildanything/graph/.`,
337
+ );
338
+ }
339
+ return okResult(result);
340
+ } catch (err) {
341
+ return errResult("graph_query_dependencies", err);
342
+ }
343
+ },
344
+ );
345
+
346
+ server.registerTool(
347
+ "graph_query_cross_contracts",
348
+ {
349
+ description:
350
+ "Returns the contract definition (request_schema + response_schema as verbatim JSON-string blobs, auth_required, error_codes), the providing feature, and all consumer features for one endpoint. Sourced from architecture.md via the Slice 4 indexer (Step 2.3.1). Used by Phase 4 implementers for pre-write contract verification.",
351
+ inputSchema: queryCrossContractsShape,
352
+ },
353
+ async ({ endpoint }) => {
354
+ try {
355
+ const loaded = loadOrError("graph_query_cross_contracts", process.cwd());
356
+ if (isErrPayload(loaded)) return loaded;
357
+ const result = queryCrossContracts(loaded.fragment, endpoint);
358
+ if (!result) {
359
+ return errResult(
360
+ "graph_query_cross_contracts",
361
+ `Endpoint '${endpoint}' not found in indexed architecture.`,
362
+ );
363
+ }
364
+ return okResult(result);
365
+ } catch (err) {
366
+ return errResult("graph_query_cross_contracts", err);
367
+ }
368
+ },
369
+ );
370
+
371
+ server.registerTool(
372
+ "graph_query_decisions",
373
+ {
374
+ description:
375
+ "Returns decision rows filtered by optional status / phase / decided_by (AND-combined). Each result includes resolved related_decision and superseded_by walks. Sourced from decisions.jsonl via the Slice 4 indexer. Returns an empty array (NOT an error) when no decisions match — empty input returns every decision. Used by the LRR aggregator's backward-routing walk and by the feedback synthesizer's open-decision lookup.",
376
+ inputSchema: queryDecisionsShape,
377
+ },
378
+ async ({ status, phase, decided_by }) => {
379
+ try {
380
+ const loaded = loadOrError("graph_query_decisions", process.cwd());
381
+ if (isErrPayload(loaded)) return loaded;
382
+ const filter: { status?: "open" | "triggered" | "resolved"; phase?: string; decided_by?: string } = {};
383
+ if (status !== undefined) filter.status = status;
384
+ if (phase !== undefined) filter.phase = phase;
385
+ if (decided_by !== undefined) filter.decided_by = decided_by;
386
+ const result = queryDecisions(loaded.fragment, filter);
387
+ return okResult(result);
388
+ } catch (err) {
389
+ return errResult("graph_query_decisions", err);
390
+ }
391
+ },
392
+ );
393
+
394
+ server.registerTool(
395
+ "graph_query_screenshot",
396
+ {
397
+ description:
398
+ "Returns full screenshot node fields (image_path, image_class, caption, perceptual_hash, dominant_palette, image_dimensions, dna_axis_tags) plus the linked screen, linked finding, and any brand_drift_observations that reference this screenshot. Sourced from screenshots indexed via the Slice 5 indexer (Steps 3.1.idx, 5.1.idx, 5.3.idx). Used by the feedback synthesizer to resolve dogfood findings to owning screen/feature/task via deterministic graph walk.",
399
+ inputSchema: queryScreenshotShape,
400
+ },
401
+ async ({ id }) => {
402
+ try {
403
+ const loaded = loadOrError("graph_query_screenshot", process.cwd());
404
+ if (isErrPayload(loaded)) return loaded;
405
+ const result = queryScreenshot(loaded.fragment, id);
406
+ if (!result) {
407
+ return errResult(
408
+ "graph_query_screenshot",
409
+ `Screenshot '${id}' not found in graph at ${loaded.path}.`,
410
+ );
411
+ }
412
+ return okResult(result);
413
+ } catch (err) {
414
+ return errResult("graph_query_screenshot", err);
415
+ }
416
+ },
417
+ );
418
+
419
+ server.registerTool(
420
+ "graph_query_similar",
421
+ {
422
+ description:
423
+ "Returns screenshots whose perceptual_hash falls within the Hamming distance threshold (default 10) of the given screenshot. Output: { input_hash, matches: [{ screenshot_id, image_path, image_class, hamming_distance, dna_axis_tags }] } sorted by ascending distance. Sourced from the Slice 5 indexer. Used by Brand Guardian (Phase 5.1, drift mode) to find matching design references for a production screenshot.",
424
+ inputSchema: queryScreenshotSimilarShape,
425
+ },
426
+ async ({ screenshot_id, threshold }) => {
427
+ try {
428
+ const loaded = loadOrError("graph_query_similar", process.cwd());
429
+ if (isErrPayload(loaded)) return loaded;
430
+ const result = queryScreenshotSimilar(loaded.fragment, screenshot_id, threshold);
431
+ if (!result) {
432
+ return errResult(
433
+ "graph_query_similar",
434
+ `Screenshot '${screenshot_id}' not found in graph at ${loaded.path}.`,
435
+ );
436
+ }
437
+ return okResult(result);
438
+ } catch (err) {
439
+ return errResult("graph_query_similar", err);
440
+ }
441
+ },
442
+ );
443
+
444
+ server.registerTool(
445
+ "graph_query_brand_drift",
446
+ {
447
+ description:
448
+ "Returns all brand_drift_observation nodes with verdicts, sorted by descending score, with prod_screenshot and reference_screenshot resolved inline. Sourced from the Slice 5 indexer (Step 5.1.idx). Returns an empty observations array (NOT an error) when none have been recorded yet. Used by the Phase 6 LRR Brand chapter aggregator to render drift findings backed by graph walks rather than prose grep.",
449
+ inputSchema: queryBrandDriftShape,
450
+ },
451
+ async () => {
452
+ try {
453
+ const loaded = loadOrError("graph_query_brand_drift", process.cwd());
454
+ if (isErrPayload(loaded)) return loaded;
455
+ const result = queryBrandDrift(loaded.fragment);
456
+ return okResult(result);
457
+ } catch (err) {
458
+ return errResult("graph_query_brand_drift", err);
459
+ }
460
+ },
461
+ );
462
+ }
463
+
464
+ async function main(): Promise<void> {
465
+ const server = new McpServer(
466
+ { name: "buildanything-graph", version: "0.1.0" },
467
+ { capabilities: { tools: {} } },
468
+ );
469
+
470
+ registerTools(server);
471
+
472
+ const transport = new StdioServerTransport();
473
+ await server.connect(transport);
474
+ }
475
+
476
+ main().catch((err) => {
477
+ process.stderr.write(
478
+ `[graph-mcp] fatal: ${err instanceof Error ? (err.stack ?? err.message) : String(err)}\n`,
479
+ );
480
+ process.exit(1);
481
+ });
@@ -0,0 +1,26 @@
1
+ #!/usr/bin/env node
2
+ 'use strict';
3
+
4
+ // Thin shim: delegates to the TypeScript entry via tsx.
5
+ // Mirrors bin/buildanything-runtime.js and the hooks pattern so Claude Code
6
+ // can launch this stdio MCP server without tsx installed globally.
7
+ const { spawn } = require('child_process');
8
+ const path = require('path');
9
+
10
+ const tsEntry = path.join(__dirname, 'orchestrator-mcp.ts');
11
+ const child = spawn('npx', ['--no-install', 'tsx', tsEntry, ...process.argv.slice(2)], {
12
+ stdio: 'inherit',
13
+ });
14
+
15
+ child.on('exit', (code, signal) => {
16
+ if (signal) {
17
+ process.kill(process.pid, signal);
18
+ return;
19
+ }
20
+ process.exit(code ?? 0);
21
+ });
22
+
23
+ child.on('error', (err) => {
24
+ process.stderr.write(`[orchestrator-mcp shim] failed to spawn tsx: ${err.message}\n`);
25
+ process.exit(1);
26
+ });