@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/config/endpoint-map.js +19 -21
- package/handlers/catalogs.js +39 -1
- package/handlers/context-manifest.js +0 -4
- package/handlers/decisions.js +43 -1
- package/handlers/epics.js +49 -1
- package/handlers/features.js +13 -2
- package/handlers/git-context.js +11 -41
- package/handlers/index.js +7 -5
- package/handlers/links.js +17 -11
- package/handlers/projects.js +1 -1
- package/handlers/tasks.js +7 -27
- package/handlers/unmapped-paths.js +60 -0
- package/lib/auto-assign.js +3 -22
- package/lib/autolink.js +11 -10
- package/lib/http-client.js +6 -0
- package/lib/instructions.generated.js +2 -2
- package/lib/local-cache.js +3 -17
- package/lib/remote-tools.js +6 -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/decisions.js +86 -8
- package/tools/epics.js +158 -6
- package/tools/features.js +41 -2
- package/tools/git-context.js +5 -5
- package/tools/graph.js +1 -1
- package/tools/index.js +2 -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/unmapped-paths.js +72 -0
- package/tools/work-templates.js +0 -2
- package/handlers/components.js +0 -282
- package/tools/components.js +0 -249
|
@@ -0,0 +1,60 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Unmapped code path handlers (E-258 #2756)
|
|
3
|
+
*
|
|
4
|
+
* Thin wrappers over /api/mcp/v1/unmapped-paths. Dispatch and validation live
|
|
5
|
+
* server-side in core/unmappedpaths.Service; these only shape the arguments.
|
|
6
|
+
*/
|
|
7
|
+
|
|
8
|
+
import { callZephlyAPI } from '../lib/http-client.js';
|
|
9
|
+
|
|
10
|
+
/**
|
|
11
|
+
* List a project's unmapped paths, pending by default.
|
|
12
|
+
*/
|
|
13
|
+
export async function listUnmappedPaths({ projectId, status }) {
|
|
14
|
+
if (!projectId) {
|
|
15
|
+
throw new Error('projectId is required');
|
|
16
|
+
}
|
|
17
|
+
const params = { projectId };
|
|
18
|
+
if (status) params.status = status;
|
|
19
|
+
return callZephlyAPI('mcpListUnmappedPaths', params);
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
/**
|
|
23
|
+
* Dispatch resolve_unmapped actions.
|
|
24
|
+
*/
|
|
25
|
+
export async function resolveUnmapped(args) {
|
|
26
|
+
const { action, ...params } = args;
|
|
27
|
+
switch (action) {
|
|
28
|
+
case 'reconcile': return reconcileUnmappedPaths(params);
|
|
29
|
+
case 'assign': return assignUnmappedPath(params);
|
|
30
|
+
case 'dismiss': return dismissUnmappedPath(params);
|
|
31
|
+
default:
|
|
32
|
+
throw new Error(`Unknown action: ${action}. Expected reconcile, assign, or dismiss.`);
|
|
33
|
+
}
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
// Close every pending row an existing feature path already covers. Rows several
|
|
37
|
+
// features could claim are left pending on purpose.
|
|
38
|
+
async function reconcileUnmappedPaths({ projectId }) {
|
|
39
|
+
requireProject(projectId, 'reconcile');
|
|
40
|
+
return callZephlyAPI('mcpReconcileUnmappedPaths', { projectId });
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
async function assignUnmappedPath({ projectId, pathId, featureId }) {
|
|
44
|
+
requireProject(projectId, 'assign');
|
|
45
|
+
if (!pathId) throw new Error('pathId is required for assign');
|
|
46
|
+
if (!featureId) throw new Error('featureId is required for assign');
|
|
47
|
+
return callZephlyAPI('mcpAssignUnmappedPath', { projectId, pathId, featureId });
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
async function dismissUnmappedPath({ projectId, pathId }) {
|
|
51
|
+
requireProject(projectId, 'dismiss');
|
|
52
|
+
if (!pathId) throw new Error('pathId is required for dismiss');
|
|
53
|
+
return callZephlyAPI('mcpDismissUnmappedPath', { projectId, pathId });
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
function requireProject(projectId, action) {
|
|
57
|
+
if (!projectId) {
|
|
58
|
+
throw new Error(`projectId is required for ${action}`);
|
|
59
|
+
}
|
|
60
|
+
}
|
package/lib/auto-assign.js
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* Auto-assign Utility
|
|
3
|
-
* Matches task/epic content against cached tags
|
|
4
|
-
*
|
|
3
|
+
* Matches task/epic content against cached tags for automatic assignment
|
|
4
|
+
* during creation.
|
|
5
5
|
*/
|
|
6
6
|
|
|
7
7
|
import { readConfig } from './local-cache.js';
|
|
@@ -13,20 +13,6 @@ function escapeRegex(str) {
|
|
|
13
13
|
return str.replace(/[.*+?^${}()|[\]\\]/g, '\\$&');
|
|
14
14
|
}
|
|
15
15
|
|
|
16
|
-
/**
|
|
17
|
-
* Match components against text content using word boundary matching.
|
|
18
|
-
* @param {string} text - Combined title + description
|
|
19
|
-
* @param {Array} components - Array of {id, name, description}
|
|
20
|
-
* @returns {Array} Matching component objects
|
|
21
|
-
*/
|
|
22
|
-
export function matchComponents(text, components) {
|
|
23
|
-
if (!text || !components?.length) return [];
|
|
24
|
-
return components.filter((c) => {
|
|
25
|
-
const regex = new RegExp(`\\b${escapeRegex(c.name)}\\b`, 'i');
|
|
26
|
-
return regex.test(text);
|
|
27
|
-
});
|
|
28
|
-
}
|
|
29
|
-
|
|
30
16
|
/**
|
|
31
17
|
* Match tags against text content using word boundary matching.
|
|
32
18
|
* @param {string} text - Combined title + description
|
|
@@ -43,10 +29,7 @@ export function matchTags(text, tags) {
|
|
|
43
29
|
|
|
44
30
|
/**
|
|
45
31
|
* Resolve auto-assignment for a task being created.
|
|
46
|
-
* Returns { matchedTags,
|
|
47
|
-
*
|
|
48
|
-
* Components are NOT auto-assigned — agents must explicitly provide componentId.
|
|
49
|
-
* This function returns available components so the handler can hint at them if needed.
|
|
32
|
+
* Returns { matchedTags, organizationId } or null if no cache available.
|
|
50
33
|
*
|
|
51
34
|
* @param {string} projectId - The task's project ID
|
|
52
35
|
* @param {string} title - Task title
|
|
@@ -59,12 +42,10 @@ export async function resolveTaskAutoAssign(projectId, title, description) {
|
|
|
59
42
|
|
|
60
43
|
const text = `${title || ''} ${description || ''}`.trim();
|
|
61
44
|
|
|
62
|
-
const components = config.projectId === projectId ? (config.components || []) : [];
|
|
63
45
|
const tags = config.tags || [];
|
|
64
46
|
|
|
65
47
|
return {
|
|
66
48
|
matchedTags: text ? matchTags(text, tags) : [],
|
|
67
|
-
availableComponents: components.map((c) => ({ id: c.id, name: c.name })),
|
|
68
49
|
organizationId: config.organizationId || null,
|
|
69
50
|
};
|
|
70
51
|
}
|
package/lib/autolink.js
CHANGED
|
@@ -3,7 +3,7 @@ import { callZephlyAPI } from './http-client.js';
|
|
|
3
3
|
/**
|
|
4
4
|
* Auto-linking helpers (E-225).
|
|
5
5
|
*
|
|
6
|
-
* The API resolves file paths to the
|
|
6
|
+
* The API resolves file paths to the features that own them and previews what
|
|
7
7
|
* the autolink engine would propose. Both are read-only, so an agent can ask
|
|
8
8
|
* "what does this touch?" before it creates or commits anything.
|
|
9
9
|
*
|
|
@@ -14,27 +14,31 @@ import { callZephlyAPI } from './http-client.js';
|
|
|
14
14
|
*/
|
|
15
15
|
|
|
16
16
|
/**
|
|
17
|
-
* Resolve repo-relative file paths to the
|
|
17
|
+
* Resolve repo-relative file paths to the features that own them through their
|
|
18
|
+
* code paths (E-258).
|
|
19
|
+
*
|
|
20
|
+
* The API's response may still carry a component `matches` array (components
|
|
21
|
+
* were retired after this server shipped); it is ignored.
|
|
18
22
|
*
|
|
19
23
|
* @param {object} params
|
|
20
24
|
* @param {string} params.projectId
|
|
21
25
|
* @param {string[]} params.paths - repo-relative paths
|
|
22
|
-
* @returns {Promise<{
|
|
26
|
+
* @returns {Promise<{features: object[], unresolved: string[]}>}
|
|
23
27
|
*/
|
|
24
|
-
export async function
|
|
28
|
+
export async function resolvePathsToFeatures({ projectId, paths }) {
|
|
25
29
|
if (!projectId || !Array.isArray(paths) || paths.length === 0) {
|
|
26
|
-
return {
|
|
30
|
+
return { features: [], unresolved: [] };
|
|
27
31
|
}
|
|
28
32
|
try {
|
|
29
33
|
const result = await callZephlyAPI('mcpResolvePaths', { projectId, paths });
|
|
30
34
|
return {
|
|
31
|
-
|
|
35
|
+
features: result?.features || [],
|
|
32
36
|
unresolved: result?.unresolved || [],
|
|
33
37
|
};
|
|
34
38
|
} catch {
|
|
35
39
|
// An API predating E-225 has no such route. Report nothing rather than
|
|
36
40
|
// surfacing a 404 the agent can do nothing about.
|
|
37
|
-
return {
|
|
41
|
+
return { features: [], unresolved: paths };
|
|
38
42
|
}
|
|
39
43
|
}
|
|
40
44
|
|
|
@@ -50,7 +54,6 @@ export async function resolvePathsToComponents({ projectId, paths }) {
|
|
|
50
54
|
* @param {string} params.subjectId
|
|
51
55
|
* @param {string[]} [params.paths]
|
|
52
56
|
* @param {string} [params.trigger]
|
|
53
|
-
* @param {string} [params.componentId]
|
|
54
57
|
* @param {string} [params.epicId]
|
|
55
58
|
* @returns {Promise<{proposals: object[]}>}
|
|
56
59
|
*/
|
|
@@ -60,7 +63,6 @@ export async function previewEntityLinks({
|
|
|
60
63
|
subjectId,
|
|
61
64
|
paths,
|
|
62
65
|
trigger,
|
|
63
|
-
componentId,
|
|
64
66
|
epicId,
|
|
65
67
|
}) {
|
|
66
68
|
if (!subjectType || !subjectId) return { proposals: [] };
|
|
@@ -71,7 +73,6 @@ export async function previewEntityLinks({
|
|
|
71
73
|
subjectId,
|
|
72
74
|
paths,
|
|
73
75
|
trigger,
|
|
74
|
-
componentId,
|
|
75
76
|
epicId,
|
|
76
77
|
});
|
|
77
78
|
return { proposals: result?.proposals || [] };
|
package/lib/http-client.js
CHANGED
|
@@ -172,6 +172,12 @@ export async function callZephlyAPI(endpoint, data) {
|
|
|
172
172
|
if (typeof error.retryable === 'boolean') {
|
|
173
173
|
thrown.retryable = error.retryable;
|
|
174
174
|
}
|
|
175
|
+
// Structured details, when the API sent an object rather than a sentence —
|
|
176
|
+
// a plan conflict (E-259) carries the current plan and what changed, which
|
|
177
|
+
// is exactly what the agent needs to redo its edit.
|
|
178
|
+
if (error.details && typeof error.details === 'object') {
|
|
179
|
+
thrown.details = error.details;
|
|
180
|
+
}
|
|
175
181
|
throw thrown;
|
|
176
182
|
}
|
|
177
183
|
|
|
@@ -9,6 +9,6 @@
|
|
|
9
9
|
* __tests__/instructions.test.js fails if this drifts from the skill.
|
|
10
10
|
*/
|
|
11
11
|
|
|
12
|
-
export const WORK_TRACKING_CORE =
|
|
12
|
+
export const WORK_TRACKING_CORE = "Work in this repository is tracked in EzModo, and the tools for it are on this\nMCP server. The contract, in short:\n\n1. **Create the task before you edit, not after.** A task written afterwards is\n a changelog; a task written first is what the next session reads to find out\n what you were doing and why.\n2. **Start the session by calling `get_current_project_context()`.** Cache the\n `projectId`. It also returns the tags and `terminology` you need.\n No `.ezmodo/config.json` means this repo is not tracked — say so rather than\n guessing at a project.\n3. **Call `get_context` with a keyword query before creating anything.** Use\n what comes back to write a task that names real files, endpoints and\n patterns. A vague task is not worth the call that made it.\n4. **Link the feature the work advances** via `links: [{targetType:\"feature\",\n targetId}]`, found with `search_features`. Code links are derived: features\n own code paths, and the files you touch link the work to their owners.\n5. **Set `taskType`** — `feature` | `bug` | `testing` | `chore`. Not cosmetic:\n `bug` feeds open-bug counts, milestone freezes gate on it, and estimation\n weights past tasks of the same kind.\n6. **Toggle steps as you finish them**, not in a batch at the end\n (`manage_task action:\"update\" toggleSteps:[...]`).\n7. **Capture knowledge the moment it happens**, not in a summary at the end:\n `addKnowledge` with `fact` for a root cause, `decision` for a choice —\n including what you rejected and why — `reference` for a key file or pattern,\n `context` for progress. Be specific: file paths, function names, exact\n errors. \"Fixed a bug in the parser\" helps nobody.\n8. **Already three edits in with no task?** Call `report_untracked_work` the\n moment you notice, rather than continuing untracked.\n9. **Resuming?** `get_task` first, and read ALL of its knowledge items. That is\n where the previous session's reasoning went — do not re-derive it.\n10. **Finish at `in_review`** with `completionNotes`, and do NOT call\n `action:\"complete\"`. A human completes a task after verifying it.\n11. **Report what actually happened.** A task moved to `in_review` claiming work\n that was not done is worse than no task, because the next session trusts it.\n\nRespect the project's `terminology`: a project can rename epics and tasks, and a\nmarketing project calls an epic a \"Campaign\". Write anything a\nhuman reads in those words; keep API field names (`epicId`, `taskId`) as they\nare.";
|
|
13
13
|
|
|
14
|
-
export const WORK_TRACKING_LOCAL =
|
|
14
|
+
export const WORK_TRACKING_LOCAL = "Running against a local checkout, two more:\n\n12. **Link every commit**: `manage_task action:\"link_commit\"` with the full\n 40-character `sha` from `git rev-parse HEAD`. A short SHA is rejected, and\n padding one is not a fix. Linking is also what derives feature links from\n the commit's files — do not link those by hand.\n13. **Pass `changedFiles`** when creating or updating a task, so the work\n resolves to the features that own those paths.";
|
package/lib/local-cache.js
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* Local Cache Utility
|
|
3
|
-
* Reads and writes the project config.json cache for tags
|
|
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: [...]
|
|
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'
|
|
115
|
+
* @param {string} section - e.g. 'tags'
|
|
130
116
|
*/
|
|
131
117
|
export async function invalidateCacheSection(section) {
|
|
132
118
|
try {
|
package/lib/remote-tools.js
CHANGED
|
@@ -58,6 +58,7 @@ export const LOCAL_ONLY_TOOLS = Object.freeze([
|
|
|
58
58
|
/** Tools safe to serve over the remote transport. */
|
|
59
59
|
export const REMOTE_SAFE_TOOLS = Object.freeze([
|
|
60
60
|
'accept_agent_suggestion',
|
|
61
|
+
'add_epic_comment',
|
|
61
62
|
'configure_agent',
|
|
62
63
|
'create_tasks',
|
|
63
64
|
'delete_attachment',
|
|
@@ -75,6 +76,7 @@ export const REMOTE_SAFE_TOOLS = Object.freeze([
|
|
|
75
76
|
'get_document',
|
|
76
77
|
'get_document_template',
|
|
77
78
|
'get_epic',
|
|
79
|
+
'get_epic_plan',
|
|
78
80
|
'get_feature',
|
|
79
81
|
'get_feature_flag',
|
|
80
82
|
'get_goal',
|
|
@@ -93,8 +95,9 @@ export const REMOTE_SAFE_TOOLS = Object.freeze([
|
|
|
93
95
|
'list_attachments',
|
|
94
96
|
'list_catalog_items',
|
|
95
97
|
'list_catalogs',
|
|
96
|
-
'
|
|
98
|
+
'list_unmapped_paths',
|
|
97
99
|
'list_designs',
|
|
100
|
+
'list_epic_comments',
|
|
98
101
|
'list_epics',
|
|
99
102
|
'list_facts',
|
|
100
103
|
'list_feature_flags',
|
|
@@ -110,7 +113,6 @@ export const REMOTE_SAFE_TOOLS = Object.freeze([
|
|
|
110
113
|
'list_watched',
|
|
111
114
|
'manage_access',
|
|
112
115
|
'manage_catalog',
|
|
113
|
-
'manage_component',
|
|
114
116
|
'manage_decision',
|
|
115
117
|
'manage_design',
|
|
116
118
|
'manage_document',
|
|
@@ -141,10 +143,12 @@ export const REMOTE_SAFE_TOOLS = Object.freeze([
|
|
|
141
143
|
'resolve_concepts',
|
|
142
144
|
'resolve_link_suggestions',
|
|
143
145
|
'resolve_links',
|
|
146
|
+
'resolve_unmapped',
|
|
144
147
|
'run_agent_now',
|
|
145
148
|
'search_epics',
|
|
146
149
|
'search_features',
|
|
147
150
|
'search_tasks',
|
|
151
|
+
'update_epic_plan',
|
|
148
152
|
'update_manifest_entries',
|
|
149
153
|
'validate_manifest',
|
|
150
154
|
]);
|
package/lib/version.js
CHANGED
package/package.json
CHANGED
|
@@ -1,14 +1,14 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@ezmodo/mcp-server",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.18.0",
|
|
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": "
|
|
9
|
-
"ezmodo-mcp-server": "
|
|
10
|
-
"zephly-mcp-server": "
|
|
11
|
-
"ezmodo-mcp-server-http": "
|
|
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
|
|
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- `
|
|
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,
|
|
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,
|
|
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', '
|
|
30
|
+
enum: ['project', 'task', 'epic', 'file', 'tag'],
|
|
31
31
|
description: 'Type of entity to get context for',
|
|
32
32
|
},
|
|
33
33
|
entityId: {
|
package/tools/decisions.js
CHANGED
|
@@ -11,6 +11,11 @@
|
|
|
11
11
|
* Decisions capture "why we built it this way" so the rationale survives the work
|
|
12
12
|
* that produced it. A task's decision-type knowledge item can be elevated into a
|
|
13
13
|
* durable Decision via promote_from_knowledge.
|
|
14
|
+
*
|
|
15
|
+
* Decisions to make (E-259): a proposed Decision asked on an epic, with a plain
|
|
16
|
+
* question, options, a recommendation and the people who should weigh in. Each
|
|
17
|
+
* of them, or their AI, adds a pick (add_input); the epic's owner or an editor
|
|
18
|
+
* decides (decide). A decision can hold tasks back until it is decided.
|
|
14
19
|
*/
|
|
15
20
|
|
|
16
21
|
import { LINKABLE_TYPES } from './linkable-types.js';
|
|
@@ -23,17 +28,81 @@ export const DECISION_TOOLS = [
|
|
|
23
28
|
'options, decision, consequences, status), link/unlink it to existing artifacts, mark it ' +
|
|
24
29
|
'superseded by another decision, or promote a task\'s decision-type knowledge item into a ' +
|
|
25
30
|
'durable Decision. Decisions are org-level and LINK to features/epics/tasks/milestones/etc., ' +
|
|
26
|
-
'they do not contain them
|
|
31
|
+
'they do not contain them.\n\n' +
|
|
32
|
+
'DECISIONS TO MAKE (open choices on an epic that several people weigh in on): create with ' +
|
|
33
|
+
'epicId + question + choices + recommendation + requestedFrom (the people whose view is ' +
|
|
34
|
+
'wanted; they are notified). Write the question, options and recommendation in plain ' +
|
|
35
|
+
'language a non-developer can answer. Then "add_input" records a pick (choiceId + a ' +
|
|
36
|
+
'one-line reason; your pick counts for the person whose key you use, and replaces their ' +
|
|
37
|
+
'earlier one). "decide" is for the epic\'s owner or an editor only: pass choiceId, ' +
|
|
38
|
+
'optionally decision (the answer in plain words) and rejectedReasons {choiceId: why}. ' +
|
|
39
|
+
'"hold_task" makes a task wait until the decision is made (it is blocked, and released on ' +
|
|
40
|
+
'decide); "release_task" undoes that. Do not decide on someone\'s behalf unless they asked ' +
|
|
41
|
+
'you to — add a pick instead.',
|
|
27
42
|
inputSchema: {
|
|
28
43
|
type: 'object',
|
|
29
44
|
properties: {
|
|
30
45
|
action: {
|
|
31
46
|
type: 'string',
|
|
32
|
-
enum: ['create', 'update', 'delete', 'link', 'unlink', 'supersede', 'promote_from_knowledge'
|
|
47
|
+
enum: ['create', 'update', 'delete', 'link', 'unlink', 'supersede', 'promote_from_knowledge',
|
|
48
|
+
'add_input', 'decide', 'hold_task', 'release_task'],
|
|
33
49
|
description: 'Action to perform. "supersede" marks a decision as superseded by another ' +
|
|
34
50
|
'(pass decisionId + supersededById). "promote_from_knowledge" elevates a task\'s ' +
|
|
35
51
|
'decision-type knowledge item into a durable Decision (pass organizationId + taskId + ' +
|
|
36
|
-
'knowledgeId, optionally title + linkToType/linkToId to link it on creation).'
|
|
52
|
+
'knowledgeId, optionally title + linkToType/linkToId to link it on creation). ' +
|
|
53
|
+
'"add_input" (decisionId + choiceId + reason), "decide" (decisionId + choiceId), ' +
|
|
54
|
+
'"hold_task" / "release_task" (decisionId + taskId) are for decisions to make on an epic.',
|
|
55
|
+
},
|
|
56
|
+
// --- Decisions to make (E-259) ---
|
|
57
|
+
epicId: {
|
|
58
|
+
type: 'string',
|
|
59
|
+
description: 'Ask this decision on an epic (create). Its owner or an editor decides it; the ' +
|
|
60
|
+
'decision is scoped to the epic\'s project and linked to it.',
|
|
61
|
+
},
|
|
62
|
+
question: {
|
|
63
|
+
type: 'string',
|
|
64
|
+
description: 'The plain question to answer, e.g. "Who should review changes to the plan?" ' +
|
|
65
|
+
'(create, update)',
|
|
66
|
+
},
|
|
67
|
+
recommendation: {
|
|
68
|
+
type: 'string',
|
|
69
|
+
description: 'The recommended answer and why, in a sentence or two (create, update)',
|
|
70
|
+
},
|
|
71
|
+
choices: {
|
|
72
|
+
type: 'array',
|
|
73
|
+
items: {
|
|
74
|
+
oneOf: [
|
|
75
|
+
{ type: 'string' },
|
|
76
|
+
{
|
|
77
|
+
type: 'object',
|
|
78
|
+
properties: { id: { type: 'string' }, label: { type: 'string' } },
|
|
79
|
+
required: ['label'],
|
|
80
|
+
},
|
|
81
|
+
],
|
|
82
|
+
},
|
|
83
|
+
description: 'The options to pick from. On create, a list of plain labels. On update, the ' +
|
|
84
|
+
'full list as {id, label}: keep an option\'s id to keep the picks made for it; leave id ' +
|
|
85
|
+
'off to add one; omit an option to remove it (create, update)',
|
|
86
|
+
},
|
|
87
|
+
requestedFrom: {
|
|
88
|
+
type: 'array',
|
|
89
|
+
items: { type: 'string' },
|
|
90
|
+
description: 'User ids of the people whose view is wanted. They are notified (on update, ' +
|
|
91
|
+
'only the newly added ones) (create, update)',
|
|
92
|
+
},
|
|
93
|
+
choiceId: {
|
|
94
|
+
type: 'string',
|
|
95
|
+
description: 'An option id from the decision\'s choices, e.g. "c2" (add_input: your pick, ' +
|
|
96
|
+
'omit for "none of these" and say why in reason; decide: the chosen option)',
|
|
97
|
+
},
|
|
98
|
+
reason: {
|
|
99
|
+
type: 'string',
|
|
100
|
+
description: 'One line on why you picked it (add_input)',
|
|
101
|
+
},
|
|
102
|
+
rejectedReasons: {
|
|
103
|
+
type: 'object',
|
|
104
|
+
additionalProperties: { type: 'string' },
|
|
105
|
+
description: 'Why each option was turned down, as {choiceId: reason} (decide)',
|
|
37
106
|
},
|
|
38
107
|
// --- Identifiers ---
|
|
39
108
|
organizationId: {
|
|
@@ -74,7 +143,9 @@ export const DECISION_TOOLS = [
|
|
|
74
143
|
status: {
|
|
75
144
|
type: 'string',
|
|
76
145
|
enum: ['proposed', 'accepted', 'rejected', 'superseded', 'deprecated'],
|
|
77
|
-
description: 'Decision lifecycle status (default: "proposed") (create, update)'
|
|
146
|
+
description: 'Decision lifecycle status (default: "proposed") (create, update). For decide: ' +
|
|
147
|
+
'"accepted" (default) or "rejected" (none of the options). A decision to make on an epic ' +
|
|
148
|
+
'cannot change status through update; use decide.',
|
|
78
149
|
},
|
|
79
150
|
projectId: {
|
|
80
151
|
type: 'string',
|
|
@@ -95,7 +166,8 @@ export const DECISION_TOOLS = [
|
|
|
95
166
|
// --- promote_from_knowledge fields ---
|
|
96
167
|
taskId: {
|
|
97
168
|
type: 'string',
|
|
98
|
-
description: 'Task whose knowledge item to promote (required for promote_from_knowledge)'
|
|
169
|
+
description: 'Task whose knowledge item to promote (required for promote_from_knowledge), ' +
|
|
170
|
+
'or the task to hold back / release (hold_task, release_task)',
|
|
99
171
|
},
|
|
100
172
|
knowledgeId: {
|
|
101
173
|
type: 'string',
|
|
@@ -121,8 +193,10 @@ export const DECISION_TOOLS = [
|
|
|
121
193
|
name: 'get_decision',
|
|
122
194
|
description: 'Retrieve a single Decision, list an organization\'s decisions, or list the decisions ' +
|
|
123
195
|
'linked to a given entity (e.g. all decisions on a feature). Provide decisionId for a single ' +
|
|
124
|
-
'lookup; provide linkedType + linkedId to list decisions linked to that entity;
|
|
125
|
-
'
|
|
196
|
+
'lookup; provide linkedType + linkedId to list decisions linked to that entity; provide ' +
|
|
197
|
+
'epicId to list the decisions to make on an epic (open ones first, each with everyone\'s ' +
|
|
198
|
+
'picks and the tasks it holds back; add status "proposed" for only the open ones); ' +
|
|
199
|
+
'otherwise provide organizationId to list.',
|
|
126
200
|
inputSchema: {
|
|
127
201
|
type: 'object',
|
|
128
202
|
properties: {
|
|
@@ -132,7 +206,11 @@ export const DECISION_TOOLS = [
|
|
|
132
206
|
},
|
|
133
207
|
organizationId: {
|
|
134
208
|
type: 'string',
|
|
135
|
-
description: 'Organization ID (required for list, unless using linkedType + linkedId)',
|
|
209
|
+
description: 'Organization ID (required for list, unless using linkedType + linkedId or epicId)',
|
|
210
|
+
},
|
|
211
|
+
epicId: {
|
|
212
|
+
type: 'string',
|
|
213
|
+
description: 'List the decisions to make on this epic, with picks and held tasks',
|
|
136
214
|
},
|
|
137
215
|
linkedType: {
|
|
138
216
|
type: 'string',
|