@ezmodo/mcp-server 0.14.4 → 0.17.1
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/config/endpoint-map.js +5 -21
- package/handlers/catalogs.js +39 -1
- package/handlers/context-manifest.js +0 -4
- package/handlers/epics.js +1 -1
- package/handlers/features.js +13 -2
- package/handlers/git-context.js +11 -41
- package/handlers/index.js +0 -5
- package/handlers/links.js +17 -11
- package/handlers/projects.js +1 -1
- package/handlers/tasks.js +7 -27
- package/lib/auto-assign.js +3 -22
- package/lib/autolink.js +11 -10
- package/lib/instructions.generated.js +2 -2
- package/lib/local-cache.js +3 -17
- package/lib/remote-tools.js +0 -2
- package/lib/version.js +1 -1
- package/package.json +5 -5
- package/prompts/commands.generated.js +2 -2
- package/tools/catalogs.js +32 -5
- package/tools/context-manifest.js +2 -2
- package/tools/epics.js +1 -6
- package/tools/features.js +41 -2
- package/tools/git-context.js +5 -5
- package/tools/graph.js +1 -1
- package/tools/index.js +0 -2
- package/tools/linkable-types.js +0 -1
- package/tools/links.js +7 -5
- package/tools/projects.js +2 -2
- package/tools/recurring-tasks.js +0 -1
- package/tools/task-item-schema.js +0 -6
- package/tools/tasks.js +9 -44
- package/tools/todos.js +0 -4
- package/tools/work-templates.js +0 -2
- package/handlers/components.js +0 -282
- package/tools/components.js +0 -249
package/handlers/components.js
DELETED
|
@@ -1,282 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Component Handlers
|
|
3
|
-
* Handler functions for component-related MCP tools
|
|
4
|
-
*
|
|
5
|
-
* Components are project-scoped entries in the unified UI inventory (E-168).
|
|
6
|
-
* A single Component concept spans four kinds:
|
|
7
|
-
* - area — coarse codebase module (api, web, mobile); the legacy meaning
|
|
8
|
-
* - screen — a mobile / Flutter screen
|
|
9
|
-
* - page — a web route / page
|
|
10
|
-
* - component — a reusable UI component
|
|
11
|
-
* Components self-nest via parentComponentId (e.g. web → Sprint Board → TaskCard)
|
|
12
|
-
* and carry sourcePath / route / framework. The task component-picker uses
|
|
13
|
-
* kind=area. This is ONE concept — there is no separate "UI surface" entity.
|
|
14
|
-
*
|
|
15
|
-
* list_components uses local cache for simple requests, falling back to API.
|
|
16
|
-
* Write operations (create, update, delete) invalidate the cache.
|
|
17
|
-
*/
|
|
18
|
-
|
|
19
|
-
import { callZephlyAPI } from '../lib/http-client.js';
|
|
20
|
-
import { getCachedComponents, updateCacheSections, invalidateCacheSection } from '../lib/local-cache.js';
|
|
21
|
-
import { attachLinks, applyLinks } from '../lib/links-at-create.js';
|
|
22
|
-
|
|
23
|
-
/**
|
|
24
|
-
* Dispatch manage_component actions to the appropriate handler
|
|
25
|
-
*/
|
|
26
|
-
export async function manageComponent(args) {
|
|
27
|
-
const { action, ...params } = args;
|
|
28
|
-
switch (action) {
|
|
29
|
-
case 'create': return createComponent(params);
|
|
30
|
-
case 'update': return updateComponent(params);
|
|
31
|
-
case 'delete': return deleteComponent(params);
|
|
32
|
-
case 'add_dependency': return addComponentDependency(params);
|
|
33
|
-
case 'remove_dependency': return removeComponentDependency(params);
|
|
34
|
-
case 'add_navigation': return addComponentNavigation(params);
|
|
35
|
-
case 'remove_navigation': return removeComponentNavigation(params);
|
|
36
|
-
case 'derive_navigation': return deriveComponentNavigation(params);
|
|
37
|
-
case 'discover': return discoverComponents(params);
|
|
38
|
-
case 'import': return importComponents(params);
|
|
39
|
-
default: throw new Error(`Unknown action: ${action}`);
|
|
40
|
-
}
|
|
41
|
-
}
|
|
42
|
-
|
|
43
|
-
/**
|
|
44
|
-
* List components with optional enrichment data.
|
|
45
|
-
* Uses local cache when available and fresh for basic list, falls back to API.
|
|
46
|
-
*/
|
|
47
|
-
export async function listComponents(args) {
|
|
48
|
-
const { componentId, componentSlug, include, ...rest } = args;
|
|
49
|
-
const isSingleLookup = componentId || componentSlug;
|
|
50
|
-
const includes = include || [];
|
|
51
|
-
|
|
52
|
-
// Timeline is NOT a supported include. It is rejected here — loudly, and on
|
|
53
|
-
// every path — rather than dropped, because the API never had the data:
|
|
54
|
-
// nothing populates Component.CachedTimeline (there is no cached_timeline
|
|
55
|
-
// column, and repository_postgres.go neither reads nor writes the field), so
|
|
56
|
-
// Service.GetComponentTimeline always returns a zeroed struct. The single
|
|
57
|
-
// lookup therefore did not "work" while lists silently degraded; it answered
|
|
58
|
-
// all-zeros, which reads as a real empty result and is the worse failure of
|
|
59
|
-
// the two. Removing it from the schema enum is not enough on its own — a
|
|
60
|
-
// client that ignores the enum must still get an error, not a plain list.
|
|
61
|
-
if (includes.includes('timeline')) {
|
|
62
|
-
throw new Error(
|
|
63
|
-
'include:["timeline"] is not supported: component timeline data is not computed by the ' +
|
|
64
|
-
'API, so it would return all zeros rather than real data. Use include:["stats"] for task ' +
|
|
65
|
-
'counts (per component with componentId/componentSlug, or project-wide without one).'
|
|
66
|
-
);
|
|
67
|
-
}
|
|
68
|
-
|
|
69
|
-
// Stats. With an identifier: that component. Without one: every component in
|
|
70
|
-
// the project (the endpoint takes the identifier as optional). This branch
|
|
71
|
-
// used to require isSingleLookup, so `include:["stats"]` on a list fell
|
|
72
|
-
// through to the plain list and the stats request vanished without an error.
|
|
73
|
-
if (includes.includes('stats')) {
|
|
74
|
-
return getComponentStats({ projectId: rest.projectId, componentId, componentSlug });
|
|
75
|
-
}
|
|
76
|
-
|
|
77
|
-
// Navigation graph (project-wide screen→screen edges, E-223).
|
|
78
|
-
if (includes.includes('navigation')) {
|
|
79
|
-
return getComponentNavigation({ projectId: rest.projectId });
|
|
80
|
-
}
|
|
81
|
-
|
|
82
|
-
// Dependency graph (project-wide). The kind filter must be forwarded: this
|
|
83
|
-
// branch bypasses the plain list entirely, so dropping `kind` here silently
|
|
84
|
-
// returned the WHOLE inventory to a caller who asked for one kind.
|
|
85
|
-
if (includes.includes('dependency_graph')) {
|
|
86
|
-
const graphArgs = { projectId: rest.projectId };
|
|
87
|
-
if (rest.kind) graphArgs.kind = rest.kind;
|
|
88
|
-
return getComponentDependencyGraph(graphArgs);
|
|
89
|
-
}
|
|
90
|
-
|
|
91
|
-
// Basic list — use cache if available. Skip the cache when a kind filter is
|
|
92
|
-
// set (E-168): the cache holds the full inventory, not kind-filtered subsets.
|
|
93
|
-
if (rest.projectId && !isSingleLookup && !rest.kind) {
|
|
94
|
-
const cached = await getCachedComponents(rest.projectId);
|
|
95
|
-
if (cached !== null) {
|
|
96
|
-
return { components: cached, cached: true };
|
|
97
|
-
}
|
|
98
|
-
}
|
|
99
|
-
|
|
100
|
-
// The identifier must be forwarded: it is destructured out of `rest` above,
|
|
101
|
-
// so without this a single lookup fell through to a plain list and returned
|
|
102
|
-
// the WHOLE inventory to a caller who asked for one component — the same
|
|
103
|
-
// dropped-filter shape as the `kind` bug, and just as silent.
|
|
104
|
-
const listArgs = { ...rest };
|
|
105
|
-
if (componentId) listArgs.componentId = componentId;
|
|
106
|
-
if (componentSlug) listArgs.componentSlug = componentSlug;
|
|
107
|
-
|
|
108
|
-
const result = await callZephlyAPI('mcpListComponents', listArgs);
|
|
109
|
-
|
|
110
|
-
// Cache the results for future use. Only cache the FULL inventory — neither a
|
|
111
|
-
// kind-filtered result (e.g. kind='area' from the project-context path) nor a
|
|
112
|
-
// single lookup may overwrite the cache, or an unfiltered list_components
|
|
113
|
-
// would then read back a subset (mirrors the cache-read skip above). The
|
|
114
|
-
// single-lookup guard matters as of the fix above: while the identifier was
|
|
115
|
-
// being dropped this call returned the full list, so caching it was harmless.
|
|
116
|
-
if (rest.projectId && !isSingleLookup && !rest.kind && result?.components) {
|
|
117
|
-
const summaries = result.components.map((c) => ({
|
|
118
|
-
id: c.id,
|
|
119
|
-
name: c.name,
|
|
120
|
-
description: c.description || '',
|
|
121
|
-
}));
|
|
122
|
-
await updateCacheSections({ components: summaries });
|
|
123
|
-
}
|
|
124
|
-
|
|
125
|
-
return result;
|
|
126
|
-
}
|
|
127
|
-
|
|
128
|
-
/**
|
|
129
|
-
* Create a new component
|
|
130
|
-
*/
|
|
131
|
-
async function createComponent(args) {
|
|
132
|
-
// `links` is applied by the MCP layer after the component exists (E-225).
|
|
133
|
-
const { links, ...createArgs } = args;
|
|
134
|
-
const result = await callZephlyAPI('mcpCreateComponent', createArgs);
|
|
135
|
-
await invalidateCacheSection('components');
|
|
136
|
-
|
|
137
|
-
// Attach create-time links (E-225) — best effort, never fails the create.
|
|
138
|
-
await attachLinks(result, {
|
|
139
|
-
sourceType: 'component',
|
|
140
|
-
sourceId: result?.componentId,
|
|
141
|
-
links,
|
|
142
|
-
});
|
|
143
|
-
|
|
144
|
-
return result;
|
|
145
|
-
}
|
|
146
|
-
|
|
147
|
-
/**
|
|
148
|
-
* Update an existing component
|
|
149
|
-
*/
|
|
150
|
-
async function updateComponent(args) {
|
|
151
|
-
const result = await callZephlyAPI('mcpUpdateComponent', args);
|
|
152
|
-
await invalidateCacheSection('components');
|
|
153
|
-
return result;
|
|
154
|
-
}
|
|
155
|
-
|
|
156
|
-
/**
|
|
157
|
-
* Delete a component
|
|
158
|
-
*/
|
|
159
|
-
async function deleteComponent(args) {
|
|
160
|
-
const result = await callZephlyAPI('mcpDeleteComponent', args);
|
|
161
|
-
await invalidateCacheSection('components');
|
|
162
|
-
return result;
|
|
163
|
-
}
|
|
164
|
-
|
|
165
|
-
/**
|
|
166
|
-
* Get stats for a component
|
|
167
|
-
*/
|
|
168
|
-
async function getComponentStats(args) {
|
|
169
|
-
return callZephlyAPI('mcpGetComponentStats', args);
|
|
170
|
-
}
|
|
171
|
-
|
|
172
|
-
/**
|
|
173
|
-
* Add a dependency between two components
|
|
174
|
-
*/
|
|
175
|
-
async function addComponentDependency(args) {
|
|
176
|
-
return callZephlyAPI('mcpAddComponentDependency', args);
|
|
177
|
-
}
|
|
178
|
-
|
|
179
|
-
/**
|
|
180
|
-
* Remove a dependency between two components
|
|
181
|
-
*/
|
|
182
|
-
async function removeComponentDependency(args) {
|
|
183
|
-
return callZephlyAPI('mcpRemoveComponentDependency', args);
|
|
184
|
-
}
|
|
185
|
-
|
|
186
|
-
/**
|
|
187
|
-
* Get the full dependency graph for a project's components
|
|
188
|
-
*/
|
|
189
|
-
async function getComponentDependencyGraph(args) {
|
|
190
|
-
return callZephlyAPI('mcpGetComponentDependencyGraph', args);
|
|
191
|
-
}
|
|
192
|
-
|
|
193
|
-
/**
|
|
194
|
-
* Add a screen→screen navigation edge (E-223). Endpoints may be given as IDs or
|
|
195
|
-
* slugs. Idempotent — re-adding an existing edge is a no-op.
|
|
196
|
-
*/
|
|
197
|
-
async function addComponentNavigation(args) {
|
|
198
|
-
return callZephlyAPI('mcpAddComponentNavigation', args);
|
|
199
|
-
}
|
|
200
|
-
|
|
201
|
-
/**
|
|
202
|
-
* Remove a screen→screen navigation edge (E-223). Idempotent.
|
|
203
|
-
*/
|
|
204
|
-
async function removeComponentNavigation(args) {
|
|
205
|
-
return callZephlyAPI('mcpRemoveComponentNavigation', args);
|
|
206
|
-
}
|
|
207
|
-
|
|
208
|
-
/**
|
|
209
|
-
* Re-derive the screen-flow map from the synced Context Manifest (E-239 #2409).
|
|
210
|
-
*
|
|
211
|
-
* Idempotent, and it cannot destroy hand-drawn work: it reconciles only edges
|
|
212
|
-
* still at origin auto/inferred under its own rules, so a human's edge — or one
|
|
213
|
-
* they promoted — is left alone and reported as `spared`.
|
|
214
|
-
*
|
|
215
|
-
* Returns { result: { derived, byRule, retracted, spared, unresolved }, projectId }.
|
|
216
|
-
* `unresolved` names navigation references no component matched, which is how a
|
|
217
|
-
* stale inventory shows itself instead of the map silently shrinking.
|
|
218
|
-
*/
|
|
219
|
-
async function deriveComponentNavigation({ projectId }) {
|
|
220
|
-
return callZephlyAPI('mcpDeriveComponentNavigation', { projectId });
|
|
221
|
-
}
|
|
222
|
-
|
|
223
|
-
/**
|
|
224
|
-
* Read the project's navigation edges (E-223) so an agent can see the existing
|
|
225
|
-
* flow before writing to it. Returns { edges, projectId }.
|
|
226
|
-
*/
|
|
227
|
-
async function getComponentNavigation(args) {
|
|
228
|
-
return callZephlyAPI('mcpGetComponentNavigation', args);
|
|
229
|
-
}
|
|
230
|
-
|
|
231
|
-
/**
|
|
232
|
-
* Discover candidate UI surfaces (page / component kinds) for a project from the
|
|
233
|
-
* Context Manifest that are not yet in the inventory (E-168). Manifest-assisted;
|
|
234
|
-
* returns { surfaces: [{kind,name,sourcePath,route,framework,summary}], projectId, count }.
|
|
235
|
-
*/
|
|
236
|
-
async function discoverComponents({ projectId }) {
|
|
237
|
-
return callZephlyAPI('mcpDiscoverComponents', { projectId });
|
|
238
|
-
}
|
|
239
|
-
|
|
240
|
-
/**
|
|
241
|
-
* Bulk-import UI surfaces as components (E-168). Each surface becomes a component,
|
|
242
|
-
* optionally nested under parentComponentId and/or linked to featureId.
|
|
243
|
-
* Returns { imported: [{componentId,slug,name,kind,sourcePath,linked}], count }.
|
|
244
|
-
*/
|
|
245
|
-
async function importComponents({ projectId, parentComponentId, featureId, surfaces }) {
|
|
246
|
-
// Per-surface `links` are applied by the MCP layer after import, so a 90-screen
|
|
247
|
-
// import can carry its links in one call (E-225) instead of 90 manage_link
|
|
248
|
-
// round trips.
|
|
249
|
-
const list = Array.isArray(surfaces) ? surfaces : [];
|
|
250
|
-
const body = {
|
|
251
|
-
projectId,
|
|
252
|
-
surfaces: list.map(({ links, ...surface }) => surface),
|
|
253
|
-
};
|
|
254
|
-
if (parentComponentId) body.parentComponentId = parentComponentId;
|
|
255
|
-
if (featureId) body.featureId = featureId;
|
|
256
|
-
const result = await callZephlyAPI('mcpImportComponents', body);
|
|
257
|
-
await invalidateCacheSection('components');
|
|
258
|
-
|
|
259
|
-
// Match each imported component back to the surface that asked for links.
|
|
260
|
-
// Matching on name + sourcePath rather than index because the API is free to
|
|
261
|
-
// skip surfaces that already exist.
|
|
262
|
-
const imported = Array.isArray(result?.imported) ? result.imported : [];
|
|
263
|
-
const linkResults = [];
|
|
264
|
-
for (const surface of list) {
|
|
265
|
-
if (!Array.isArray(surface?.links) || surface.links.length === 0) continue;
|
|
266
|
-
const match = imported.find(
|
|
267
|
-
(c) => c?.name === surface.name && (!surface.sourcePath || c?.sourcePath === surface.sourcePath),
|
|
268
|
-
);
|
|
269
|
-
if (!match?.componentId) continue;
|
|
270
|
-
const outcome = await applyLinks({
|
|
271
|
-
sourceType: 'component',
|
|
272
|
-
sourceId: match.componentId,
|
|
273
|
-
links: surface.links,
|
|
274
|
-
});
|
|
275
|
-
linkResults.push({ componentId: match.componentId, ...outcome });
|
|
276
|
-
}
|
|
277
|
-
if (linkResults.length > 0 && result && typeof result === 'object') {
|
|
278
|
-
result.links = linkResults;
|
|
279
|
-
}
|
|
280
|
-
|
|
281
|
-
return result;
|
|
282
|
-
}
|
package/tools/components.js
DELETED
|
@@ -1,249 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Component Tools
|
|
3
|
-
* MCP tools for the unified UI inventory (E-168).
|
|
4
|
-
*
|
|
5
|
-
* NAMING (E-107): a project's type renames the `area` kind only — a marketing
|
|
6
|
-
* project calls an area a "Channel", a sales project a "Segment". The screen,
|
|
7
|
-
* page and component kinds keep their literal names, because "Channel: screen"
|
|
8
|
-
* is nonsense. Read the word from `get_current_project_context().terminology`.
|
|
9
|
-
*
|
|
10
|
-
* A Component is ONE concept that spans four kinds:
|
|
11
|
-
* - area — coarse codebase module / domain (api, web, mcp-server); the
|
|
12
|
-
* legacy meaning. The task component-picker uses kind=area.
|
|
13
|
-
* - screen — a mobile / Flutter screen
|
|
14
|
-
* - page — a web route / page
|
|
15
|
-
* - component — a reusable UI component
|
|
16
|
-
* There is NO separate "UI surface" entity — surfaces ARE components. Components
|
|
17
|
-
* self-nest via parentComponentId (e.g. web → Sprint Board → TaskCard) and carry
|
|
18
|
-
* sourcePath / route / framework. They can be discovered from the Context
|
|
19
|
-
* Manifest and bulk-imported.
|
|
20
|
-
*/
|
|
21
|
-
|
|
22
|
-
import { LINKS_ARRAY_SCHEMA } from './link-params.js';
|
|
23
|
-
|
|
24
|
-
export const COMPONENT_TOOLS = [
|
|
25
|
-
{
|
|
26
|
-
name: 'manage_component',
|
|
27
|
-
description: 'Create, update, delete components, manage component dependencies and screen ' +
|
|
28
|
-
'navigation edges, or discover/import ' +
|
|
29
|
-
'UI surfaces. A Component is the unified UI inventory entry — ONE concept spanning kinds ' +
|
|
30
|
-
'area|screen|page|component (there is no separate "UI surface" entity). "area" is the legacy ' +
|
|
31
|
-
'coarse codebase module (api, web, mobile) and is what the task component-picker uses; ' +
|
|
32
|
-
'"screen"/"page"/"component" describe mobile screens, web pages, and reusable UI components. ' +
|
|
33
|
-
'Components self-nest via parentComponentId and carry sourcePath/route/framework. ' +
|
|
34
|
-
'"discover" proposes page/component surfaces from the Context Manifest not yet in the inventory; ' +
|
|
35
|
-
'"import" bulk-creates surfaces (optionally nested under a parent and/or linked to a feature).',
|
|
36
|
-
inputSchema: {
|
|
37
|
-
type: 'object',
|
|
38
|
-
properties: {
|
|
39
|
-
action: {
|
|
40
|
-
type: 'string',
|
|
41
|
-
enum: ['create', 'update', 'delete', 'add_dependency', 'remove_dependency',
|
|
42
|
-
'add_navigation', 'remove_navigation', 'derive_navigation', 'discover', 'import'],
|
|
43
|
-
description: 'Action to perform. "discover" (pass projectId) returns candidate UI surfaces ' +
|
|
44
|
-
'from the Context Manifest not yet in the inventory. "import" (pass projectId + surfaces, ' +
|
|
45
|
-
'optionally parentComponentId + featureId) bulk-creates components from surfaces. ' +
|
|
46
|
-
'"add_navigation"/"remove_navigation" (pass projectId + sourceComponentId + ' +
|
|
47
|
-
'targetComponentId) manage screen\u2192screen navigation edges for the screen-flow map ' +
|
|
48
|
-
'(E-223) \u2014 a DIFFERENT relation from add_dependency: navigation is "you can get ' +
|
|
49
|
-
'there from here" and only valid between screens/pages, whereas a dependency is a code ' +
|
|
50
|
-
'relationship. Read the current flow with list_components include:["navigation"]. ' +
|
|
51
|
-
'"derive_navigation" (pass projectId) RE-DERIVES the whole flow map from the synced ' +
|
|
52
|
-
'Context Manifest \u2014 prefer it to drawing edges by hand, because a derived map stays ' +
|
|
53
|
-
'true for free while an asserted one decays from the moment it is written (E-239). It is ' +
|
|
54
|
-
'idempotent and cannot delete a human\u2019s edge: it reconciles only edges still at ' +
|
|
55
|
-
'origin auto/inferred under its own rules, reporting the rest as `spared`. Check the ' +
|
|
56
|
-
'`unresolved` list in the response \u2014 references no component matched mean the ' +
|
|
57
|
-
'inventory is missing a screen, not that the code has no navigation.',
|
|
58
|
-
},
|
|
59
|
-
// --- Identifiers ---
|
|
60
|
-
projectId: {
|
|
61
|
-
type: 'string',
|
|
62
|
-
description: 'Project ID (required for create, delete, add_dependency, remove_dependency)',
|
|
63
|
-
},
|
|
64
|
-
componentId: {
|
|
65
|
-
type: 'string',
|
|
66
|
-
description: 'Component ID (required for update, delete, add_dependency, remove_dependency)',
|
|
67
|
-
},
|
|
68
|
-
componentSlug: {
|
|
69
|
-
type: 'string',
|
|
70
|
-
description: 'URL-friendly slug of the component (alternative to componentId for update, delete, add_dependency, remove_dependency)',
|
|
71
|
-
},
|
|
72
|
-
// --- Create / Update fields ---
|
|
73
|
-
name: {
|
|
74
|
-
type: 'string',
|
|
75
|
-
description: 'Component name (required for create, e.g., "api", "web", "mcp-server", "functions")',
|
|
76
|
-
},
|
|
77
|
-
description: {
|
|
78
|
-
type: 'string',
|
|
79
|
-
description: 'Description of what this component covers (create, update). ' +
|
|
80
|
-
'Writing this stays plain text; it does not create a backing document.',
|
|
81
|
-
},
|
|
82
|
-
color: {
|
|
83
|
-
type: 'string',
|
|
84
|
-
description: 'Hex color for UI display (e.g., "#3B82F6"). Defaults to blue if not provided. (create, update)',
|
|
85
|
-
},
|
|
86
|
-
icon: {
|
|
87
|
-
type: 'string',
|
|
88
|
-
description: 'Icon name for the component (create, update)',
|
|
89
|
-
},
|
|
90
|
-
// --- Unified UI-inventory fields (E-168) (create, update) ---
|
|
91
|
-
kind: {
|
|
92
|
-
type: 'string',
|
|
93
|
-
enum: ['area', 'screen', 'page', 'component'],
|
|
94
|
-
description: 'Kind of inventory entry (default "area"). "area" = coarse codebase module ' +
|
|
95
|
-
'(api/web/mobile — the legacy meaning, used by the task component-picker); "screen" = ' +
|
|
96
|
-
'mobile/Flutter screen; "page" = web route/page; "component" = reusable UI component. ' +
|
|
97
|
-
'(create, update)',
|
|
98
|
-
},
|
|
99
|
-
parentComponentId: {
|
|
100
|
-
type: 'string',
|
|
101
|
-
description: 'Parent component ID for self-nesting (e.g. web → Sprint Board → TaskCard). ' +
|
|
102
|
-
'On update, pass an empty string to clear it (make top-level). (create, update)',
|
|
103
|
-
},
|
|
104
|
-
sourcePath: {
|
|
105
|
-
type: 'string',
|
|
106
|
-
description: 'Repo file/dir path this surface maps to (create, update)',
|
|
107
|
-
},
|
|
108
|
-
route: {
|
|
109
|
-
type: 'string',
|
|
110
|
-
description: 'Route this surface serves, for page/screen kinds (create, update)',
|
|
111
|
-
},
|
|
112
|
-
framework: {
|
|
113
|
-
type: 'string',
|
|
114
|
-
description: 'Framework, e.g. "nextjs", "flutter", "react" (create, update)',
|
|
115
|
-
},
|
|
116
|
-
// --- import fields ---
|
|
117
|
-
featureId: {
|
|
118
|
-
type: 'string',
|
|
119
|
-
description: 'Optionally link every imported component to this feature (import only)',
|
|
120
|
-
},
|
|
121
|
-
surfaces: {
|
|
122
|
-
type: 'array',
|
|
123
|
-
description: 'UI surfaces to import as components (required for import)',
|
|
124
|
-
items: {
|
|
125
|
-
type: 'object',
|
|
126
|
-
properties: {
|
|
127
|
-
kind: {
|
|
128
|
-
type: 'string',
|
|
129
|
-
enum: ['area', 'screen', 'page', 'component'],
|
|
130
|
-
description: 'Kind of surface',
|
|
131
|
-
},
|
|
132
|
-
name: { type: 'string', description: 'Surface / component name' },
|
|
133
|
-
sourcePath: { type: 'string', description: 'Repo file/dir path it maps to' },
|
|
134
|
-
route: { type: 'string', description: 'Route it serves (page/screen kinds)' },
|
|
135
|
-
framework: { type: 'string', description: 'Framework (nextjs, flutter, react, ...)' },
|
|
136
|
-
links: LINKS_ARRAY_SCHEMA,
|
|
137
|
-
},
|
|
138
|
-
required: ['kind', 'name'],
|
|
139
|
-
},
|
|
140
|
-
},
|
|
141
|
-
links: LINKS_ARRAY_SCHEMA,
|
|
142
|
-
// --- Create-only fields ---
|
|
143
|
-
ownerId: {
|
|
144
|
-
type: 'string',
|
|
145
|
-
description: 'Owner user ID — who maintains this component (create only)',
|
|
146
|
-
},
|
|
147
|
-
ownerName: {
|
|
148
|
-
type: 'string',
|
|
149
|
-
description: 'Display name of the owner (required if ownerId is set, create only)',
|
|
150
|
-
},
|
|
151
|
-
teamId: {
|
|
152
|
-
type: 'string',
|
|
153
|
-
description: 'Team ID — which team is responsible (create, update)',
|
|
154
|
-
},
|
|
155
|
-
// --- Update-only fields ---
|
|
156
|
-
order: {
|
|
157
|
-
type: 'number',
|
|
158
|
-
description: 'Display order, lower = first (update only)',
|
|
159
|
-
},
|
|
160
|
-
owner: {
|
|
161
|
-
type: 'object',
|
|
162
|
-
description: 'Component owner object (set to null to clear). Object with id and name fields. (update only)',
|
|
163
|
-
properties: {
|
|
164
|
-
id: { type: 'string', description: 'Owner user ID' },
|
|
165
|
-
name: { type: 'string', description: 'Owner display name' },
|
|
166
|
-
},
|
|
167
|
-
nullable: true,
|
|
168
|
-
},
|
|
169
|
-
// --- Dependency fields (add_dependency, remove_dependency) ---
|
|
170
|
-
targetComponentId: {
|
|
171
|
-
type: 'string',
|
|
172
|
-
description: 'The other end of the edge. For add_dependency/remove_dependency: the ' +
|
|
173
|
-
'component depended on. For add_navigation/remove_navigation: the screen navigated TO ' +
|
|
174
|
-
'(must be kind screen or page).',
|
|
175
|
-
},
|
|
176
|
-
type: {
|
|
177
|
-
type: 'string',
|
|
178
|
-
enum: ['depends_on', 'blocks'],
|
|
179
|
-
description: 'Dependency type: "depends_on" (source needs target) or "blocks" ' +
|
|
180
|
-
'(source blocks target). Required for add_dependency. Cycles are ALLOWED \u2014 code ' +
|
|
181
|
-
'imports are legitimately mutual \u2014 but a dependency that closes one comes back with ' +
|
|
182
|
-
'a `warning` and the `cyclePath` naming the loop. Re-adding an existing dependency is ' +
|
|
183
|
-
'an idempotent no-op that returns `alreadyExisted: true`.',
|
|
184
|
-
},
|
|
185
|
-
// --- Navigation fields (add_navigation, remove_navigation) — E-223 ---
|
|
186
|
-
sourceComponentId: {
|
|
187
|
-
type: 'string',
|
|
188
|
-
description: 'Screen the user navigates FROM (required for add_navigation/remove_navigation, ' +
|
|
189
|
-
'unless sourceComponentSlug is given). Must be kind screen or page.',
|
|
190
|
-
},
|
|
191
|
-
sourceComponentSlug: {
|
|
192
|
-
type: 'string',
|
|
193
|
-
description: 'Slug alternative to sourceComponentId (add_navigation, remove_navigation)',
|
|
194
|
-
},
|
|
195
|
-
targetComponentSlug: {
|
|
196
|
-
type: 'string',
|
|
197
|
-
description: 'Slug alternative to targetComponentId (add_navigation, remove_navigation)',
|
|
198
|
-
},
|
|
199
|
-
},
|
|
200
|
-
required: ['action'],
|
|
201
|
-
},
|
|
202
|
-
},
|
|
203
|
-
{
|
|
204
|
-
name: 'list_components',
|
|
205
|
-
description: 'List components in a project, optionally with stats, dependency graph, or navigation data. ' +
|
|
206
|
-
'Provide componentId (or componentSlug) to get data for a single component — the response is the ' +
|
|
207
|
-
'same shape as a list, with `components` holding just that one; an identifier that matches nothing ' +
|
|
208
|
-
'is an error, never the full inventory. ' +
|
|
209
|
-
'Responses include `descriptionDocumentId` — the id of the backing rich-description Document ' +
|
|
210
|
-
'when the description has been promoted to one (E-189), otherwise omitted.',
|
|
211
|
-
inputSchema: {
|
|
212
|
-
type: 'object',
|
|
213
|
-
properties: {
|
|
214
|
-
projectId: {
|
|
215
|
-
type: 'string',
|
|
216
|
-
description: 'The project ID (required)',
|
|
217
|
-
},
|
|
218
|
-
componentId: {
|
|
219
|
-
type: 'string',
|
|
220
|
-
description: 'Optional component ID — narrows the listing to that one component',
|
|
221
|
-
},
|
|
222
|
-
componentSlug: {
|
|
223
|
-
type: 'string',
|
|
224
|
-
description: 'Optional component slug (alternative to componentId; the slug wins if both are given)',
|
|
225
|
-
},
|
|
226
|
-
kind: {
|
|
227
|
-
type: 'string',
|
|
228
|
-
enum: ['area', 'screen', 'page', 'component'],
|
|
229
|
-
description: 'Optional filter — only return components of this kind. Use kind="area" for ' +
|
|
230
|
-
'the coarse codebase modules the task component-picker uses.',
|
|
231
|
-
},
|
|
232
|
-
include: {
|
|
233
|
-
type: 'array',
|
|
234
|
-
items: {
|
|
235
|
-
type: 'string',
|
|
236
|
-
enum: ['stats', 'dependency_graph', 'navigation'],
|
|
237
|
-
},
|
|
238
|
-
description: 'Optional additional data to include: stats (task counts), dependency_graph ' +
|
|
239
|
-
'(all dependencies; honours the kind filter), ' +
|
|
240
|
-
'navigation (screen\u2192screen navigates_to edges for the screen-flow map, E-223). ' +
|
|
241
|
-
'Note: "timeline" is NOT available \u2014 component timeline data is not computed by the ' +
|
|
242
|
-
'API (nothing populates it, so it read back as all zeros on every path). Use ' +
|
|
243
|
-
'"stats" for task counts.',
|
|
244
|
-
},
|
|
245
|
-
},
|
|
246
|
-
required: ['projectId'],
|
|
247
|
-
},
|
|
248
|
-
},
|
|
249
|
-
];
|