@projitive/mcp 2.0.4 → 2.1.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/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.test.js +24 -0
- package/output/source/prompts/taskDiscovery.test.js +24 -0
- package/output/source/prompts/taskExecution.js +17 -1
- 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 +598 -508
- package/output/source/tools/task.test.js +323 -2
- package/output/source/types.js +6 -0
- package/package.json +1 -1
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
import fs from 'node:fs/promises';
|
|
2
|
+
import os from 'node:os';
|
|
3
|
+
import path from 'node:path';
|
|
4
|
+
import { afterEach, describe, expect, it, vi } from 'vitest';
|
|
5
|
+
import { registerGovernanceResources } from './governance.js';
|
|
6
|
+
const tempPaths = [];
|
|
7
|
+
async function createTempDir() {
|
|
8
|
+
const dir = await fs.mkdtemp(path.join(os.tmpdir(), 'projitive-governance-resource-test-'));
|
|
9
|
+
tempPaths.push(dir);
|
|
10
|
+
return dir;
|
|
11
|
+
}
|
|
12
|
+
afterEach(async () => {
|
|
13
|
+
await Promise.all(tempPaths.splice(0).map((dir) => fs.rm(dir, { recursive: true, force: true })));
|
|
14
|
+
vi.restoreAllMocks();
|
|
15
|
+
});
|
|
16
|
+
describe('governance resources', () => {
|
|
17
|
+
it('registers workspace, tasks, and roadmap resources from markdown files', async () => {
|
|
18
|
+
const root = await createTempDir();
|
|
19
|
+
const governanceDir = path.join(root, '.projitive');
|
|
20
|
+
await fs.mkdir(governanceDir, { recursive: true });
|
|
21
|
+
await fs.writeFile(path.join(governanceDir, 'README.md'), '# Workspace\n', 'utf-8');
|
|
22
|
+
await fs.writeFile(path.join(governanceDir, 'tasks.md'), '# Tasks\n', 'utf-8');
|
|
23
|
+
await fs.writeFile(path.join(governanceDir, 'roadmap.md'), '# Roadmap\n', 'utf-8');
|
|
24
|
+
const server = { registerResource: vi.fn() };
|
|
25
|
+
registerGovernanceResources(server, root);
|
|
26
|
+
const calls = server.registerResource.mock.calls;
|
|
27
|
+
expect(calls).toHaveLength(3);
|
|
28
|
+
const workspaceHandler = calls[0][3];
|
|
29
|
+
const tasksHandler = calls[1][3];
|
|
30
|
+
const roadmapHandler = calls[2][3];
|
|
31
|
+
await expect(workspaceHandler()).resolves.toMatchObject({ contents: [{ uri: 'projitive://governance/workspace', text: '# Workspace\n' }] });
|
|
32
|
+
await expect(tasksHandler()).resolves.toMatchObject({ contents: [{ uri: 'projitive://governance/tasks', text: '# Tasks\n' }] });
|
|
33
|
+
await expect(roadmapHandler()).resolves.toMatchObject({ contents: [{ uri: 'projitive://governance/roadmap', text: '# Roadmap\n' }] });
|
|
34
|
+
});
|
|
35
|
+
});
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
import { describe, expect, it, vi } from 'vitest';
|
|
2
|
+
vi.mock('./governance.js', () => ({
|
|
3
|
+
registerGovernanceResources: vi.fn(),
|
|
4
|
+
}));
|
|
5
|
+
vi.mock('./designs.js', () => ({
|
|
6
|
+
registerDesignFilesResources: vi.fn(),
|
|
7
|
+
}));
|
|
8
|
+
import { registerGovernanceResources } from './governance.js';
|
|
9
|
+
import { registerDesignFilesResources } from './designs.js';
|
|
10
|
+
import { registerResources } from './index.js';
|
|
11
|
+
describe('resources index module', () => {
|
|
12
|
+
it('registers governance and design resources', () => {
|
|
13
|
+
const server = {};
|
|
14
|
+
registerResources(server, '/workspace/repo');
|
|
15
|
+
expect(registerGovernanceResources).toHaveBeenCalledWith(server, '/workspace/repo');
|
|
16
|
+
expect(registerDesignFilesResources).toHaveBeenCalledWith(server, '/workspace/repo');
|
|
17
|
+
});
|
|
18
|
+
});
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
import { describe, expect, it, vi } from 'vitest';
|
|
2
|
+
vi.mock('./project.js', () => ({
|
|
3
|
+
registerProjectTools: vi.fn(),
|
|
4
|
+
}));
|
|
5
|
+
vi.mock('./task.js', () => ({
|
|
6
|
+
registerTaskTools: vi.fn(),
|
|
7
|
+
}));
|
|
8
|
+
vi.mock('./roadmap.js', () => ({
|
|
9
|
+
registerRoadmapTools: vi.fn(),
|
|
10
|
+
}));
|
|
11
|
+
import { registerProjectTools } from './project.js';
|
|
12
|
+
import { registerTaskTools } from './task.js';
|
|
13
|
+
import { registerRoadmapTools } from './roadmap.js';
|
|
14
|
+
import { registerTools } from './index.js';
|
|
15
|
+
describe('tools index module', () => {
|
|
16
|
+
it('registers all tool groups', () => {
|
|
17
|
+
const server = {};
|
|
18
|
+
registerTools(server);
|
|
19
|
+
expect(registerProjectTools).toHaveBeenCalledWith(server);
|
|
20
|
+
expect(registerTaskTools).toHaveBeenCalledWith(server);
|
|
21
|
+
expect(registerRoadmapTools).toHaveBeenCalledWith(server);
|
|
22
|
+
});
|
|
23
|
+
});
|
|
@@ -2,8 +2,7 @@ import fs from 'node:fs/promises';
|
|
|
2
2
|
import path from 'node:path';
|
|
3
3
|
import process from 'node:process';
|
|
4
4
|
import { z } from 'zod';
|
|
5
|
-
import { discoverGovernanceArtifacts, catchIt, PROJECT_LINT_CODES, renderLintSuggestions, ensureStore, replaceRoadmapsInStore, replaceTasksInStore,
|
|
6
|
-
import { asText, evidenceSection, getDefaultToolTemplateMarkdown, guidanceSection, lintSection, nextCallSection, renderToolResponseMarkdown, summarySection, } from '../common/index.js';
|
|
5
|
+
import { discoverGovernanceArtifacts, catchIt, PROJECT_LINT_CODES, renderLintSuggestions, ensureStore, replaceRoadmapsInStore, replaceTasksInStore, loadTaskStatusStatsFromStore, createGovernedTool, getDefaultToolTemplateMarkdown, } from '../common/index.js';
|
|
7
6
|
import { collectTaskLintSuggestions, loadTasksDocument, loadTasksDocumentWithOptions, renderTasksMarkdown } from './task.js';
|
|
8
7
|
import { loadRoadmapDocumentWithOptions, renderRoadmapMarkdown } from './roadmap.js';
|
|
9
8
|
export const PROJECT_MARKER = '.projitive';
|
|
@@ -15,9 +14,6 @@ function normalizePath(inputPath) {
|
|
|
15
14
|
}
|
|
16
15
|
function normalizeGovernanceDirName(input) {
|
|
17
16
|
const name = input?.trim() || DEFAULT_GOVERNANCE_DIR;
|
|
18
|
-
if (!name) {
|
|
19
|
-
throw new Error('governanceDir cannot be empty');
|
|
20
|
-
}
|
|
21
17
|
if (path.isAbsolute(name)) {
|
|
22
18
|
throw new Error('governanceDir must be a relative directory name');
|
|
23
19
|
}
|
|
@@ -30,9 +26,6 @@ function normalizeGovernanceDirName(input) {
|
|
|
30
26
|
return name;
|
|
31
27
|
}
|
|
32
28
|
function parseDepthFromEnv(rawDepth) {
|
|
33
|
-
if (typeof rawDepth !== 'string' || rawDepth.trim().length === 0) {
|
|
34
|
-
return undefined;
|
|
35
|
-
}
|
|
36
29
|
const parsed = Number.parseInt(rawDepth, 10);
|
|
37
30
|
if (!Number.isFinite(parsed)) {
|
|
38
31
|
return undefined;
|
|
@@ -321,16 +314,14 @@ function defaultTemplateReadmeMarkdown() {
|
|
|
321
314
|
'- Prefer one template per tool: <toolName>.md (e.g. taskNext.md).',
|
|
322
315
|
'- Template directory mode only loads <toolName>.md files.',
|
|
323
316
|
'- If a tool template file is missing, Projitive will auto-generate that file before rendering.',
|
|
324
|
-
'- Include {{content}} to render original tool output.',
|
|
325
|
-
'- If {{content}} is missing, original output is appended after template text.',
|
|
326
317
|
'',
|
|
327
318
|
'Basic Variables:',
|
|
328
319
|
'- {{tool_name}}',
|
|
329
320
|
'- {{summary}}',
|
|
330
321
|
'- {{evidence}}',
|
|
331
322
|
'- {{guidance}}',
|
|
323
|
+
'- {{lint_suggestions}}',
|
|
332
324
|
'- {{next_call}}',
|
|
333
|
-
'- {{content}}',
|
|
334
325
|
].join('\n');
|
|
335
326
|
}
|
|
336
327
|
export async function initializeProjectStructure(inputPath, governanceDir, force = false) {
|
|
@@ -393,7 +384,8 @@ export async function initializeProjectStructure(inputPath, governanceDir, force
|
|
|
393
384
|
};
|
|
394
385
|
}
|
|
395
386
|
export function registerProjectTools(server) {
|
|
396
|
-
server.registerTool(
|
|
387
|
+
server.registerTool(...createGovernedTool({
|
|
388
|
+
name: 'projectInit',
|
|
397
389
|
title: 'Project Init',
|
|
398
390
|
description: 'Bootstrap governance files when a project has no .projitive yet (requires projectPath)',
|
|
399
391
|
inputSchema: {
|
|
@@ -401,172 +393,152 @@ export function registerProjectTools(server) {
|
|
|
401
393
|
governanceDir: z.string().optional(),
|
|
402
394
|
force: z.boolean().optional(),
|
|
403
395
|
},
|
|
404
|
-
|
|
405
|
-
|
|
406
|
-
|
|
407
|
-
|
|
408
|
-
|
|
409
|
-
|
|
410
|
-
|
|
411
|
-
|
|
412
|
-
|
|
413
|
-
|
|
414
|
-
|
|
415
|
-
|
|
416
|
-
|
|
417
|
-
|
|
418
|
-
|
|
419
|
-
|
|
420
|
-
|
|
421
|
-
|
|
422
|
-
|
|
423
|
-
|
|
424
|
-
|
|
425
|
-
|
|
426
|
-
|
|
427
|
-
|
|
428
|
-
|
|
429
|
-
|
|
430
|
-
|
|
431
|
-
|
|
432
|
-
|
|
433
|
-
|
|
434
|
-
|
|
435
|
-
|
|
436
|
-
|
|
437
|
-
|
|
438
|
-
|
|
439
|
-
return asText(markdown);
|
|
440
|
-
});
|
|
441
|
-
server.registerTool('projectScan', {
|
|
396
|
+
async execute({ projectPath, governanceDir, force }) {
|
|
397
|
+
const initialized = await initializeProjectStructure(projectPath, governanceDir, force ?? false);
|
|
398
|
+
const filesByAction = {
|
|
399
|
+
created: initialized.files.filter((item) => item.action === 'created'),
|
|
400
|
+
updated: initialized.files.filter((item) => item.action === 'updated'),
|
|
401
|
+
skipped: initialized.files.filter((item) => item.action === 'skipped'),
|
|
402
|
+
};
|
|
403
|
+
return { initialized, filesByAction, force: force ?? false };
|
|
404
|
+
},
|
|
405
|
+
summary: ({ initialized, force }) => [
|
|
406
|
+
`- projectPath: ${initialized.projectPath}`,
|
|
407
|
+
`- governanceDir: ${initialized.governanceDir}`,
|
|
408
|
+
`- force: ${force ? 'true' : 'false'}`,
|
|
409
|
+
],
|
|
410
|
+
evidence: ({ initialized, filesByAction }) => [
|
|
411
|
+
`- createdFiles: ${filesByAction.created.length}`,
|
|
412
|
+
`- updatedFiles: ${filesByAction.updated.length}`,
|
|
413
|
+
`- skippedFiles: ${filesByAction.skipped.length}`,
|
|
414
|
+
'- directories:',
|
|
415
|
+
...initialized.directories.map((item) => ` - ${item.action}: ${item.path}`),
|
|
416
|
+
'- files:',
|
|
417
|
+
...initialized.files.map((item) => ` - ${item.action}: ${item.path}`),
|
|
418
|
+
],
|
|
419
|
+
guidance: () => [
|
|
420
|
+
'- If files were skipped and you want to overwrite templates, rerun with force=true.',
|
|
421
|
+
'- Continue with projectContext and taskList for execution.',
|
|
422
|
+
],
|
|
423
|
+
suggestions: () => [
|
|
424
|
+
'- After init, fill owner/roadmapRefs/links in .projitive task table before marking DONE.',
|
|
425
|
+
'- Keep task source-of-truth inside .projitive governance store.',
|
|
426
|
+
],
|
|
427
|
+
nextCall: ({ initialized }) => `projectContext(projectPath="${initialized.projectPath}")`,
|
|
428
|
+
}));
|
|
429
|
+
server.registerTool(...createGovernedTool({
|
|
430
|
+
name: 'projectScan',
|
|
442
431
|
title: 'Project Scan',
|
|
443
432
|
description: 'Start here when project path is unknown; discover all governance roots',
|
|
444
433
|
inputSchema: {},
|
|
445
|
-
|
|
446
|
-
|
|
447
|
-
|
|
448
|
-
|
|
449
|
-
|
|
450
|
-
|
|
451
|
-
|
|
452
|
-
|
|
453
|
-
|
|
454
|
-
|
|
455
|
-
|
|
456
|
-
|
|
457
|
-
|
|
458
|
-
|
|
459
|
-
|
|
460
|
-
|
|
461
|
-
|
|
462
|
-
|
|
463
|
-
|
|
464
|
-
|
|
465
|
-
|
|
466
|
-
|
|
467
|
-
|
|
468
|
-
|
|
469
|
-
|
|
470
|
-
|
|
471
|
-
|
|
472
|
-
|
|
473
|
-
],
|
|
474
|
-
});
|
|
475
|
-
return asText(markdown);
|
|
476
|
-
});
|
|
477
|
-
server.registerTool('projectNext', {
|
|
434
|
+
async execute() {
|
|
435
|
+
const roots = resolveScanRoots();
|
|
436
|
+
const depth = resolveScanDepth();
|
|
437
|
+
const governanceDirs = await discoverProjectsAcrossRoots(roots, depth);
|
|
438
|
+
const projects = Array.from(new Set(governanceDirs.map((governanceDir) => toProjectPath(governanceDir)))).sort();
|
|
439
|
+
return { roots, depth, projects };
|
|
440
|
+
},
|
|
441
|
+
summary: ({ roots, depth, projects }) => [
|
|
442
|
+
`- rootPaths: ${roots.join(', ')}`,
|
|
443
|
+
`- rootCount: ${roots.length}`,
|
|
444
|
+
`- maxDepth: ${depth}`,
|
|
445
|
+
`- discoveredCount: ${projects.length}`,
|
|
446
|
+
],
|
|
447
|
+
evidence: ({ projects }) => [
|
|
448
|
+
'- projects:',
|
|
449
|
+
...projects.map((project, index) => `${index + 1}. ${project}`),
|
|
450
|
+
],
|
|
451
|
+
guidance: () => [
|
|
452
|
+
'- Use one discovered project path and call `projectLocate` to lock governance root.',
|
|
453
|
+
'- Then call `projectContext` to inspect current governance state.',
|
|
454
|
+
],
|
|
455
|
+
suggestions: ({ projects }) => projects.length === 0
|
|
456
|
+
? ['- No governance root discovered. Add `.projitive` marker and baseline artifacts before execution.']
|
|
457
|
+
: ['- Run `projectContext` on a discovered project to receive module-level lint suggestions.'],
|
|
458
|
+
nextCall: ({ projects }) => projects[0] ? `projectLocate(inputPath="${projects[0]}")` : undefined,
|
|
459
|
+
}));
|
|
460
|
+
server.registerTool(...createGovernedTool({
|
|
461
|
+
name: 'projectNext',
|
|
478
462
|
title: 'Project Next',
|
|
479
463
|
description: 'Rank actionable projects and return the best execution target',
|
|
480
464
|
inputSchema: {
|
|
481
465
|
limit: z.number().int().min(1).max(50).optional(),
|
|
482
466
|
},
|
|
483
|
-
|
|
484
|
-
|
|
485
|
-
|
|
486
|
-
|
|
487
|
-
|
|
488
|
-
|
|
489
|
-
|
|
490
|
-
|
|
491
|
-
|
|
492
|
-
|
|
493
|
-
|
|
494
|
-
|
|
495
|
-
|
|
496
|
-
|
|
497
|
-
|
|
498
|
-
|
|
499
|
-
|
|
500
|
-
|
|
501
|
-
|
|
502
|
-
|
|
503
|
-
|
|
504
|
-
|
|
505
|
-
|
|
506
|
-
|
|
507
|
-
|
|
508
|
-
|
|
509
|
-
|
|
510
|
-
|
|
511
|
-
|
|
512
|
-
|
|
513
|
-
|
|
514
|
-
|
|
515
|
-
|
|
516
|
-
|
|
517
|
-
|
|
518
|
-
|
|
519
|
-
|
|
520
|
-
|
|
521
|
-
|
|
522
|
-
|
|
523
|
-
|
|
524
|
-
|
|
525
|
-
|
|
526
|
-
|
|
527
|
-
|
|
528
|
-
|
|
529
|
-
|
|
530
|
-
|
|
531
|
-
|
|
532
|
-
|
|
533
|
-
|
|
534
|
-
|
|
535
|
-
|
|
536
|
-
lintSection(ranked[0]?.lintSuggestions ?? []),
|
|
537
|
-
nextCallSection(ranked[0]
|
|
538
|
-
? `projectContext(projectPath="${toProjectPath(ranked[0].governanceDir)}")`
|
|
539
|
-
: undefined),
|
|
540
|
-
],
|
|
541
|
-
});
|
|
542
|
-
return asText(markdown);
|
|
543
|
-
});
|
|
544
|
-
server.registerTool('projectLocate', {
|
|
467
|
+
async execute({ limit }) {
|
|
468
|
+
const roots = resolveScanRoots();
|
|
469
|
+
const depth = resolveScanDepth();
|
|
470
|
+
const projects = await discoverProjectsAcrossRoots(roots, depth);
|
|
471
|
+
const snapshots = await Promise.all(projects.map(async (governanceDir) => {
|
|
472
|
+
const snapshot = await readTasksSnapshot(governanceDir);
|
|
473
|
+
const actionable = snapshot.inProgress + snapshot.todo;
|
|
474
|
+
return {
|
|
475
|
+
governanceDir,
|
|
476
|
+
tasksExists: snapshot.exists,
|
|
477
|
+
lintSuggestions: snapshot.lintSuggestions,
|
|
478
|
+
inProgress: snapshot.inProgress,
|
|
479
|
+
todo: snapshot.todo,
|
|
480
|
+
blocked: snapshot.blocked,
|
|
481
|
+
done: snapshot.done,
|
|
482
|
+
actionable,
|
|
483
|
+
latestUpdatedAt: snapshot.latestUpdatedAt,
|
|
484
|
+
score: snapshot.score,
|
|
485
|
+
};
|
|
486
|
+
}));
|
|
487
|
+
const ranked = snapshots
|
|
488
|
+
.filter((item) => item.actionable > 0)
|
|
489
|
+
.sort((a, b) => {
|
|
490
|
+
if (b.score !== a.score)
|
|
491
|
+
return b.score - a.score;
|
|
492
|
+
return b.latestUpdatedAt.localeCompare(a.latestUpdatedAt);
|
|
493
|
+
})
|
|
494
|
+
.slice(0, limit ?? 10);
|
|
495
|
+
const topTasks = ranked[0] ? (await loadTasksDocument(ranked[0].governanceDir)).tasks : undefined;
|
|
496
|
+
return { roots, depth, projects, ranked, limit: limit ?? 10, topTasks };
|
|
497
|
+
},
|
|
498
|
+
summary: ({ roots, depth, projects, ranked, limit }) => [
|
|
499
|
+
`- rootPaths: ${roots.join(', ')}`,
|
|
500
|
+
`- rootCount: ${roots.length}`,
|
|
501
|
+
`- maxDepth: ${depth}`,
|
|
502
|
+
`- matchedProjects: ${projects.length}`,
|
|
503
|
+
`- actionableProjects: ${ranked.length}`,
|
|
504
|
+
`- limit: ${limit}`,
|
|
505
|
+
],
|
|
506
|
+
evidence: ({ ranked }) => [
|
|
507
|
+
'- rankedProjects:',
|
|
508
|
+
...ranked.map((item, index) => `${index + 1}. ${toProjectPath(item.governanceDir)} | actionable=${item.actionable} | in_progress=${item.inProgress} | todo=${item.todo} | blocked=${item.blocked} | done=${item.done} | latest=${item.latestUpdatedAt}${item.tasksExists ? '' : ' | store=missing'}`),
|
|
509
|
+
],
|
|
510
|
+
guidance: () => [
|
|
511
|
+
'- Pick top 1 project and call `projectContext` with its projectPath.',
|
|
512
|
+
'- Then call `taskList` and `taskContext` to continue execution.',
|
|
513
|
+
'- If governance store is missing, initialize governance before task-level operations.',
|
|
514
|
+
],
|
|
515
|
+
suggestions: ({ topTasks }) => topTasks ? collectTaskLintSuggestions(topTasks) : [],
|
|
516
|
+
nextCall: ({ ranked }) => ranked[0] ? `projectContext(projectPath="${toProjectPath(ranked[0].governanceDir)}")` : undefined,
|
|
517
|
+
}));
|
|
518
|
+
server.registerTool(...createGovernedTool({
|
|
519
|
+
name: 'projectLocate',
|
|
545
520
|
title: 'Project Locate',
|
|
546
521
|
description: 'Resolve the nearest governance root from any in-project path',
|
|
547
522
|
inputSchema: {
|
|
548
523
|
inputPath: z.string(),
|
|
549
524
|
},
|
|
550
|
-
|
|
551
|
-
|
|
552
|
-
|
|
553
|
-
|
|
554
|
-
|
|
555
|
-
|
|
556
|
-
|
|
557
|
-
|
|
558
|
-
|
|
559
|
-
|
|
560
|
-
|
|
561
|
-
|
|
562
|
-
|
|
563
|
-
|
|
564
|
-
|
|
565
|
-
|
|
566
|
-
|
|
567
|
-
return asText(markdown);
|
|
568
|
-
});
|
|
569
|
-
server.registerTool('syncViews', {
|
|
525
|
+
async execute({ inputPath }) {
|
|
526
|
+
const resolvedFrom = normalizePath(inputPath);
|
|
527
|
+
const governanceDir = await resolveGovernanceDir(resolvedFrom);
|
|
528
|
+
const projectPath = toProjectPath(governanceDir);
|
|
529
|
+
return { resolvedFrom, governanceDir, projectPath };
|
|
530
|
+
},
|
|
531
|
+
summary: ({ resolvedFrom, projectPath, governanceDir }) => [
|
|
532
|
+
`- resolvedFrom: ${resolvedFrom}`,
|
|
533
|
+
`- projectPath: ${projectPath}`,
|
|
534
|
+
`- governanceDir: ${governanceDir}`,
|
|
535
|
+
],
|
|
536
|
+
guidance: () => ['- Call `projectContext` with this projectPath to get task and roadmap summaries.'],
|
|
537
|
+
suggestions: () => ['- Run `projectContext` to get governance/module lint suggestions for this project.'],
|
|
538
|
+
nextCall: ({ projectPath }) => `projectContext(projectPath="${projectPath}")`,
|
|
539
|
+
}));
|
|
540
|
+
server.registerTool(...createGovernedTool({
|
|
541
|
+
name: 'syncViews',
|
|
570
542
|
title: 'Sync Views',
|
|
571
543
|
description: 'Materialize markdown views from .projitive governance store (tasks.md / roadmap.md)',
|
|
572
544
|
inputSchema: {
|
|
@@ -574,105 +546,86 @@ export function registerProjectTools(server) {
|
|
|
574
546
|
views: z.array(z.enum(['tasks', 'roadmap'])).optional(),
|
|
575
547
|
force: z.boolean().optional(),
|
|
576
548
|
},
|
|
577
|
-
|
|
578
|
-
|
|
579
|
-
|
|
580
|
-
|
|
581
|
-
|
|
582
|
-
|
|
583
|
-
|
|
584
|
-
|
|
585
|
-
|
|
586
|
-
const forceSync = force === true;
|
|
587
|
-
if (forceSync) {
|
|
549
|
+
async execute({ projectPath, views, force }) {
|
|
550
|
+
const governanceDir = await resolveGovernanceDir(projectPath);
|
|
551
|
+
const normalizedProjectPath = toProjectPath(governanceDir);
|
|
552
|
+
const tasksViewPath = path.join(governanceDir, 'tasks.md');
|
|
553
|
+
const roadmapViewPath = path.join(governanceDir, 'roadmap.md');
|
|
554
|
+
const selectedViews = views && views.length > 0 ? Array.from(new Set(views)) : ['tasks', 'roadmap'];
|
|
555
|
+
const forceSync = force === true;
|
|
556
|
+
let taskCount;
|
|
557
|
+
let roadmapCount;
|
|
588
558
|
if (selectedViews.includes('tasks')) {
|
|
589
|
-
await
|
|
559
|
+
const taskDoc = await loadTasksDocumentWithOptions(governanceDir, forceSync);
|
|
560
|
+
taskCount = taskDoc.tasks.length;
|
|
590
561
|
}
|
|
591
562
|
if (selectedViews.includes('roadmap')) {
|
|
592
|
-
await
|
|
563
|
+
const roadmapDoc = await loadRoadmapDocumentWithOptions(governanceDir, forceSync);
|
|
564
|
+
roadmapCount = roadmapDoc.milestones.length;
|
|
593
565
|
}
|
|
594
|
-
|
|
595
|
-
|
|
596
|
-
|
|
597
|
-
|
|
598
|
-
|
|
599
|
-
|
|
600
|
-
|
|
601
|
-
|
|
602
|
-
|
|
603
|
-
|
|
604
|
-
}
|
|
605
|
-
|
|
606
|
-
|
|
607
|
-
|
|
608
|
-
|
|
609
|
-
|
|
610
|
-
|
|
611
|
-
|
|
612
|
-
|
|
613
|
-
|
|
614
|
-
|
|
615
|
-
|
|
616
|
-
|
|
617
|
-
...(typeof taskCount === 'number' ? [`- tasks.md synced | taskCount=${taskCount}`] : []),
|
|
618
|
-
...(typeof roadmapCount === 'number' ? [`- roadmap.md synced | roadmapCount=${roadmapCount}`] : []),
|
|
619
|
-
]),
|
|
620
|
-
guidanceSection([
|
|
621
|
-
'Use this tool after batch updates when you need immediate markdown materialization.',
|
|
622
|
-
'Routine workflows can rely on lazy sync and usually do not require force=true.',
|
|
623
|
-
]),
|
|
624
|
-
lintSection([]),
|
|
625
|
-
nextCallSection(`projectContext(projectPath="${normalizedProjectPath}")`),
|
|
626
|
-
],
|
|
627
|
-
});
|
|
628
|
-
return asText(markdown);
|
|
629
|
-
});
|
|
630
|
-
server.registerTool('projectContext', {
|
|
566
|
+
return { normalizedProjectPath, governanceDir, tasksViewPath, roadmapViewPath, selectedViews, forceSync, taskCount, roadmapCount };
|
|
567
|
+
},
|
|
568
|
+
summary: ({ normalizedProjectPath, governanceDir, tasksViewPath, roadmapViewPath, selectedViews, forceSync }) => [
|
|
569
|
+
`- projectPath: ${normalizedProjectPath}`,
|
|
570
|
+
`- governanceDir: ${governanceDir}`,
|
|
571
|
+
`- tasksView: ${tasksViewPath}`,
|
|
572
|
+
`- roadmapView: ${roadmapViewPath}`,
|
|
573
|
+
`- views: ${selectedViews.join(', ')}`,
|
|
574
|
+
`- force: ${forceSync ? 'true' : 'false'}`,
|
|
575
|
+
],
|
|
576
|
+
evidence: ({ taskCount, roadmapCount }) => [
|
|
577
|
+
...(typeof taskCount === 'number' ? [`- tasks.md synced | taskCount=${taskCount}`] : []),
|
|
578
|
+
...(typeof roadmapCount === 'number' ? [`- roadmap.md synced | roadmapCount=${roadmapCount}`] : []),
|
|
579
|
+
],
|
|
580
|
+
guidance: () => [
|
|
581
|
+
'Use this tool after batch updates when you need immediate markdown materialization.',
|
|
582
|
+
'Routine workflows can rely on lazy sync and usually do not require force=true.',
|
|
583
|
+
],
|
|
584
|
+
suggestions: () => [],
|
|
585
|
+
nextCall: ({ normalizedProjectPath }) => `projectContext(projectPath="${normalizedProjectPath}")`,
|
|
586
|
+
}));
|
|
587
|
+
server.registerTool(...createGovernedTool({
|
|
588
|
+
name: 'projectContext',
|
|
631
589
|
title: 'Project Context',
|
|
632
590
|
description: 'Get project-level summary before selecting or executing a task',
|
|
633
591
|
inputSchema: {
|
|
634
592
|
projectPath: z.string(),
|
|
635
593
|
},
|
|
636
|
-
|
|
637
|
-
|
|
638
|
-
|
|
639
|
-
|
|
640
|
-
|
|
641
|
-
|
|
642
|
-
|
|
643
|
-
|
|
644
|
-
|
|
645
|
-
|
|
646
|
-
|
|
647
|
-
|
|
648
|
-
|
|
649
|
-
|
|
650
|
-
|
|
651
|
-
|
|
652
|
-
|
|
653
|
-
|
|
654
|
-
|
|
655
|
-
|
|
656
|
-
|
|
657
|
-
|
|
658
|
-
|
|
659
|
-
|
|
660
|
-
|
|
661
|
-
|
|
662
|
-
|
|
663
|
-
|
|
664
|
-
|
|
665
|
-
|
|
666
|
-
|
|
667
|
-
|
|
668
|
-
|
|
669
|
-
|
|
670
|
-
|
|
671
|
-
|
|
672
|
-
|
|
673
|
-
nextCallSection(`taskList(projectPath="${normalizedProjectPath}")`),
|
|
674
|
-
],
|
|
675
|
-
});
|
|
676
|
-
return asText(markdown);
|
|
677
|
-
});
|
|
594
|
+
async execute({ projectPath }) {
|
|
595
|
+
const governanceDir = await resolveGovernanceDir(projectPath);
|
|
596
|
+
const normalizedProjectPath = toProjectPath(governanceDir);
|
|
597
|
+
const artifacts = await discoverGovernanceArtifacts(governanceDir);
|
|
598
|
+
const dbPath = path.join(governanceDir, PROJECT_MARKER);
|
|
599
|
+
await ensureStore(dbPath);
|
|
600
|
+
const taskStats = await loadTaskStatusStatsFromStore(dbPath);
|
|
601
|
+
const { markdownPath: tasksMarkdownPath, tasks } = await loadTasksDocument(governanceDir);
|
|
602
|
+
const { markdownPath: roadmapMarkdownPath, milestones } = await loadRoadmapDocumentWithOptions(governanceDir, false);
|
|
603
|
+
const roadmapIds = milestones.map((item) => item.id);
|
|
604
|
+
return { normalizedProjectPath, governanceDir, tasksMarkdownPath, roadmapMarkdownPath, roadmapIds, taskStats, artifacts, tasks };
|
|
605
|
+
},
|
|
606
|
+
summary: ({ normalizedProjectPath, governanceDir, tasksMarkdownPath, roadmapMarkdownPath, roadmapIds }) => [
|
|
607
|
+
`- projectPath: ${normalizedProjectPath}`,
|
|
608
|
+
`- governanceDir: ${governanceDir}`,
|
|
609
|
+
`- tasksView: ${tasksMarkdownPath}`,
|
|
610
|
+
`- roadmapView: ${roadmapMarkdownPath}`,
|
|
611
|
+
`- roadmapIds: ${roadmapIds.length}`,
|
|
612
|
+
],
|
|
613
|
+
evidence: ({ taskStats, artifacts }) => [
|
|
614
|
+
'### Task Summary',
|
|
615
|
+
`- total: ${taskStats.total}`,
|
|
616
|
+
`- TODO: ${taskStats.todo}`,
|
|
617
|
+
`- IN_PROGRESS: ${taskStats.inProgress}`,
|
|
618
|
+
`- BLOCKED: ${taskStats.blocked}`,
|
|
619
|
+
`- DONE: ${taskStats.done}`,
|
|
620
|
+
'',
|
|
621
|
+
'### Artifacts',
|
|
622
|
+
renderArtifactsMarkdown(artifacts),
|
|
623
|
+
],
|
|
624
|
+
guidance: () => [
|
|
625
|
+
'- Start from `taskList` to choose a target task.',
|
|
626
|
+
'- Then call `taskContext` with a task ID to retrieve evidence locations and reading order.',
|
|
627
|
+
],
|
|
628
|
+
suggestions: ({ tasks }) => collectTaskLintSuggestions(tasks),
|
|
629
|
+
nextCall: ({ normalizedProjectPath }) => `taskList(projectPath="${normalizedProjectPath}")`,
|
|
630
|
+
}));
|
|
678
631
|
}
|