@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.
@@ -1,6 +1,6 @@
1
1
  /**
2
2
  * Local Cache Utility
3
- * Reads and writes the project config.json cache for tags and components.
3
+ * Reads and writes the project config.json cache for tags.
4
4
  * Looks in `.ezmodo/` first, falls back to legacy `.zephly/`.
5
5
  * Used by MCP handlers to avoid unnecessary API calls for frequently-read data.
6
6
  */
@@ -94,23 +94,9 @@ export async function getCachedTags() {
94
94
  return config.tags || null;
95
95
  }
96
96
 
97
- /**
98
- * Get cached components for a specific project if the cache is fresh.
99
- * @param {string} projectId - Only return components if they match this project
100
- * @returns {Promise<Array|null>} Cached components array, or null if stale/missing/wrong project
101
- */
102
- export async function getCachedComponents(projectId) {
103
- const config = await readConfig();
104
- if (!config) return null;
105
- if (!isCacheFresh(config.lastUpdatedAt)) return null;
106
- // Only return if the cached config belongs to this project
107
- if (config.projectId !== projectId) return null;
108
- return config.components || null;
109
- }
110
-
111
97
  /**
112
98
  * Update specific sections in the config cache. Non-fatal on failure.
113
- * @param {object} updates - Key-value pairs to merge into config (e.g., { tags: [...], components: [...] })
99
+ * @param {object} updates - Key-value pairs to merge into config (e.g., { tags: [...] })
114
100
  */
115
101
  export async function updateCacheSections(updates) {
116
102
  try {
@@ -126,7 +112,7 @@ export async function updateCacheSections(updates) {
126
112
  /**
127
113
  * Invalidate a specific cache section by removing it from config.
128
114
  * The next read will return null, triggering a fresh API call.
129
- * @param {string} section - 'tags' or 'components'
115
+ * @param {string} section - e.g. 'tags'
130
116
  */
131
117
  export async function invalidateCacheSection(section) {
132
118
  try {
@@ -93,7 +93,6 @@ export const REMOTE_SAFE_TOOLS = Object.freeze([
93
93
  'list_attachments',
94
94
  'list_catalog_items',
95
95
  'list_catalogs',
96
- 'list_components',
97
96
  'list_designs',
98
97
  'list_epics',
99
98
  'list_facts',
@@ -110,7 +109,6 @@ export const REMOTE_SAFE_TOOLS = Object.freeze([
110
109
  'list_watched',
111
110
  'manage_access',
112
111
  'manage_catalog',
113
- 'manage_component',
114
112
  'manage_decision',
115
113
  'manage_design',
116
114
  'manage_document',
package/lib/version.js CHANGED
@@ -7,4 +7,4 @@
7
7
  *
8
8
  * Update this when bumping the version in package.json.
9
9
  */
10
- export const MCP_VERSION = '0.14.4';
10
+ export const MCP_VERSION = '0.17.1';
package/package.json CHANGED
@@ -1,14 +1,14 @@
1
1
  {
2
2
  "name": "@ezmodo/mcp-server",
3
- "version": "0.14.4",
3
+ "version": "0.17.1",
4
4
  "description": "MCP server for ezmodo - AI-first project management",
5
5
  "main": "index.js",
6
6
  "type": "module",
7
7
  "bin": {
8
- "mcp-server": "./index.js",
9
- "ezmodo-mcp-server": "./index.js",
10
- "zephly-mcp-server": "./index.js",
11
- "ezmodo-mcp-server-http": "./http.js"
8
+ "mcp-server": "index.js",
9
+ "ezmodo-mcp-server": "index.js",
10
+ "zephly-mcp-server": "index.js",
11
+ "ezmodo-mcp-server-http": "http.js"
12
12
  },
13
13
  "scripts": {
14
14
  "dev": "BUILD_ENV=development node index.js",
@@ -18,7 +18,7 @@ export const COMMAND_PROMPTS = [
18
18
  "local",
19
19
  "remote"
20
20
  ],
21
- "body": "Start tracked work on: **$ARGUMENTS**\n\nFollow the work-tracking contract in this server's instructions. In short:\n\n1. `get_current_project_context()` — cache the `projectId`, note the components,\n tags and `terminology`.\n2. `get_context` with a keyword query drawn from the request above. Read what\n comes back before writing anything: it tells you which files exist, what\n patterns they follow, and what the change will touch.\n3. `resolve_links` on the paths you expect to change. A component you did not\n expect means the work is broader than the request sounds.\n4. Create the work:\n - **Single scope** (a fix, a small feature, a config or docs change) —\n `manage_task action:\"create\"` with `status:\"in_progress\"`, a description\n that says why/where/how, steps that name real files, `componentIds` for\n every component involved, and the right `taskType`.\n - **Multi scope** (spanning areas, or a large refactor) — `manage_epic\n action:\"create\"` with its child tasks in the same request, ordered by\n dependency.\n\nThen report the task number and web URL and begin. Do not edit anything before\nthe task exists — a task created afterwards is a task written from memory.\n\nIf no `.ezmodo/config.json` is found, say so and stop rather than guessing at a\nproject."
21
+ "body": "Start tracked work on: **$ARGUMENTS**\n\nFollow the work-tracking contract in this server's instructions. In short:\n\n1. `get_current_project_context()` — cache the `projectId`, note the tags and\n `terminology`.\n2. `get_context` with a keyword query drawn from the request above. Read what\n comes back before writing anything: it tells you which files exist, what\n patterns they follow, and what the change will touch.\n3. `resolve_links` on the paths you expect to change. A feature you did not\n expect means the work is broader than the request sounds.\n4. Create the work:\n - **Single scope** (a fix, a small feature, a config or docs change) —\n `manage_task action:\"create\"` with `status:\"in_progress\"`, a description\n that says why/where/how, steps that name real files, `changedFiles` for\n the paths involved, a `links` entry for the feature it advances, and the\n right `taskType`.\n - **Multi scope** (spanning several parts of the codebase, or a large refactor) — `manage_epic\n action:\"create\"` with its child tasks in the same request, ordered by\n dependency.\n\nThen report the task number and web URL and begin. Do not edit anything before\nthe task exists — a task created afterwards is a task written from memory.\n\nIf no `.ezmodo/config.json` is found, say so and stop rather than guessing at a\nproject."
22
22
  },
23
23
  {
24
24
  "name": "resume",
@@ -47,6 +47,6 @@ export const COMMAND_PROMPTS = [
47
47
  "local",
48
48
  "remote"
49
49
  ],
50
- "body": "Capture the current uncommitted work as a task.\n\nCurrent branch: run `git rev-parse --abbrev-ref HEAD` and use its output\nChanged files: run `git status --porcelain` and use its output\n\nUse `report_untracked_work` with:\n\n- `projectId` from `get_current_project_context()`\n- `title` — concise, describing what was actually done\n- `description` — what and **why**. Do not restate the branch or file list; they\n are appended automatically as evidence.\n- `changedFiles` — the paths above\n- `branch` — as above\n- `componentIds` — resolve the changed paths with `resolve_links` rather than\n guessing; untracked work often spans more than one area, which is part of why\n it went untracked\n- `origin`:\n - `discovered` — found while working on another task (set `discoveredDuringTaskId`)\n - `scope-creep` — went beyond the active task's scope (set `discoveredDuringTaskId`)\n - `rework` — redoing prior work\n - `untracked` — unplanned standalone work (the default)\n\nIf there is an active task in this session, prefer `discovered` or\n`scope-creep` and link it — the discovery chain is the point of the\nclassification.\n\nThe new task comes back `in_progress` and becomes the active one. Track against\nit for the rest of the work.\n\nExtra context from the user, if any: $ARGUMENTS"
50
+ "body": "Capture the current uncommitted work as a task.\n\nCurrent branch: run `git rev-parse --abbrev-ref HEAD` and use its output\nChanged files: run `git status --porcelain` and use its output\n\nUse `report_untracked_work` with:\n\n- `projectId` from `get_current_project_context()`\n- `title` — concise, describing what was actually done\n- `description` — what and **why**. Do not restate the branch or file list; they\n are appended automatically as evidence.\n- `changedFiles` — the paths above\n- `branch` — as above\n- `origin`:\n - `discovered` — found while working on another task (set `discoveredDuringTaskId`)\n - `scope-creep` — went beyond the active task's scope (set `discoveredDuringTaskId`)\n - `rework` — redoing prior work\n - `untracked` — unplanned standalone work (the default)\n\nIf there is an active task in this session, prefer `discovered` or\n`scope-creep` and link it — the discovery chain is the point of the\nclassification.\n\nThe new task comes back `in_progress` and becomes the active one. Track against\nit for the rest of the work.\n\nExtra context from the user, if any: $ARGUMENTS"
51
51
  }
52
52
  ];
package/tools/catalogs.js CHANGED
@@ -80,8 +80,14 @@ export const CATALOG_TOOLS = [
80
80
  name: 'manage_catalog',
81
81
  description: 'Create, update, or delete an org-level Catalog, link/unlink it to other artifacts, ' +
82
82
  'or push a new versioned snapshot of its contents. A Catalog is a generalized, code-derived ' +
83
- 'catalog (kind = notifications | analytics_events | db_schema | custom); its versions are ' +
83
+ 'catalog (kind = notifications | analytics_events | db_schema | screens | custom); its versions are ' +
84
84
  'immutable { columns, items } snapshots.\n\n' +
85
+ 'SCREENS (E-258): each project has one kind:"screens" catalog, its UI inventory (screens and pages, ' +
86
+ 'keyed by source path). Do not snapshot it by hand: action:"discover_screens" (projectId) lists what ' +
87
+ 'the Context Manifest shows that the catalog lacks, and action:"import_screens" (projectId, screens, ' +
88
+ 'optional featureId) adds them and links each to the feature. action:"sync_screens" (projectId) ' +
89
+ 'reconciles the catalog with the manifest and re-derives the screen flow map (navigates_to edges ' +
90
+ 'between screens); it cannot remove a hand-added screen or a human-confirmed edge.\n\n' +
85
91
  'CAPTURE-AT-BUILD (important): whenever you change the catalog\'s source of truth in code, call ' +
86
92
  'action:"snapshot" and put the source path/commit in `source`. The server is checksum-gated — ' +
87
93
  're-snapshotting unchanged content creates no new version and is a cheap no-op — so it is safe ' +
@@ -97,7 +103,7 @@ export const CATALOG_TOOLS = [
97
103
  '`go run ./cmd/dbschema-snapshot -upload -catalog-id <id>` from api/ introspects and uploads the ' +
98
104
  'whole schema without passing it through the model at all — cheaper still than a patch.\n\n' +
99
105
  'LINK, DON\'T CONTAIN: use action:"link"/"unlink" to relate a catalog to a feature, feature_flag, ' +
100
- 'epic, task, document, component, milestone, goal or project (e.g. link the notification catalog ' +
106
+ 'epic, task, document, milestone, goal or project (e.g. link the notification catalog ' +
101
107
  'to its Notifications feature and the ezmodo doc that describes it).\n\n' +
102
108
  'ITEM-LEVEL LINKS (E-218): pass `itemKey` on action:"link"/"unlink" to attach the link to a SINGLE ' +
103
109
  'catalog entry instead of the whole catalog — this is how a task/epic that builds one entry records ' +
@@ -109,7 +115,8 @@ export const CATALOG_TOOLS = [
109
115
  properties: {
110
116
  action: {
111
117
  type: 'string',
112
- enum: ['create', 'update', 'delete', 'link', 'unlink', 'snapshot'],
118
+ enum: ['create', 'update', 'delete', 'link', 'unlink', 'snapshot', 'discover_screens', 'import_screens',
119
+ 'sync_screens'],
113
120
  description: 'Action to perform. "snapshot" pushes a new version (checksum-gated) — send a ' +
114
121
  'delta with mode:"patch" unless this is the catalog\'s first snapshot. "link"/"unlink" ' +
115
122
  'manage relates_to edges to other artifacts.',
@@ -127,10 +134,30 @@ export const CATALOG_TOOLS = [
127
134
  type: 'string',
128
135
  description: 'Catalog ID (required for update, delete, link, unlink, snapshot)',
129
136
  },
137
+ // --- Screens (E-258) ---
138
+ screens: {
139
+ type: 'array',
140
+ description: 'Screens to add for import_screens, usually taken from discover_screens.',
141
+ items: {
142
+ type: 'object',
143
+ properties: {
144
+ name: { type: 'string', description: 'Display name, e.g. "CookSessionDetailScreen"' },
145
+ sourcePath: { type: 'string', description: 'Repo-relative file; becomes the item key' },
146
+ kind: { type: 'string', enum: ['screen', 'page'], description: 'Default "screen"' },
147
+ route: { type: 'string', description: 'Route, for web pages' },
148
+ framework: { type: 'string', description: 'e.g. "nextjs", "flutter"' },
149
+ },
150
+ required: ['name', 'sourcePath'],
151
+ },
152
+ },
153
+ featureId: {
154
+ type: 'string',
155
+ description: 'Feature to link every imported screen to (import_screens)',
156
+ },
130
157
  // --- Create / update fields ---
131
158
  kind: {
132
159
  type: 'string',
133
- enum: ['db_schema', 'notifications', 'analytics_events', 'custom'],
160
+ enum: ['db_schema', 'notifications', 'analytics_events', 'screens', 'custom'],
134
161
  description: 'What the catalog catalogs (default "custom") (create, update)',
135
162
  },
136
163
  name: {
@@ -276,7 +303,7 @@ export const CATALOG_TOOLS = [
276
303
  },
277
304
  kind: {
278
305
  type: 'string',
279
- enum: ['db_schema', 'notifications', 'analytics_events', 'custom'],
306
+ enum: ['db_schema', 'notifications', 'analytics_events', 'screens', 'custom'],
280
307
  description: 'Filter to a single kind',
281
308
  },
282
309
  linkedType: {
@@ -13,7 +13,7 @@ export const CONTEXT_MANIFEST_TOOLS = [
13
13
  {
14
14
  name: 'get_context',
15
15
  description:
16
- 'Get contextual information about any entity (project, task, epic, component, file, tag). ' +
16
+ 'Get contextual information about any entity (project, task, epic, file, tag). ' +
17
17
  'Replaces search_project_context, get_related_files, get_project_overview, get_critical_files, ' +
18
18
  'query_project_graph, analyze_impact, get_graph_stats, analyze_project_organization. ' +
19
19
  'Use `query` for keyword search, or `entityType` + `entityId` for entity-specific context. ' +
@@ -27,7 +27,7 @@ export const CONTEXT_MANIFEST_TOOLS = [
27
27
  },
28
28
  entityType: {
29
29
  type: 'string',
30
- enum: ['project', 'task', 'epic', 'component', 'file', 'tag'],
30
+ enum: ['project', 'task', 'epic', 'file', 'tag'],
31
31
  description: 'Type of entity to get context for',
32
32
  },
33
33
  entityId: {
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
@@ -126,11 +126,6 @@ export const EPIC_TOOLS = [
126
126
  },
127
127
  links: LINKS_ARRAY_SCHEMA,
128
128
  // --- 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
129
  tasks: {
135
130
  type: 'array',
136
131
  description: 'Child tasks to create WITH the epic, in the same request (create only, 1-40). ' +
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,7 +10,6 @@
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';
@@ -48,7 +47,6 @@ export const TOOLS = [
48
47
  ...AUTH_TOOLS,
49
48
  ...ORGANIZATION_TOOLS,
50
49
  ...PROJECT_TOOLS,
51
- ...COMPONENT_TOOLS,
52
50
  ...EPIC_TOOLS,
53
51
  ...MILESTONE_TOOLS,
54
52
  ...FEATURE_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
  },
@@ -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'],