@calibrate-ds/cli 0.1.25 → 0.1.27

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 (43) hide show
  1. package/dist/commands/apply.d.ts.map +1 -1
  2. package/dist/commands/apply.js +4 -2
  3. package/dist/commands/apply.js.map +1 -1
  4. package/dist/commands/diff.d.ts.map +1 -1
  5. package/dist/commands/diff.js +26 -26
  6. package/dist/commands/diff.js.map +1 -1
  7. package/dist/commands/document.d.ts.map +1 -1
  8. package/dist/commands/document.js +2 -1
  9. package/dist/commands/document.js.map +1 -1
  10. package/dist/commands/generate-components.d.ts.map +1 -1
  11. package/dist/commands/generate-components.js +11 -5
  12. package/dist/commands/generate-components.js.map +1 -1
  13. package/dist/commands/implement.d.ts.map +1 -1
  14. package/dist/commands/implement.js +2 -1
  15. package/dist/commands/implement.js.map +1 -1
  16. package/dist/commands/prompt.js +2 -1
  17. package/dist/commands/prompt.js.map +1 -1
  18. package/dist/commands/scan.d.ts.map +1 -1
  19. package/dist/commands/scan.js +3 -13
  20. package/dist/commands/scan.js.map +1 -1
  21. package/dist/mcp/tools/action-tools.d.ts.map +1 -1
  22. package/dist/mcp/tools/action-tools.js +189 -217
  23. package/dist/mcp/tools/action-tools.js.map +1 -1
  24. package/dist/mcp/tools/component-tools.d.ts.map +1 -1
  25. package/dist/mcp/tools/component-tools.js +84 -29
  26. package/dist/mcp/tools/component-tools.js.map +1 -1
  27. package/dist/mcp/tools/governance-tools.d.ts.map +1 -1
  28. package/dist/mcp/tools/governance-tools.js +70 -184
  29. package/dist/mcp/tools/governance-tools.js.map +1 -1
  30. package/dist/mcp/tools/info-tools.d.ts.map +1 -1
  31. package/dist/mcp/tools/info-tools.js +44 -172
  32. package/dist/mcp/tools/info-tools.js.map +1 -1
  33. package/dist/mcp/tools/token-tools.d.ts.map +1 -1
  34. package/dist/mcp/tools/token-tools.js +71 -59
  35. package/dist/mcp/tools/token-tools.js.map +1 -1
  36. package/dist/mcp/tools/verify-tool.d.ts.map +1 -1
  37. package/dist/mcp/tools/verify-tool.js +0 -37
  38. package/dist/mcp/tools/verify-tool.js.map +1 -1
  39. package/dist/utils/managed-components.d.ts +14 -0
  40. package/dist/utils/managed-components.d.ts.map +1 -0
  41. package/dist/utils/managed-components.js +23 -0
  42. package/dist/utils/managed-components.js.map +1 -0
  43. package/package.json +5 -5
@@ -1 +1 @@
1
- {"version":3,"file":"scan.d.ts","sourceRoot":"","sources":["../../src/commands/scan.ts"],"names":[],"mappings":"AA+BA,wBAAsB,WAAW,kBAoHhC"}
1
+ {"version":3,"file":"scan.d.ts","sourceRoot":"","sources":["../../src/commands/scan.ts"],"names":[],"mappings":"AAyBA,wBAAsB,WAAW,kBA+GhC"}
@@ -1,12 +1,7 @@
1
1
  import { loadConfig, resolveTokenSource, exportAllComponentContexts, loadManifest, resolveDSPaths, enrichIconsWithSvg, enrichBitmapAssets, enrichComponentThumbnails, getDesignToolAdapter, saveSnapshot, generateSystemClaude } from "@calibrate-ds/core";
2
2
  import * as fs from "node:fs/promises";
3
3
  import * as path from "node:path";
4
- import * as readline from "node:readline";
5
4
  import { logger } from "../utils/logger.js";
6
- function ask(question) {
7
- const rl = readline.createInterface({ input: process.stdin, output: process.stdout });
8
- return new Promise(resolve => rl.question(question, ans => { rl.close(); resolve(ans.trim()); }));
9
- }
10
5
  /**
11
6
  * Counts layout-tree nodes that received an `imageAssetPath` tag from
12
7
  * enrichBitmapAssets. Used purely for the post-scan summary line — the
@@ -34,7 +29,8 @@ export async function scanCommand() {
34
29
  logger.info("Loading ptb.config.json...");
35
30
  const config = await loadConfig();
36
31
  const { provider, fileKey, accessTokenEnv } = config.designTool;
37
- // Warn if there are unimplemented/stale components — scanning will overwrite the diff
32
+ // Inform (don't block) if there are pending components — scanning accumulates changes,
33
+ // it does not reset the diff baseline. Stamps are preserved across scans.
38
34
  if (process.stdout.isTTY) {
39
35
  const { workspaceRoot } = resolveDSPaths(config);
40
36
  const lockPath = path.join(workspaceRoot, "ptb.lock");
@@ -42,13 +38,7 @@ export async function scanCommand() {
42
38
  const lock = JSON.parse(await fs.readFile(lockPath, "utf-8"));
43
39
  const pending = Object.values(lock.components).filter(e => !e.stampedHash || e.stampedHash !== e.designHash);
44
40
  if (pending.length > 0) {
45
- logger.warn(`You have ${pending.length} component(s) with unimplemented design changes.`);
46
- logger.dim(" Scanning now will overwrite the current diff baseline.");
47
- const answer = await ask(" Continue scanning? (Y/n) ");
48
- if (answer.toLowerCase() === "n") {
49
- logger.info("Scan cancelled. Run 'ptb diff' to review pending changes.");
50
- return;
51
- }
41
+ logger.info(`${pending.length} component(s) with pending changes — scanning will accumulate, not reset.`);
52
42
  }
53
43
  }
54
44
  catch { /* no ptb.lock yet — first scan, proceed */ }
@@ -1 +1 @@
1
- {"version":3,"file":"scan.js","sourceRoot":"","sources":["../../src/commands/scan.ts"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"scan.js","sourceRoot":"","sources":["../../src/commands/scan.ts"],"names":[],"mappings":"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"}
@@ -1 +1 @@
1
- {"version":3,"file":"action-tools.d.ts","sourceRoot":"","sources":["../../../src/mcp/tools/action-tools.ts"],"names":[],"mappings":"AAgCA,OAAO,KAAK,EAAE,YAAY,EAAc,MAAM,gBAAgB,CAAC;AAqC/D,eAAO,MAAM,WAAW,EAAE,YAmyBzB,CAAC"}
1
+ {"version":3,"file":"action-tools.d.ts","sourceRoot":"","sources":["../../../src/mcp/tools/action-tools.ts"],"names":[],"mappings":"AAiCA,OAAO,KAAK,EAAE,YAAY,EAAc,MAAM,gBAAgB,CAAC;AAqC/D,eAAO,MAAM,WAAW,EAAE,YAwuBzB,CAAC"}
@@ -4,6 +4,7 @@ import { execSync } from "node:child_process";
4
4
  import { z } from "zod";
5
5
  import { getDesignToolAdapter, saveSnapshot, exportAllComponentContexts, loadManifest, updateComponentArtifacts, loadConfig, getGenerator, syncManifestWithReport, createNamingStrategy, resolveDSPaths, isIconComponent, normalizeIconDisplayName, getComponentContext, generateComponentStories, generateComponentPrompt, generateComponentDoc, buildReverseDepsMap, formatMetadataHeader, pruneStaleComponents, resolveTokenSource, enrichIconsWithSvg, enrichBitmapAssets, enrichComponentThumbnails, } from "@calibrate-ds/core";
6
6
  import { invalidateCache, resolveCwd } from "../server.js";
7
+ import { getManagedSnapshot } from "../../utils/managed-components.js";
7
8
  function toSlug(name) {
8
9
  return name.toLowerCase().trim().replace(/[^a-z0-9]+/g, "-").replace(/^-+|-+$/g, "");
9
10
  }
@@ -42,136 +43,144 @@ async function saveLock(workspaceRoot, lock) {
42
43
  export const actionTools = {
43
44
  name: "action",
44
45
  register(server, getContext) {
45
- server.tool("run_scan", "Scan the Figma design file and update the local snapshot. Equivalent to running 'ptb scan' in the terminal. Requires FIGMA_ACCESS_TOKEN to be set in the environment.", async () => {
46
- // Load config independently — no snapshot required (this tool creates the snapshot).
47
- let config;
48
- try {
49
- const cwd = await resolveCwd();
50
- config = await loadConfig(cwd);
51
- }
52
- catch (e) {
53
- return { content: [{ type: "text", text: e.message }] };
54
- }
55
- // Check token first so the error points at the real problem.
56
- const tokenEnv = config.designTool.accessTokenEnv;
57
- const token = process.env[tokenEnv];
58
- if (!token) {
59
- return {
60
- content: [{
61
- type: "text",
62
- text: `${tokenEnv} is not set in the MCP server environment.\n\nThe MCP server is a separate process from your terminal — it does not inherit your shell exports.\n\nFix options:\n 1. Add to ~/.zshrc or ~/.bashrc: export ${tokenEnv}=your_token\n Then launch your IDE from the terminal (not the dock): e.g. 'cursor .' or 'code .'\n 2. Mac only (dock-launched IDEs): run in terminal: launchctl setenv ${tokenEnv} your_token\n Then fully quit and relaunch your IDE.\n NOTE: If you already ran launchctl setenv, the MCP server may have started before the token was set. Fully quitting and relaunching your IDE is required — the running server cannot see tokens set after it started.\n 3. Alternatively, run 'ptb scan' directly in your terminal where the token is already exported.`,
63
- }],
64
- };
65
- }
66
- try {
67
- const fileKey = config.designTool.fileKey;
68
- const adapter = getDesignToolAdapter(config.designTool.provider);
69
- const model = await adapter.scanDesignFile({ fileKey, accessToken: token });
70
- // Mirror the full enrichment pipeline from scan.ts so MCP scans
71
- // produce the same snapshot as CLI scans (tokens, icons, bitmaps, thumbnails).
72
- const tokenResolution = await resolveTokenSource(config, token);
73
- if (tokenResolution.tokens.length > 0) {
74
- model.tokens = tokenResolution.tokens;
46
+ server.tool("run", `Execute a PTB pipeline stage. Use this to scan Figma, generate code, or maintain the local design system state.
47
+
48
+ PIPELINE ORDER — run stages in this sequence after first setup:
49
+ 1. scan → fetch Figma snapshot, enrich tokens/icons/thumbnails, auto-exports context
50
+ 2. tokens → regenerate CSS design token files from snapshot
51
+ 3. generate scaffold typed React component shells (accepts optional name param)
52
+ 4. context → re-export context files + update ptb.lock without re-scanning Figma
53
+ 5. prune → remove generated folders for components deleted from Figma
54
+
55
+ WHEN TO USE EACH:
56
+ scan — Figma file has changed or this is the first run. Requires FIGMA_ACCESS_TOKEN (or configured env var) in env.
57
+ tokens — After scan, or whenever token values changed in Figma.
58
+ generate After scan, or to scaffold a specific new component (use name param).
59
+ context — After implementing components to refresh ptb.lock hashes; or after scan if you skipped it.
60
+ prune — Components were deleted in Figma and their folders should be removed from the repo.`, {
61
+ stage: z.enum(["scan", "generate", "tokens", "context", "prune"])
62
+ .describe("Pipeline stage to execute"),
63
+ name: z.string().optional().default(".")
64
+ .describe("For stage:'generate' only — component name or '.' for all. Ignored by other stages."),
65
+ }, async ({ stage, name = "." }) => {
66
+ if (stage === "scan") {
67
+ let config;
68
+ try {
69
+ const cwd = await resolveCwd();
70
+ config = await loadConfig(cwd);
75
71
  }
76
- await enrichIconsWithSvg(model, fileKey, token, adapter);
77
- await enrichBitmapAssets(model, fileKey, token, config);
78
- await enrichComponentThumbnails(model, fileKey, token, config, adapter);
79
- await saveSnapshot(model, config);
80
- invalidateCache();
81
- const manifest = await loadManifest(config).catch(() => ({ components: {} }));
82
- await exportAllComponentContexts(model, config, manifest);
83
- return { content: [{ type: "text", text: `Scan complete. ${model.components.length} components, ${model.tokens?.length ?? 0} token collections.` }] };
84
- }
85
- catch (e) {
86
- return { content: [{ type: "text", text: `Scan failed: ${e.message}` }] };
87
- }
88
- });
89
- server.tool("generate_components", "Regenerate typed React component shells from the latest snapshot. Equivalent to 'ptb generate-components'. Pass a component name or '.' for all.", { name: z.string().default(".").describe("Component name or '.' for all") }, async ({ name }) => {
90
- let ctx;
91
- try {
92
- ctx = await getContext();
93
- }
94
- catch (e) {
95
- return { content: [{ type: "text", text: e.message }] };
96
- }
97
- try {
98
- const { snapshot, config } = ctx;
99
- const targets = name === "." || name === "*"
100
- ? snapshot.components
101
- : snapshot.components.filter(c => c.name.toLowerCase() === name.toLowerCase() || toSlug(c.name) === toSlug(name));
102
- if (targets.length === 0) {
103
- return { content: [{ type: "text", text: `No component found matching '${name}'.` }] };
72
+ catch (e) {
73
+ return { content: [{ type: "text", text: e.message }] };
104
74
  }
105
- const generator = getGenerator(config.framework.name || "react");
106
- const report = await generator.writeComponents(snapshot, config);
107
- await syncManifestWithReport(config, report);
108
- // Update Symbol Bridge
109
- await updateBridge(targets, config);
110
- const totalFiles = report.createdFiles.length + report.skippedFiles.length;
111
- const lines = [
112
- `Generated ${report.createdFiles.length} new files, skipped ${report.skippedFiles.length} (already exist). Total: ${totalFiles} files across ${targets.length} components.`,
113
- ``,
114
- `These are scaffold shells — prop signatures and CSS stubs, not full implementations.`,
115
- `After you implement each component, run stamp_component to record it:`,
116
- ` • One component: stamp_component { name: "ComponentName" }`,
117
- ` • All at once: stamp_component { name: "." }`,
118
- ``,
119
- `Until stamped, coverage_summary will show them as unstamped and ptb status --fail-on-stale will flag them.`,
120
- ];
121
- return { content: [{ type: "text", text: lines.join("\n") }] };
122
- }
123
- catch (e) {
124
- return { content: [{ type: "text", text: `Generation failed: ${e.message}` }] };
125
- }
126
- });
127
- server.tool("generate_tokens", "Regenerate CSS design token files from the latest snapshot. Equivalent to 'ptb generate-tokens'.", async () => {
128
- let ctx;
129
- try {
130
- ctx = await getContext();
131
- }
132
- catch (e) {
133
- return { content: [{ type: "text", text: e.message }] };
134
- }
135
- try {
136
- const { snapshot, config } = ctx;
137
- if (!snapshot.tokens?.length) {
138
- return { content: [{ type: "text", text: "No token collections in snapshot. Run run_scan first." }] };
75
+ const tokenEnv = config.designTool.accessTokenEnv;
76
+ const token = process.env[tokenEnv];
77
+ if (!token) {
78
+ return {
79
+ content: [{
80
+ type: "text",
81
+ text: `${tokenEnv} is not set in the MCP server environment.\n\nThe MCP server is a separate process from your terminal — it does not inherit your shell exports.\n\nFix options:\n 1. Add to ~/.zshrc or ~/.bashrc: export ${tokenEnv}=your_token\n Then launch your IDE from the terminal (not the dock): e.g. 'cursor .' or 'code .'\n 2. Mac only (dock-launched IDEs): run in terminal: launchctl setenv ${tokenEnv} your_token\n Then fully quit and relaunch your IDE.\n NOTE: If you already ran launchctl setenv, the MCP server may have started before the token was set. Fully quitting and relaunching your IDE is required — the running server cannot see tokens set after it started.\n 3. Alternatively, run 'ptb scan' directly in your terminal where the token is already exported.`,
82
+ }],
83
+ };
84
+ }
85
+ try {
86
+ const fileKey = config.designTool.fileKey;
87
+ const adapter = getDesignToolAdapter(config.designTool.provider);
88
+ const model = await adapter.scanDesignFile({ fileKey, accessToken: token });
89
+ const tokenResolution = await resolveTokenSource(config, token);
90
+ if (tokenResolution.tokens.length > 0)
91
+ model.tokens = tokenResolution.tokens;
92
+ await enrichIconsWithSvg(model, fileKey, token, adapter);
93
+ await enrichBitmapAssets(model, fileKey, token, config);
94
+ await enrichComponentThumbnails(model, fileKey, token, config, adapter);
95
+ await saveSnapshot(model, config);
96
+ invalidateCache();
97
+ const manifest = await loadManifest(config).catch(() => ({ components: {} }));
98
+ await exportAllComponentContexts(model, config, manifest);
99
+ return { content: [{ type: "text", text: `Scan complete. ${model.components.length} components, ${model.tokens?.length ?? 0} token collections.` }] };
100
+ }
101
+ catch (e) {
102
+ return { content: [{ type: "text", text: `Scan failed: ${e.message}` }] };
139
103
  }
140
- const generator = getGenerator(config.framework.name || "react");
141
- const report = await generator.writeTokens(snapshot, config);
142
- const totalTokenFiles = report.createdFiles.length + report.skippedFiles.length;
143
- return { content: [{ type: "text", text: `Generated ${report.createdFiles.length} new token files, skipped ${report.skippedFiles.length} (already exist). Total: ${totalTokenFiles} token files on disk.` }] };
144
- }
145
- catch (e) {
146
- return { content: [{ type: "text", text: `Token generation failed: ${e.message}` }] };
147
- }
148
- });
149
- server.tool("export_context", "Export component context files to .ptb/context/ and update ptb.lock. Equivalent to 'ptb export context'. Run after a scan to make context available to AI tools.", async () => {
150
- let ctx;
151
- try {
152
- ctx = await getContext();
153
104
  }
154
- catch (e) {
155
- return { content: [{ type: "text", text: e.message }] };
105
+ if (stage === "generate") {
106
+ let ctx;
107
+ try {
108
+ ctx = await getContext();
109
+ }
110
+ catch (e) {
111
+ return { content: [{ type: "text", text: e.message }] };
112
+ }
113
+ try {
114
+ const { snapshot, config } = ctx;
115
+ const managedSnapshot = getManagedSnapshot(snapshot, config);
116
+ const targets = name === "." || name === "*"
117
+ ? managedSnapshot.components
118
+ : managedSnapshot.components.filter(c => c.name.toLowerCase() === name.toLowerCase() || toSlug(c.name) === toSlug(name));
119
+ if (targets.length === 0) {
120
+ return { content: [{ type: "text", text: `No component found matching '${name}'.` }] };
121
+ }
122
+ const generator = getGenerator(config.framework.name || "react");
123
+ const report = await generator.writeComponents(managedSnapshot, config);
124
+ await syncManifestWithReport(config, report);
125
+ await updateBridge(targets, config);
126
+ const totalFiles = report.createdFiles.length + report.skippedFiles.length;
127
+ const lines = [
128
+ `Generated ${report.createdFiles.length} new files, skipped ${report.skippedFiles.length} (already exist). Total: ${totalFiles} files across ${targets.length} components.`,
129
+ ``,
130
+ `These are scaffold shells — prop signatures and CSS stubs, not full implementations.`,
131
+ `After you implement each component, call submit_work to record it as done:`,
132
+ ` • submit_work({ name: "ComponentName", folder: "src/components/..." })`,
133
+ ` • Bulk stamp (CLI only): ptb stamp component .`,
134
+ ``,
135
+ `Until stamped, get_status will show them as not-stamped and ptb status --fail-on-stale will flag them.`,
136
+ ];
137
+ return { content: [{ type: "text", text: lines.join("\n") }] };
138
+ }
139
+ catch (e) {
140
+ return { content: [{ type: "text", text: `Generation failed: ${e.message}` }] };
141
+ }
156
142
  }
157
- try {
158
- const { snapshot, config } = ctx;
159
- const manifest = await loadManifest(config).catch(() => ({ components: {} }));
160
- const paths = await exportAllComponentContexts(snapshot, config, manifest);
161
- return { content: [{ type: "text", text: `Exported ${paths.length} context files to .ptb/context/.` }] };
143
+ if (stage === "tokens") {
144
+ let ctx;
145
+ try {
146
+ ctx = await getContext();
147
+ }
148
+ catch (e) {
149
+ return { content: [{ type: "text", text: e.message }] };
150
+ }
151
+ try {
152
+ const { snapshot, config } = ctx;
153
+ if (!snapshot.tokens?.length) {
154
+ return { content: [{ type: "text", text: "No token collections in snapshot. Run run({ stage:'scan' }) first." }] };
155
+ }
156
+ const generator = getGenerator(config.framework.name || "react");
157
+ const report = await generator.writeTokens(snapshot, config);
158
+ const totalTokenFiles = report.createdFiles.length + report.skippedFiles.length;
159
+ return { content: [{ type: "text", text: `Generated ${report.createdFiles.length} new token files, skipped ${report.skippedFiles.length} (already exist). Total: ${totalTokenFiles} token files on disk.` }] };
160
+ }
161
+ catch (e) {
162
+ return { content: [{ type: "text", text: `Token generation failed: ${e.message}` }] };
163
+ }
162
164
  }
163
- catch (e) {
164
- return { content: [{ type: "text", text: `Export failed: ${e.message}` }] };
165
+ if (stage === "context") {
166
+ let ctx;
167
+ try {
168
+ ctx = await getContext();
169
+ }
170
+ catch (e) {
171
+ return { content: [{ type: "text", text: e.message }] };
172
+ }
173
+ try {
174
+ const { snapshot, config } = ctx;
175
+ const manifest = await loadManifest(config).catch(() => ({ components: {} }));
176
+ const paths = await exportAllComponentContexts(snapshot, config, manifest);
177
+ return { content: [{ type: "text", text: `Exported ${paths.length} context files to .ptb/context/.` }] };
178
+ }
179
+ catch (e) {
180
+ return { content: [{ type: "text", text: `Export failed: ${e.message}` }] };
181
+ }
165
182
  }
166
- });
167
- server.tool("stamp_component", `Mark a component as implemented against the current design. Equivalent to 'ptb stamp component <name>'. Use '.' to stamp all components.
168
-
169
- Optionally pass folder + artifacts to register the component's files in manifest.json — this is required for the ptb dev playground to pick up the component automatically. If ptb dev is running, the playground will hot-reload as soon as the manifest is updated.`, {
170
- name: z.string().describe("Component name or '.' for all"),
171
- message: z.string().optional().describe("Optional note about what was implemented"),
172
- folder: z.string().optional().describe("Relative path from packageRoot to the component folder, e.g. 'src/components/web/btn-web'. Required for playground auto-registration."),
173
- artifacts: z.array(z.string()).optional().describe("List of artifact paths relative to packageRoot, e.g. ['src/components/web/btn-web/BtnWeb.tsx', ...]. Defaults to [folder] if omitted."),
174
- }, async ({ name, message, folder, artifacts }) => {
183
+ // stage === "prune"
175
184
  let ctx;
176
185
  try {
177
186
  ctx = await getContext();
@@ -180,61 +189,19 @@ Optionally pass folder + artifacts to register the component's files in manifest
180
189
  return { content: [{ type: "text", text: e.message }] };
181
190
  }
182
191
  try {
183
- const lock = await loadLock(ctx.workspaceRoot);
184
- if (!lock) {
185
- return { content: [{ type: "text", text: "No ptb.lock found. Run export_context first." }] };
186
- }
187
- const identity = getGitIdentity();
188
- const now = new Date().toISOString();
189
- const stamped = [];
190
- const entries = name === "." || name === "*"
191
- ? Object.entries(lock.components)
192
- : Object.entries(lock.components).filter(([slug, e]) => slug === toSlug(name) || e.displayName.toLowerCase() === name.toLowerCase());
193
- if (entries.length === 0) {
194
- return { content: [{ type: "text", text: `No component found in ptb.lock matching '${name}'.` }] };
195
- }
196
- for (const [slug, entry] of entries) {
197
- // Build stampedContract from the live snapshot so diff is
198
- // scan-timing-independent (same logic as CLI stampInLock).
199
- const modelComponent = ctx.snapshot.components.find(c => c.name.toLowerCase() === entry.displayName.toLowerCase() ||
200
- c.name.toLowerCase().replace(/[^a-z0-9]+/g, "-").replace(/^-+|-+$/g, "") === slug);
201
- const stampedContract = modelComponent ? {
202
- ...(modelComponent.variantAxes ? { variantAxes: modelComponent.variantAxes } : {}),
203
- ...(modelComponent.properties ? { properties: modelComponent.properties } : {}),
204
- ...(modelComponent.tokenBindings ? { tokenBindings: modelComponent.tokenBindings } : {}),
205
- ...(modelComponent.styleMetadata ? { styleMetadata: modelComponent.styleMetadata } : {}),
206
- ...(modelComponent.layout ? { layout: modelComponent.layout } : {}),
207
- } : undefined;
208
- lock.components[slug] = {
209
- ...entry,
210
- stampedHash: entry.designHash,
211
- stampedAt: now,
212
- stampedBy: identity,
213
- workStatus: "stamped",
214
- ...(message ? { stampedMessage: message } : {}),
215
- ...(stampedContract ? { stampedContract } : {}),
216
- };
217
- stamped.push(entry.displayName);
218
- }
219
- await saveLock(ctx.workspaceRoot, lock);
220
- // If the caller provided file paths, register them in manifest.json.
221
- // This triggers the ptb dev playground watcher so the component appears
222
- // in the playground without a manual `ptb dev` restart.
223
- const manifestNotes = [];
224
- if (folder && entries.length === 1) {
225
- const [, entry] = entries[0];
226
- const comp = ctx.snapshot.components.find((c) => c.name === entry.displayName);
227
- if (comp) {
228
- const resolvedArtifacts = artifacts ?? [folder];
229
- await updateComponentArtifacts(ctx.config, comp.id, comp.name, folder, resolvedArtifacts, ctx.config.framework?.name ?? "react");
230
- manifestNotes.push(`Registered in manifest.json → folder: ${folder}`);
231
- }
192
+ const result = await pruneStaleComponents(ctx.config, ctx.snapshot);
193
+ if (result.found === 0) {
194
+ return { content: [{ type: "text", text: "No stale artifacts to prune." }] };
232
195
  }
233
- const detail = manifestNotes.length > 0 ? `\n${manifestNotes.join("\n")}` : "";
234
- return { content: [{ type: "text", text: `Stamped ${stamped.length} component(s): ${stamped.join(", ")}${detail}` }] };
196
+ return {
197
+ content: [{
198
+ type: "text",
199
+ text: JSON.stringify({ found: result.found, deleted: result.deleted, skipped: result.skipped, errors: result.errors }, null, 2),
200
+ }],
201
+ };
235
202
  }
236
203
  catch (e) {
237
- return { content: [{ type: "text", text: `Stamp failed: ${e.message}` }] };
204
+ return { content: [{ type: "text", text: `Prune failed: ${e.message}` }] };
238
205
  }
239
206
  });
240
207
  server.tool("assign_component", "Assign a component to a developer. Stores assignment metadata (priority, due date, notes, acceptance criteria) in ptb.lock.", {
@@ -301,7 +268,7 @@ TOKEN AUTHORITY — what to trust, what to verify:
301
268
  DIVISION OF LABOR WITH FIGMA MCP (composite workflow — most accurate):
302
269
  PTB → token names, dependency graph, variant axes, state/content contracts (read contextFile)
303
270
  Figma → exact hex values, spacing, visual arrangement (get_variable_defs, get_screenshot)
304
- Map: Figma hex → get_token_by_value(hex) → --ptb-* cssVar name → emit both together
271
+ Map: Figma hex → get_token(hex) → --ptb-* cssVar name → emit both together
305
272
 
306
273
  TYPICAL WORKFLOW:
307
274
  1. Call start_work("<name>") — read blockedBy and tokenConflicts first
@@ -433,18 +400,24 @@ TYPICAL WORKFLOW:
433
400
  return { content: [{ type: "text", text: `start_work failed: ${e.message}` }] };
434
401
  }
435
402
  });
436
- server.tool("submit_work", `Submit completed implementation work for a component.
403
+ server.tool("submit_work", `Submit completed implementation work for a component. Use after writing all component files.
437
404
 
438
- Stamps the component (records the current design hash as implemented) and transitions
439
- its work lifecycle. Use after writing all component files.
405
+ Stamps the component (records the current design hash + design contract snapshot) and
406
+ transitions its work lifecycle. This is the canonical "I'm done" signal — always call
407
+ this after implement_component and writing the code.
440
408
 
441
409
  transitionTo options:
442
410
  - "stamped" (default): stamps immediately — for solo work or when you're the reviewer
443
- - "in_review": marks as ready for review without stamping — for teams where a reviewer stamps later`, {
411
+ - "in_review": marks as ready for review without stamping — for teams where a reviewer stamps later
412
+
413
+ folder + artifacts: register the component's files in manifest.json so the ptb dev
414
+ playground picks it up automatically (hot-reloads on save if ptb dev is running).`, {
444
415
  name: z.string().describe("Component name to submit"),
445
- transitionTo: z.enum(["stamped", "in_review"]).optional().describe("Target status after submitting. Default: 'stamped'"),
416
+ transitionTo: z.enum(["stamped", "in_review"]).optional().describe("Target status. Default: 'stamped'"),
446
417
  message: z.string().optional().describe("Optional implementation note"),
447
- }, async ({ name, transitionTo = "stamped", message }) => {
418
+ folder: z.string().optional().describe("Relative path from packageRoot to the component folder, e.g. 'src/components/web/btn-web'. Required for playground auto-registration."),
419
+ artifacts: z.array(z.string()).optional().describe("Artifact paths relative to packageRoot, e.g. ['src/components/web/btn-web/BtnWeb.tsx']. Defaults to [folder] if omitted."),
420
+ }, async ({ name, transitionTo = "stamped", message, folder, artifacts }) => {
448
421
  let ctx;
449
422
  try {
450
423
  ctx = await getContext();
@@ -455,7 +428,7 @@ transitionTo options:
455
428
  try {
456
429
  const lock = await loadLock(ctx.workspaceRoot);
457
430
  if (!lock) {
458
- return { content: [{ type: "text", text: "No ptb.lock found. Run export_context first." }] };
431
+ return { content: [{ type: "text", text: "No ptb.lock found. Run run({ stage:'context' }) first." }] };
459
432
  }
460
433
  const entries = Object.entries(lock.components).filter(([slug, e]) => slug === toSlug(name) || e.displayName.toLowerCase() === name.toLowerCase());
461
434
  if (entries.length === 0) {
@@ -465,6 +438,15 @@ transitionTo options:
465
438
  const now = new Date().toISOString();
466
439
  for (const [slug, entry] of entries) {
467
440
  if (transitionTo === "stamped") {
441
+ const modelComponent = ctx.snapshot.components.find(c => c.name.toLowerCase() === entry.displayName.toLowerCase() ||
442
+ c.name.toLowerCase().replace(/[^a-z0-9]+/g, "-").replace(/^-+|-+$/g, "") === slug);
443
+ const stampedContract = modelComponent ? {
444
+ ...(modelComponent.variantAxes ? { variantAxes: modelComponent.variantAxes } : {}),
445
+ ...(modelComponent.properties ? { properties: modelComponent.properties } : {}),
446
+ ...(modelComponent.tokenBindings ? { tokenBindings: modelComponent.tokenBindings } : {}),
447
+ ...(modelComponent.styleMetadata ? { styleMetadata: modelComponent.styleMetadata } : {}),
448
+ ...(modelComponent.layout ? { layout: modelComponent.layout } : {}),
449
+ } : undefined;
468
450
  lock.components[slug] = {
469
451
  ...entry,
470
452
  stampedHash: entry.designHash,
@@ -472,6 +454,7 @@ transitionTo options:
472
454
  stampedBy: identity,
473
455
  workStatus: "stamped",
474
456
  ...(message ? { stampedMessage: message } : {}),
457
+ ...(stampedContract ? { stampedContract } : {}),
475
458
  };
476
459
  }
477
460
  else {
@@ -483,11 +466,20 @@ transitionTo options:
483
466
  }
484
467
  }
485
468
  await saveLock(ctx.workspaceRoot, lock);
469
+ // Register artifact paths in manifest.json for playground hot-reload
470
+ const manifestNotes = [];
471
+ if (folder && transitionTo === "stamped" && entries.length === 1) {
472
+ const [, entry] = entries[0];
473
+ const comp = ctx.snapshot.components.find(c => c.name === entry.displayName);
474
+ if (comp) {
475
+ await updateComponentArtifacts(ctx.config, comp.id, comp.name, folder, artifacts ?? [folder], ctx.config.framework?.name ?? "react");
476
+ manifestNotes.push(`Registered in manifest.json → folder: ${folder}`);
477
+ }
478
+ }
486
479
  const names = entries.map(([, e]) => e.displayName).join(", ");
487
- const action = transitionTo === "stamped"
488
- ? `Stamped and marked as done`
489
- : `Submitted for review (not yet stamped)`;
490
- return { content: [{ type: "text", text: `${action}: ${names}` }] };
480
+ const action = transitionTo === "stamped" ? `Stamped and marked as done` : `Submitted for review (not yet stamped)`;
481
+ const detail = manifestNotes.length > 0 ? `\n${manifestNotes.join("\n")}` : "";
482
+ return { content: [{ type: "text", text: `${action}: ${names}${detail}` }] };
491
483
  }
492
484
  catch (e) {
493
485
  return { content: [{ type: "text", text: `submit_work failed: ${e.message}` }] };
@@ -509,8 +501,7 @@ TOKEN AUTHORITY — what to trust, what to verify:
509
501
 
510
502
  WORKFLOW — follow this exactly:
511
503
  1. Check unbuiltDependencies. If any exist, call implement_component for each one in buildOrder first.
512
- - autoGeneratedIcons are handled by ptb automatically — do NOT call stamp_component for them,
513
- they are not tracked in ptb.lock.
504
+ - autoGeneratedIcons are handled by ptb automatically — they are not tracked in ptb.lock.
514
505
  2. For each component in buildOrder (dependencies first, then the requested component):
515
506
  a. Read contextFile for the full implementation brief (promptText, renderTree, variantDiffs).
516
507
  b. Write the required files: <Name>.tsx, <Name>.types.ts, <Name>.module.css, index.ts
@@ -518,8 +509,12 @@ WORKFLOW — follow this exactly:
518
509
  - Populate story args from contentSourceMap (username, title, etc.) using the base values
519
510
  shown in the Content Contracts section of promptText.
520
511
  - Include at least one populated story so the playground/Storybook preview matches the design.
521
- d. After writing ALL files, call stamp_component with the folder path so the playground updates.
522
- 3. Never skip stamp_component it is what marks the component as done and keeps ptb.lock in sync.
512
+ d. If Storybook is running, call run_verify now (before submit_work).
513
+ - Use the pixel match score and AI differences to correct visual drift.
514
+ - Target score ≥ 0.90. If score < 0.90 or assessment is "significant-differences", fix the CSS/TSX and re-verify.
515
+ - The scan thumbnail in thumbnailPath is the visual ground truth — do not invent chrome not shown there.
516
+ e. Call submit_work with the folder path so the playground updates and stampedContract is written.
517
+ 3. Never skip submit_work — it records stampedContract and keeps ptb.lock in sync.
523
518
 
524
519
  If a dependency also has its own unbuilt dependencies, implement_component will surface those
525
520
  recursively when you call it for that dependency.`, { name: z.string().describe("Component name to implement, e.g. 'SearchBar' or 'BtnWeb'") }, async ({ name }) => {
@@ -552,7 +547,7 @@ recursively when you call it for that dependency.`, { name: z.string().describe(
552
547
  depIds.delete(comp.id);
553
548
  const depComponents = ctx.snapshot.components.filter((c) => depIds.has(c.id));
554
549
  // Icons are auto-generated deterministically by ptb — they are NOT tracked
555
- // in ptb.lock (writeLockFile skips them), so calling stamp_component on them
550
+ // in ptb.lock (writeLockFile skips them), so calling submit_work on them
556
551
  // will always fail with "not found". Split them out so the agent never tries.
557
552
  const iconDeps = depComponents.filter((c) => isIconComponent(c.name));
558
553
  const nonIconDeps = depComponents.filter((c) => !isIconComponent(c.name));
@@ -598,6 +593,7 @@ recursively when you call it for that dependency.`, { name: z.string().describe(
598
593
  slug: toSlug(comp.name),
599
594
  kind: comp.kind?.value ?? "unknown",
600
595
  category: comp.categoryPath ?? "unclassified",
596
+ ...(comp.thumbnailPath ? { thumbnailPath: comp.thumbnailPath } : {}),
601
597
  },
602
598
  contextFile: contextFilePath,
603
599
  unbuiltDependencies,
@@ -639,30 +635,6 @@ recursively when you call it for that dependency.`, { name: z.string().describe(
639
635
  return { content: [{ type: "text", text: `implement_component failed: ${e.message}` }] };
640
636
  }
641
637
  });
642
- server.tool("run_prune", "Remove stale generated component folders that are no longer in the design system (present in manifest but deleted from Figma). Also cleans up ptb.lock entries. Equivalent to 'ptb prune'.", async () => {
643
- let ctx;
644
- try {
645
- ctx = await getContext();
646
- }
647
- catch (e) {
648
- return { content: [{ type: "text", text: e.message }] };
649
- }
650
- try {
651
- const result = await pruneStaleComponents(ctx.config, ctx.snapshot);
652
- if (result.found === 0) {
653
- return { content: [{ type: "text", text: "No stale artifacts to prune." }] };
654
- }
655
- return {
656
- content: [{
657
- type: "text",
658
- text: JSON.stringify({ found: result.found, deleted: result.deleted, skipped: result.skipped, errors: result.errors }, null, 2),
659
- }],
660
- };
661
- }
662
- catch (e) {
663
- return { content: [{ type: "text", text: `run_prune failed: ${e.message}` }] };
664
- }
665
- });
666
638
  server.tool("document_component", "Generate MDX docs and/or Storybook stories for a component. Pass '.' to document all. The docs tool is read from ptb.config.json (docs.tool). Equivalent to 'ptb document component <name>'.", {
667
639
  name: z.string().describe("Component name or '.' for all"),
668
640
  format: z.enum(["mdx", "stories", "both"]).optional().describe("Output format. Default: mdx"),
@@ -726,7 +698,7 @@ recursively when you call it for that dependency.`, { name: z.string().describe(
726
698
  const componentFile = path.join(baseOutputDir, `${codeName}.${ext}`);
727
699
  const componentExists = await fs.access(componentFile).then(() => true).catch(() => false);
728
700
  if (!componentExists) {
729
- skipped.push(`${codeName}.stories.tsx (${codeName}.${ext} not found — run generate_components first)`);
701
+ skipped.push(`${codeName}.stories.tsx (${codeName}.${ext} not found — run run({ stage:'generate' }) first)`);
730
702
  continue;
731
703
  }
732
704
  const storiesContent = generateComponentStories(context, codeName, fileKey);