@planu/cli 4.1.0 → 4.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.
- package/CHANGELOG.md +21 -0
- package/dist/config/license-plans.json +65 -361
- package/dist/engine/hooks/git-hook-generator.js +31 -0
- package/dist/tools/git/hook-ops.js +53 -9
- package/dist/tools/tool-registry/group-infra.js +22 -0
- package/package.json +8 -7
- package/dist/engine/escalator/index.d.ts +0 -5
- package/dist/engine/escalator/index.js +0 -5
- package/dist/engine/freeze/retro-audit.d.ts +0 -6
- package/dist/engine/freeze/retro-audit.js +0 -24
- package/dist/engine/heal/backup.d.ts +0 -9
- package/dist/engine/heal/backup.js +0 -21
- package/dist/engine/idioma-validator/index.d.ts +0 -17
- package/dist/engine/idioma-validator/index.js +0 -89
- package/dist/engine/saga/index.d.ts +0 -4
- package/dist/engine/saga/index.js +0 -4
- package/dist/engine/spec-state-machine/index.d.ts +0 -3
- package/dist/engine/spec-state-machine/index.js +0 -2
- package/dist/engine/spec-summary-html/dashboard-renderer.d.ts +0 -6
- package/dist/engine/spec-summary-html/dashboard-renderer.js +0 -333
- package/dist/engine/triagier/index.d.ts +0 -5
- package/dist/engine/triagier/index.js +0 -5
- package/dist/engine/universal-rules/index.d.ts +0 -5
- package/dist/engine/universal-rules/index.js +0 -6
- package/dist/testing/cassette/index.d.ts +0 -23
- package/dist/testing/cassette/index.js +0 -26
- package/dist/tools/domain-bundle-handler.d.ts +0 -37
- package/dist/tools/domain-bundle-handler.js +0 -71
- package/dist/tools/figma/rules-file.d.ts +0 -5
- package/dist/tools/figma/rules-file.js +0 -45
- package/dist/tools/heal-planu-root.d.ts +0 -8
- package/dist/tools/heal-planu-root.js +0 -144
- package/dist/tools/opencode-host-adapter.d.ts +0 -3
- package/dist/tools/opencode-host-adapter.js +0 -33
- package/dist/tools/plan-team-distribution.d.ts +0 -3
- package/dist/tools/plan-team-distribution.js +0 -71
- package/dist/tools/reconcile-status-json.d.ts +0 -4
- package/dist/tools/reconcile-status-json.js +0 -209
- package/dist/tools/register-all-tools.d.ts +0 -8
- package/dist/tools/register-all-tools.js +0 -239
- package/dist/tools/tool-registry/group-analysis-monitoring.d.ts +0 -3
- package/dist/tools/tool-registry/group-analysis-monitoring.js +0 -942
- package/dist/tools/tool-registry/group-integrations.d.ts +0 -3
- package/dist/tools/tool-registry/group-integrations.js +0 -1046
- package/dist/tools/tool-registry/group-misc.d.ts +0 -3
- package/dist/tools/tool-registry/group-misc.js +0 -1367
- package/dist/tools/tool-registry/group-platform.d.ts +0 -3
- package/dist/tools/tool-registry/group-platform.js +0 -1681
- package/dist/tools/tool-registry/group-session-knowledge.d.ts +0 -3
- package/dist/tools/tool-registry/group-session-knowledge.js +0 -1416
- package/dist/tools/tool-registry/group-spec-ops.d.ts +0 -3
- package/dist/tools/tool-registry/group-spec-ops.js +0 -917
- package/dist/tools/workspace-overview.d.ts +0 -4
- package/dist/tools/workspace-overview.js +0 -316
- package/dist/transports/middleware/index.d.ts +0 -9
- package/dist/transports/middleware/index.js +0 -7
- package/dist/transports/middleware/with-sandbox.d.ts +0 -21
- package/dist/transports/middleware/with-sandbox.js +0 -68
- package/dist/types/heal.d.ts +0 -18
- package/dist/types/heal.js +0 -3
|
@@ -1,1046 +0,0 @@
|
|
|
1
|
-
/* eslint-disable max-lines -- thematic registry group consolidated by SPEC refactor-phase3 (commit aafeea60); each block is a declarative tool registration ~20 lines, split would re-create the ~120 register-*.ts files this phase intentionally collapsed */
|
|
2
|
-
// tools/tool-registry/group-integrations.ts — Group D: Integration tools (inline registry)
|
|
3
|
-
//
|
|
4
|
-
// Phase 3 (refactor/registry-phase3): inlines register-*.ts files for:
|
|
5
|
-
// github, slack, figma, storybook, sentry, supabase, oauth, ci,
|
|
6
|
-
// pr-description, e2e-test-generator, api-spec-generator, generate-spec-from-design,
|
|
7
|
-
// living-spec, living-spec-advanced
|
|
8
|
-
import { z } from 'zod';
|
|
9
|
-
import { safeLicensed, safeTracked } from '../safe-handler.js';
|
|
10
|
-
import { projectIdSchema, withProject } from '../tool-registry-helpers.js';
|
|
11
|
-
// ── GitHub (register-github-tools.ts) ────────────────────────────────────────
|
|
12
|
-
import { CreatePRFromSpecSchema, PRStatusSchema, ReviewPRSchema, CreateReleaseSchema, GenerateChangelogSchema, } from '../schemas/github.js';
|
|
13
|
-
import { handleCreatePRFromSpec, handlePRStatus } from '../github-pr-handler.js';
|
|
14
|
-
import { handleReviewPR } from '../github-review-handler.js';
|
|
15
|
-
import { handleCreateRelease, handleGenerateChangelog } from '../github-release-handler.js';
|
|
16
|
-
// ── Slack (register-slack-tools.ts) ──────────────────────────────────────────
|
|
17
|
-
import { handleConfigureSlack, handleSlackNotify, handleSlackStatus, handleRemoveSlack, } from '../slack-handler.js';
|
|
18
|
-
// ── Figma (register-figma-tools.ts) ──────────────────────────────────────────
|
|
19
|
-
import { handleConfigureFigma, handleFigmaStatus, handleListFigmaFrames, handleGenerateSpecsFromFigma, handleAnalyzeFigmaFlows, handleExtractFigmaDesignTokens, handleSyncFigmaChanges, handleListFigmaResponsiveFrames, handleConfigureFigmaWebhook, handleRemoveFigmaWebhook, handleSyncFigmaCodeConnect, handleAddFigmaFile, handleRemoveFigmaFile, handleListFigmaFiles, handleCheckFigmaDesignDrift, handleFigmaVisualDiffReport, handleGenerateFigmaE2ETests, handleEnrichSpecsFromFigma, handleGetFigmaContextForSpec, handleImportFigmaProject, } from '../figma-handler.js';
|
|
20
|
-
import { handleSyncFigmaTokensToCode, handleValidateTokenConsistency, } from '../figma-token-handler.js';
|
|
21
|
-
import { handleGenerateFigmaRulesFile } from '../figma/rules-file.js';
|
|
22
|
-
// ── Storybook (register-storybook-tools.ts) ───────────────────────────────────
|
|
23
|
-
import { handleSyncStorybook, handleListStorybookComponents, handleInjectComponentContext, } from '../storybook-handler.js';
|
|
24
|
-
// ── Sentry (register-sentry-tools.ts) ────────────────────────────────────────
|
|
25
|
-
import { handleConfigureSentry, handleSentryStatus, handleSentryErrorsReport, handleSentryErrorToSpec, } from '../sentry-handler.js';
|
|
26
|
-
// ── Supabase (register-supabase-tools.ts) ────────────────────────────────────
|
|
27
|
-
import { handleConfigureSupabase, handleSupabaseStatus, handleSyncSupabaseSchema, handleGenerateRlsSpec, } from '../supabase-handler.js';
|
|
28
|
-
import { handleApproveTestimonial, handleListTestimonials } from '../testimonial-handler.js';
|
|
29
|
-
// ── OAuth (register-oauth-tools.ts) ──────────────────────────────────────────
|
|
30
|
-
import { handleConfigureOAuth } from '../configure-oauth-handler.js';
|
|
31
|
-
import { handleStartOAuthFlow, handleOAuthStatus, StartOAuthFlowInputSchema, OAuthStatusInputSchema, } from '../oauth-handler.js';
|
|
32
|
-
// ── CI (register-ci-tools.ts) ─────────────────────────────────────────────────
|
|
33
|
-
import { handleGenerateCI } from '../ci-generator.js';
|
|
34
|
-
import { handleGeneratePlanuCI } from '../ci-planu-handler.js';
|
|
35
|
-
// ── PR Description (register-pr-description-tools.ts) ────────────────────────
|
|
36
|
-
import { handleGeneratePrDescription, handleLinkPrToSpec, handleListSpecPrs, } from '../pr-description-handler.js';
|
|
37
|
-
// ── E2E Test Generator (register-e2e-test-generator-tools.ts) ────────────────
|
|
38
|
-
import { handleGenerateE2eTests, handleE2eTestStatus } from '../e2e-test-generator-handler.js';
|
|
39
|
-
// ── API Spec Generator (register-api-spec-generator.ts) ──────────────────────
|
|
40
|
-
import { handleGenerateSpecFromApi } from '../api-spec-generator-handler.js';
|
|
41
|
-
// ── Generate Spec From Design (register-generate-spec-from-design.ts) ─────────
|
|
42
|
-
import { handleGenerateSpecFromDesign } from '../generate-spec-from-design.js';
|
|
43
|
-
// ── Living Spec (register-living-spec-tools.ts) ───────────────────────────────
|
|
44
|
-
import { handleValidateAnnotations } from '../validate-annotations.js';
|
|
45
|
-
import { handleGenerateAnnotations } from '../generate-annotations.js';
|
|
46
|
-
import { handleSnapshotSpecHashes } from '../snapshot-spec-hashes.js';
|
|
47
|
-
import { handleAutoReconcile } from '../auto-reconcile.js';
|
|
48
|
-
// ── Living Spec Advanced (register-living-spec-advanced-tools.ts) ─────────────
|
|
49
|
-
import { handleLivingSpecWatch, handleLivingSpecStatus } from '../living-spec-watch.js';
|
|
50
|
-
import { handleSpecDiff } from '../spec-diff.js';
|
|
51
|
-
import { handleLivingSpecCoverage } from '../living-spec-coverage.js';
|
|
52
|
-
import { handleSyncSpecToCode } from '../sync-spec-to-code.js';
|
|
53
|
-
import { handleSyncCodeToSpec } from '../sync-code-to-spec.js';
|
|
54
|
-
import { handleResolveSyncConflict } from '../resolve-sync-conflict.js';
|
|
55
|
-
// eslint-disable-next-line max-lines-per-function -- registration catalog: one block per tool. ~34 tools from 14 register-*.ts files.
|
|
56
|
-
export function registerIntegrationsGroupTools(s) {
|
|
57
|
-
// ── GitHub ──────────────────────────────────────────────────────────────────
|
|
58
|
-
s.registerTool('create_pr_from_spec', {
|
|
59
|
-
description: 'Create a GitHub Pull Request from a spec. Extracts title and body from spec.md, ' +
|
|
60
|
-
'detects the current branch as head, and links it to the spec via branch naming convention.',
|
|
61
|
-
inputSchema: CreatePRFromSpecSchema.shape,
|
|
62
|
-
annotations: { title: 'Create PR from Spec', readOnlyHint: false },
|
|
63
|
-
}, safeLicensed('create_pr_from_spec', async (args) => handleCreatePRFromSpec(args)));
|
|
64
|
-
s.registerTool('pr_status', {
|
|
65
|
-
description: 'List open Pull Requests with review status and CI checks. ' +
|
|
66
|
-
'Query by PR number, spec ID, or list all open PRs. ' +
|
|
67
|
-
'Supports views: prs (default) | milestones.',
|
|
68
|
-
inputSchema: PRStatusSchema.shape,
|
|
69
|
-
annotations: { title: 'PR Status', readOnlyHint: true },
|
|
70
|
-
}, safeTracked('pr_status', async (args) => handlePRStatus(args)));
|
|
71
|
-
s.registerTool('review_pr', {
|
|
72
|
-
description: 'Analyze a Pull Request diff for review findings. ' +
|
|
73
|
-
'Supports review types: summary | detailed | security | performance | expert | quality. ' +
|
|
74
|
-
'Use expert for multi-dimensional analysis (architecture, security, tests, conventions, spec-drift). ' +
|
|
75
|
-
'Use quality for semantic code quality analysis (duplication, complexity, dead-code). ' +
|
|
76
|
-
'Does NOT post comments — returns analysis for the user to decide.',
|
|
77
|
-
inputSchema: ReviewPRSchema.shape,
|
|
78
|
-
annotations: { title: 'Review PR', readOnlyHint: true },
|
|
79
|
-
}, safeLicensed('review_pr', async (args) => handleReviewPR(args)));
|
|
80
|
-
s.registerTool('create_release', {
|
|
81
|
-
description: 'Create a GitHub Release with tag and auto-generated release notes from merged PRs. ' +
|
|
82
|
-
'Creates a git tag with the specified semver version.',
|
|
83
|
-
inputSchema: CreateReleaseSchema.shape,
|
|
84
|
-
annotations: { title: 'Create Release', readOnlyHint: false, destructiveHint: true },
|
|
85
|
-
}, safeLicensed('create_release', async (args) => handleCreateRelease(args)));
|
|
86
|
-
s.registerTool('generate_changelog', {
|
|
87
|
-
description: 'Generate a changelog from merged Pull Requests between two git refs. ' +
|
|
88
|
-
'Groups by type (feat, fix, docs, etc.) using conventional commit prefixes. ' +
|
|
89
|
-
'Output format: markdown (Keep a Changelog) | json.',
|
|
90
|
-
inputSchema: GenerateChangelogSchema.shape,
|
|
91
|
-
annotations: { title: 'Generate Changelog', readOnlyHint: true },
|
|
92
|
-
}, safeLicensed('generate_changelog', async (args) => handleGenerateChangelog(args)));
|
|
93
|
-
// ── Slack ───────────────────────────────────────────────────────────────────
|
|
94
|
-
s.registerTool('configure_slack', {
|
|
95
|
-
description: 'Configure a Slack incoming webhook for this project. Once configured, spec status changes will automatically send rich Block Kit notifications to your Slack channel.',
|
|
96
|
-
inputSchema: {
|
|
97
|
-
projectPath: z
|
|
98
|
-
.string()
|
|
99
|
-
.describe('Absolute path to the project root (e.g. /home/user/my-project).'),
|
|
100
|
-
webhookUrl: z
|
|
101
|
-
.url()
|
|
102
|
-
.describe('Slack incoming webhook URL (e.g. https://hooks.slack.com/services/T.../B.../...).'),
|
|
103
|
-
channel: z
|
|
104
|
-
.string()
|
|
105
|
-
.optional()
|
|
106
|
-
.describe('Optional channel override (e.g. #deployments). If omitted, uses the webhook default.'),
|
|
107
|
-
},
|
|
108
|
-
annotations: { title: 'Configure Slack', destructiveHint: false },
|
|
109
|
-
}, safeTracked('configure_slack', async (args) => handleConfigureSlack(args)));
|
|
110
|
-
s.registerTool('slack_notify', {
|
|
111
|
-
description: 'Send a manual Slack notification to the configured webhook. Optionally link the message to a specific spec.',
|
|
112
|
-
inputSchema: {
|
|
113
|
-
projectPath: z
|
|
114
|
-
.string()
|
|
115
|
-
.describe('Absolute path to the project root (e.g. /home/user/my-project).'),
|
|
116
|
-
message: z.string().min(1).describe('Notification message text to send to Slack.'),
|
|
117
|
-
specId: z
|
|
118
|
-
.string()
|
|
119
|
-
.optional()
|
|
120
|
-
.describe('Optional SPEC-XXX identifier to link this notification to a spec.'),
|
|
121
|
-
},
|
|
122
|
-
annotations: { title: 'Slack Notify', destructiveHint: false },
|
|
123
|
-
}, safeTracked('slack_notify', async (args) => handleSlackNotify(args)));
|
|
124
|
-
s.registerTool('slack_status', {
|
|
125
|
-
description: 'Show the current Slack configuration and the last 20 notification delivery records for this project.',
|
|
126
|
-
inputSchema: {
|
|
127
|
-
projectPath: z
|
|
128
|
-
.string()
|
|
129
|
-
.describe('Absolute path to the project root (e.g. /home/user/my-project).'),
|
|
130
|
-
},
|
|
131
|
-
annotations: { title: 'Slack Status', readOnlyHint: true },
|
|
132
|
-
}, safeTracked('slack_status', async (args) => handleSlackStatus(args)));
|
|
133
|
-
s.registerTool('remove_slack', {
|
|
134
|
-
description: 'Remove the Slack webhook configuration for this project. Notifications will stop immediately.',
|
|
135
|
-
inputSchema: {
|
|
136
|
-
projectPath: z
|
|
137
|
-
.string()
|
|
138
|
-
.describe('Absolute path to the project root (e.g. /home/user/my-project).'),
|
|
139
|
-
},
|
|
140
|
-
annotations: { title: 'Remove Slack', destructiveHint: true },
|
|
141
|
-
}, safeTracked('remove_slack', async (args) => handleRemoveSlack(args)));
|
|
142
|
-
// ── Figma ───────────────────────────────────────────────────────────────────
|
|
143
|
-
s.registerTool('configure_figma', {
|
|
144
|
-
description: 'Connect a Figma Personal Access Token (PAT) to this project. ' +
|
|
145
|
-
'The token is verified against the Figma API before saving. ' +
|
|
146
|
-
'Once configured, you can list frames and generate specs from Figma files.',
|
|
147
|
-
inputSchema: {
|
|
148
|
-
projectPath: z.string().min(1).describe('Absolute path to the project root directory.'),
|
|
149
|
-
accessToken: z
|
|
150
|
-
.string()
|
|
151
|
-
.min(1)
|
|
152
|
-
.describe('Figma Personal Access Token (PAT). Generate one at figma.com → Account Settings → Access tokens.'),
|
|
153
|
-
},
|
|
154
|
-
annotations: { title: 'Configure Figma', destructiveHint: false },
|
|
155
|
-
}, safeLicensed('configure_figma', async (args) => handleConfigureFigma(args)));
|
|
156
|
-
s.registerTool('figma_status', {
|
|
157
|
-
description: 'Show the current Figma configuration and the last 3 sync log entries for this project.',
|
|
158
|
-
inputSchema: {
|
|
159
|
-
projectPath: z.string().min(1).describe('Absolute path to the project root directory.'),
|
|
160
|
-
},
|
|
161
|
-
annotations: { title: 'Figma Status', readOnlyHint: true },
|
|
162
|
-
}, safeTracked('figma_status', async (args) => handleFigmaStatus(args)));
|
|
163
|
-
s.registerTool('list_figma_frames', {
|
|
164
|
-
description: 'List all frames in a Figma file, grouped by section prefix. ' +
|
|
165
|
-
'Use this to preview the structure before generating specs.',
|
|
166
|
-
inputSchema: {
|
|
167
|
-
projectPath: z.string().min(1).describe('Absolute path to the project root directory.'),
|
|
168
|
-
figmaUrl: z
|
|
169
|
-
.url()
|
|
170
|
-
.describe('URL of the Figma file (e.g. https://www.figma.com/design/FILEID/FileName).'),
|
|
171
|
-
},
|
|
172
|
-
annotations: { title: 'List Figma Frames', readOnlyHint: true },
|
|
173
|
-
}, safeTracked('list_figma_frames', async (args) => handleListFigmaFrames(args)));
|
|
174
|
-
s.registerTool('generate_specs_from_figma_file', {
|
|
175
|
-
description: 'Generate Planu specs from frames in a Figma file. ' +
|
|
176
|
-
'Frames are grouped into sections (by name prefix or page) and one spec is created per section. ' +
|
|
177
|
-
'Use sectionFilter to process only a subset of sections.',
|
|
178
|
-
inputSchema: {
|
|
179
|
-
projectPath: z.string().min(1).describe('Absolute path to the project root directory.'),
|
|
180
|
-
figmaUrl: z
|
|
181
|
-
.url()
|
|
182
|
-
.describe('URL of the Figma file (e.g. https://www.figma.com/design/FILEID/FileName).'),
|
|
183
|
-
groupBy: z
|
|
184
|
-
.enum(['prefix', 'page'])
|
|
185
|
-
.optional()
|
|
186
|
-
.describe('How to group frames into sections. Values: prefix — group by name prefix (default), page — group by Figma page.'),
|
|
187
|
-
sectionFilter: z
|
|
188
|
-
.string()
|
|
189
|
-
.optional()
|
|
190
|
-
.describe('Optional prefix filter. Only frames whose section matches this string will be processed.'),
|
|
191
|
-
},
|
|
192
|
-
annotations: { title: 'Generate Specs from Figma File', destructiveHint: false },
|
|
193
|
-
}, safeLicensed('generate_specs_from_figma_file', async (args) => handleGenerateSpecsFromFigma(args)));
|
|
194
|
-
s.registerTool('analyze_figma_flows', {
|
|
195
|
-
description: 'Extract prototype interaction flows from a Figma file. Saves the flow graph locally for use by generate_figma_e2e_tests.',
|
|
196
|
-
inputSchema: {
|
|
197
|
-
projectPath: z.string().min(1).describe('Absolute path to the project root.'),
|
|
198
|
-
figmaUrl: z.url().describe('Figma file URL.'),
|
|
199
|
-
},
|
|
200
|
-
annotations: { title: 'Analyze Figma Flows' },
|
|
201
|
-
}, safeLicensed('analyze_figma_flows', async (args) => handleAnalyzeFigmaFlows(args)));
|
|
202
|
-
s.registerTool('extract_figma_design_tokens', {
|
|
203
|
-
description: 'Extract design tokens (colors, typography, spacing, radii) from Figma variables and save as structured JSON.',
|
|
204
|
-
inputSchema: {
|
|
205
|
-
projectPath: z.string().min(1).describe('Absolute path to the project root.'),
|
|
206
|
-
figmaUrl: z.url().describe('Figma file URL.'),
|
|
207
|
-
},
|
|
208
|
-
annotations: { title: 'Extract Figma Design Tokens' },
|
|
209
|
-
}, safeLicensed('extract_figma_design_tokens', async (args) => handleExtractFigmaDesignTokens(args)));
|
|
210
|
-
s.registerTool('sync_figma_changes', {
|
|
211
|
-
description: 'Detect renamed or moved frames since the last sync. Takes a new snapshot and reports the diff.',
|
|
212
|
-
inputSchema: {
|
|
213
|
-
projectPath: z.string().min(1).describe('Absolute path to the project root.'),
|
|
214
|
-
figmaUrl: z.url().describe('Figma file URL.'),
|
|
215
|
-
},
|
|
216
|
-
annotations: { title: 'Sync Figma Changes' },
|
|
217
|
-
}, safeLicensed('sync_figma_changes', async (args) => handleSyncFigmaChanges(args)));
|
|
218
|
-
s.registerTool('list_figma_responsive_frames', {
|
|
219
|
-
description: 'List frames grouped by breakpoint (mobile/tablet/desktop) based on page name or frame name suffix.',
|
|
220
|
-
inputSchema: {
|
|
221
|
-
projectPath: z.string().min(1).describe('Absolute path to the project root.'),
|
|
222
|
-
figmaUrl: z.url().describe('Figma file URL.'),
|
|
223
|
-
},
|
|
224
|
-
annotations: { title: 'List Figma Responsive Frames', readOnlyHint: true },
|
|
225
|
-
}, safeLicensed('list_figma_responsive_frames', async (args) => handleListFigmaResponsiveFrames(args)));
|
|
226
|
-
s.registerTool('configure_figma_webhook', {
|
|
227
|
-
description: 'Register a Figma webhook to receive notifications when the file changes.',
|
|
228
|
-
inputSchema: {
|
|
229
|
-
projectPath: z.string().min(1).describe('Absolute path to the project root.'),
|
|
230
|
-
teamId: z.string().min(1).describe('Figma team ID (visible in team URL).'),
|
|
231
|
-
callbackUrl: z.url().describe('Public HTTPS URL to receive webhook events.'),
|
|
232
|
-
},
|
|
233
|
-
annotations: { title: 'Configure Figma Webhook' },
|
|
234
|
-
}, safeLicensed('configure_figma_webhook', async (args) => handleConfigureFigmaWebhook(args)));
|
|
235
|
-
s.registerTool('remove_figma_webhook', {
|
|
236
|
-
description: 'Deregister a previously configured Figma webhook.',
|
|
237
|
-
inputSchema: {
|
|
238
|
-
projectPath: z.string().min(1).describe('Absolute path to the project root.'),
|
|
239
|
-
webhookId: z
|
|
240
|
-
.string()
|
|
241
|
-
.min(1)
|
|
242
|
-
.describe('Webhook ID to remove (from configure_figma_webhook output).'),
|
|
243
|
-
},
|
|
244
|
-
annotations: { title: 'Remove Figma Webhook', destructiveHint: true },
|
|
245
|
-
}, safeLicensed('remove_figma_webhook', async (args) => handleRemoveFigmaWebhook(args)));
|
|
246
|
-
s.registerTool('sync_figma_code_connect', {
|
|
247
|
-
description: 'Fetch Figma Code Connect mappings linking design components to codebase components.',
|
|
248
|
-
inputSchema: {
|
|
249
|
-
projectPath: z.string().min(1).describe('Absolute path to the project root.'),
|
|
250
|
-
figmaUrl: z.url().describe('Figma file URL.'),
|
|
251
|
-
},
|
|
252
|
-
annotations: { title: 'Sync Figma Code Connect' },
|
|
253
|
-
}, safeLicensed('sync_figma_code_connect', async (args) => handleSyncFigmaCodeConnect(args)));
|
|
254
|
-
s.registerTool('add_figma_file', {
|
|
255
|
-
description: 'Register an additional Figma file for this project (multi-file support).',
|
|
256
|
-
inputSchema: {
|
|
257
|
-
projectPath: z.string().min(1).describe('Absolute path to the project root.'),
|
|
258
|
-
figmaUrl: z.url().describe('Figma file URL to add.'),
|
|
259
|
-
label: z
|
|
260
|
-
.string()
|
|
261
|
-
.min(1)
|
|
262
|
-
.describe('Short label to identify this file (e.g. "Design System", "Checkout").'),
|
|
263
|
-
},
|
|
264
|
-
annotations: { title: 'Add Figma File' },
|
|
265
|
-
}, safeLicensed('add_figma_file', async (args) => handleAddFigmaFile(args)));
|
|
266
|
-
s.registerTool('remove_figma_file', {
|
|
267
|
-
description: 'Remove a registered Figma file from this project.',
|
|
268
|
-
inputSchema: {
|
|
269
|
-
projectPath: z.string().min(1).describe('Absolute path to the project root.'),
|
|
270
|
-
fileKey: z.string().min(1).describe('Figma file key to remove.'),
|
|
271
|
-
},
|
|
272
|
-
annotations: { title: 'Remove Figma File', destructiveHint: true },
|
|
273
|
-
}, safeLicensed('remove_figma_file', async (args) => handleRemoveFigmaFile(args)));
|
|
274
|
-
s.registerTool('list_figma_files', {
|
|
275
|
-
description: 'List all Figma files registered for this project.',
|
|
276
|
-
inputSchema: {
|
|
277
|
-
projectPath: z.string().min(1).describe('Absolute path to the project root.'),
|
|
278
|
-
},
|
|
279
|
-
annotations: { title: 'List Figma Files', readOnlyHint: true },
|
|
280
|
-
}, safeTracked('list_figma_files', async (args) => handleListFigmaFiles(args)));
|
|
281
|
-
s.registerTool('check_figma_design_drift', {
|
|
282
|
-
description: 'Check if Figma specs are out of date with the latest Figma file version.',
|
|
283
|
-
inputSchema: {
|
|
284
|
-
projectPath: z.string().min(1).describe('Absolute path to the project root.'),
|
|
285
|
-
figmaUrl: z.url().describe('Figma file URL.'),
|
|
286
|
-
},
|
|
287
|
-
annotations: { title: 'Check Figma Design Drift', readOnlyHint: true },
|
|
288
|
-
}, safeLicensed('check_figma_design_drift', async (args) => handleCheckFigmaDesignDrift(args)));
|
|
289
|
-
s.registerTool('figma_visual_diff_report', {
|
|
290
|
-
description: 'Export Figma frame images and generate a visual QA report comparing design vs implementation.',
|
|
291
|
-
inputSchema: {
|
|
292
|
-
projectPath: z.string().min(1).describe('Absolute path to the project root.'),
|
|
293
|
-
figmaUrl: z.url().describe('Figma file URL.'),
|
|
294
|
-
specId: z
|
|
295
|
-
.string()
|
|
296
|
-
.min(1)
|
|
297
|
-
.describe('Spec ID to validate (must have been generated from Figma).'),
|
|
298
|
-
},
|
|
299
|
-
annotations: { title: 'Figma Visual Diff Report' },
|
|
300
|
-
}, safeLicensed('figma_visual_diff_report', async (args) => handleFigmaVisualDiffReport(args)));
|
|
301
|
-
s.registerTool('generate_figma_e2e_tests', {
|
|
302
|
-
description: 'Generate Playwright E2E test files from Figma prototype flows.',
|
|
303
|
-
inputSchema: {
|
|
304
|
-
projectPath: z.string().min(1).describe('Absolute path to the project root.'),
|
|
305
|
-
figmaUrl: z.url().describe('Figma file URL.'),
|
|
306
|
-
outputDir: z
|
|
307
|
-
.string()
|
|
308
|
-
.optional()
|
|
309
|
-
.describe('Output directory for test files relative to projectPath. Default: e2e/figma'),
|
|
310
|
-
sectionFilter: z
|
|
311
|
-
.string()
|
|
312
|
-
.optional()
|
|
313
|
-
.describe('Only generate tests for flows in this section.'),
|
|
314
|
-
},
|
|
315
|
-
annotations: { title: 'Generate Figma E2E Tests' },
|
|
316
|
-
}, safeLicensed('generate_figma_e2e_tests', async (args) => handleGenerateFigmaE2ETests(args)));
|
|
317
|
-
s.registerTool('enrich_specs_from_figma', {
|
|
318
|
-
description: 'Enrich existing Planu specs with design context from Figma (component hints, layout notes, colors).',
|
|
319
|
-
inputSchema: {
|
|
320
|
-
projectPath: z.string().min(1).describe('Absolute path to the project root.'),
|
|
321
|
-
figmaUrl: z.url().describe('Figma file URL.'),
|
|
322
|
-
specIds: z
|
|
323
|
-
.array(z.string())
|
|
324
|
-
.optional()
|
|
325
|
-
.describe('Specific spec IDs to enrich. If omitted, enriches all Figma-generated specs.'),
|
|
326
|
-
},
|
|
327
|
-
annotations: { title: 'Enrich Specs from Figma' },
|
|
328
|
-
}, safeLicensed('enrich_specs_from_figma', async (args) => handleEnrichSpecsFromFigma(args)));
|
|
329
|
-
s.registerTool('get_figma_context_for_spec', {
|
|
330
|
-
description: 'Get stored design context for a specific spec during implementation.',
|
|
331
|
-
inputSchema: {
|
|
332
|
-
projectPath: z.string().min(1).describe('Absolute path to the project root.'),
|
|
333
|
-
specId: z.string().min(1).describe('Spec ID to get design context for.'),
|
|
334
|
-
},
|
|
335
|
-
annotations: { title: 'Get Figma Context for Spec', readOnlyHint: true },
|
|
336
|
-
}, safeLicensed('get_figma_context_for_spec', async (args) => handleGetFigmaContextForSpec(args)));
|
|
337
|
-
s.registerTool('import_figma_project', {
|
|
338
|
-
description: 'One-command Figma import: fetches frames, extracts flows, generates specs, enriches with design context, and tracks version.',
|
|
339
|
-
inputSchema: {
|
|
340
|
-
projectPath: z.string().min(1).describe('Absolute path to the project root.'),
|
|
341
|
-
figmaUrl: z.url().describe('Figma file URL.'),
|
|
342
|
-
teamId: z
|
|
343
|
-
.string()
|
|
344
|
-
.optional()
|
|
345
|
-
.describe('Figma team ID (required only if you want to configure webhooks).'),
|
|
346
|
-
webhookCallbackUrl: z
|
|
347
|
-
.url()
|
|
348
|
-
.optional()
|
|
349
|
-
.describe('Public HTTPS callback URL for Figma webhooks (optional).'),
|
|
350
|
-
},
|
|
351
|
-
annotations: { title: 'Import Figma Project' },
|
|
352
|
-
}, safeLicensed('import_figma_project', async (args) => handleImportFigmaProject(args)));
|
|
353
|
-
s.registerTool('sync_figma_tokens_to_code', {
|
|
354
|
-
description: 'Write Figma design tokens to a CSS, TypeScript, or JSON file in the project. Run extract_figma_design_tokens first to fetch tokens from Figma.',
|
|
355
|
-
inputSchema: {
|
|
356
|
-
projectPath: z.string().min(1).describe('Absolute path to the project root.'),
|
|
357
|
-
format: z
|
|
358
|
-
.enum(['css', 'ts', 'json'])
|
|
359
|
-
.describe('Output format. Values: css — CSS custom properties (:root {}), ts — TypeScript const object, json — W3C Design Token Format (DTCG).'),
|
|
360
|
-
outputPath: z
|
|
361
|
-
.string()
|
|
362
|
-
.min(1)
|
|
363
|
-
.describe('Output file path relative to projectPath (e.g. "src/styles/tokens.css").'),
|
|
364
|
-
prefix: z
|
|
365
|
-
.string()
|
|
366
|
-
.optional()
|
|
367
|
-
.describe('CSS variable prefix (only for css format). Default: planu.'),
|
|
368
|
-
},
|
|
369
|
-
annotations: { title: 'Sync Figma Tokens to Code' },
|
|
370
|
-
}, safeLicensed('sync_figma_tokens_to_code', async (args) => handleSyncFigmaTokensToCode(args)));
|
|
371
|
-
s.registerTool('validate_token_consistency', {
|
|
372
|
-
description: 'Compare stored Figma tokens against an existing token file to detect drift (tokens out of sync with the design).',
|
|
373
|
-
inputSchema: {
|
|
374
|
-
projectPath: z.string().min(1).describe('Absolute path to the project root.'),
|
|
375
|
-
format: z
|
|
376
|
-
.enum(['css', 'ts', 'json'])
|
|
377
|
-
.describe('Format of the token file. Values: css, ts, json.'),
|
|
378
|
-
filePath: z
|
|
379
|
-
.string()
|
|
380
|
-
.min(1)
|
|
381
|
-
.describe('Absolute path to the existing token file to validate.'),
|
|
382
|
-
},
|
|
383
|
-
annotations: { title: 'Validate Token Consistency', readOnlyHint: true },
|
|
384
|
-
}, safeLicensed('validate_token_consistency', async (args) => handleValidateTokenConsistency(args)));
|
|
385
|
-
s.registerTool('generate_figma_rules_file', {
|
|
386
|
-
description: 'Generate .claude/rules/figma-design-system.md in the client project with the official Figma MCP implementation rules. ' +
|
|
387
|
-
'Includes: localhost asset rules (no placeholders), no icon package installs, Code Connect component map, ' +
|
|
388
|
-
'design token names, and stack-adapted output guidance. ' +
|
|
389
|
-
'Run after configure_figma and sync_figma_code_connect to keep the rules file up to date.',
|
|
390
|
-
inputSchema: {
|
|
391
|
-
projectPath: z
|
|
392
|
-
.string()
|
|
393
|
-
.min(1)
|
|
394
|
-
.max(4096)
|
|
395
|
-
.describe('Absolute path to the project root where .claude/rules/ will be created.'),
|
|
396
|
-
},
|
|
397
|
-
annotations: { title: 'Generate Figma Rules File', readOnlyHint: false },
|
|
398
|
-
}, safeLicensed('generate_figma_rules_file', async (args) => handleGenerateFigmaRulesFile(args)));
|
|
399
|
-
// ── Storybook ───────────────────────────────────────────────────────────────
|
|
400
|
-
s.registerTool('sync_storybook_components', {
|
|
401
|
-
description: 'Scan a project for .stories.ts/.stories.tsx/.stories.js/.stories.jsx files and extract ' +
|
|
402
|
-
'a structured catalog of UI components, stories, and props. Saves the catalog for later queries.',
|
|
403
|
-
inputSchema: {
|
|
404
|
-
projectPath: z
|
|
405
|
-
.string()
|
|
406
|
-
.min(1)
|
|
407
|
-
.describe('Absolute path to the project root directory to scan for story files.'),
|
|
408
|
-
},
|
|
409
|
-
annotations: { title: 'Sync Storybook Components', destructiveHint: false },
|
|
410
|
-
}, safeLicensed('sync_storybook_components', async (args) => handleSyncStorybook(args)));
|
|
411
|
-
s.registerTool('list_storybook_components', {
|
|
412
|
-
description: 'Show the stored Storybook component catalog for a project. ' +
|
|
413
|
-
'Run sync_storybook_components first to populate the catalog.',
|
|
414
|
-
inputSchema: {
|
|
415
|
-
projectPath: z.string().min(1).describe('Absolute path to the project root directory.'),
|
|
416
|
-
},
|
|
417
|
-
annotations: { title: 'List Storybook Components', readOnlyHint: true },
|
|
418
|
-
}, safeLicensed('list_storybook_components', async (args) => handleListStorybookComponents(args)));
|
|
419
|
-
s.registerTool('inject_component_context', {
|
|
420
|
-
description: 'Find Storybook components matching a query string (name, category, or title). ' +
|
|
421
|
-
'Returns matching components with their stories and props for use in spec enrichment.',
|
|
422
|
-
inputSchema: {
|
|
423
|
-
projectPath: z.string().min(1).describe('Absolute path to the project root directory.'),
|
|
424
|
-
query: z
|
|
425
|
-
.string()
|
|
426
|
-
.min(1)
|
|
427
|
-
.describe('Search query to filter components by name, category, or title. ' +
|
|
428
|
-
'Case-insensitive. Examples: "button", "auth", "UI".'),
|
|
429
|
-
},
|
|
430
|
-
annotations: { title: 'Inject Component Context', readOnlyHint: true },
|
|
431
|
-
}, safeLicensed('inject_component_context', async (args) => handleInjectComponentContext(args)));
|
|
432
|
-
// ── Sentry ──────────────────────────────────────────────────────────────────
|
|
433
|
-
s.registerTool('configure_sentry', {
|
|
434
|
-
description: 'Configure Sentry error monitoring integration for this project. Requires a Sentry DSN, auth token, organization slug, and project slug.',
|
|
435
|
-
inputSchema: {
|
|
436
|
-
projectPath: z
|
|
437
|
-
.string()
|
|
438
|
-
.describe('Absolute path to the project root (e.g. /home/user/my-project).'),
|
|
439
|
-
dsn: z
|
|
440
|
-
.string()
|
|
441
|
-
.describe('Sentry DSN URL for the project (e.g. https://...@sentry.io/...).'),
|
|
442
|
-
authToken: z
|
|
443
|
-
.string()
|
|
444
|
-
.describe('Sentry auth token with project:read and event:read scopes.'),
|
|
445
|
-
organizationSlug: z.string().describe('Sentry organization slug (e.g. my-org).'),
|
|
446
|
-
projectSlug: z.string().describe('Sentry project slug (e.g. my-project).'),
|
|
447
|
-
},
|
|
448
|
-
annotations: { title: 'Configure Sentry', destructiveHint: false },
|
|
449
|
-
}, safeTracked('configure_sentry', async (args) => handleConfigureSentry(args)));
|
|
450
|
-
s.registerTool('sentry_status', {
|
|
451
|
-
description: 'Show the current Sentry configuration for this project.',
|
|
452
|
-
inputSchema: {
|
|
453
|
-
projectPath: z
|
|
454
|
-
.string()
|
|
455
|
-
.describe('Absolute path to the project root (e.g. /home/user/my-project).'),
|
|
456
|
-
},
|
|
457
|
-
annotations: { title: 'Sentry Status', readOnlyHint: true },
|
|
458
|
-
}, safeTracked('sentry_status', async (args) => handleSentryStatus(args)));
|
|
459
|
-
s.registerTool('sentry_errors_report', {
|
|
460
|
-
description: 'Fetch and display a report of recent Sentry issues for this project. Supports filtering by level and unresolved status. Requires Sentry configured via configure_sentry.',
|
|
461
|
-
inputSchema: {
|
|
462
|
-
projectPath: z
|
|
463
|
-
.string()
|
|
464
|
-
.describe('Absolute path to the project root (e.g. /home/user/my-project).'),
|
|
465
|
-
limit: z
|
|
466
|
-
.number()
|
|
467
|
-
.int()
|
|
468
|
-
.min(1)
|
|
469
|
-
.max(100)
|
|
470
|
-
.optional()
|
|
471
|
-
.describe('Maximum number of issues to fetch (default: 10, max: 100).'),
|
|
472
|
-
level: z
|
|
473
|
-
.enum(['fatal', 'error', 'warning', 'info', 'debug'])
|
|
474
|
-
.optional()
|
|
475
|
-
.describe('Filter by error level. Valid values: fatal, error, warning, info, debug.'),
|
|
476
|
-
unresolved: z.boolean().optional().describe('Only show unresolved issues (default: true).'),
|
|
477
|
-
},
|
|
478
|
-
annotations: { title: 'Sentry Errors Report', readOnlyHint: true },
|
|
479
|
-
}, safeLicensed('sentry_errors_report', async (args) => handleSentryErrorsReport(args)));
|
|
480
|
-
s.registerTool('sentry_error_to_spec', {
|
|
481
|
-
description: 'Convert a Sentry issue into a Planu bugfix spec. Fetches the issue details and stacktrace, generates acceptance criteria, and creates a spec automatically. Requires Sentry configured via configure_sentry.',
|
|
482
|
-
inputSchema: {
|
|
483
|
-
projectPath: z
|
|
484
|
-
.string()
|
|
485
|
-
.describe('Absolute path to the project root (e.g. /home/user/my-project).'),
|
|
486
|
-
issueId: z.string().describe('Sentry issue ID (numeric string, e.g. "12345678").'),
|
|
487
|
-
priority: z
|
|
488
|
-
.enum(['low', 'medium', 'high', 'critical'])
|
|
489
|
-
.optional()
|
|
490
|
-
.describe('Override the auto-detected priority. Valid values: low, medium, high, critical. Auto-detected from Sentry level if omitted.'),
|
|
491
|
-
},
|
|
492
|
-
annotations: { title: 'Sentry Error to Spec', destructiveHint: false },
|
|
493
|
-
}, safeLicensed('sentry_error_to_spec', async (args) => handleSentryErrorToSpec(args)));
|
|
494
|
-
// ── Supabase ────────────────────────────────────────────────────────────────
|
|
495
|
-
s.registerTool('configure_supabase', {
|
|
496
|
-
description: 'Connect a Supabase project to Planu by saving the project URL and service role key. ' +
|
|
497
|
-
'Once configured, you can sync the database schema and generate security specs.',
|
|
498
|
-
inputSchema: {
|
|
499
|
-
projectPath: z.string().min(1).describe('Absolute path to the project root directory.'),
|
|
500
|
-
projectUrl: z
|
|
501
|
-
.string()
|
|
502
|
-
.min(1)
|
|
503
|
-
.describe('Supabase project URL (e.g. https://abc123.supabase.co).'),
|
|
504
|
-
serviceRoleKey: z
|
|
505
|
-
.string()
|
|
506
|
-
.min(1)
|
|
507
|
-
.describe('Supabase service role key (from Project Settings → API).'),
|
|
508
|
-
anonKey: z
|
|
509
|
-
.string()
|
|
510
|
-
.optional()
|
|
511
|
-
.describe('Supabase anon/public key (optional, from Project Settings → API).'),
|
|
512
|
-
},
|
|
513
|
-
annotations: { title: 'Configure Supabase', destructiveHint: false },
|
|
514
|
-
}, safeTracked('configure_supabase', async (args) => handleConfigureSupabase(args)));
|
|
515
|
-
s.registerTool('supabase_status', {
|
|
516
|
-
description: 'Show the current Supabase configuration and last schema sync summary for this project.',
|
|
517
|
-
inputSchema: {
|
|
518
|
-
projectPath: z.string().min(1).describe('Absolute path to the project root directory.'),
|
|
519
|
-
},
|
|
520
|
-
annotations: { title: 'Supabase Status', readOnlyHint: true },
|
|
521
|
-
}, safeTracked('supabase_status', async (args) => handleSupabaseStatus(args)));
|
|
522
|
-
s.registerTool('sync_supabase_schema', {
|
|
523
|
-
description: 'Fetch the Supabase database schema (tables, columns, RLS policies) and store it locally. ' +
|
|
524
|
-
'Run this before generate_rls_spec to get up-to-date schema information.',
|
|
525
|
-
inputSchema: {
|
|
526
|
-
projectPath: z.string().min(1).describe('Absolute path to the project root directory.'),
|
|
527
|
-
schemas: z
|
|
528
|
-
.array(z.string())
|
|
529
|
-
.optional()
|
|
530
|
-
.describe('Database schemas to include. Values: public, auth, etc. Default: ["public"].'),
|
|
531
|
-
},
|
|
532
|
-
annotations: { title: 'Sync Supabase Schema', destructiveHint: false },
|
|
533
|
-
}, safeLicensed('sync_supabase_schema', async (args) => handleSyncSupabaseSchema(args)));
|
|
534
|
-
s.registerTool('generate_rls_spec', {
|
|
535
|
-
description: 'Generate Planu security specs from Supabase RLS (Row Level Security) policies. ' +
|
|
536
|
-
'Creates one spec per table with acceptance criteria derived from the actual policies. ' +
|
|
537
|
-
'Run sync_supabase_schema first.',
|
|
538
|
-
inputSchema: {
|
|
539
|
-
projectPath: z.string().min(1).describe('Absolute path to the project root directory.'),
|
|
540
|
-
tableName: z
|
|
541
|
-
.string()
|
|
542
|
-
.optional()
|
|
543
|
-
.describe('Generate spec only for this table. If omitted, processes all synced tables.'),
|
|
544
|
-
createMissingRlsSpecs: z
|
|
545
|
-
.boolean()
|
|
546
|
-
.optional()
|
|
547
|
-
.describe('When true (default), also creates specs for tables that have no RLS policies, flagging them as critical security gaps.'),
|
|
548
|
-
},
|
|
549
|
-
annotations: { title: 'Generate RLS Spec', destructiveHint: false },
|
|
550
|
-
}, safeLicensed('generate_rls_spec', async (args) => handleGenerateRlsSpec(args)));
|
|
551
|
-
s.registerTool('approve_testimonial', {
|
|
552
|
-
description: 'Approve a testimonial for display on planu.dev. Accepts feedback_id to promote existing feedback, ' +
|
|
553
|
-
'or manual data (authorName, quote, rating) for new testimonials.',
|
|
554
|
-
inputSchema: {
|
|
555
|
-
projectPath: z.string().min(1).describe('Absolute path to the project root directory.'),
|
|
556
|
-
feedbackId: z
|
|
557
|
-
.string()
|
|
558
|
-
.optional()
|
|
559
|
-
.describe('ID of existing feedback to promote as a testimonial.'),
|
|
560
|
-
authorName: z.string().optional().describe('Display name of the testimonial author.'),
|
|
561
|
-
authorRole: z
|
|
562
|
-
.string()
|
|
563
|
-
.optional()
|
|
564
|
-
.describe('Job title or role of the author (e.g. "Senior Engineer at Acme").'),
|
|
565
|
-
authorAvatarUrl: z
|
|
566
|
-
.string()
|
|
567
|
-
.optional()
|
|
568
|
-
.describe('URL of the author avatar image (public URL).'),
|
|
569
|
-
authorGithub: z.string().optional().describe('GitHub username of the author (without @).'),
|
|
570
|
-
quote: z.string().optional().describe('The testimonial text to display on planu.dev.'),
|
|
571
|
-
rating: z
|
|
572
|
-
.number()
|
|
573
|
-
.int()
|
|
574
|
-
.min(1)
|
|
575
|
-
.max(5)
|
|
576
|
-
.optional()
|
|
577
|
-
.describe('Star rating between 1 and 5 (inclusive). Default: 5.'),
|
|
578
|
-
source: z
|
|
579
|
-
.enum(['github', 'twitter', 'linkedin', 'email', 'manual'])
|
|
580
|
-
.optional()
|
|
581
|
-
.describe('Origin of the testimonial. Values: github, twitter, linkedin, email, manual. Default: manual.'),
|
|
582
|
-
featured: z
|
|
583
|
-
.boolean()
|
|
584
|
-
.optional()
|
|
585
|
-
.describe('Whether to feature this testimonial prominently on planu.dev. Default: false.'),
|
|
586
|
-
locale: z
|
|
587
|
-
.string()
|
|
588
|
-
.optional()
|
|
589
|
-
.describe('Locale code for the testimonial language (e.g. "en", "es"). Default: en.'),
|
|
590
|
-
projectType: z
|
|
591
|
-
.string()
|
|
592
|
-
.optional()
|
|
593
|
-
.describe('Type of project the author used Planu for (e.g. "saas", "mobile", "enterprise").'),
|
|
594
|
-
companySize: z
|
|
595
|
-
.string()
|
|
596
|
-
.optional()
|
|
597
|
-
.describe('Company size category (e.g. "1-10", "11-50", "51-200", "200+").'),
|
|
598
|
-
},
|
|
599
|
-
annotations: { title: 'Approve Testimonial', destructiveHint: false },
|
|
600
|
-
}, safeLicensed('approve_testimonial', async (args) => handleApproveTestimonial(args)));
|
|
601
|
-
s.registerTool('list_testimonials', {
|
|
602
|
-
description: "List testimonials from Supabase with filters. Use to audit what's shown on planu.dev.",
|
|
603
|
-
inputSchema: {
|
|
604
|
-
projectPath: z.string().min(1).describe('Absolute path to the project root directory.'),
|
|
605
|
-
filter: z
|
|
606
|
-
.enum(['approved', 'pending', 'all'])
|
|
607
|
-
.optional()
|
|
608
|
-
.describe('Filter testimonials by approval status. Values: approved (default), pending, all.'),
|
|
609
|
-
locale: z.string().optional().describe('Filter by locale code (e.g. "en", "es").'),
|
|
610
|
-
featured: z.boolean().optional().describe('When true, only return featured testimonials.'),
|
|
611
|
-
source: z
|
|
612
|
-
.enum(['github', 'twitter', 'linkedin', 'email', 'manual'])
|
|
613
|
-
.optional()
|
|
614
|
-
.describe('Filter by testimonial source. Values: github, twitter, linkedin, email, manual.'),
|
|
615
|
-
limit: z
|
|
616
|
-
.number()
|
|
617
|
-
.int()
|
|
618
|
-
.min(1)
|
|
619
|
-
.max(200)
|
|
620
|
-
.optional()
|
|
621
|
-
.describe('Maximum number of testimonials to return (default: 50, max: 200).'),
|
|
622
|
-
},
|
|
623
|
-
annotations: { title: 'List Testimonials', readOnlyHint: true },
|
|
624
|
-
}, safeLicensed('list_testimonials', async (args) => handleListTestimonials(args)));
|
|
625
|
-
// ── OAuth ───────────────────────────────────────────────────────────────────
|
|
626
|
-
s.registerTool('configure_oauth', {
|
|
627
|
-
description: 'Check or test the OAuth / API-key authentication protecting this MCP server. ' +
|
|
628
|
-
'Use action=status to see which env vars are set, action=test to validate a token.',
|
|
629
|
-
inputSchema: {
|
|
630
|
-
action: z
|
|
631
|
-
.enum(['status', 'test'])
|
|
632
|
-
.describe('Action to perform. Values: status=show current config, test=validate a token'),
|
|
633
|
-
token: z.string().optional().describe('Token to validate (only used with action=test)'),
|
|
634
|
-
},
|
|
635
|
-
}, safeTracked('configure_oauth', (input) => handleConfigureOAuth(input)));
|
|
636
|
-
s.registerTool('start_oauth_flow', {
|
|
637
|
-
description: 'Inicia el flujo de autorización para conectar Figma, GitHub, Sentry o Supabase. ' +
|
|
638
|
-
'Para Figma y GitHub abre un link OAuth. Para Sentry y Supabase proporciona instrucciones ' +
|
|
639
|
-
'paso a paso. La autorización se guarda automáticamente.',
|
|
640
|
-
inputSchema: StartOAuthFlowInputSchema,
|
|
641
|
-
annotations: { title: 'Start OAuth Flow' },
|
|
642
|
-
}, safeTracked('start_oauth_flow', (input) => handleStartOAuthFlow(input)));
|
|
643
|
-
s.registerTool('oauth_status', {
|
|
644
|
-
description: 'Muestra qué integraciones están autorizadas y cuándo expiran sus tokens.',
|
|
645
|
-
inputSchema: OAuthStatusInputSchema,
|
|
646
|
-
annotations: { title: 'OAuth Status', readOnlyHint: true },
|
|
647
|
-
}, safeTracked('oauth_status', (input) => handleOAuthStatus(input)));
|
|
648
|
-
// ── CI ──────────────────────────────────────────────────────────────────────
|
|
649
|
-
s.registerTool('generate_ci', {
|
|
650
|
-
description: 'Generate a GitHub Actions CI workflow YAML for a project. Auto-detects ecosystem (Node.js, Python, Go, Rust, Java/Kotlin, Ruby, PHP, Dart, .NET) and configures lint, test, build, and deploy jobs with proper caching and dependency installation.',
|
|
651
|
-
inputSchema: {
|
|
652
|
-
...projectIdSchema,
|
|
653
|
-
jobs: z
|
|
654
|
-
.array(z
|
|
655
|
-
.enum(['lint', 'test', 'build', 'deploy'])
|
|
656
|
-
.describe('Job type: lint | test | build | deploy'))
|
|
657
|
-
.max(100)
|
|
658
|
-
.optional()
|
|
659
|
-
.describe('CI jobs to include. Defaults to [lint, test, build]. Valid values: lint | test | build | deploy'),
|
|
660
|
-
triggers: z
|
|
661
|
-
.array(z
|
|
662
|
-
.enum(['push', 'pull_request', 'workflow_dispatch', 'schedule'])
|
|
663
|
-
.describe('Trigger: push | pull_request | workflow_dispatch | schedule'))
|
|
664
|
-
.max(100)
|
|
665
|
-
.optional()
|
|
666
|
-
.describe('GitHub Actions triggers. Defaults to [push, pull_request]. Valid values: push | pull_request | workflow_dispatch | schedule'),
|
|
667
|
-
nodeVersion: z
|
|
668
|
-
.string()
|
|
669
|
-
.max(500)
|
|
670
|
-
.optional()
|
|
671
|
-
.describe('Node.js version to use (e.g. "20", "22"). Auto-detected from .nvmrc or package.json if omitted'),
|
|
672
|
-
pythonVersion: z
|
|
673
|
-
.string()
|
|
674
|
-
.max(500)
|
|
675
|
-
.optional()
|
|
676
|
-
.describe('Python version to use (e.g. "3.11", "3.12"). Auto-detected from .python-version or pyproject.toml if omitted'),
|
|
677
|
-
goVersion: z
|
|
678
|
-
.string()
|
|
679
|
-
.max(500)
|
|
680
|
-
.optional()
|
|
681
|
-
.describe('Go version to use (e.g. "1.22", "1.23"). Auto-detected from go.mod if omitted'),
|
|
682
|
-
rustChannel: z
|
|
683
|
-
.enum(['stable', 'beta', 'nightly'])
|
|
684
|
-
.optional()
|
|
685
|
-
.describe('Rust toolchain channel: stable | beta | nightly. Auto-detected from rust-toolchain.toml if omitted'),
|
|
686
|
-
javaVersion: z
|
|
687
|
-
.string()
|
|
688
|
-
.max(500)
|
|
689
|
-
.optional()
|
|
690
|
-
.describe('Java version to use (e.g. "21", "17"). Auto-detected from pom.xml or build.gradle if omitted'),
|
|
691
|
-
workflowName: z
|
|
692
|
-
.string()
|
|
693
|
-
.max(500)
|
|
694
|
-
.optional()
|
|
695
|
-
.describe('Name for the GitHub Actions workflow (default: "CI"). Used as the workflow name and filename'),
|
|
696
|
-
},
|
|
697
|
-
annotations: { readOnlyHint: true },
|
|
698
|
-
}, safeLicensed('generate_ci', withProject((args) => handleGenerateCI(args))));
|
|
699
|
-
s.registerTool('generate_planu_ci', {
|
|
700
|
-
description: 'Generate a Planu-specific GitHub Actions CI workflow that validates specs, detects drift between code and specs, posts results as PR comments, generates badges, and supports .planu.yml configuration. Produces ready-to-use workflow YAML + config file.',
|
|
701
|
-
inputSchema: {
|
|
702
|
-
...projectIdSchema,
|
|
703
|
-
specsDir: z
|
|
704
|
-
.string()
|
|
705
|
-
.max(4096)
|
|
706
|
-
.optional()
|
|
707
|
-
.describe('Directory containing specs (default: "docs/sdd/specs")'),
|
|
708
|
-
detectDrift: z
|
|
709
|
-
.boolean()
|
|
710
|
-
.optional()
|
|
711
|
-
.describe('Enable drift detection between code changes and specs (default: true)'),
|
|
712
|
-
failOnDrift: z
|
|
713
|
-
.boolean()
|
|
714
|
-
.optional()
|
|
715
|
-
.describe('Exit with code 1 when drift/issues detected above threshold (default: true)'),
|
|
716
|
-
severityThreshold: z
|
|
717
|
-
.enum(['critical', 'warning', 'info'])
|
|
718
|
-
.optional()
|
|
719
|
-
.describe('Severity threshold for CI failure: critical | warning | info (default: "warning"). critical = only mandatory broken specs; warning = any drift or incomplete criteria; info = always passes'),
|
|
720
|
-
postComment: z
|
|
721
|
-
.boolean()
|
|
722
|
-
.optional()
|
|
723
|
-
.describe('Post/update a markdown results comment on PRs (default: true)'),
|
|
724
|
-
badge: z
|
|
725
|
-
.boolean()
|
|
726
|
-
.optional()
|
|
727
|
-
.describe('Generate a shields.io badge endpoint for README (default: true)'),
|
|
728
|
-
workingDirectory: z
|
|
729
|
-
.string()
|
|
730
|
-
.max(4096)
|
|
731
|
-
.optional()
|
|
732
|
-
.describe('Working directory for monorepo projects (default: ".")'),
|
|
733
|
-
includeGithubActions: z
|
|
734
|
-
.boolean()
|
|
735
|
-
.optional()
|
|
736
|
-
.describe('Include GitHub Actions workflow YAML (default: true). Set false for users without GitHub billing — a local planu-check.sh script is always generated regardless.'),
|
|
737
|
-
gitHook: z
|
|
738
|
-
.boolean()
|
|
739
|
-
.optional()
|
|
740
|
-
.describe('Mark for git pre-push hook integration in the output instructions (default: false)'),
|
|
741
|
-
},
|
|
742
|
-
annotations: { readOnlyHint: true },
|
|
743
|
-
}, safeLicensed('generate_planu_ci', withProject((args) => Promise.resolve(handleGeneratePlanuCI(args)))));
|
|
744
|
-
// ── PR Description ──────────────────────────────────────────────────────────
|
|
745
|
-
s.registerTool('generate_pr_description', {
|
|
746
|
-
description: 'Generates a pull request description in markdown from a Planu spec. ' +
|
|
747
|
-
'Supports GitHub, GitLab, and Linear formats. Includes title suggestion, labels, ' +
|
|
748
|
-
'acceptance criteria link, test plan, and optional checklist.',
|
|
749
|
-
annotations: { title: 'Generate PR Description', readOnlyHint: true },
|
|
750
|
-
inputSchema: {
|
|
751
|
-
projectPath: z
|
|
752
|
-
.string()
|
|
753
|
-
.describe('Absolute path to the project root containing planu/specs/'),
|
|
754
|
-
specId: z.string().describe('Spec ID to generate PR description from (e.g. SPEC-042)'),
|
|
755
|
-
platform: z
|
|
756
|
-
.enum(['github', 'gitlab', 'linear'])
|
|
757
|
-
.optional()
|
|
758
|
-
.describe('Target platform for the PR description. Valid values: github, gitlab, linear. Default: github'),
|
|
759
|
-
audience: z
|
|
760
|
-
.enum(['developer', 'reviewer', 'stakeholder'])
|
|
761
|
-
.optional()
|
|
762
|
-
.describe('Intended audience for the description. Valid values: developer, reviewer, stakeholder. Default: reviewer'),
|
|
763
|
-
includeChecklist: z
|
|
764
|
-
.boolean()
|
|
765
|
-
.optional()
|
|
766
|
-
.describe('Whether to include a pre-merge checklist section. Default: true'),
|
|
767
|
-
},
|
|
768
|
-
}, safeTracked('generate_pr_description', (args) => handleGeneratePrDescription(args)));
|
|
769
|
-
s.registerTool('link_pr_to_spec', {
|
|
770
|
-
description: 'Links a pull request URL to a Planu spec for traceability. ' +
|
|
771
|
-
'Records the PR number, URL, and platform. Use list_spec_prs to view all linked PRs.',
|
|
772
|
-
annotations: { title: 'Link PR to Spec', destructiveHint: false },
|
|
773
|
-
inputSchema: {
|
|
774
|
-
projectPath: z
|
|
775
|
-
.string()
|
|
776
|
-
.describe('Absolute path to the project root containing planu/specs/'),
|
|
777
|
-
specId: z.string().describe('Spec ID to link the PR to (e.g. SPEC-042)'),
|
|
778
|
-
prUrl: z.url().describe('Full URL of the pull request'),
|
|
779
|
-
prNumber: z
|
|
780
|
-
.number()
|
|
781
|
-
.int()
|
|
782
|
-
.positive()
|
|
783
|
-
.optional()
|
|
784
|
-
.describe('Pull request number (integer, e.g. 123)'),
|
|
785
|
-
platform: z
|
|
786
|
-
.enum(['github', 'gitlab', 'linear', 'other'])
|
|
787
|
-
.optional()
|
|
788
|
-
.describe('Platform hosting the PR. Valid values: github, gitlab, linear, other. Default: github'),
|
|
789
|
-
},
|
|
790
|
-
}, safeLicensed('link_pr_to_spec', (args) => handleLinkPrToSpec(args)));
|
|
791
|
-
s.registerTool('list_spec_prs', {
|
|
792
|
-
description: 'Lists all pull requests linked to specs in the project. ' +
|
|
793
|
-
'Optionally filter by specId to show PRs for a specific spec.',
|
|
794
|
-
annotations: { title: 'List Spec PRs', readOnlyHint: true },
|
|
795
|
-
inputSchema: {
|
|
796
|
-
projectPath: z
|
|
797
|
-
.string()
|
|
798
|
-
.describe('Absolute path to the project root containing planu/specs/'),
|
|
799
|
-
specId: z
|
|
800
|
-
.string()
|
|
801
|
-
.optional()
|
|
802
|
-
.describe('Spec ID to filter by (e.g. SPEC-042). If omitted, lists all linked PRs.'),
|
|
803
|
-
},
|
|
804
|
-
}, safeLicensed('list_spec_prs', (args) => handleListSpecPrs(args)));
|
|
805
|
-
// ── E2E Test Generator ──────────────────────────────────────────────────────
|
|
806
|
-
s.registerTool('generate_e2e_tests', {
|
|
807
|
-
description: 'Generates E2E test scaffolding from spec acceptance criteria. ' +
|
|
808
|
-
'Supports gherkin (BDD feature files), playwright (TypeScript), ' +
|
|
809
|
-
'testRigor (plain English), and plain (numbered list) formats. ' +
|
|
810
|
-
'Writes the output file to tests/e2e/ by default.',
|
|
811
|
-
annotations: { title: 'Generate E2E Tests' },
|
|
812
|
-
inputSchema: {
|
|
813
|
-
specId: z.string().describe('Spec ID to generate tests for (e.g. SPEC-042)'),
|
|
814
|
-
projectPath: z
|
|
815
|
-
.string()
|
|
816
|
-
.describe('Absolute path to the project root containing planu/specs/'),
|
|
817
|
-
framework: z
|
|
818
|
-
.enum(['gherkin', 'playwright', 'testRigor', 'plain'])
|
|
819
|
-
.optional()
|
|
820
|
-
.describe('Test framework to generate for. Valid values: gherkin (BDD feature files, default), playwright (TypeScript), testRigor (plain English steps), plain (numbered list)'),
|
|
821
|
-
outputPath: z
|
|
822
|
-
.string()
|
|
823
|
-
.optional()
|
|
824
|
-
.describe('Output file path (absolute or relative to projectPath). Defaults to tests/e2e/SPEC-XXX-{slug}.{ext}'),
|
|
825
|
-
},
|
|
826
|
-
}, safeLicensed('generate_e2e_tests', (args) => handleGenerateE2eTests(args)));
|
|
827
|
-
s.registerTool('e2e_test_status', {
|
|
828
|
-
description: 'Lists existing E2E test files in tests/e2e/ that match the SPEC-XXX naming pattern. ' +
|
|
829
|
-
'Shows which specs already have E2E test coverage and which framework was used.',
|
|
830
|
-
annotations: { title: 'E2E Test Status', readOnlyHint: true },
|
|
831
|
-
inputSchema: {
|
|
832
|
-
projectPath: z.string().describe('Absolute path to the project root'),
|
|
833
|
-
},
|
|
834
|
-
}, safeLicensed('e2e_test_status', (args) => handleE2eTestStatus(args)));
|
|
835
|
-
// ── API Spec Generator ──────────────────────────────────────────────────────
|
|
836
|
-
s.registerTool('generate_spec_from_api', {
|
|
837
|
-
description: 'Generate Planu specs from an existing API contract. ' +
|
|
838
|
-
'Accepts OpenAPI 3.x / Swagger 2.0 JSON or GraphQL SDL and creates structured specs with ' +
|
|
839
|
-
'acceptance criteria derived from required fields, response codes, and field formats. ' +
|
|
840
|
-
'Useful for reverse-engineering specs from documented APIs.',
|
|
841
|
-
inputSchema: {
|
|
842
|
-
projectPath: z.string().min(1).max(4096).describe('Absolute path to the project root'),
|
|
843
|
-
content: z
|
|
844
|
-
.string()
|
|
845
|
-
.min(2)
|
|
846
|
-
.max(200000)
|
|
847
|
-
.optional()
|
|
848
|
-
.describe('Raw content of the API contract. For OpenAPI: JSON string. For GraphQL: SDL string.'),
|
|
849
|
-
filePath: z
|
|
850
|
-
.string()
|
|
851
|
-
.min(1)
|
|
852
|
-
.max(4096)
|
|
853
|
-
.optional()
|
|
854
|
-
.describe('Absolute path to the API contract file (.json for OpenAPI, .graphql/.gql/.sdl for GraphQL). ' +
|
|
855
|
-
'Provide either content or filePath.'),
|
|
856
|
-
mode: z
|
|
857
|
-
.enum(['by-endpoint', 'by-resource', 'by-operation'])
|
|
858
|
-
.describe('How to group API operations into specs. ' +
|
|
859
|
-
'by-endpoint: one spec per REST endpoint (POST /users → spec "Create User"). ' +
|
|
860
|
-
'by-resource: one spec per resource grouping all CRUD ops (User → spec with all operations). ' +
|
|
861
|
-
'by-operation: one spec per GraphQL query/mutation/subscription.'),
|
|
862
|
-
extraTags: z
|
|
863
|
-
.array(z.string().max(50))
|
|
864
|
-
.max(10)
|
|
865
|
-
.optional()
|
|
866
|
-
.describe("Additional tags to attach to all generated specs, e.g. ['payments', 'v2']"),
|
|
867
|
-
},
|
|
868
|
-
annotations: {
|
|
869
|
-
title: 'Generate Specs from API Contract',
|
|
870
|
-
readOnlyHint: false,
|
|
871
|
-
destructiveHint: false,
|
|
872
|
-
},
|
|
873
|
-
}, safeTracked('generate_spec_from_api', async (args) => handleGenerateSpecFromApi(args)));
|
|
874
|
-
// ── Generate Spec From Design ───────────────────────────────────────────────
|
|
875
|
-
s.registerTool('generate_spec_from_design', {
|
|
876
|
-
description: 'Generate a Planu spec from a UI/UX design description. Accepts screen descriptions, user flows, and component lists to produce a structured spec with acceptance criteria derived from the design intent.',
|
|
877
|
-
inputSchema: {
|
|
878
|
-
projectPath: z.string().min(1).max(4096).describe('Absolute path to the project root'),
|
|
879
|
-
title: z.string().min(3).max(200).describe("Feature title, e.g. 'User Profile Screen'"),
|
|
880
|
-
designDescription: z
|
|
881
|
-
.string()
|
|
882
|
-
.min(10)
|
|
883
|
-
.max(5000)
|
|
884
|
-
.describe('Full description of the design: what the user sees, what they can do, visual structure'),
|
|
885
|
-
screens: z
|
|
886
|
-
.array(z.string().max(500))
|
|
887
|
-
.max(20)
|
|
888
|
-
.optional()
|
|
889
|
-
.describe('List of screen names or descriptions included in this design'),
|
|
890
|
-
userFlows: z
|
|
891
|
-
.array(z.string().max(500))
|
|
892
|
-
.max(20)
|
|
893
|
-
.optional()
|
|
894
|
-
.describe("User flows, e.g. 'User taps Save → shows success toast → returns to list'"),
|
|
895
|
-
components: z
|
|
896
|
-
.array(z.string().max(200))
|
|
897
|
-
.max(30)
|
|
898
|
-
.optional()
|
|
899
|
-
.describe('UI components involved: buttons, forms, modals, etc.'),
|
|
900
|
-
type: z
|
|
901
|
-
.enum(['feature', 'bugfix', 'refactor'])
|
|
902
|
-
.optional()
|
|
903
|
-
.default('feature')
|
|
904
|
-
.describe('Spec type: feature, bugfix, or refactor'),
|
|
905
|
-
tags: z
|
|
906
|
-
.array(z.string().max(50))
|
|
907
|
-
.max(10)
|
|
908
|
-
.optional()
|
|
909
|
-
.describe("Tags for the spec, e.g. ['frontend', 'mobile', 'ux']"),
|
|
910
|
-
},
|
|
911
|
-
annotations: { readOnlyHint: false, destructiveHint: false },
|
|
912
|
-
}, safeTracked('generate_spec_from_design', async (args) => handleGenerateSpecFromDesign(args)));
|
|
913
|
-
// ── Living Spec ─────────────────────────────────────────────────────────────
|
|
914
|
-
s.registerTool('validate_annotations', {
|
|
915
|
-
description: 'Validate @spec annotations in source code. Checks that all annotated spec IDs exist and criteria numbers are valid. Use after adding annotations to verify correctness.',
|
|
916
|
-
annotations: { readOnlyHint: true },
|
|
917
|
-
inputSchema: {
|
|
918
|
-
...projectIdSchema,
|
|
919
|
-
specId: z
|
|
920
|
-
.string()
|
|
921
|
-
.max(500)
|
|
922
|
-
.optional()
|
|
923
|
-
.describe('Optional spec ID to filter annotations for a specific spec'),
|
|
924
|
-
},
|
|
925
|
-
}, safeLicensed('validate_annotations', withProject((args) => handleValidateAnnotations(args))));
|
|
926
|
-
s.registerTool('generate_annotations', {
|
|
927
|
-
description: 'Suggest @spec annotations for unannotated source files. Uses living-spec-analyzer heuristics to match files to spec criteria and generates annotation comments to add.',
|
|
928
|
-
annotations: { readOnlyHint: true },
|
|
929
|
-
inputSchema: {
|
|
930
|
-
...projectIdSchema,
|
|
931
|
-
specId: z.string().min(1).max(500).describe('Spec ID to generate annotations for'),
|
|
932
|
-
},
|
|
933
|
-
}, safeLicensed('generate_annotations', withProject((args) => handleGenerateAnnotations(args))));
|
|
934
|
-
s.registerTool('snapshot_spec_hashes', {
|
|
935
|
-
description: 'Create or update a SHA-256 hash snapshot for a spec. Tracks file hashes and exported function signatures of all files referenced in the ## Technical section of spec.md (File Plan). Used for drift detection.',
|
|
936
|
-
inputSchema: {
|
|
937
|
-
...projectIdSchema,
|
|
938
|
-
specId: z.string().min(1).max(500).describe('Spec ID to snapshot'),
|
|
939
|
-
},
|
|
940
|
-
}, safeLicensed('snapshot_spec_hashes', withProject((args) => handleSnapshotSpecHashes(args))));
|
|
941
|
-
s.registerTool('auto_reconcile', {
|
|
942
|
-
description: 'Auto-reconcile specs after code changes. Applies safe changes automatically (completion %, status flip, hash update, timestamp). Unsafe changes (status downgrade, criterion removal) are skipped and require manual approval. Rate-limited: max 1 per spec per 5 minutes.',
|
|
943
|
-
inputSchema: {
|
|
944
|
-
...projectIdSchema,
|
|
945
|
-
specId: z
|
|
946
|
-
.string()
|
|
947
|
-
.max(500)
|
|
948
|
-
.optional()
|
|
949
|
-
.describe('Specific spec ID to reconcile (if omitted, finds affected specs from changedFiles)'),
|
|
950
|
-
changedFiles: z
|
|
951
|
-
.array(z.string().max(4096))
|
|
952
|
-
.max(1000)
|
|
953
|
-
.optional()
|
|
954
|
-
.describe('List of changed file paths to find affected specs'),
|
|
955
|
-
},
|
|
956
|
-
}, safeLicensed('auto_reconcile', withProject((args) => handleAutoReconcile(args))));
|
|
957
|
-
// ── Living Spec Advanced ────────────────────────────────────────────────────
|
|
958
|
-
s.registerTool('living_spec_watch', {
|
|
959
|
-
description: 'Start, stop, or check status of the in-memory drift watcher for a project. The watcher caches drift detection results with a configurable TTL to avoid redundant computations.',
|
|
960
|
-
inputSchema: {
|
|
961
|
-
...projectIdSchema,
|
|
962
|
-
action: z
|
|
963
|
-
.enum(['start', 'stop', 'status'])
|
|
964
|
-
.describe('Action to perform. Valid values: "start", "stop", "status"'),
|
|
965
|
-
ttlSeconds: z
|
|
966
|
-
.number()
|
|
967
|
-
.int()
|
|
968
|
-
.min(5)
|
|
969
|
-
.max(3600)
|
|
970
|
-
.optional()
|
|
971
|
-
.describe('Cache TTL in seconds (5-3600). Default: 60. Only used with "start" action'),
|
|
972
|
-
excludePatterns: z
|
|
973
|
-
.array(z.string().max(500))
|
|
974
|
-
.max(50)
|
|
975
|
-
.optional()
|
|
976
|
-
.describe('Glob patterns to exclude from drift watching (e.g., ["node_modules/**", "dist/**"])'),
|
|
977
|
-
},
|
|
978
|
-
}, safeLicensed('living_spec_watch', withProject((args) => handleLivingSpecWatch(args))));
|
|
979
|
-
s.registerTool('living_spec_status', {
|
|
980
|
-
description: 'Get detailed status of the living spec watcher including cache state and configuration. Read-only diagnostic tool.',
|
|
981
|
-
annotations: { readOnlyHint: true },
|
|
982
|
-
inputSchema: {
|
|
983
|
-
...projectIdSchema,
|
|
984
|
-
specId: z
|
|
985
|
-
.string()
|
|
986
|
-
.max(500)
|
|
987
|
-
.optional()
|
|
988
|
-
.describe('Optional spec ID to get cached drift data for'),
|
|
989
|
-
},
|
|
990
|
-
}, safeLicensed('living_spec_status', withProject((args) => handleLivingSpecStatus(args))));
|
|
991
|
-
s.registerTool('spec_diff', {
|
|
992
|
-
description: 'Compare spec hash snapshots to detect file and signature changes. Shows a Markdown-formatted diff table. Use after snapshot_spec_hashes to see what changed.',
|
|
993
|
-
annotations: { readOnlyHint: true },
|
|
994
|
-
inputSchema: {
|
|
995
|
-
...projectIdSchema,
|
|
996
|
-
specId: z
|
|
997
|
-
.string()
|
|
998
|
-
.max(500)
|
|
999
|
-
.optional()
|
|
1000
|
-
.describe('Specific spec ID to diff (if omitted, diffs all specs)'),
|
|
1001
|
-
},
|
|
1002
|
-
}, safeLicensed('spec_diff', withProject((args) => handleSpecDiff(args))));
|
|
1003
|
-
s.registerTool('living_spec_coverage', {
|
|
1004
|
-
description: 'Correlate test coverage with spec acceptance criteria. Parses Vitest JSON or JUnit XML reports and groups results by @spec annotations to determine criterion status (verified/regression/untested).',
|
|
1005
|
-
annotations: { readOnlyHint: true },
|
|
1006
|
-
inputSchema: {
|
|
1007
|
-
...projectIdSchema,
|
|
1008
|
-
coverageJsonPath: z
|
|
1009
|
-
.string()
|
|
1010
|
-
.max(4096)
|
|
1011
|
-
.optional()
|
|
1012
|
-
.describe('Path to Vitest JSON report file'),
|
|
1013
|
-
junitXmlPath: z.string().max(4096).optional().describe('Path to JUnit XML report file'),
|
|
1014
|
-
},
|
|
1015
|
-
}, safeLicensed('living_spec_coverage', withProject((args) => handleLivingSpecCoverage(args))));
|
|
1016
|
-
s.registerTool('sync_spec_to_code', {
|
|
1017
|
-
description: 'Sync from spec to code: generate TODO comments for pending acceptance criteria that lack @spec annotations. Also detects conflicts between spec and code state.',
|
|
1018
|
-
inputSchema: {
|
|
1019
|
-
...projectIdSchema,
|
|
1020
|
-
specId: z.string().min(1).max(500).describe('Spec ID to sync from'),
|
|
1021
|
-
},
|
|
1022
|
-
}, safeLicensed('sync_spec_to_code', withProject((args) => handleSyncSpecToCode(args))));
|
|
1023
|
-
s.registerTool('sync_code_to_spec', {
|
|
1024
|
-
description: 'Sync from code to spec: detect @spec annotations in source code and generate progress updates for the corresponding spec criteria. Also detects conflicts.',
|
|
1025
|
-
inputSchema: {
|
|
1026
|
-
...projectIdSchema,
|
|
1027
|
-
specId: z.string().min(1).max(500).describe('Spec ID to sync to'),
|
|
1028
|
-
},
|
|
1029
|
-
}, safeLicensed('sync_code_to_spec', withProject((args) => handleSyncCodeToSpec(args))));
|
|
1030
|
-
s.registerTool('resolve_sync_conflict', {
|
|
1031
|
-
description: 'Resolve a sync conflict detected by sync_spec_to_code or sync_code_to_spec. Choose to accept the code state, accept the spec state, or provide a manual resolution.',
|
|
1032
|
-
inputSchema: {
|
|
1033
|
-
...projectIdSchema,
|
|
1034
|
-
conflictId: z.string().max(100).describe('Conflict ID from the sync result'),
|
|
1035
|
-
resolution: z
|
|
1036
|
-
.enum(['accept-code', 'accept-spec', 'manual'])
|
|
1037
|
-
.describe('Resolution strategy. Valid values: "accept-code", "accept-spec", "manual"'),
|
|
1038
|
-
manualResolution: z
|
|
1039
|
-
.string()
|
|
1040
|
-
.max(2000)
|
|
1041
|
-
.optional()
|
|
1042
|
-
.describe('Description of manual resolution (required when resolution is "manual")'),
|
|
1043
|
-
},
|
|
1044
|
-
}, safeLicensed('resolve_sync_conflict', withProject((args) => handleResolveSyncConflict(args))));
|
|
1045
|
-
}
|
|
1046
|
-
//# sourceMappingURL=group-integrations.js.map
|