@projitive/mcp 2.0.2 → 2.0.4
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/README.md +14 -1
- package/output/package.json +8 -2
- package/output/source/common/artifacts.js +1 -1
- package/output/source/common/artifacts.test.js +11 -11
- package/output/source/common/errors.js +19 -19
- package/output/source/common/files.js +11 -11
- package/output/source/common/files.test.js +14 -14
- package/output/source/common/index.js +10 -10
- package/output/source/common/linter.js +27 -27
- package/output/source/common/linter.test.js +9 -9
- package/output/source/common/markdown.js +3 -3
- package/output/source/common/markdown.test.js +15 -15
- package/output/source/common/response.js +74 -74
- package/output/source/common/response.test.js +30 -30
- package/output/source/common/store.js +40 -40
- package/output/source/common/store.test.js +72 -72
- package/output/source/common/types.js +3 -3
- package/output/source/common/utils.js +8 -8
- package/output/source/index.js +16 -16
- package/output/source/index.test.js +64 -64
- package/output/source/prompts/index.js +3 -3
- package/output/source/prompts/quickStart.js +96 -96
- package/output/source/prompts/taskDiscovery.js +184 -180
- package/output/source/prompts/taskExecution.js +148 -147
- package/output/source/resources/designs.js +26 -26
- package/output/source/resources/designs.test.js +88 -88
- package/output/source/resources/governance.js +19 -19
- package/output/source/resources/index.js +2 -2
- package/output/source/resources/readme.js +7 -7
- package/output/source/resources/readme.test.js +113 -113
- package/output/source/resources/reports.js +10 -10
- package/output/source/resources/reports.test.js +83 -83
- package/output/source/tools/index.js +3 -3
- package/output/source/tools/project.js +196 -191
- package/output/source/tools/project.test.js +187 -164
- package/output/source/tools/roadmap.js +173 -76
- package/output/source/tools/roadmap.test.js +58 -42
- package/output/source/tools/task.js +380 -255
- package/output/source/tools/task.test.js +117 -110
- package/output/source/types.js +22 -22
- package/package.json +8 -2
|
@@ -1,15 +1,14 @@
|
|
|
1
|
-
import fs from
|
|
2
|
-
import path from
|
|
3
|
-
import process from
|
|
4
|
-
import { z } from
|
|
5
|
-
import { discoverGovernanceArtifacts, catchIt, PROJECT_LINT_CODES, renderLintSuggestions, ensureStore, replaceRoadmapsInStore, replaceTasksInStore, markMarkdownViewDirty, loadTaskStatusStatsFromStore,
|
|
6
|
-
import { asText, evidenceSection, getDefaultToolTemplateMarkdown, guidanceSection, lintSection, nextCallSection, renderToolResponseMarkdown, summarySection, } from
|
|
7
|
-
import { collectTaskLintSuggestions, loadTasksDocument, loadTasksDocumentWithOptions, renderTasksMarkdown } from
|
|
8
|
-
import { loadRoadmapDocumentWithOptions, renderRoadmapMarkdown } from
|
|
9
|
-
export const PROJECT_MARKER =
|
|
10
|
-
const DEFAULT_GOVERNANCE_DIR =
|
|
11
|
-
const ignoreNames = new Set([
|
|
12
|
-
const DEFAULT_SCAN_DEPTH = 3;
|
|
1
|
+
import fs from 'node:fs/promises';
|
|
2
|
+
import path from 'node:path';
|
|
3
|
+
import process from 'node:process';
|
|
4
|
+
import { z } from 'zod';
|
|
5
|
+
import { discoverGovernanceArtifacts, catchIt, PROJECT_LINT_CODES, renderLintSuggestions, ensureStore, replaceRoadmapsInStore, replaceTasksInStore, markMarkdownViewDirty, loadTaskStatusStatsFromStore, } from '../common/index.js';
|
|
6
|
+
import { asText, evidenceSection, getDefaultToolTemplateMarkdown, guidanceSection, lintSection, nextCallSection, renderToolResponseMarkdown, summarySection, } from '../common/index.js';
|
|
7
|
+
import { collectTaskLintSuggestions, loadTasksDocument, loadTasksDocumentWithOptions, renderTasksMarkdown } from './task.js';
|
|
8
|
+
import { loadRoadmapDocumentWithOptions, renderRoadmapMarkdown } from './roadmap.js';
|
|
9
|
+
export const PROJECT_MARKER = '.projitive';
|
|
10
|
+
const DEFAULT_GOVERNANCE_DIR = '.projitive';
|
|
11
|
+
const ignoreNames = new Set(['node_modules', '.git', '.next', 'dist', 'build']);
|
|
13
12
|
const MAX_SCAN_DEPTH = 8;
|
|
14
13
|
function normalizePath(inputPath) {
|
|
15
14
|
return inputPath ? path.resolve(inputPath) : process.cwd();
|
|
@@ -17,21 +16,21 @@ function normalizePath(inputPath) {
|
|
|
17
16
|
function normalizeGovernanceDirName(input) {
|
|
18
17
|
const name = input?.trim() || DEFAULT_GOVERNANCE_DIR;
|
|
19
18
|
if (!name) {
|
|
20
|
-
throw new Error(
|
|
19
|
+
throw new Error('governanceDir cannot be empty');
|
|
21
20
|
}
|
|
22
21
|
if (path.isAbsolute(name)) {
|
|
23
|
-
throw new Error(
|
|
22
|
+
throw new Error('governanceDir must be a relative directory name');
|
|
24
23
|
}
|
|
25
|
-
if (name.includes(
|
|
26
|
-
throw new Error(
|
|
24
|
+
if (name.includes('/') || name.includes('\\')) {
|
|
25
|
+
throw new Error('governanceDir must not contain path separators');
|
|
27
26
|
}
|
|
28
|
-
if (name ===
|
|
29
|
-
throw new Error(
|
|
27
|
+
if (name === '.' || name === '..') {
|
|
28
|
+
throw new Error('governanceDir must be a normal directory name');
|
|
30
29
|
}
|
|
31
30
|
return name;
|
|
32
31
|
}
|
|
33
32
|
function parseDepthFromEnv(rawDepth) {
|
|
34
|
-
if (typeof rawDepth !==
|
|
33
|
+
if (typeof rawDepth !== 'string' || rawDepth.trim().length === 0) {
|
|
35
34
|
return undefined;
|
|
36
35
|
}
|
|
37
36
|
const parsed = Number.parseInt(rawDepth, 10);
|
|
@@ -42,7 +41,7 @@ function parseDepthFromEnv(rawDepth) {
|
|
|
42
41
|
}
|
|
43
42
|
function requireEnvVar(name) {
|
|
44
43
|
const value = process.env[name];
|
|
45
|
-
if (typeof value !==
|
|
44
|
+
if (typeof value !== 'string' || value.trim().length === 0) {
|
|
46
45
|
throw new Error(`Missing required environment variable: ${name}`);
|
|
47
46
|
}
|
|
48
47
|
return value.trim();
|
|
@@ -67,47 +66,47 @@ export function resolveScanRoots(inputPaths) {
|
|
|
67
66
|
return normalizedInputPaths;
|
|
68
67
|
}
|
|
69
68
|
const configuredRoots = process.env.PROJITIVE_SCAN_ROOT_PATHS;
|
|
70
|
-
const rootsFromMultiEnv = typeof configuredRoots ===
|
|
69
|
+
const rootsFromMultiEnv = typeof configuredRoots === 'string'
|
|
71
70
|
? normalizeScanRoots(parseScanRoots(configuredRoots))
|
|
72
71
|
: [];
|
|
73
72
|
if (rootsFromMultiEnv.length > 0) {
|
|
74
73
|
return rootsFromMultiEnv;
|
|
75
74
|
}
|
|
76
75
|
const legacyRoot = process.env.PROJITIVE_SCAN_ROOT_PATH;
|
|
77
|
-
const rootsFromLegacyEnv = typeof legacyRoot ===
|
|
76
|
+
const rootsFromLegacyEnv = typeof legacyRoot === 'string'
|
|
78
77
|
? normalizeScanRoots([legacyRoot])
|
|
79
78
|
: [];
|
|
80
79
|
if (rootsFromLegacyEnv.length > 0) {
|
|
81
80
|
return rootsFromLegacyEnv;
|
|
82
81
|
}
|
|
83
|
-
throw new Error(
|
|
82
|
+
throw new Error('Missing required environment variable: PROJITIVE_SCAN_ROOT_PATHS (or legacy PROJITIVE_SCAN_ROOT_PATH)');
|
|
84
83
|
}
|
|
85
84
|
export function resolveScanRoot(inputPath) {
|
|
86
85
|
return resolveScanRoots(inputPath ? [inputPath] : undefined)[0];
|
|
87
86
|
}
|
|
88
87
|
export function resolveScanDepth(inputDepth) {
|
|
89
|
-
const configuredDepthRaw = requireEnvVar(
|
|
88
|
+
const configuredDepthRaw = requireEnvVar('PROJITIVE_SCAN_MAX_DEPTH');
|
|
90
89
|
const configuredDepth = parseDepthFromEnv(configuredDepthRaw);
|
|
91
|
-
if (typeof configuredDepth !==
|
|
92
|
-
throw new Error(
|
|
90
|
+
if (typeof configuredDepth !== 'number') {
|
|
91
|
+
throw new Error('Invalid PROJITIVE_SCAN_MAX_DEPTH: expected integer in range 0-8');
|
|
93
92
|
}
|
|
94
|
-
if (typeof inputDepth ===
|
|
93
|
+
if (typeof inputDepth === 'number') {
|
|
95
94
|
return inputDepth;
|
|
96
95
|
}
|
|
97
96
|
return configuredDepth;
|
|
98
97
|
}
|
|
99
98
|
function renderArtifactsMarkdown(artifacts) {
|
|
100
99
|
const rows = artifacts.map((item) => {
|
|
101
|
-
if (item.kind ===
|
|
102
|
-
const lineText = item.lineCount == null ?
|
|
103
|
-
return `- ${item.exists ?
|
|
100
|
+
if (item.kind === 'file') {
|
|
101
|
+
const lineText = item.lineCount == null ? '-' : String(item.lineCount);
|
|
102
|
+
return `- ${item.exists ? '✅' : '❌'} ${item.name} \n path: ${item.path} \n lineCount: ${lineText}`;
|
|
104
103
|
}
|
|
105
104
|
const nested = (item.markdownFiles ?? [])
|
|
106
105
|
.map((entry) => ` - ${entry.path} (lines: ${entry.lineCount})`)
|
|
107
|
-
.join(
|
|
108
|
-
return `- ${item.exists ?
|
|
106
|
+
.join('\n');
|
|
107
|
+
return `- ${item.exists ? '✅' : '❌'} ${item.name}/ \n path: ${item.path}${nested ? `\n markdownFiles:\n${nested}` : ''}`;
|
|
109
108
|
});
|
|
110
|
-
return rows.join(
|
|
109
|
+
return rows.join('\n');
|
|
111
110
|
}
|
|
112
111
|
async function readTasksSnapshot(governanceDir) {
|
|
113
112
|
const tasksPath = path.join(governanceDir, PROJECT_MARKER);
|
|
@@ -118,8 +117,8 @@ async function readTasksSnapshot(governanceDir) {
|
|
|
118
117
|
lintSuggestions: renderLintSuggestions([
|
|
119
118
|
{
|
|
120
119
|
code: PROJECT_LINT_CODES.TASKS_FILE_MISSING,
|
|
121
|
-
message:
|
|
122
|
-
fixHint:
|
|
120
|
+
message: 'governance store is missing.',
|
|
121
|
+
fixHint: 'Initialize governance tasks structure first.',
|
|
123
122
|
},
|
|
124
123
|
]),
|
|
125
124
|
todo: 0,
|
|
@@ -127,7 +126,7 @@ async function readTasksSnapshot(governanceDir) {
|
|
|
127
126
|
blocked: 0,
|
|
128
127
|
done: 0,
|
|
129
128
|
total: 0,
|
|
130
|
-
latestUpdatedAt:
|
|
129
|
+
latestUpdatedAt: '(unknown)',
|
|
131
130
|
score: 0,
|
|
132
131
|
};
|
|
133
132
|
}
|
|
@@ -141,15 +140,10 @@ async function readTasksSnapshot(governanceDir) {
|
|
|
141
140
|
blocked: stats.blocked,
|
|
142
141
|
done: stats.done,
|
|
143
142
|
total: stats.total,
|
|
144
|
-
latestUpdatedAt: stats.latestUpdatedAt ||
|
|
143
|
+
latestUpdatedAt: stats.latestUpdatedAt || '(unknown)',
|
|
145
144
|
score: stats.inProgress * 2 + stats.todo,
|
|
146
145
|
};
|
|
147
146
|
}
|
|
148
|
-
async function readRoadmapIds(governanceDir) {
|
|
149
|
-
const dbPath = path.join(governanceDir, PROJECT_MARKER);
|
|
150
|
-
await ensureStore(dbPath);
|
|
151
|
-
return loadRoadmapIdsFromStore(dbPath);
|
|
152
|
-
}
|
|
153
147
|
export async function hasProjectMarker(dirPath) {
|
|
154
148
|
const markerPath = path.join(dirPath, PROJECT_MARKER);
|
|
155
149
|
const statResult = await catchIt(fs.stat(markerPath));
|
|
@@ -246,19 +240,21 @@ export async function discoverProjectsAcrossRoots(rootPaths, maxDepth) {
|
|
|
246
240
|
return Array.from(new Set(perRootResults.flat())).sort();
|
|
247
241
|
}
|
|
248
242
|
const DEFAULT_TOOL_TEMPLATE_NAMES = [
|
|
249
|
-
|
|
250
|
-
|
|
251
|
-
|
|
252
|
-
|
|
253
|
-
|
|
254
|
-
|
|
255
|
-
|
|
256
|
-
|
|
257
|
-
|
|
258
|
-
|
|
259
|
-
|
|
260
|
-
|
|
261
|
-
|
|
243
|
+
'projectInit',
|
|
244
|
+
'projectScan',
|
|
245
|
+
'projectNext',
|
|
246
|
+
'projectLocate',
|
|
247
|
+
'projectContext',
|
|
248
|
+
'syncViews',
|
|
249
|
+
'taskList',
|
|
250
|
+
'taskNext',
|
|
251
|
+
'taskContext',
|
|
252
|
+
'taskCreate',
|
|
253
|
+
'taskUpdate',
|
|
254
|
+
'roadmapList',
|
|
255
|
+
'roadmapContext',
|
|
256
|
+
'roadmapCreate',
|
|
257
|
+
'roadmapUpdate',
|
|
262
258
|
];
|
|
263
259
|
async function pathExists(targetPath) {
|
|
264
260
|
const accessResult = await catchIt(fs.access(targetPath));
|
|
@@ -267,23 +263,23 @@ async function pathExists(targetPath) {
|
|
|
267
263
|
async function writeTextFile(targetPath, content, force) {
|
|
268
264
|
const exists = await pathExists(targetPath);
|
|
269
265
|
if (exists && !force) {
|
|
270
|
-
return { path: targetPath, action:
|
|
266
|
+
return { path: targetPath, action: 'skipped' };
|
|
271
267
|
}
|
|
272
|
-
await fs.writeFile(targetPath, content,
|
|
273
|
-
return { path: targetPath, action: exists ?
|
|
268
|
+
await fs.writeFile(targetPath, content, 'utf-8');
|
|
269
|
+
return { path: targetPath, action: exists ? 'updated' : 'created' };
|
|
274
270
|
}
|
|
275
271
|
function defaultReadmeMarkdown(governanceDirName) {
|
|
276
272
|
return [
|
|
277
|
-
|
|
278
|
-
|
|
273
|
+
'# Projitive Governance Workspace',
|
|
274
|
+
'',
|
|
279
275
|
`This directory (\`${governanceDirName}/\`) is the governance root for this project.`,
|
|
280
|
-
|
|
281
|
-
|
|
282
|
-
|
|
283
|
-
|
|
284
|
-
|
|
285
|
-
|
|
286
|
-
].join(
|
|
276
|
+
'',
|
|
277
|
+
'## Conventions',
|
|
278
|
+
'- Keep roadmap/task source of truth in .projitive governance store.',
|
|
279
|
+
'- Treat roadmap.md/tasks.md as generated views from governance store.',
|
|
280
|
+
'- Keep IDs stable (TASK-xxxx / ROADMAP-xxxx).',
|
|
281
|
+
'- Update report evidence before status transitions.',
|
|
282
|
+
].join('\n');
|
|
287
283
|
}
|
|
288
284
|
function defaultRoadmapMarkdown(milestones = defaultRoadmapMilestones()) {
|
|
289
285
|
return renderRoadmapMarkdown(milestones);
|
|
@@ -291,51 +287,51 @@ function defaultRoadmapMarkdown(milestones = defaultRoadmapMilestones()) {
|
|
|
291
287
|
function defaultTasksMarkdown(updatedAt = new Date().toISOString()) {
|
|
292
288
|
return renderTasksMarkdown([
|
|
293
289
|
{
|
|
294
|
-
id:
|
|
295
|
-
title:
|
|
296
|
-
status:
|
|
297
|
-
owner:
|
|
298
|
-
summary:
|
|
290
|
+
id: 'TASK-0001',
|
|
291
|
+
title: 'Bootstrap governance workspace',
|
|
292
|
+
status: 'TODO',
|
|
293
|
+
owner: 'unassigned',
|
|
294
|
+
summary: 'Create initial governance artifacts and confirm task execution loop.',
|
|
299
295
|
updatedAt,
|
|
300
296
|
links: [],
|
|
301
|
-
roadmapRefs: [
|
|
297
|
+
roadmapRefs: ['ROADMAP-0001'],
|
|
302
298
|
},
|
|
303
299
|
]);
|
|
304
300
|
}
|
|
305
301
|
function defaultRoadmapMilestones() {
|
|
306
302
|
return [{
|
|
307
|
-
id:
|
|
308
|
-
title:
|
|
309
|
-
status:
|
|
310
|
-
time:
|
|
303
|
+
id: 'ROADMAP-0001',
|
|
304
|
+
title: 'Bootstrap governance baseline',
|
|
305
|
+
status: 'active',
|
|
306
|
+
time: '2026-Q1',
|
|
311
307
|
updatedAt: new Date().toISOString(),
|
|
312
308
|
}];
|
|
313
309
|
}
|
|
314
310
|
function defaultTemplateReadmeMarkdown() {
|
|
315
311
|
return [
|
|
316
|
-
|
|
317
|
-
|
|
318
|
-
|
|
319
|
-
|
|
320
|
-
|
|
321
|
-
|
|
322
|
-
|
|
323
|
-
|
|
324
|
-
|
|
325
|
-
|
|
326
|
-
|
|
327
|
-
|
|
328
|
-
|
|
329
|
-
|
|
330
|
-
|
|
331
|
-
|
|
332
|
-
|
|
333
|
-
|
|
334
|
-
|
|
335
|
-
|
|
336
|
-
|
|
337
|
-
|
|
338
|
-
].join(
|
|
312
|
+
'# Template Guide',
|
|
313
|
+
'',
|
|
314
|
+
'This directory stores response templates (one file per tool).',
|
|
315
|
+
'',
|
|
316
|
+
'How to enable:',
|
|
317
|
+
'- Set env `PROJITIVE_MESSAGE_TEMPLATE_PATH` to a template directory.',
|
|
318
|
+
'- Example: .projitive/templates/tools',
|
|
319
|
+
'',
|
|
320
|
+
'Rule:',
|
|
321
|
+
'- Prefer one template per tool: <toolName>.md (e.g. taskNext.md).',
|
|
322
|
+
'- Template directory mode only loads <toolName>.md files.',
|
|
323
|
+
'- 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
|
+
'',
|
|
327
|
+
'Basic Variables:',
|
|
328
|
+
'- {{tool_name}}',
|
|
329
|
+
'- {{summary}}',
|
|
330
|
+
'- {{evidence}}',
|
|
331
|
+
'- {{guidance}}',
|
|
332
|
+
'- {{next_call}}',
|
|
333
|
+
'- {{content}}',
|
|
334
|
+
].join('\n');
|
|
339
335
|
}
|
|
340
336
|
export async function initializeProjectStructure(inputPath, governanceDir, force = false) {
|
|
341
337
|
const projectPath = normalizePath(inputPath);
|
|
@@ -351,15 +347,15 @@ export async function initializeProjectStructure(inputPath, governanceDir, force
|
|
|
351
347
|
const directories = [];
|
|
352
348
|
const requiredDirectories = [
|
|
353
349
|
governancePath,
|
|
354
|
-
path.join(governancePath,
|
|
355
|
-
path.join(governancePath,
|
|
356
|
-
path.join(governancePath,
|
|
357
|
-
path.join(governancePath,
|
|
350
|
+
path.join(governancePath, 'designs'),
|
|
351
|
+
path.join(governancePath, 'reports'),
|
|
352
|
+
path.join(governancePath, 'templates'),
|
|
353
|
+
path.join(governancePath, 'templates', 'tools'),
|
|
358
354
|
];
|
|
359
355
|
for (const dirPath of requiredDirectories) {
|
|
360
356
|
const exists = await pathExists(dirPath);
|
|
361
357
|
await fs.mkdir(dirPath, { recursive: true });
|
|
362
|
-
directories.push({ path: dirPath, action: exists ?
|
|
358
|
+
directories.push({ path: dirPath, action: exists ? 'skipped' : 'created' });
|
|
363
359
|
}
|
|
364
360
|
const markerPath = path.join(governancePath, PROJECT_MARKER);
|
|
365
361
|
const defaultRoadmapData = defaultRoadmapMilestones();
|
|
@@ -370,24 +366,24 @@ export async function initializeProjectStructure(inputPath, governanceDir, force
|
|
|
370
366
|
await replaceRoadmapsInStore(markerPath, defaultRoadmapData);
|
|
371
367
|
await replaceTasksInStore(markerPath, [
|
|
372
368
|
{
|
|
373
|
-
id:
|
|
374
|
-
title:
|
|
375
|
-
status:
|
|
376
|
-
owner:
|
|
377
|
-
summary:
|
|
369
|
+
id: 'TASK-0001',
|
|
370
|
+
title: 'Bootstrap governance workspace',
|
|
371
|
+
status: 'TODO',
|
|
372
|
+
owner: 'unassigned',
|
|
373
|
+
summary: 'Create initial governance artifacts and confirm task execution loop.',
|
|
378
374
|
updatedAt: defaultTaskUpdatedAt,
|
|
379
375
|
links: [],
|
|
380
|
-
roadmapRefs: [
|
|
376
|
+
roadmapRefs: ['ROADMAP-0001'],
|
|
381
377
|
},
|
|
382
378
|
]);
|
|
383
379
|
}
|
|
384
380
|
const baseFiles = await Promise.all([
|
|
385
|
-
writeTextFile(path.join(governancePath,
|
|
386
|
-
writeTextFile(path.join(governancePath,
|
|
387
|
-
writeTextFile(path.join(governancePath,
|
|
388
|
-
writeTextFile(path.join(governancePath,
|
|
381
|
+
writeTextFile(path.join(governancePath, 'README.md'), defaultReadmeMarkdown(governanceDirName), force),
|
|
382
|
+
writeTextFile(path.join(governancePath, 'roadmap.md'), defaultRoadmapMarkdown(defaultRoadmapData), force),
|
|
383
|
+
writeTextFile(path.join(governancePath, 'tasks.md'), defaultTasksMarkdown(defaultTaskUpdatedAt), force),
|
|
384
|
+
writeTextFile(path.join(governancePath, 'templates', 'README.md'), defaultTemplateReadmeMarkdown(), force),
|
|
389
385
|
]);
|
|
390
|
-
const toolTemplateFiles = await Promise.all(DEFAULT_TOOL_TEMPLATE_NAMES.map((toolName) => writeTextFile(path.join(governancePath,
|
|
386
|
+
const toolTemplateFiles = await Promise.all(DEFAULT_TOOL_TEMPLATE_NAMES.map((toolName) => writeTextFile(path.join(governancePath, 'templates', 'tools', `${toolName}.md`), getDefaultToolTemplateMarkdown(toolName), force)));
|
|
391
387
|
const files = [...baseFiles, ...toolTemplateFiles];
|
|
392
388
|
return {
|
|
393
389
|
projectPath,
|
|
@@ -397,9 +393,9 @@ export async function initializeProjectStructure(inputPath, governanceDir, force
|
|
|
397
393
|
};
|
|
398
394
|
}
|
|
399
395
|
export function registerProjectTools(server) {
|
|
400
|
-
server.registerTool(
|
|
401
|
-
title:
|
|
402
|
-
description:
|
|
396
|
+
server.registerTool('projectInit', {
|
|
397
|
+
title: 'Project Init',
|
|
398
|
+
description: 'Bootstrap governance files when a project has no .projitive yet (requires projectPath)',
|
|
403
399
|
inputSchema: {
|
|
404
400
|
projectPath: z.string(),
|
|
405
401
|
governanceDir: z.string().optional(),
|
|
@@ -408,78 +404,79 @@ export function registerProjectTools(server) {
|
|
|
408
404
|
}, async ({ projectPath, governanceDir, force }) => {
|
|
409
405
|
const initialized = await initializeProjectStructure(projectPath, governanceDir, force ?? false);
|
|
410
406
|
const filesByAction = {
|
|
411
|
-
created: initialized.files.filter((item) => item.action ===
|
|
412
|
-
updated: initialized.files.filter((item) => item.action ===
|
|
413
|
-
skipped: initialized.files.filter((item) => item.action ===
|
|
407
|
+
created: initialized.files.filter((item) => item.action === 'created'),
|
|
408
|
+
updated: initialized.files.filter((item) => item.action === 'updated'),
|
|
409
|
+
skipped: initialized.files.filter((item) => item.action === 'skipped'),
|
|
414
410
|
};
|
|
415
411
|
const markdown = renderToolResponseMarkdown({
|
|
416
|
-
toolName:
|
|
412
|
+
toolName: 'projectInit',
|
|
417
413
|
sections: [
|
|
418
414
|
summarySection([
|
|
419
415
|
`- projectPath: ${initialized.projectPath}`,
|
|
420
416
|
`- governanceDir: ${initialized.governanceDir}`,
|
|
421
|
-
`- force: ${force === true ?
|
|
417
|
+
`- force: ${force === true ? 'true' : 'false'}`,
|
|
422
418
|
]),
|
|
423
419
|
evidenceSection([
|
|
424
420
|
`- createdFiles: ${filesByAction.created.length}`,
|
|
425
421
|
`- updatedFiles: ${filesByAction.updated.length}`,
|
|
426
422
|
`- skippedFiles: ${filesByAction.skipped.length}`,
|
|
427
|
-
|
|
423
|
+
'- directories:',
|
|
428
424
|
...initialized.directories.map((item) => ` - ${item.action}: ${item.path}`),
|
|
429
|
-
|
|
425
|
+
'- files:',
|
|
430
426
|
...initialized.files.map((item) => ` - ${item.action}: ${item.path}`),
|
|
431
427
|
]),
|
|
432
428
|
guidanceSection([
|
|
433
|
-
|
|
434
|
-
|
|
429
|
+
'- If files were skipped and you want to overwrite templates, rerun with force=true.',
|
|
430
|
+
'- Continue with projectContext and taskList for execution.',
|
|
435
431
|
]),
|
|
436
432
|
lintSection([
|
|
437
|
-
|
|
438
|
-
|
|
433
|
+
'- After init, fill owner/roadmapRefs/links in .projitive task table before marking DONE.',
|
|
434
|
+
'- Keep task source-of-truth inside .projitive governance store.',
|
|
439
435
|
]),
|
|
440
|
-
nextCallSection(`projectContext(projectPath
|
|
436
|
+
nextCallSection(`projectContext(projectPath="${initialized.projectPath}")`),
|
|
441
437
|
],
|
|
442
438
|
});
|
|
443
439
|
return asText(markdown);
|
|
444
440
|
});
|
|
445
|
-
server.registerTool(
|
|
446
|
-
title:
|
|
447
|
-
description:
|
|
441
|
+
server.registerTool('projectScan', {
|
|
442
|
+
title: 'Project Scan',
|
|
443
|
+
description: 'Start here when project path is unknown; discover all governance roots',
|
|
448
444
|
inputSchema: {},
|
|
449
445
|
}, async () => {
|
|
450
446
|
const roots = resolveScanRoots();
|
|
451
447
|
const depth = resolveScanDepth();
|
|
452
|
-
const
|
|
448
|
+
const governanceDirs = await discoverProjectsAcrossRoots(roots, depth);
|
|
449
|
+
const projects = Array.from(new Set(governanceDirs.map((governanceDir) => toProjectPath(governanceDir)))).sort();
|
|
453
450
|
const markdown = renderToolResponseMarkdown({
|
|
454
|
-
toolName:
|
|
451
|
+
toolName: 'projectScan',
|
|
455
452
|
sections: [
|
|
456
453
|
summarySection([
|
|
457
|
-
`- rootPaths: ${roots.join(
|
|
454
|
+
`- rootPaths: ${roots.join(', ')}`,
|
|
458
455
|
`- rootCount: ${roots.length}`,
|
|
459
456
|
`- maxDepth: ${depth}`,
|
|
460
457
|
`- discoveredCount: ${projects.length}`,
|
|
461
458
|
]),
|
|
462
459
|
evidenceSection([
|
|
463
|
-
|
|
460
|
+
'- projects:',
|
|
464
461
|
...projects.map((project, index) => `${index + 1}. ${project}`),
|
|
465
462
|
]),
|
|
466
463
|
guidanceSection([
|
|
467
|
-
|
|
468
|
-
|
|
464
|
+
'- Use one discovered project path and call `projectLocate` to lock governance root.',
|
|
465
|
+
'- Then call `projectContext` to inspect current governance state.',
|
|
469
466
|
]),
|
|
470
467
|
lintSection(projects.length === 0
|
|
471
|
-
? [
|
|
472
|
-
: [
|
|
468
|
+
? ['- No governance root discovered. Add `.projitive` marker and baseline artifacts before execution.']
|
|
469
|
+
: ['- Run `projectContext` on a discovered project to receive module-level lint suggestions.']),
|
|
473
470
|
nextCallSection(projects[0]
|
|
474
|
-
? `projectLocate(inputPath
|
|
471
|
+
? `projectLocate(inputPath="${projects[0]}")`
|
|
475
472
|
: undefined),
|
|
476
473
|
],
|
|
477
474
|
});
|
|
478
475
|
return asText(markdown);
|
|
479
476
|
});
|
|
480
|
-
server.registerTool(
|
|
481
|
-
title:
|
|
482
|
-
description:
|
|
477
|
+
server.registerTool('projectNext', {
|
|
478
|
+
title: 'Project Next',
|
|
479
|
+
description: 'Rank actionable projects and return the best execution target',
|
|
483
480
|
inputSchema: {
|
|
484
481
|
limit: z.number().int().min(1).max(50).optional(),
|
|
485
482
|
},
|
|
@@ -517,10 +514,10 @@ export function registerProjectTools(server) {
|
|
|
517
514
|
ranked[0].lintSuggestions = collectTaskLintSuggestions(topDoc.tasks);
|
|
518
515
|
}
|
|
519
516
|
const markdown = renderToolResponseMarkdown({
|
|
520
|
-
toolName:
|
|
517
|
+
toolName: 'projectNext',
|
|
521
518
|
sections: [
|
|
522
519
|
summarySection([
|
|
523
|
-
`- rootPaths: ${roots.join(
|
|
520
|
+
`- rootPaths: ${roots.join(', ')}`,
|
|
524
521
|
`- rootCount: ${roots.length}`,
|
|
525
522
|
`- maxDepth: ${depth}`,
|
|
526
523
|
`- matchedProjects: ${projects.length}`,
|
|
@@ -528,25 +525,25 @@ export function registerProjectTools(server) {
|
|
|
528
525
|
`- limit: ${limit ?? 10}`,
|
|
529
526
|
]),
|
|
530
527
|
evidenceSection([
|
|
531
|
-
|
|
532
|
-
...ranked.map((item, index) => `${index + 1}. ${item.governanceDir} | actionable=${item.actionable} | in_progress=${item.inProgress} | todo=${item.todo} | blocked=${item.blocked} | done=${item.done} | latest=${item.latestUpdatedAt}${item.tasksExists ?
|
|
528
|
+
'- rankedProjects:',
|
|
529
|
+
...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'}`),
|
|
533
530
|
]),
|
|
534
531
|
guidanceSection([
|
|
535
|
-
|
|
536
|
-
|
|
537
|
-
|
|
532
|
+
'- Pick top 1 project and call `projectContext` with its projectPath.',
|
|
533
|
+
'- Then call `taskList` and `taskContext` to continue execution.',
|
|
534
|
+
'- If governance store is missing, initialize governance before task-level operations.',
|
|
538
535
|
]),
|
|
539
536
|
lintSection(ranked[0]?.lintSuggestions ?? []),
|
|
540
537
|
nextCallSection(ranked[0]
|
|
541
|
-
? `projectContext(projectPath
|
|
538
|
+
? `projectContext(projectPath="${toProjectPath(ranked[0].governanceDir)}")`
|
|
542
539
|
: undefined),
|
|
543
540
|
],
|
|
544
541
|
});
|
|
545
542
|
return asText(markdown);
|
|
546
543
|
});
|
|
547
|
-
server.registerTool(
|
|
548
|
-
title:
|
|
549
|
-
description:
|
|
544
|
+
server.registerTool('projectLocate', {
|
|
545
|
+
title: 'Project Locate',
|
|
546
|
+
description: 'Resolve the nearest governance root from any in-project path',
|
|
550
547
|
inputSchema: {
|
|
551
548
|
inputPath: z.string(),
|
|
552
549
|
},
|
|
@@ -555,78 +552,84 @@ export function registerProjectTools(server) {
|
|
|
555
552
|
const governanceDir = await resolveGovernanceDir(resolvedFrom);
|
|
556
553
|
const projectPath = toProjectPath(governanceDir);
|
|
557
554
|
const markdown = renderToolResponseMarkdown({
|
|
558
|
-
toolName:
|
|
555
|
+
toolName: 'projectLocate',
|
|
559
556
|
sections: [
|
|
560
557
|
summarySection([
|
|
561
558
|
`- resolvedFrom: ${resolvedFrom}`,
|
|
562
559
|
`- projectPath: ${projectPath}`,
|
|
563
560
|
`- governanceDir: ${governanceDir}`,
|
|
564
561
|
]),
|
|
565
|
-
guidanceSection([
|
|
566
|
-
lintSection([
|
|
567
|
-
nextCallSection(`projectContext(projectPath
|
|
562
|
+
guidanceSection(['- Call `projectContext` with this projectPath to get task and roadmap summaries.']),
|
|
563
|
+
lintSection(['- Run `projectContext` to get governance/module lint suggestions for this project.']),
|
|
564
|
+
nextCallSection(`projectContext(projectPath="${projectPath}")`),
|
|
568
565
|
],
|
|
569
566
|
});
|
|
570
567
|
return asText(markdown);
|
|
571
568
|
});
|
|
572
|
-
server.registerTool(
|
|
573
|
-
title:
|
|
574
|
-
description:
|
|
569
|
+
server.registerTool('syncViews', {
|
|
570
|
+
title: 'Sync Views',
|
|
571
|
+
description: 'Materialize markdown views from .projitive governance store (tasks.md / roadmap.md)',
|
|
575
572
|
inputSchema: {
|
|
576
573
|
projectPath: z.string(),
|
|
577
|
-
views: z.array(z.enum([
|
|
574
|
+
views: z.array(z.enum(['tasks', 'roadmap'])).optional(),
|
|
578
575
|
force: z.boolean().optional(),
|
|
579
576
|
},
|
|
580
577
|
}, async ({ projectPath, views, force }) => {
|
|
581
578
|
const governanceDir = await resolveGovernanceDir(projectPath);
|
|
579
|
+
const normalizedProjectPath = toProjectPath(governanceDir);
|
|
580
|
+
const tasksViewPath = path.join(governanceDir, 'tasks.md');
|
|
581
|
+
const roadmapViewPath = path.join(governanceDir, 'roadmap.md');
|
|
582
582
|
const dbPath = path.join(governanceDir, PROJECT_MARKER);
|
|
583
583
|
const selectedViews = views && views.length > 0
|
|
584
584
|
? Array.from(new Set(views))
|
|
585
|
-
: [
|
|
585
|
+
: ['tasks', 'roadmap'];
|
|
586
586
|
const forceSync = force === true;
|
|
587
587
|
if (forceSync) {
|
|
588
|
-
if (selectedViews.includes(
|
|
589
|
-
await markMarkdownViewDirty(dbPath,
|
|
588
|
+
if (selectedViews.includes('tasks')) {
|
|
589
|
+
await markMarkdownViewDirty(dbPath, 'tasks_markdown');
|
|
590
590
|
}
|
|
591
|
-
if (selectedViews.includes(
|
|
592
|
-
await markMarkdownViewDirty(dbPath,
|
|
591
|
+
if (selectedViews.includes('roadmap')) {
|
|
592
|
+
await markMarkdownViewDirty(dbPath, 'roadmaps_markdown');
|
|
593
593
|
}
|
|
594
594
|
}
|
|
595
595
|
let taskCount;
|
|
596
596
|
let roadmapCount;
|
|
597
|
-
if (selectedViews.includes(
|
|
597
|
+
if (selectedViews.includes('tasks')) {
|
|
598
598
|
const taskDoc = await loadTasksDocumentWithOptions(governanceDir, forceSync);
|
|
599
599
|
taskCount = taskDoc.tasks.length;
|
|
600
600
|
}
|
|
601
|
-
if (selectedViews.includes(
|
|
601
|
+
if (selectedViews.includes('roadmap')) {
|
|
602
602
|
const roadmapDoc = await loadRoadmapDocumentWithOptions(governanceDir, forceSync);
|
|
603
603
|
roadmapCount = roadmapDoc.milestones.length;
|
|
604
604
|
}
|
|
605
605
|
const markdown = renderToolResponseMarkdown({
|
|
606
|
-
toolName:
|
|
606
|
+
toolName: 'syncViews',
|
|
607
607
|
sections: [
|
|
608
608
|
summarySection([
|
|
609
|
+
`- projectPath: ${normalizedProjectPath}`,
|
|
609
610
|
`- governanceDir: ${governanceDir}`,
|
|
610
|
-
`-
|
|
611
|
-
`-
|
|
611
|
+
`- tasksView: ${tasksViewPath}`,
|
|
612
|
+
`- roadmapView: ${roadmapViewPath}`,
|
|
613
|
+
`- views: ${selectedViews.join(', ')}`,
|
|
614
|
+
`- force: ${forceSync ? 'true' : 'false'}`,
|
|
612
615
|
]),
|
|
613
616
|
evidenceSection([
|
|
614
|
-
...(typeof taskCount ===
|
|
615
|
-
...(typeof roadmapCount ===
|
|
617
|
+
...(typeof taskCount === 'number' ? [`- tasks.md synced | taskCount=${taskCount}`] : []),
|
|
618
|
+
...(typeof roadmapCount === 'number' ? [`- roadmap.md synced | roadmapCount=${roadmapCount}`] : []),
|
|
616
619
|
]),
|
|
617
620
|
guidanceSection([
|
|
618
|
-
|
|
619
|
-
|
|
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.',
|
|
620
623
|
]),
|
|
621
624
|
lintSection([]),
|
|
622
|
-
nextCallSection(`projectContext(projectPath="${
|
|
625
|
+
nextCallSection(`projectContext(projectPath="${normalizedProjectPath}")`),
|
|
623
626
|
],
|
|
624
627
|
});
|
|
625
628
|
return asText(markdown);
|
|
626
629
|
});
|
|
627
|
-
server.registerTool(
|
|
628
|
-
title:
|
|
629
|
-
description:
|
|
630
|
+
server.registerTool('projectContext', {
|
|
631
|
+
title: 'Project Context',
|
|
632
|
+
description: 'Get project-level summary before selecting or executing a task',
|
|
630
633
|
inputSchema: {
|
|
631
634
|
projectPath: z.string(),
|
|
632
635
|
},
|
|
@@ -638,34 +641,36 @@ export function registerProjectTools(server) {
|
|
|
638
641
|
await ensureStore(dbPath);
|
|
639
642
|
const taskStats = await loadTaskStatusStatsFromStore(dbPath);
|
|
640
643
|
const { markdownPath: tasksMarkdownPath, tasks } = await loadTasksDocument(governanceDir);
|
|
641
|
-
const
|
|
644
|
+
const { markdownPath: roadmapMarkdownPath, milestones } = await loadRoadmapDocumentWithOptions(governanceDir, false);
|
|
645
|
+
const roadmapIds = milestones.map((item) => item.id);
|
|
642
646
|
const lintSuggestions = collectTaskLintSuggestions(tasks);
|
|
643
647
|
const markdown = renderToolResponseMarkdown({
|
|
644
|
-
toolName:
|
|
648
|
+
toolName: 'projectContext',
|
|
645
649
|
sections: [
|
|
646
650
|
summarySection([
|
|
647
651
|
`- projectPath: ${normalizedProjectPath}`,
|
|
648
652
|
`- governanceDir: ${governanceDir}`,
|
|
649
653
|
`- tasksView: ${tasksMarkdownPath}`,
|
|
654
|
+
`- roadmapView: ${roadmapMarkdownPath}`,
|
|
650
655
|
`- roadmapIds: ${roadmapIds.length}`,
|
|
651
656
|
]),
|
|
652
657
|
evidenceSection([
|
|
653
|
-
|
|
658
|
+
'### Task Summary',
|
|
654
659
|
`- total: ${taskStats.total}`,
|
|
655
660
|
`- TODO: ${taskStats.todo}`,
|
|
656
661
|
`- IN_PROGRESS: ${taskStats.inProgress}`,
|
|
657
662
|
`- BLOCKED: ${taskStats.blocked}`,
|
|
658
663
|
`- DONE: ${taskStats.done}`,
|
|
659
|
-
|
|
660
|
-
|
|
664
|
+
'',
|
|
665
|
+
'### Artifacts',
|
|
661
666
|
renderArtifactsMarkdown(artifacts),
|
|
662
667
|
]),
|
|
663
668
|
guidanceSection([
|
|
664
|
-
|
|
665
|
-
|
|
669
|
+
'- Start from `taskList` to choose a target task.',
|
|
670
|
+
'- Then call `taskContext` with a task ID to retrieve evidence locations and reading order.',
|
|
666
671
|
]),
|
|
667
672
|
lintSection(lintSuggestions),
|
|
668
|
-
nextCallSection(`taskList(projectPath
|
|
673
|
+
nextCallSection(`taskList(projectPath="${normalizedProjectPath}")`),
|
|
669
674
|
],
|
|
670
675
|
});
|
|
671
676
|
return asText(markdown);
|