@ezmodo/mcp-server 0.14.4 → 0.18.0

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/tools/epics.js CHANGED
@@ -3,7 +3,7 @@
3
3
  * MCP tools for managing epics
4
4
  *
5
5
  * Project-First Hierarchy: Epics belong to projects (required),
6
- * with optional component grouping and milestone linking. *
6
+ * with optional milestone linking. *
7
7
  * NAMING (E-107): an epic is not called "Epic" everywhere. A project's type
8
8
  * decides its vocabulary — a marketing project calls this a Campaign, a sales
9
9
  * project a Deal, a research project a Study. Read the words from
@@ -15,6 +15,28 @@
15
15
  import { LINKS_ARRAY_SCHEMA, RELATED_ITEM_SCHEMA } from './link-params.js';
16
16
  import { TASK_ITEM_PROPERTIES } from './task-item-schema.js';
17
17
 
18
+ // A link staged on a plan or planned task before it exists (#2199, #2752).
19
+ // Mirrors the desktop LinkDraft shape so a plan an AI saves back keeps the
20
+ // links the desktop staged.
21
+ const PLAN_LINKS_SCHEMA = {
22
+ type: 'array',
23
+ items: {
24
+ type: 'object',
25
+ properties: {
26
+ targetType: { type: 'string' },
27
+ targetId: { type: 'string' },
28
+ title: { type: 'string' },
29
+ source: { type: 'string', enum: ['deterministic', 'suggested', 'manual'] },
30
+ accepted: { type: 'boolean', description: 'false only for a suggestion that was turned down' },
31
+ rule: { type: 'string' },
32
+ confidence: { type: 'number' },
33
+ suggestionId: { type: 'string' },
34
+ matchedPaths: { type: 'array', items: { type: 'string' } },
35
+ },
36
+ required: ['targetType', 'targetId'],
37
+ },
38
+ };
39
+
18
40
  export const EPIC_TOOLS = [
19
41
  {
20
42
  name: 'manage_epic',
@@ -126,11 +148,6 @@ export const EPIC_TOOLS = [
126
148
  },
127
149
  links: LINKS_ARRAY_SCHEMA,
128
150
  // --- Create-only: the epic's breakdown, created in the same request ---
129
- componentId: {
130
- type: 'string',
131
- description: 'Component applied to every task in `tasks` that does not set its own ' +
132
- '(create only). Get available components from get_current_project_context().',
133
- },
134
151
  tasks: {
135
152
  type: 'array',
136
153
  description: 'Child tasks to create WITH the epic, in the same request (create only, 1-40). ' +
@@ -264,4 +281,139 @@ export const EPIC_TOOLS = [
264
281
  },
265
282
  },
266
283
  },
284
+ {
285
+ name: 'get_epic_plan',
286
+ description: 'Read an epic\'s plan (E-259): the planned tasks, notes, status, staged links and any ' +
287
+ 'open planner questions, with ' +
288
+ '`currentRevision`, the version number you must send back to `update_epic_plan`. ' +
289
+ 'Several people and their AIs can work on one plan, so read it right before you change it. ' +
290
+ 'Pass `includeHistory` to see who changed what, in plain sentences, and `revision` to read an older version.',
291
+ inputSchema: {
292
+ type: 'object',
293
+ properties: {
294
+ epicId: { type: 'string', description: 'The epic ID (required)' },
295
+ revision: { type: 'number', description: 'Read this saved version instead of the current one' },
296
+ includeHistory: {
297
+ type: 'boolean',
298
+ description: 'Also return recent versions: who saved each, which AI, and what changed',
299
+ },
300
+ historyLimit: { type: 'number', description: 'How many versions of history to return (default 20)' },
301
+ },
302
+ required: ['epicId'],
303
+ },
304
+ },
305
+ {
306
+ name: 'update_epic_plan',
307
+ description: 'Save an epic\'s plan as a new version (E-259). Send the WHOLE plan, changed where you ' +
308
+ 'mean to change it, plus `baseRevision`: the `currentRevision` you read with `get_epic_plan` ' +
309
+ '(0 when the epic has no plan). Keep each planned task\'s `id` so the change is matched to the right task. ' +
310
+ 'If someone else saved since you read it, nothing is written and you get `PLAN_CONFLICT` with the ' +
311
+ 'current plan and what changed — apply your change to THAT plan and save again with its revision. ' +
312
+ 'Never resend your old copy: that erases their work. ' +
313
+ 'Keep plans simple and readable by anyone: a plain title and one line on why for each task. ' +
314
+ 'Only the epic\'s owner, its creator or an organization admin can save the plan; anyone else ' +
315
+ 'suggests the change with add_epic_comment or asks it as a decision to make (manage_decision).',
316
+ inputSchema: {
317
+ type: 'object',
318
+ properties: {
319
+ epicId: { type: 'string', description: 'The epic ID (required)' },
320
+ baseRevision: {
321
+ type: 'number',
322
+ description: 'The currentRevision you started from (required; 0 for a new plan)',
323
+ },
324
+ plan: {
325
+ type: 'object',
326
+ description: 'The full plan. Fields not listed here (conversation, targetFeatureId, …) ' +
327
+ 'are kept only if you send them back.',
328
+ properties: {
329
+ notes: { type: 'string', description: 'Assumptions, risks and scope notes' },
330
+ status: { type: 'string', enum: ['draft', 'approved'], description: 'draft (default) or approved' },
331
+ proposedTasks: {
332
+ type: 'array',
333
+ items: {
334
+ type: 'object',
335
+ properties: {
336
+ id: { type: 'string', description: 'Keep the id from get_epic_plan; omit for a new task' },
337
+ title: { type: 'string' },
338
+ workType: { type: 'string' },
339
+ description: { type: 'string' },
340
+ steps: { type: 'array', items: { type: 'string' } },
341
+ rationale: { type: 'string', description: 'One line on why this task exists' },
342
+ dependsOnIndices: { type: 'array', items: { type: 'number' } },
343
+ needsHumanGate: { type: 'boolean' },
344
+ links: {
345
+ ...PLAN_LINKS_SCHEMA,
346
+ description: 'Links to create with this task when the plan is approved. Send back ' +
347
+ 'what get_epic_plan returned',
348
+ },
349
+ },
350
+ required: ['title'],
351
+ },
352
+ },
353
+ links: {
354
+ ...PLAN_LINKS_SCHEMA,
355
+ description: 'Links for the epic, applied when the plan is approved. Send back what ' +
356
+ 'get_epic_plan returned',
357
+ },
358
+ questions: {
359
+ type: 'array',
360
+ description: 'The planner\'s open clarifying questions, kept as returned by get_epic_plan. ' +
361
+ 'A choice that needs several people\'s view belongs on the epic as a decision to make ' +
362
+ '(manage_decision with epicId) instead.',
363
+ items: {
364
+ type: 'object',
365
+ properties: {
366
+ id: { type: 'string' },
367
+ question: { type: 'string' },
368
+ header: { type: 'string' },
369
+ options: {
370
+ type: 'array',
371
+ items: {
372
+ type: 'object',
373
+ properties: { label: { type: 'string' }, description: { type: 'string' } },
374
+ required: ['label'],
375
+ },
376
+ },
377
+ multiSelect: { type: 'boolean' },
378
+ },
379
+ required: ['question'],
380
+ },
381
+ },
382
+ },
383
+ },
384
+ },
385
+ required: ['epicId', 'baseRevision', 'plan'],
386
+ },
387
+ },
388
+ {
389
+ name: 'list_epic_comments',
390
+ description: 'Read an epic\'s discussion (E-259), oldest first. Each comment says who wrote it and, ' +
391
+ 'when an AI wrote it for them, which AI (`agentName`). Replies carry `parentId`. ' +
392
+ 'Read this before planning or changing a shared epic: other people\'s questions and objections live here.',
393
+ inputSchema: {
394
+ type: 'object',
395
+ properties: {
396
+ epicId: { type: 'string', description: 'The epic ID (required)' },
397
+ limit: { type: 'number', description: 'Maximum comments to return (default 100, max 500)' },
398
+ },
399
+ required: ['epicId'],
400
+ },
401
+ },
402
+ {
403
+ name: 'add_epic_comment',
404
+ description: 'Post to an epic\'s discussion (E-259), or reply to a comment with `parentId`. ' +
405
+ 'Posted as the person whose key you use, marked as written by you. ' +
406
+ 'Mentioned people, the author you reply to and everyone following the epic are notified, ' +
407
+ 'and posting makes that person follow it. Write plainly: one point per comment, readable by anyone.',
408
+ inputSchema: {
409
+ type: 'object',
410
+ properties: {
411
+ epicId: { type: 'string', description: 'The epic ID (required)' },
412
+ content: { type: 'string', description: 'The comment (markdown)' },
413
+ parentId: { type: 'string', description: 'Reply to this comment' },
414
+ mentions: { type: 'array', items: { type: 'string' }, description: 'User IDs to notify' },
415
+ },
416
+ required: ['epicId', 'content'],
417
+ },
418
+ },
267
419
  ];
package/tools/features.js CHANGED
@@ -33,8 +33,13 @@ export const FEATURE_TOOLS = [
33
33
  properties: {
34
34
  action: {
35
35
  type: 'string',
36
- enum: ['create', 'update', 'delete', 'link', 'unlink', 'promote_epic', 'generate_how_it_works', 'apply_how_it_works', 'apply_init'],
37
- description: 'Action to perform. "promote_epic" creates a feature from an existing epic ' +
36
+ enum: [
37
+ 'create', 'update', 'delete', 'link', 'unlink', 'paths',
38
+ 'promote_epic', 'generate_how_it_works', 'apply_how_it_works', 'apply_init',
39
+ ],
40
+ description: 'Action to perform. "paths" adds, removes or replaces the code paths the ' +
41
+ 'feature OWNS (featureId + paths + pathsMode) — work touching a file under an owned path ' +
42
+ 'auto-links to the feature. "promote_epic" creates a feature from an existing epic ' +
38
43
  '(inheriting its title/description/scope) and links the epic to it. ' +
39
44
  '"generate_how_it_works" (re)generates the feature\'s grounded, source-attributed ' +
40
45
  '"how it works" living description from its linked work + code (requires featureId; ' +
@@ -116,6 +121,36 @@ export const FEATURE_TOOLS = [
116
121
  type: 'string',
117
122
  description: 'ID of the artifact to link/unlink (required for link, unlink)',
118
123
  },
124
+ // --- paths fields (E-258) ---
125
+ paths: {
126
+ type: 'array',
127
+ items: {
128
+ type: 'object',
129
+ properties: {
130
+ projectId: {
131
+ type: 'string',
132
+ description: 'Project the path lives in. Must be one the feature spans ' +
133
+ '(any project in the org for an org-wide feature).',
134
+ },
135
+ sourcePath: {
136
+ type: 'string',
137
+ description: 'Repo-relative file or folder, e.g. "api/internal/core/features" or ' +
138
+ '"web/src/app/checkout/page.tsx". Matched by exact prefix, NOT glob.',
139
+ },
140
+ },
141
+ required: ['projectId', 'sourcePath'],
142
+ },
143
+ description: 'Code paths for the "paths" action. A file resolves to the feature owning its ' +
144
+ 'LONGEST matching folder or the exact file. Own what is characteristic of the capability; ' +
145
+ 'leave shared plumbing (e.g. web/src/components/ui, api/internal/api/router.go) owned by no ' +
146
+ 'feature — a path owned by several features only ever produces suggestions.',
147
+ },
148
+ pathsMode: {
149
+ type: 'string',
150
+ enum: ['add', 'remove', 'replace'],
151
+ description: 'How "paths" applies (default "add"). "replace" makes the list the feature\'s ' +
152
+ 'entire path set; an empty list clears it.',
153
+ },
119
154
  // --- apply_init fields (E-167) ---
120
155
  nodes: {
121
156
  type: 'array',
@@ -164,6 +199,10 @@ export const FEATURE_TOOLS = [
164
199
  type: 'boolean',
165
200
  description: 'If true (single lookup), also return the feature\'s linked artifacts',
166
201
  },
202
+ includePaths: {
203
+ type: 'boolean',
204
+ description: 'If true (single lookup), also return the code paths the feature owns (E-258).',
205
+ },
167
206
  includeDetail: {
168
207
  type: 'boolean',
169
208
  description: 'If true (single lookup), return the aggregated detail: linked artifacts ' +
@@ -33,13 +33,13 @@ export const GIT_CONTEXT_TOOLS = [
33
33
  'Returns project context if found, ' +
34
34
  'or null if not configured. Use this BEFORE other MCP tools to ' +
35
35
  'auto-detect which project you\'re working on. Project-First: ' +
36
- 'Returns projectId, components, and tags that can be used ' +
37
- 'directly with task/epic creation tools. Caches tags and ' +
38
- 'components locally with 1-day TTL for fast access. ' +
36
+ 'Returns projectId and tags that can be used ' +
37
+ 'directly with task/epic creation tools. Caches tags ' +
38
+ 'locally with 1-day TTL for fast access. ' +
39
39
  'Also returns `projectType` and `terminology` — the words this project ' +
40
- 'uses for epics, tasks and components and for their statuses (E-107). ' +
40
+ 'uses for epics and tasks and for their statuses (E-107). ' +
41
41
  'Write anything a person reads in those words: a marketing project calls ' +
42
- 'an epic a "Campaign" and a component a "Channel". Keep the API field ' +
42
+ 'an epic a "Campaign". Keep the API field ' +
43
43
  'names (epicId, taskId) as they are. A null `terminology` means plain ' +
44
44
  'English, not an error.',
45
45
  inputSchema: {
package/tools/graph.js CHANGED
@@ -4,7 +4,7 @@
4
4
  * The living navigation graph (E-209): a browsable, cross-entity projection
5
5
  * over the entity_links link graph (relates_to / blocked_by). One read lets an
6
6
  * agent walk "what connects to this" from any node — features, epics, tasks,
7
- * goals, milestones, components, designs, decisions, documents, test suites,
7
+ * goals, milestones, catalogs, designs, decisions, documents, test suites,
8
8
  * feature flags — instead of issuing N separate list_links calls and stitching
9
9
  * the results together.
10
10
  *
package/tools/index.js CHANGED
@@ -10,13 +10,13 @@
10
10
 
11
11
  import { ORGANIZATION_TOOLS } from './organizations.js';
12
12
  import { PROJECT_TOOLS } from './projects.js';
13
- import { COMPONENT_TOOLS } from './components.js';
14
13
  import { EPIC_TOOLS } from './epics.js';
15
14
  import { MILESTONE_TOOLS } from './milestones.js';
16
15
  import { FEATURE_TOOLS } from './features.js';
17
16
  import { DECISION_TOOLS } from './decisions.js';
18
17
  import { DESIGN_TOOLS } from './designs.js';
19
18
  import { CATALOG_TOOLS } from './catalogs.js';
19
+ import { UNMAPPED_PATH_TOOLS } from './unmapped-paths.js';
20
20
  import { FEATURE_FLAG_TOOLS } from './feature-flags.js';
21
21
  import { TASK_TOOLS } from './tasks.js';
22
22
  import { DOCUMENT_TOOLS } from './documents.js';
@@ -48,13 +48,13 @@ export const TOOLS = [
48
48
  ...AUTH_TOOLS,
49
49
  ...ORGANIZATION_TOOLS,
50
50
  ...PROJECT_TOOLS,
51
- ...COMPONENT_TOOLS,
52
51
  ...EPIC_TOOLS,
53
52
  ...MILESTONE_TOOLS,
54
53
  ...FEATURE_TOOLS,
55
54
  ...DECISION_TOOLS,
56
55
  ...DESIGN_TOOLS,
57
56
  ...CATALOG_TOOLS,
57
+ ...UNMAPPED_PATH_TOOLS,
58
58
  ...FEATURE_FLAG_TOOLS,
59
59
  ...TASK_TOOLS,
60
60
  ...RECURRING_TASK_TOOLS,
@@ -20,7 +20,6 @@ export const LINKABLE_TYPES = [
20
20
  'epic',
21
21
  'project',
22
22
  'document',
23
- 'component',
24
23
  'feature',
25
24
  'decision',
26
25
  'design',
package/tools/links.js CHANGED
@@ -21,7 +21,7 @@ export const LINK_TOOLS = [
21
21
  'dependencies (blocked_by) and soft cross-entity links (relates_to) ' +
22
22
  'uniformly. blocked_by is only valid for task→task and epic→epic; ' +
23
23
  'relates_to works between any of: task, epic, project, document, ' +
24
- 'component, feature, decision, design, test_suite, feature_flag ' +
24
+ 'feature, decision, design, test_suite, feature_flag, catalog, catalog_item ' +
25
25
  '(e.g. link a feature to the feature_flag that gates it). ' +
26
26
  'Idempotent — re-adding an existing link is a no-op, removing an ' +
27
27
  'absent link is a no-op. Use action "verify" to stamp a link\'s ' +
@@ -143,10 +143,13 @@ export const LINK_TOOLS = [
143
143
  name: 'resolve_links',
144
144
  description:
145
145
  'Given files you have touched (or are about to), return the entities you should link to: ' +
146
- 'the components screens, pages, codebase areas that own those paths, plus how confident ' +
147
- 'the match is. Read-only; nothing is written.\n\n' +
146
+ 'the features (product capabilities) whose owned code paths cover them, plus how confident ' +
147
+ 'each match is. Read-only; nothing is written.\n\n' +
148
+ '`features.owned` are capabilities that solely own a path: link them. `features.shared` are ' +
149
+ 'paths several features claim: pick the one your work actually advanced. `unmatchedPaths` ' +
150
+ 'are owned by no feature — a gap in the feature map, fixed with manage_feature action:"paths".\n\n' +
148
151
  'Call it BEFORE creating work to fill in the `links` param, or after a change set to check ' +
149
- 'you have not missed anything. If it returns a component you did not expect, that is a signal ' +
152
+ 'you have not missed anything. If it returns a feature you did not expect, that is a signal ' +
150
153
  'your change is broader than you thought — worth reading before you continue.\n\n' +
151
154
  'Paths are repo-relative (e.g. "mobile/lib/features/auth/screens/login_screen.dart").',
152
155
  inputSchema: {
@@ -185,7 +188,6 @@ export const LINK_TOOLS = [
185
188
  items: { type: 'string' },
186
189
  description: 'Repo-relative file paths the work touches',
187
190
  },
188
- componentId: { type: 'string', description: 'A component the work explicitly names' },
189
191
  epicId: { type: 'string', description: 'The epic the work belongs to' },
190
192
  trigger: {
191
193
  type: 'string',
package/tools/projects.js CHANGED
@@ -7,7 +7,7 @@
7
7
  * creative, research, implementation, custom.
8
8
  *
9
9
  * A project's type decides what its work is CALLED (E-107). A marketing project
10
- * calls an epic a "Campaign" and a component a "Channel"; a sales project calls
10
+ * calls an epic a "Campaign"; a sales project calls
11
11
  * a task an "Activity". `get_project` returns the resolved vocabulary as
12
12
  * `terminology`, and `get_current_project_context` caches it. Use those words
13
13
  * when writing anything a person reads — titles, descriptions, chat replies —
@@ -106,7 +106,7 @@ export const PROJECT_TOOLS = [
106
106
  'Responses include `descriptionDocumentId` — the id of the backing rich-description Document ' +
107
107
  'when the description has been promoted to one (E-189), otherwise omitted. ' +
108
108
  'By-ID responses also carry `type` and `terminology` — the words this project uses for ' +
109
- 'epics, tasks and components, and for their statuses (E-107). Write prose in those words; ' +
109
+ 'epics and tasks, and for their statuses (E-107). Write prose in those words; ' +
110
110
  'a marketing project calls an epic a "Campaign". `terminology` is absent for a type with no ' +
111
111
  'template, which means plain English, not an error.',
112
112
  inputSchema: {
@@ -49,7 +49,6 @@ const INLINE_TASK_SCHEMA = {
49
49
  steps: { type: 'array', items: { type: 'string' } },
50
50
  priority: { type: 'string', enum: ['low', 'medium', 'high', 'urgent'] },
51
51
  taskType: TASK_TYPE_PROPERTY,
52
- componentId: { type: 'string' },
53
52
  epicId: { type: 'string' },
54
53
  tagIds: { type: 'array', items: { type: 'string' } },
55
54
  assigneeId: { type: 'string' },
@@ -32,12 +32,6 @@ export const TASK_ITEM_PROPERTIES = {
32
32
  description: 'Initial status (defaults to todo)',
33
33
  },
34
34
  taskType: TASK_TYPE_PROPERTY,
35
- componentId: { type: 'string', description: 'DEPRECATED single component; overrides the shared componentId. Prefer componentIds.' },
36
- componentIds: {
37
- type: 'array',
38
- items: { type: 'string' },
39
- description: 'Every component this task touches; overrides the shared componentId/componentIds.',
40
- },
41
35
  parentTaskId: { type: 'string', description: 'Parent task link' },
42
36
  estimatedHours: { type: 'number' },
43
37
  assigneeType: { type: 'string', enum: ['human', 'ai'] },
package/tools/tasks.js CHANGED
@@ -3,7 +3,8 @@
3
3
  * MCP tools for managing tasks
4
4
  *
5
5
  * Project-First Hierarchy: Tasks belong to projects (required), with optional
6
- * component and epic grouping.
6
+ * epic grouping. Code links are derived from the files a task touches, which
7
+ * resolve to the features that own them (E-258).
7
8
  *
8
9
  * NAMING (E-107): a project's type renames a task and its statuses — a sales
9
10
  * project calls a task an "Activity" and calls `in_review` "Awaiting Approval".
@@ -24,7 +25,8 @@ export const TASK_TOOLS = [
24
25
  'to discover relevant files, patterns, and dependencies. (2) Write descriptions that explain WHY ' +
25
26
  '(problem/goal), WHERE (specific files/endpoints from context), and HOW (approach using existing patterns). ' +
26
27
  '(3) Write steps that reference specific file paths, not vague instructions. ' +
27
- '(4) Every task should name the components it touches via componentIds — use get_current_project_context() to get available components.',
28
+ '(4) Link the feature the work advances with links:[{targetType:"feature", targetId}] (find it with ' +
29
+ 'search_features), and pass changedFiles — the files resolve to the features that own those paths.',
28
30
  inputSchema: {
29
31
  type: 'object',
30
32
  properties: {
@@ -68,16 +70,6 @@ export const TASK_TOOLS = [
68
70
  description: 'Task description (supports markdown). Use real newlines, not literal \\n. Used by create and update. ' +
69
71
  'Writing this stays plain text; it does not create a backing document.',
70
72
  },
71
- componentId: {
72
- type: 'string',
73
- description: 'DEPRECATED single component. Still accepted \u2014 it is treated as a one-element componentIds \u2014 but prefer componentIds: a task touches every component its work spans, and this field can only name one of them.',
74
- },
75
- componentIds: {
76
- type: 'array',
77
- items: { type: 'string' },
78
- description: 'Every component this task touches. Get available components from get_current_project_context(). ' +
79
- 'On update this REPLACES the set: pass the full list, and an empty array clears it. Wins over componentId when both are given.',
80
- },
81
73
  epicId: {
82
74
  type: 'string',
83
75
  description: 'Epic ID to link this task to. Used by create and update (set to empty string to remove on update).',
@@ -212,8 +204,9 @@ export const TASK_TOOLS = [
212
204
  items: { type: 'string' },
213
205
  description:
214
206
  'Repo-relative paths you touched. Simpler alternative to linkedFiles — just the paths. ' +
215
- 'ezmodo resolves them to the components (screens/pages/areas) that own them and links ' +
216
- 'this task to them automatically; anything it is unsure about comes back as ' +
207
+ 'ezmodo resolves them to the features that own those paths and links this task to them ' +
208
+ 'automatically; a path several features share comes back as a suggestion, as does anything ' +
209
+ 'else it is unsure about, in ' +
217
210
  'linkSuggestions for you to accept or reject. Used by create and update.',
218
211
  },
219
212
  autolink: {
@@ -458,7 +451,7 @@ export const TASK_TOOLS = [
458
451
  'the authentication, plan check, rate-limit accounting and database connection ' +
459
452
  'setup once instead of per task; a burst of individual creates is what made the ' +
460
453
  'app unresponsive for 45 minutes on 2026-07-28. ' +
461
- 'Put shared values (projectId, epicId, componentIds) at the TOP LEVEL and let the ' +
454
+ 'Put shared values (projectId, epicId) at the TOP LEVEL and let the ' +
462
455
  'items inherit them — only override per item where a task genuinely differs. ' +
463
456
  'Each item accepts the same fields as manage_task action:"create". ' +
464
457
  'Returns a per-item result array: on partial failure retry ONLY the items marked ' +
@@ -480,18 +473,6 @@ export const TASK_TOOLS = [
480
473
  description: 'Epic applied to every item that does not set its own. The ' +
481
474
  'usual case for a breakdown: one epic for the whole batch.',
482
475
  },
483
- componentId: {
484
- type: 'string',
485
- description: 'DEPRECATED single component applied to every item that does ' +
486
- 'not name its own. Prefer componentIds.',
487
- },
488
- componentIds: {
489
- type: 'array',
490
- items: { type: 'string' },
491
- description: 'Components applied to every item that does not name its own. ' +
492
- 'An item naming either form keeps its own set. ' +
493
- 'Get available components from get_current_project_context().',
494
- },
495
476
  tasks: {
496
477
  type: 'array',
497
478
  description: 'The tasks to create (1-40). Each item takes the same fields ' +
@@ -515,7 +496,7 @@ export const TASK_TOOLS = [
515
496
  {
516
497
  name: 'search_tasks',
517
498
  description: 'Search for tasks with filters or semantic search. ' +
518
- 'Use for filtered queries (by status, priority, component, etc). ' +
499
+ 'Use for filtered queries (by status, priority, epic, etc). ' +
519
500
  'To get ALL tasks in an epic, prefer get_epic with includeTasks=true. ' +
520
501
  'Use search_tasks with epicId only when you need additional filtering ' +
521
502
  '(e.g., only in_progress tasks within an epic). ' +
@@ -528,10 +509,6 @@ export const TASK_TOOLS = [
528
509
  type: 'string',
529
510
  description: 'Filter by project ID (primary scope)',
530
511
  },
531
- componentId: {
532
- type: 'string',
533
- description: 'Filter by component ID (codebase area)',
534
- },
535
512
  epicId: {
536
513
  type: 'string',
537
514
  description: 'Filter by epic ID (feature grouping)',
@@ -620,18 +597,6 @@ export const TASK_TOOLS = [
620
597
  description: 'Repo-relative paths of files that were changed. Recorded in the ' +
621
598
  'description and linked to the task for dependency inference.',
622
599
  },
623
- componentId: {
624
- type: 'string',
625
- description: 'DEPRECATED single component. Still accepted as a one-element ' +
626
- 'componentIds, but prefer componentIds \u2014 untracked work often spans ' +
627
- 'more than one area, which is part of why it was untracked.',
628
- },
629
- componentIds: {
630
- type: 'array',
631
- items: { type: 'string' },
632
- description: 'Every component this work touched. Get available components ' +
633
- 'from get_current_project_context().',
634
- },
635
600
  epicId: {
636
601
  type: 'string',
637
602
  description: 'Epic this work belongs under. Set it when the untracked work ' +
package/tools/todos.js CHANGED
@@ -44,10 +44,6 @@ export const TODO_TOOLS = [
44
44
  type: 'string',
45
45
  description: 'Optional epic to assign the task to (move_to_project only)',
46
46
  },
47
- componentId: {
48
- type: 'string',
49
- description: 'Optional component to assign the task to (move_to_project only)',
50
- },
51
47
  },
52
48
  required: ['action'],
53
49
  },
@@ -0,0 +1,72 @@
1
+ /**
2
+ * Unmapped code path tools (E-258 #2756)
3
+ *
4
+ * When Components were retired, every component source path that belonged to no
5
+ * feature was recorded for review instead of being dropped. Each row is one
6
+ * question — "which capability owns this code?" — and until it is answered, work
7
+ * touching those files auto-links to nothing.
8
+ *
9
+ * The list lived only on the project Features page, so an agent asked to tidy it
10
+ * could set feature paths but never see or close the rows. These two tools make
11
+ * that loop completable.
12
+ */
13
+
14
+ export const UNMAPPED_PATH_TOOLS = [
15
+ {
16
+ name: 'list_unmapped_paths',
17
+ description: 'List a project\'s unmapped code paths — former component paths that no feature owns ' +
18
+ '(E-258). Each row carries the source path and the component it came from. A pending row means ' +
19
+ 'work touching those files links to no capability, so this is the backlog to work through when ' +
20
+ 'a project\'s features are missing code ownership.',
21
+ inputSchema: {
22
+ type: 'object',
23
+ properties: {
24
+ projectId: {
25
+ type: 'string',
26
+ description: 'Project ID (required)',
27
+ },
28
+ status: {
29
+ type: 'string',
30
+ enum: ['pending', 'assigned', 'dismissed', 'all'],
31
+ description: 'Which rows to return (default "pending")',
32
+ },
33
+ },
34
+ required: ['projectId'],
35
+ },
36
+ },
37
+ {
38
+ name: 'resolve_unmapped',
39
+ description: 'Resolve unmapped code paths (E-258). "reconcile" is the one to reach for first: it ' +
40
+ 'closes every pending row a feature has SINCE been given a path for, so the usual flow is to set ' +
41
+ 'ownership with manage_feature action:"paths" and then reconcile, rather than answering rows one ' +
42
+ 'by one. A path several features own stays pending — shared ownership is a judgement, and those ' +
43
+ 'only ever produce link suggestions anyway.\n\n' +
44
+ '"assign" gives ONE row to a feature (adding that feature path, with the usual scope check), and ' +
45
+ '"dismiss" records that the path belongs to no capability — the right answer for shared plumbing.',
46
+ inputSchema: {
47
+ type: 'object',
48
+ properties: {
49
+ action: {
50
+ type: 'string',
51
+ enum: ['reconcile', 'assign', 'dismiss'],
52
+ description: 'reconcile: close every pending row an existing feature path already covers. ' +
53
+ 'assign: give one row (pathId) to featureId. dismiss: mark one row (pathId) as owned by nobody.',
54
+ },
55
+ projectId: {
56
+ type: 'string',
57
+ description: 'Project ID (required for every action)',
58
+ },
59
+ pathId: {
60
+ type: 'string',
61
+ description: 'Unmapped path ID, from list_unmapped_paths (required for assign and dismiss)',
62
+ },
63
+ featureId: {
64
+ type: 'string',
65
+ description: 'Feature to give the path to (required for assign). It must span the project, ' +
66
+ 'or be org-wide.',
67
+ },
68
+ },
69
+ required: ['action', 'projectId'],
70
+ },
71
+ },
72
+ ];
@@ -20,7 +20,6 @@ const TASK_BLUEPRINT_SCHEMA = {
20
20
  steps: { type: 'array', items: { type: 'string' } },
21
21
  taskType: TASK_TYPE_PROPERTY,
22
22
  priority: { type: 'string', enum: ['low', 'medium', 'high', 'urgent'] },
23
- componentId: { type: 'string' },
24
23
  tagIds: { type: 'array', items: { type: 'string' } },
25
24
  },
26
25
  required: ['title'],
@@ -79,7 +78,6 @@ export const WORK_TEMPLATE_TOOLS = [
79
78
  title: { type: 'string', description: 'Instantiate: override the blueprint title.' },
80
79
  epicId: { type: 'string', description: 'Instantiate (task kind): attach the new task to this epic.' },
81
80
  milestoneId: { type: 'string', description: 'Instantiate (epic kind): link the new epic to this milestone.' },
82
- componentId: { type: 'string', description: 'Instantiate (task kind): override component.' },
83
81
  priority: {
84
82
  type: 'string',
85
83
  enum: ['low', 'medium', 'high', 'urgent'],