@projitive/mcp 2.0.4 → 2.1.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/output/package.json +1 -1
- package/output/source/common/errors.test.js +59 -0
- package/output/source/common/files.js +20 -9
- package/output/source/common/index.js +1 -0
- package/output/source/common/linter.js +3 -1
- package/output/source/common/response.js +51 -67
- package/output/source/common/tool.js +43 -0
- package/output/source/common/utils.test.js +48 -0
- package/output/source/index.runtime.test.js +57 -0
- package/output/source/prompts/index.test.js +23 -0
- package/output/source/prompts/quickStart.js +58 -22
- package/output/source/prompts/quickStart.test.js +24 -0
- package/output/source/prompts/taskDiscovery.js +13 -4
- package/output/source/prompts/taskDiscovery.test.js +24 -0
- package/output/source/prompts/taskExecution.js +71 -12
- package/output/source/prompts/taskExecution.test.js +27 -0
- package/output/source/resources/designs.resources.test.js +52 -0
- package/output/source/resources/governance.test.js +35 -0
- package/output/source/resources/index.test.js +18 -0
- package/output/source/tools/index.test.js +23 -0
- package/output/source/tools/project.js +210 -257
- package/output/source/tools/project.test.js +136 -4
- package/output/source/tools/roadmap.js +182 -216
- package/output/source/tools/roadmap.test.js +187 -0
- package/output/source/tools/task.js +779 -515
- package/output/source/tools/task.test.js +323 -2
- package/output/source/types.js +6 -0
- package/package.json +1 -1
|
@@ -1,8 +1,22 @@
|
|
|
1
|
-
import { describe, expect, it } from 'vitest';
|
|
2
|
-
import { collectTaskLintSuggestions, isValidTaskId, normalizeTask, rankActionableTaskCandidates, resolveNoTaskDiscoveryGuidance, renderTaskSeedTemplate, renderTasksMarkdown, loadTasksDocument, saveTasks, taskPriority, toTaskUpdatedAtMs, validateTransition, } from './task.js';
|
|
1
|
+
import { describe, expect, it, vi } from 'vitest';
|
|
2
|
+
import { collectTaskLintSuggestions, isValidTaskId, normalizeTask, rankActionableTaskCandidates, resolveNoTaskDiscoveryGuidance, renderTaskSeedTemplate, renderTasksMarkdown, loadTasksDocument, registerTaskTools, saveTasks, taskPriority, toTaskUpdatedAtMs, validateTransition, } from './task.js';
|
|
3
3
|
import fs from 'node:fs/promises';
|
|
4
4
|
import os from 'node:os';
|
|
5
5
|
import path from 'node:path';
|
|
6
|
+
import { replaceRoadmapsInStore } from '../common/store.js';
|
|
7
|
+
async function createGovernanceWorkspace() {
|
|
8
|
+
const projectRoot = await fs.mkdtemp(path.join(os.tmpdir(), 'projitive-mcp-task-workspace-'));
|
|
9
|
+
const governanceDir = path.join(projectRoot, '.projitive');
|
|
10
|
+
const dbPath = path.join(governanceDir, '.projitive');
|
|
11
|
+
await fs.mkdir(governanceDir, { recursive: true });
|
|
12
|
+
await fs.writeFile(dbPath, '', 'utf-8');
|
|
13
|
+
return { projectRoot, governanceDir, dbPath };
|
|
14
|
+
}
|
|
15
|
+
function getToolHandler(mockServer, toolName) {
|
|
16
|
+
const call = mockServer.registerTool.mock.calls.find((entry) => entry[0] === toolName);
|
|
17
|
+
expect(call).toBeTruthy();
|
|
18
|
+
return call?.[2];
|
|
19
|
+
}
|
|
6
20
|
function buildCandidate(partial) {
|
|
7
21
|
const task = normalizeTask({
|
|
8
22
|
id: partial.id,
|
|
@@ -173,4 +187,311 @@ describe('tasks module', () => {
|
|
|
173
187
|
expect(markdown).toContain('Repository: https://github.com/yinxulai/projitive');
|
|
174
188
|
await fs.rm(root, { recursive: true, force: true });
|
|
175
189
|
});
|
|
190
|
+
it('taskCreate auto-generates the next task id and syncs tasks view', async () => {
|
|
191
|
+
const { projectRoot, governanceDir, dbPath } = await createGovernanceWorkspace();
|
|
192
|
+
await replaceRoadmapsInStore(dbPath, [
|
|
193
|
+
{ id: 'ROADMAP-0001', title: 'Bootstrap', status: 'active', updatedAt: '2026-03-14T00:00:00.000Z' },
|
|
194
|
+
]);
|
|
195
|
+
await saveTasks(dbPath, [
|
|
196
|
+
normalizeTask({ id: 'TASK-0009', title: 'existing', status: 'TODO', roadmapRefs: ['ROADMAP-0001'] }),
|
|
197
|
+
]);
|
|
198
|
+
const mockServer = { registerTool: vi.fn() };
|
|
199
|
+
registerTaskTools(mockServer);
|
|
200
|
+
const taskCreate = getToolHandler(mockServer, 'taskCreate');
|
|
201
|
+
const result = await taskCreate({
|
|
202
|
+
projectPath: projectRoot,
|
|
203
|
+
title: 'newly generated',
|
|
204
|
+
summary: 'auto id test',
|
|
205
|
+
roadmapRefs: ['ROADMAP-0001'],
|
|
206
|
+
});
|
|
207
|
+
expect(result.isError).toBeUndefined();
|
|
208
|
+
expect(result.content[0].text).toContain('TASK-0010');
|
|
209
|
+
const loaded = await loadTasksDocument(governanceDir);
|
|
210
|
+
expect(loaded.tasks.some((task) => task.id === 'TASK-0010' && task.title === 'newly generated')).toBe(true);
|
|
211
|
+
await fs.rm(projectRoot, { recursive: true, force: true });
|
|
212
|
+
});
|
|
213
|
+
it('taskUpdate rejects invalid status transitions', async () => {
|
|
214
|
+
const { projectRoot, dbPath } = await createGovernanceWorkspace();
|
|
215
|
+
await saveTasks(dbPath, [
|
|
216
|
+
normalizeTask({ id: 'TASK-0001', title: 'done task', status: 'DONE' }),
|
|
217
|
+
]);
|
|
218
|
+
const mockServer = { registerTool: vi.fn() };
|
|
219
|
+
registerTaskTools(mockServer);
|
|
220
|
+
const taskUpdate = getToolHandler(mockServer, 'taskUpdate');
|
|
221
|
+
const result = await taskUpdate({
|
|
222
|
+
projectPath: projectRoot,
|
|
223
|
+
taskId: 'TASK-0001',
|
|
224
|
+
updates: { status: 'IN_PROGRESS' },
|
|
225
|
+
});
|
|
226
|
+
expect(result.isError).toBe(true);
|
|
227
|
+
expect(result.content[0].text).toContain('Invalid status transition');
|
|
228
|
+
await fs.rm(projectRoot, { recursive: true, force: true });
|
|
229
|
+
});
|
|
230
|
+
it('taskNext emits no-actionable guidance when only blocked tasks exist', async () => {
|
|
231
|
+
const { projectRoot, dbPath } = await createGovernanceWorkspace();
|
|
232
|
+
await replaceRoadmapsInStore(dbPath, [
|
|
233
|
+
{ id: 'ROADMAP-0001', title: 'Blocked milestone', status: 'active', updatedAt: '2026-03-14T00:00:00.000Z' },
|
|
234
|
+
]);
|
|
235
|
+
await saveTasks(dbPath, [
|
|
236
|
+
normalizeTask({
|
|
237
|
+
id: 'TASK-0001',
|
|
238
|
+
title: 'blocked task',
|
|
239
|
+
status: 'BLOCKED',
|
|
240
|
+
summary: 'waiting',
|
|
241
|
+
roadmapRefs: ['ROADMAP-0001'],
|
|
242
|
+
}),
|
|
243
|
+
]);
|
|
244
|
+
vi.stubEnv('PROJITIVE_SCAN_ROOT_PATHS', projectRoot);
|
|
245
|
+
vi.stubEnv('PROJITIVE_SCAN_MAX_DEPTH', '3');
|
|
246
|
+
const mockServer = { registerTool: vi.fn() };
|
|
247
|
+
registerTaskTools(mockServer);
|
|
248
|
+
const taskNext = getToolHandler(mockServer, 'taskNext');
|
|
249
|
+
const result = await taskNext({});
|
|
250
|
+
expect(result.content[0].text).toContain('No TODO/IN_PROGRESS task is available.');
|
|
251
|
+
expect(result.content[0].text).toContain('No-Task Discovery Checklist');
|
|
252
|
+
expect(result.content[0].text).toContain('taskCreate(projectPath=');
|
|
253
|
+
vi.unstubAllEnvs();
|
|
254
|
+
await fs.rm(projectRoot, { recursive: true, force: true });
|
|
255
|
+
});
|
|
256
|
+
it('taskList and taskContext expose synced views and evidence for an existing task', async () => {
|
|
257
|
+
const { projectRoot, governanceDir, dbPath } = await createGovernanceWorkspace();
|
|
258
|
+
await replaceRoadmapsInStore(dbPath, [
|
|
259
|
+
{ id: 'ROADMAP-0001', title: 'Roadmap', status: 'active', updatedAt: '2026-03-14T00:00:00.000Z' },
|
|
260
|
+
]);
|
|
261
|
+
await fs.writeFile(path.join(projectRoot, 'README.md'), '# Root Readme\nTASK-0001\n', 'utf-8');
|
|
262
|
+
await saveTasks(dbPath, [
|
|
263
|
+
normalizeTask({
|
|
264
|
+
id: 'TASK-0001',
|
|
265
|
+
title: 'inspect me',
|
|
266
|
+
status: 'IN_PROGRESS',
|
|
267
|
+
owner: 'copilot',
|
|
268
|
+
summary: 'has evidence',
|
|
269
|
+
roadmapRefs: ['ROADMAP-0001'],
|
|
270
|
+
links: ['README.md'],
|
|
271
|
+
}),
|
|
272
|
+
]);
|
|
273
|
+
const mockServer = { registerTool: vi.fn() };
|
|
274
|
+
registerTaskTools(mockServer);
|
|
275
|
+
const taskList = getToolHandler(mockServer, 'taskList');
|
|
276
|
+
const listResult = await taskList({ projectPath: projectRoot, status: 'IN_PROGRESS' });
|
|
277
|
+
expect(listResult.content[0].text).toContain(`tasksView: ${path.join(governanceDir, 'tasks.md')}`);
|
|
278
|
+
expect(listResult.content[0].text).toContain('TASK-0001 | IN_PROGRESS | inspect me');
|
|
279
|
+
const taskContext = getToolHandler(mockServer, 'taskContext');
|
|
280
|
+
const contextResult = await taskContext({ projectPath: projectRoot, taskId: 'TASK-0001' });
|
|
281
|
+
expect(contextResult.content[0].text).toContain(`roadmapView: ${path.join(governanceDir, 'roadmap.md')}`);
|
|
282
|
+
expect(contextResult.content[0].text).toContain('### Reference Locations');
|
|
283
|
+
expect(contextResult.content[0].text).toContain('README.md');
|
|
284
|
+
expect(contextResult.content[0].text).toContain('### Pre-Execution Research Brief');
|
|
285
|
+
expect(contextResult.content[0].text).toContain('researchBriefStatus: MISSING');
|
|
286
|
+
expect(contextResult.content[0].text).toContain('designs/research/TASK-0001.implementation-research.md');
|
|
287
|
+
expect(contextResult.content[0].text).toContain('architectureDocsStatus: MISSING');
|
|
288
|
+
expect(contextResult.content[0].text).toContain('styleDocsStatus: MISSING');
|
|
289
|
+
expect(contextResult.content[0].text).toContain('Project context docs gate is NOT satisfied');
|
|
290
|
+
expect(contextResult.content[0].text).toContain('PROJECT_ARCHITECTURE_DOC_MISSING');
|
|
291
|
+
expect(contextResult.content[0].text).toContain('PROJECT_STYLE_DOC_MISSING');
|
|
292
|
+
await fs.rm(projectRoot, { recursive: true, force: true });
|
|
293
|
+
});
|
|
294
|
+
it('taskUpdate allows TODO -> IN_PROGRESS when research brief is missing, with lint guidance', async () => {
|
|
295
|
+
const { projectRoot, dbPath } = await createGovernanceWorkspace();
|
|
296
|
+
await replaceRoadmapsInStore(dbPath, [
|
|
297
|
+
{ id: 'ROADMAP-0001', title: 'Bootstrap', status: 'active', updatedAt: '2026-03-14T00:00:00.000Z' },
|
|
298
|
+
]);
|
|
299
|
+
await saveTasks(dbPath, [
|
|
300
|
+
normalizeTask({ id: 'TASK-0001', title: 'gate test', status: 'TODO', roadmapRefs: ['ROADMAP-0001'] }),
|
|
301
|
+
]);
|
|
302
|
+
const mockServer = { registerTool: vi.fn() };
|
|
303
|
+
registerTaskTools(mockServer);
|
|
304
|
+
const taskUpdate = getToolHandler(mockServer, 'taskUpdate');
|
|
305
|
+
const result = await taskUpdate({
|
|
306
|
+
projectPath: projectRoot,
|
|
307
|
+
taskId: 'TASK-0001',
|
|
308
|
+
updates: { status: 'IN_PROGRESS' },
|
|
309
|
+
});
|
|
310
|
+
expect(result.isError).toBeUndefined();
|
|
311
|
+
expect(result.content[0].text).toContain('newStatus: IN_PROGRESS');
|
|
312
|
+
expect(result.content[0].text).toContain('TASK_RESEARCH_BRIEF_MISSING');
|
|
313
|
+
await fs.rm(projectRoot, { recursive: true, force: true });
|
|
314
|
+
});
|
|
315
|
+
it('taskUpdate allows TODO -> IN_PROGRESS when research brief is ready', async () => {
|
|
316
|
+
const { projectRoot, dbPath } = await createGovernanceWorkspace();
|
|
317
|
+
await replaceRoadmapsInStore(dbPath, [
|
|
318
|
+
{ id: 'ROADMAP-0001', title: 'Bootstrap', status: 'active', updatedAt: '2026-03-14T00:00:00.000Z' },
|
|
319
|
+
]);
|
|
320
|
+
await saveTasks(dbPath, [
|
|
321
|
+
normalizeTask({ id: 'TASK-0001', title: 'gate test pass', status: 'TODO', roadmapRefs: ['ROADMAP-0001'] }),
|
|
322
|
+
]);
|
|
323
|
+
const researchDir = path.join(projectRoot, 'designs', 'research');
|
|
324
|
+
await fs.mkdir(researchDir, { recursive: true });
|
|
325
|
+
await fs.writeFile(path.join(researchDir, 'TASK-0001.implementation-research.md'), [
|
|
326
|
+
'# TASK-0001 Implementation Research Brief',
|
|
327
|
+
'',
|
|
328
|
+
'## Design Guidelines and Specs',
|
|
329
|
+
'- designs/PROJITIVE.md#L10-L20',
|
|
330
|
+
'',
|
|
331
|
+
'## Code Architecture and Implementation Findings',
|
|
332
|
+
'- packages/mcp/source/tools/task.ts#L1-L50',
|
|
333
|
+
].join('\n'), 'utf-8');
|
|
334
|
+
const mockServer = { registerTool: vi.fn() };
|
|
335
|
+
registerTaskTools(mockServer);
|
|
336
|
+
const taskUpdate = getToolHandler(mockServer, 'taskUpdate');
|
|
337
|
+
const result = await taskUpdate({
|
|
338
|
+
projectPath: projectRoot,
|
|
339
|
+
taskId: 'TASK-0001',
|
|
340
|
+
updates: { status: 'IN_PROGRESS' },
|
|
341
|
+
});
|
|
342
|
+
expect(result.isError).toBeUndefined();
|
|
343
|
+
expect(result.content[0].text).toContain('newStatus: IN_PROGRESS');
|
|
344
|
+
await fs.rm(projectRoot, { recursive: true, force: true });
|
|
345
|
+
});
|
|
346
|
+
it('taskContext requires project core docs under designs/core (docs outside core do not satisfy)', async () => {
|
|
347
|
+
const { projectRoot, dbPath } = await createGovernanceWorkspace();
|
|
348
|
+
await replaceRoadmapsInStore(dbPath, [
|
|
349
|
+
{ id: 'ROADMAP-0001', title: 'Roadmap', status: 'active', updatedAt: '2026-03-14T00:00:00.000Z' },
|
|
350
|
+
]);
|
|
351
|
+
await saveTasks(dbPath, [
|
|
352
|
+
normalizeTask({ id: 'TASK-0001', title: 'core docs rule', status: 'TODO', roadmapRefs: ['ROADMAP-0001'] }),
|
|
353
|
+
]);
|
|
354
|
+
await fs.mkdir(path.join(projectRoot, 'designs'), { recursive: true });
|
|
355
|
+
await fs.writeFile(path.join(projectRoot, 'designs', 'architecture.md'), '# Architecture\n', 'utf-8');
|
|
356
|
+
await fs.writeFile(path.join(projectRoot, 'designs', 'style-guide.md'), '# Style\n', 'utf-8');
|
|
357
|
+
const mockServer = { registerTool: vi.fn() };
|
|
358
|
+
registerTaskTools(mockServer);
|
|
359
|
+
const taskContext = getToolHandler(mockServer, 'taskContext');
|
|
360
|
+
const contextResult = await taskContext({ projectPath: projectRoot, taskId: 'TASK-0001' });
|
|
361
|
+
expect(contextResult.content[0].text).toContain('architectureDocsStatus: MISSING');
|
|
362
|
+
expect(contextResult.content[0].text).toContain('styleDocsStatus: MISSING');
|
|
363
|
+
expect(contextResult.content[0].text).toContain('designs/core/architecture.md');
|
|
364
|
+
expect(contextResult.content[0].text).toContain('PROJECT_ARCHITECTURE_DOC_MISSING');
|
|
365
|
+
expect(contextResult.content[0].text).toContain('PROJECT_STYLE_DOC_MISSING');
|
|
366
|
+
await fs.rm(projectRoot, { recursive: true, force: true });
|
|
367
|
+
});
|
|
368
|
+
it('taskContext marks project core docs ready when architecture/style docs exist under designs/core', async () => {
|
|
369
|
+
const { projectRoot, governanceDir, dbPath } = await createGovernanceWorkspace();
|
|
370
|
+
await replaceRoadmapsInStore(dbPath, [
|
|
371
|
+
{ id: 'ROADMAP-0001', title: 'Roadmap', status: 'active', updatedAt: '2026-03-14T00:00:00.000Z' },
|
|
372
|
+
]);
|
|
373
|
+
await saveTasks(dbPath, [
|
|
374
|
+
normalizeTask({ id: 'TASK-0001', title: 'core docs ready', status: 'TODO', roadmapRefs: ['ROADMAP-0001'] }),
|
|
375
|
+
]);
|
|
376
|
+
const coreDir = path.join(governanceDir, 'designs', 'core');
|
|
377
|
+
await fs.mkdir(coreDir, { recursive: true });
|
|
378
|
+
await fs.writeFile(path.join(coreDir, 'architecture.md'), '# Architecture\n', 'utf-8');
|
|
379
|
+
await fs.writeFile(path.join(coreDir, 'style-guide.md'), '# Style\n', 'utf-8');
|
|
380
|
+
const mockServer = { registerTool: vi.fn() };
|
|
381
|
+
registerTaskTools(mockServer);
|
|
382
|
+
const taskContext = getToolHandler(mockServer, 'taskContext');
|
|
383
|
+
const contextResult = await taskContext({ projectPath: projectRoot, taskId: 'TASK-0001' });
|
|
384
|
+
expect(contextResult.content[0].text).toContain('architectureDocsStatus: READY');
|
|
385
|
+
expect(contextResult.content[0].text).toContain('styleDocsStatus: READY');
|
|
386
|
+
expect(contextResult.content[0].text).toContain('Project context docs gate satisfied');
|
|
387
|
+
await fs.rm(projectRoot, { recursive: true, force: true });
|
|
388
|
+
});
|
|
389
|
+
it('taskContext requires fixed core doc filenames even under designs/core', async () => {
|
|
390
|
+
const { projectRoot, governanceDir, dbPath } = await createGovernanceWorkspace();
|
|
391
|
+
await replaceRoadmapsInStore(dbPath, [
|
|
392
|
+
{ id: 'ROADMAP-0001', title: 'Roadmap', status: 'active', updatedAt: '2026-03-14T00:00:00.000Z' },
|
|
393
|
+
]);
|
|
394
|
+
await saveTasks(dbPath, [
|
|
395
|
+
normalizeTask({ id: 'TASK-0001', title: 'fixed filename rule', status: 'TODO', roadmapRefs: ['ROADMAP-0001'] }),
|
|
396
|
+
]);
|
|
397
|
+
const coreDir = path.join(governanceDir, 'designs', 'core');
|
|
398
|
+
await fs.mkdir(coreDir, { recursive: true });
|
|
399
|
+
await fs.writeFile(path.join(coreDir, 'system-architecture.md'), '# Architecture\n', 'utf-8');
|
|
400
|
+
await fs.writeFile(path.join(coreDir, 'visual-style.md'), '# Style\n', 'utf-8');
|
|
401
|
+
const mockServer = { registerTool: vi.fn() };
|
|
402
|
+
registerTaskTools(mockServer);
|
|
403
|
+
const taskContext = getToolHandler(mockServer, 'taskContext');
|
|
404
|
+
const contextResult = await taskContext({ projectPath: projectRoot, taskId: 'TASK-0001' });
|
|
405
|
+
expect(contextResult.content[0].text).toContain('architectureDocsStatus: MISSING');
|
|
406
|
+
expect(contextResult.content[0].text).toContain('styleDocsStatus: MISSING');
|
|
407
|
+
expect(contextResult.content[0].text).toContain('add required file designs/core/architecture.md');
|
|
408
|
+
expect(contextResult.content[0].text).toContain('add required file designs/core/style-guide.md');
|
|
409
|
+
await fs.rm(projectRoot, { recursive: true, force: true });
|
|
410
|
+
});
|
|
411
|
+
it('taskUpdate allows IN_PROGRESS -> DONE with lint guidance when conformance fails', async () => {
|
|
412
|
+
const { projectRoot, governanceDir, dbPath } = await createGovernanceWorkspace();
|
|
413
|
+
await replaceRoadmapsInStore(dbPath, [
|
|
414
|
+
{ id: 'ROADMAP-0001', title: 'Roadmap', status: 'active', updatedAt: '2026-03-14T00:00:00.000Z' },
|
|
415
|
+
]);
|
|
416
|
+
await saveTasks(dbPath, [
|
|
417
|
+
normalizeTask({
|
|
418
|
+
id: 'TASK-0001',
|
|
419
|
+
title: 'done gate fail',
|
|
420
|
+
status: 'IN_PROGRESS',
|
|
421
|
+
owner: 'ai-copilot',
|
|
422
|
+
roadmapRefs: ['ROADMAP-0001'],
|
|
423
|
+
links: [],
|
|
424
|
+
}),
|
|
425
|
+
]);
|
|
426
|
+
const researchDir = path.join(projectRoot, 'designs', 'research');
|
|
427
|
+
await fs.mkdir(researchDir, { recursive: true });
|
|
428
|
+
await fs.writeFile(path.join(researchDir, 'TASK-0001.implementation-research.md'), [
|
|
429
|
+
'# TASK-0001 Implementation Research Brief',
|
|
430
|
+
'',
|
|
431
|
+
'## Design Guidelines and Specs',
|
|
432
|
+
'- designs/core/architecture.md#L1',
|
|
433
|
+
'',
|
|
434
|
+
'## Code Architecture and Implementation Findings',
|
|
435
|
+
'- packages/mcp/source/tools/task.ts#L1',
|
|
436
|
+
].join('\n'), 'utf-8');
|
|
437
|
+
const coreDir = path.join(governanceDir, 'designs', 'core');
|
|
438
|
+
await fs.mkdir(coreDir, { recursive: true });
|
|
439
|
+
await fs.writeFile(path.join(coreDir, 'architecture.md'), '# Architecture\n', 'utf-8');
|
|
440
|
+
await fs.writeFile(path.join(coreDir, 'style-guide.md'), '# Style\n', 'utf-8');
|
|
441
|
+
const mockServer = { registerTool: vi.fn() };
|
|
442
|
+
registerTaskTools(mockServer);
|
|
443
|
+
const taskUpdate = getToolHandler(mockServer, 'taskUpdate');
|
|
444
|
+
const result = await taskUpdate({
|
|
445
|
+
projectPath: projectRoot,
|
|
446
|
+
taskId: 'TASK-0001',
|
|
447
|
+
updates: { status: 'DONE' },
|
|
448
|
+
});
|
|
449
|
+
expect(result.isError).toBeUndefined();
|
|
450
|
+
expect(result.content[0].text).toContain('newStatus: DONE');
|
|
451
|
+
expect(result.content[0].text).toContain('TASK_DONE_LINKS_MISSING');
|
|
452
|
+
await fs.rm(projectRoot, { recursive: true, force: true });
|
|
453
|
+
});
|
|
454
|
+
it('taskUpdate allows IN_PROGRESS -> DONE when conformance re-check passes', async () => {
|
|
455
|
+
const { projectRoot, governanceDir, dbPath } = await createGovernanceWorkspace();
|
|
456
|
+
await replaceRoadmapsInStore(dbPath, [
|
|
457
|
+
{ id: 'ROADMAP-0001', title: 'Roadmap', status: 'active', updatedAt: '2026-03-14T00:00:00.000Z' },
|
|
458
|
+
]);
|
|
459
|
+
await fs.writeFile(path.join(projectRoot, 'README.md'), '# Evidence\n', 'utf-8');
|
|
460
|
+
await saveTasks(dbPath, [
|
|
461
|
+
normalizeTask({
|
|
462
|
+
id: 'TASK-0001',
|
|
463
|
+
title: 'done gate pass',
|
|
464
|
+
status: 'IN_PROGRESS',
|
|
465
|
+
owner: 'ai-copilot',
|
|
466
|
+
roadmapRefs: ['ROADMAP-0001'],
|
|
467
|
+
links: ['README.md'],
|
|
468
|
+
}),
|
|
469
|
+
]);
|
|
470
|
+
const researchDir = path.join(projectRoot, 'designs', 'research');
|
|
471
|
+
await fs.mkdir(researchDir, { recursive: true });
|
|
472
|
+
await fs.writeFile(path.join(researchDir, 'TASK-0001.implementation-research.md'), [
|
|
473
|
+
'# TASK-0001 Implementation Research Brief',
|
|
474
|
+
'',
|
|
475
|
+
'## Design Guidelines and Specs',
|
|
476
|
+
'- designs/core/architecture.md#L1',
|
|
477
|
+
'',
|
|
478
|
+
'## Code Architecture and Implementation Findings',
|
|
479
|
+
'- packages/mcp/source/tools/task.ts#L1',
|
|
480
|
+
].join('\n'), 'utf-8');
|
|
481
|
+
const coreDir = path.join(governanceDir, 'designs', 'core');
|
|
482
|
+
await fs.mkdir(coreDir, { recursive: true });
|
|
483
|
+
await fs.writeFile(path.join(coreDir, 'architecture.md'), '# Architecture\n', 'utf-8');
|
|
484
|
+
await fs.writeFile(path.join(coreDir, 'style-guide.md'), '# Style\n', 'utf-8');
|
|
485
|
+
const mockServer = { registerTool: vi.fn() };
|
|
486
|
+
registerTaskTools(mockServer);
|
|
487
|
+
const taskUpdate = getToolHandler(mockServer, 'taskUpdate');
|
|
488
|
+
const result = await taskUpdate({
|
|
489
|
+
projectPath: projectRoot,
|
|
490
|
+
taskId: 'TASK-0001',
|
|
491
|
+
updates: { status: 'DONE' },
|
|
492
|
+
});
|
|
493
|
+
expect(result.isError).toBeUndefined();
|
|
494
|
+
expect(result.content[0].text).toContain('newStatus: DONE');
|
|
495
|
+
await fs.rm(projectRoot, { recursive: true, force: true });
|
|
496
|
+
});
|
|
176
497
|
});
|
package/output/source/types.js
CHANGED
|
@@ -45,4 +45,10 @@ export const TASK_LINT_CODES = {
|
|
|
45
45
|
IN_PROGRESS_WITHOUT_SUBSTATE: 'TASK_IN_PROGRESS_WITHOUT_SUBSTATE',
|
|
46
46
|
SUBSTATE_PHASE_INVALID: 'TASK_SUBSTATE_PHASE_INVALID',
|
|
47
47
|
SUBSTATE_CONFIDENCE_INVALID: 'TASK_SUBSTATE_CONFIDENCE_INVALID',
|
|
48
|
+
RESEARCH_BRIEF_MISSING: 'TASK_RESEARCH_BRIEF_MISSING',
|
|
49
|
+
RESEARCH_BRIEF_INCOMPLETE: 'TASK_RESEARCH_BRIEF_INCOMPLETE',
|
|
50
|
+
};
|
|
51
|
+
export const PROJECT_LINT_CODES = {
|
|
52
|
+
ARCHITECTURE_DOC_MISSING: 'PROJECT_ARCHITECTURE_DOC_MISSING',
|
|
53
|
+
STYLE_DOC_MISSING: 'PROJECT_STYLE_DOC_MISSING',
|
|
48
54
|
};
|