@projitive/mcp 2.0.3 → 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/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 -184
- package/output/source/prompts/taskExecution.js +148 -148
- 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 +191 -191
- package/output/source/tools/project.test.js +174 -173
- package/output/source/tools/roadmap.js +110 -95
- package/output/source/tools/roadmap.test.js +54 -46
- package/output/source/tools/task.js +305 -277
- 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,21 +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
|
-
|
|
262
|
-
|
|
263
|
-
|
|
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',
|
|
264
258
|
];
|
|
265
259
|
async function pathExists(targetPath) {
|
|
266
260
|
const accessResult = await catchIt(fs.access(targetPath));
|
|
@@ -269,23 +263,23 @@ async function pathExists(targetPath) {
|
|
|
269
263
|
async function writeTextFile(targetPath, content, force) {
|
|
270
264
|
const exists = await pathExists(targetPath);
|
|
271
265
|
if (exists && !force) {
|
|
272
|
-
return { path: targetPath, action:
|
|
266
|
+
return { path: targetPath, action: 'skipped' };
|
|
273
267
|
}
|
|
274
|
-
await fs.writeFile(targetPath, content,
|
|
275
|
-
return { path: targetPath, action: exists ?
|
|
268
|
+
await fs.writeFile(targetPath, content, 'utf-8');
|
|
269
|
+
return { path: targetPath, action: exists ? 'updated' : 'created' };
|
|
276
270
|
}
|
|
277
271
|
function defaultReadmeMarkdown(governanceDirName) {
|
|
278
272
|
return [
|
|
279
|
-
|
|
280
|
-
|
|
273
|
+
'# Projitive Governance Workspace',
|
|
274
|
+
'',
|
|
281
275
|
`This directory (\`${governanceDirName}/\`) is the governance root for this project.`,
|
|
282
|
-
|
|
283
|
-
|
|
284
|
-
|
|
285
|
-
|
|
286
|
-
|
|
287
|
-
|
|
288
|
-
].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');
|
|
289
283
|
}
|
|
290
284
|
function defaultRoadmapMarkdown(milestones = defaultRoadmapMilestones()) {
|
|
291
285
|
return renderRoadmapMarkdown(milestones);
|
|
@@ -293,51 +287,51 @@ function defaultRoadmapMarkdown(milestones = defaultRoadmapMilestones()) {
|
|
|
293
287
|
function defaultTasksMarkdown(updatedAt = new Date().toISOString()) {
|
|
294
288
|
return renderTasksMarkdown([
|
|
295
289
|
{
|
|
296
|
-
id:
|
|
297
|
-
title:
|
|
298
|
-
status:
|
|
299
|
-
owner:
|
|
300
|
-
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.',
|
|
301
295
|
updatedAt,
|
|
302
296
|
links: [],
|
|
303
|
-
roadmapRefs: [
|
|
297
|
+
roadmapRefs: ['ROADMAP-0001'],
|
|
304
298
|
},
|
|
305
299
|
]);
|
|
306
300
|
}
|
|
307
301
|
function defaultRoadmapMilestones() {
|
|
308
302
|
return [{
|
|
309
|
-
id:
|
|
310
|
-
title:
|
|
311
|
-
status:
|
|
312
|
-
time:
|
|
303
|
+
id: 'ROADMAP-0001',
|
|
304
|
+
title: 'Bootstrap governance baseline',
|
|
305
|
+
status: 'active',
|
|
306
|
+
time: '2026-Q1',
|
|
313
307
|
updatedAt: new Date().toISOString(),
|
|
314
308
|
}];
|
|
315
309
|
}
|
|
316
310
|
function defaultTemplateReadmeMarkdown() {
|
|
317
311
|
return [
|
|
318
|
-
|
|
319
|
-
|
|
320
|
-
|
|
321
|
-
|
|
322
|
-
|
|
323
|
-
|
|
324
|
-
|
|
325
|
-
|
|
326
|
-
|
|
327
|
-
|
|
328
|
-
|
|
329
|
-
|
|
330
|
-
|
|
331
|
-
|
|
332
|
-
|
|
333
|
-
|
|
334
|
-
|
|
335
|
-
|
|
336
|
-
|
|
337
|
-
|
|
338
|
-
|
|
339
|
-
|
|
340
|
-
].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');
|
|
341
335
|
}
|
|
342
336
|
export async function initializeProjectStructure(inputPath, governanceDir, force = false) {
|
|
343
337
|
const projectPath = normalizePath(inputPath);
|
|
@@ -353,15 +347,15 @@ export async function initializeProjectStructure(inputPath, governanceDir, force
|
|
|
353
347
|
const directories = [];
|
|
354
348
|
const requiredDirectories = [
|
|
355
349
|
governancePath,
|
|
356
|
-
path.join(governancePath,
|
|
357
|
-
path.join(governancePath,
|
|
358
|
-
path.join(governancePath,
|
|
359
|
-
path.join(governancePath,
|
|
350
|
+
path.join(governancePath, 'designs'),
|
|
351
|
+
path.join(governancePath, 'reports'),
|
|
352
|
+
path.join(governancePath, 'templates'),
|
|
353
|
+
path.join(governancePath, 'templates', 'tools'),
|
|
360
354
|
];
|
|
361
355
|
for (const dirPath of requiredDirectories) {
|
|
362
356
|
const exists = await pathExists(dirPath);
|
|
363
357
|
await fs.mkdir(dirPath, { recursive: true });
|
|
364
|
-
directories.push({ path: dirPath, action: exists ?
|
|
358
|
+
directories.push({ path: dirPath, action: exists ? 'skipped' : 'created' });
|
|
365
359
|
}
|
|
366
360
|
const markerPath = path.join(governancePath, PROJECT_MARKER);
|
|
367
361
|
const defaultRoadmapData = defaultRoadmapMilestones();
|
|
@@ -372,24 +366,24 @@ export async function initializeProjectStructure(inputPath, governanceDir, force
|
|
|
372
366
|
await replaceRoadmapsInStore(markerPath, defaultRoadmapData);
|
|
373
367
|
await replaceTasksInStore(markerPath, [
|
|
374
368
|
{
|
|
375
|
-
id:
|
|
376
|
-
title:
|
|
377
|
-
status:
|
|
378
|
-
owner:
|
|
379
|
-
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.',
|
|
380
374
|
updatedAt: defaultTaskUpdatedAt,
|
|
381
375
|
links: [],
|
|
382
|
-
roadmapRefs: [
|
|
376
|
+
roadmapRefs: ['ROADMAP-0001'],
|
|
383
377
|
},
|
|
384
378
|
]);
|
|
385
379
|
}
|
|
386
380
|
const baseFiles = await Promise.all([
|
|
387
|
-
writeTextFile(path.join(governancePath,
|
|
388
|
-
writeTextFile(path.join(governancePath,
|
|
389
|
-
writeTextFile(path.join(governancePath,
|
|
390
|
-
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),
|
|
391
385
|
]);
|
|
392
|
-
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)));
|
|
393
387
|
const files = [...baseFiles, ...toolTemplateFiles];
|
|
394
388
|
return {
|
|
395
389
|
projectPath,
|
|
@@ -399,9 +393,9 @@ export async function initializeProjectStructure(inputPath, governanceDir, force
|
|
|
399
393
|
};
|
|
400
394
|
}
|
|
401
395
|
export function registerProjectTools(server) {
|
|
402
|
-
server.registerTool(
|
|
403
|
-
title:
|
|
404
|
-
description:
|
|
396
|
+
server.registerTool('projectInit', {
|
|
397
|
+
title: 'Project Init',
|
|
398
|
+
description: 'Bootstrap governance files when a project has no .projitive yet (requires projectPath)',
|
|
405
399
|
inputSchema: {
|
|
406
400
|
projectPath: z.string(),
|
|
407
401
|
governanceDir: z.string().optional(),
|
|
@@ -410,43 +404,43 @@ export function registerProjectTools(server) {
|
|
|
410
404
|
}, async ({ projectPath, governanceDir, force }) => {
|
|
411
405
|
const initialized = await initializeProjectStructure(projectPath, governanceDir, force ?? false);
|
|
412
406
|
const filesByAction = {
|
|
413
|
-
created: initialized.files.filter((item) => item.action ===
|
|
414
|
-
updated: initialized.files.filter((item) => item.action ===
|
|
415
|
-
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'),
|
|
416
410
|
};
|
|
417
411
|
const markdown = renderToolResponseMarkdown({
|
|
418
|
-
toolName:
|
|
412
|
+
toolName: 'projectInit',
|
|
419
413
|
sections: [
|
|
420
414
|
summarySection([
|
|
421
415
|
`- projectPath: ${initialized.projectPath}`,
|
|
422
416
|
`- governanceDir: ${initialized.governanceDir}`,
|
|
423
|
-
`- force: ${force === true ?
|
|
417
|
+
`- force: ${force === true ? 'true' : 'false'}`,
|
|
424
418
|
]),
|
|
425
419
|
evidenceSection([
|
|
426
420
|
`- createdFiles: ${filesByAction.created.length}`,
|
|
427
421
|
`- updatedFiles: ${filesByAction.updated.length}`,
|
|
428
422
|
`- skippedFiles: ${filesByAction.skipped.length}`,
|
|
429
|
-
|
|
423
|
+
'- directories:',
|
|
430
424
|
...initialized.directories.map((item) => ` - ${item.action}: ${item.path}`),
|
|
431
|
-
|
|
425
|
+
'- files:',
|
|
432
426
|
...initialized.files.map((item) => ` - ${item.action}: ${item.path}`),
|
|
433
427
|
]),
|
|
434
428
|
guidanceSection([
|
|
435
|
-
|
|
436
|
-
|
|
429
|
+
'- If files were skipped and you want to overwrite templates, rerun with force=true.',
|
|
430
|
+
'- Continue with projectContext and taskList for execution.',
|
|
437
431
|
]),
|
|
438
432
|
lintSection([
|
|
439
|
-
|
|
440
|
-
|
|
433
|
+
'- After init, fill owner/roadmapRefs/links in .projitive task table before marking DONE.',
|
|
434
|
+
'- Keep task source-of-truth inside .projitive governance store.',
|
|
441
435
|
]),
|
|
442
|
-
nextCallSection(`projectContext(projectPath
|
|
436
|
+
nextCallSection(`projectContext(projectPath="${initialized.projectPath}")`),
|
|
443
437
|
],
|
|
444
438
|
});
|
|
445
439
|
return asText(markdown);
|
|
446
440
|
});
|
|
447
|
-
server.registerTool(
|
|
448
|
-
title:
|
|
449
|
-
description:
|
|
441
|
+
server.registerTool('projectScan', {
|
|
442
|
+
title: 'Project Scan',
|
|
443
|
+
description: 'Start here when project path is unknown; discover all governance roots',
|
|
450
444
|
inputSchema: {},
|
|
451
445
|
}, async () => {
|
|
452
446
|
const roots = resolveScanRoots();
|
|
@@ -454,35 +448,35 @@ export function registerProjectTools(server) {
|
|
|
454
448
|
const governanceDirs = await discoverProjectsAcrossRoots(roots, depth);
|
|
455
449
|
const projects = Array.from(new Set(governanceDirs.map((governanceDir) => toProjectPath(governanceDir)))).sort();
|
|
456
450
|
const markdown = renderToolResponseMarkdown({
|
|
457
|
-
toolName:
|
|
451
|
+
toolName: 'projectScan',
|
|
458
452
|
sections: [
|
|
459
453
|
summarySection([
|
|
460
|
-
`- rootPaths: ${roots.join(
|
|
454
|
+
`- rootPaths: ${roots.join(', ')}`,
|
|
461
455
|
`- rootCount: ${roots.length}`,
|
|
462
456
|
`- maxDepth: ${depth}`,
|
|
463
457
|
`- discoveredCount: ${projects.length}`,
|
|
464
458
|
]),
|
|
465
459
|
evidenceSection([
|
|
466
|
-
|
|
460
|
+
'- projects:',
|
|
467
461
|
...projects.map((project, index) => `${index + 1}. ${project}`),
|
|
468
462
|
]),
|
|
469
463
|
guidanceSection([
|
|
470
|
-
|
|
471
|
-
|
|
464
|
+
'- Use one discovered project path and call `projectLocate` to lock governance root.',
|
|
465
|
+
'- Then call `projectContext` to inspect current governance state.',
|
|
472
466
|
]),
|
|
473
467
|
lintSection(projects.length === 0
|
|
474
|
-
? [
|
|
475
|
-
: [
|
|
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.']),
|
|
476
470
|
nextCallSection(projects[0]
|
|
477
|
-
? `projectLocate(inputPath
|
|
471
|
+
? `projectLocate(inputPath="${projects[0]}")`
|
|
478
472
|
: undefined),
|
|
479
473
|
],
|
|
480
474
|
});
|
|
481
475
|
return asText(markdown);
|
|
482
476
|
});
|
|
483
|
-
server.registerTool(
|
|
484
|
-
title:
|
|
485
|
-
description:
|
|
477
|
+
server.registerTool('projectNext', {
|
|
478
|
+
title: 'Project Next',
|
|
479
|
+
description: 'Rank actionable projects and return the best execution target',
|
|
486
480
|
inputSchema: {
|
|
487
481
|
limit: z.number().int().min(1).max(50).optional(),
|
|
488
482
|
},
|
|
@@ -520,10 +514,10 @@ export function registerProjectTools(server) {
|
|
|
520
514
|
ranked[0].lintSuggestions = collectTaskLintSuggestions(topDoc.tasks);
|
|
521
515
|
}
|
|
522
516
|
const markdown = renderToolResponseMarkdown({
|
|
523
|
-
toolName:
|
|
517
|
+
toolName: 'projectNext',
|
|
524
518
|
sections: [
|
|
525
519
|
summarySection([
|
|
526
|
-
`- rootPaths: ${roots.join(
|
|
520
|
+
`- rootPaths: ${roots.join(', ')}`,
|
|
527
521
|
`- rootCount: ${roots.length}`,
|
|
528
522
|
`- maxDepth: ${depth}`,
|
|
529
523
|
`- matchedProjects: ${projects.length}`,
|
|
@@ -531,25 +525,25 @@ export function registerProjectTools(server) {
|
|
|
531
525
|
`- limit: ${limit ?? 10}`,
|
|
532
526
|
]),
|
|
533
527
|
evidenceSection([
|
|
534
|
-
|
|
535
|
-
...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 ?
|
|
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'}`),
|
|
536
530
|
]),
|
|
537
531
|
guidanceSection([
|
|
538
|
-
|
|
539
|
-
|
|
540
|
-
|
|
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.',
|
|
541
535
|
]),
|
|
542
536
|
lintSection(ranked[0]?.lintSuggestions ?? []),
|
|
543
537
|
nextCallSection(ranked[0]
|
|
544
|
-
? `projectContext(projectPath
|
|
538
|
+
? `projectContext(projectPath="${toProjectPath(ranked[0].governanceDir)}")`
|
|
545
539
|
: undefined),
|
|
546
540
|
],
|
|
547
541
|
});
|
|
548
542
|
return asText(markdown);
|
|
549
543
|
});
|
|
550
|
-
server.registerTool(
|
|
551
|
-
title:
|
|
552
|
-
description:
|
|
544
|
+
server.registerTool('projectLocate', {
|
|
545
|
+
title: 'Project Locate',
|
|
546
|
+
description: 'Resolve the nearest governance root from any in-project path',
|
|
553
547
|
inputSchema: {
|
|
554
548
|
inputPath: z.string(),
|
|
555
549
|
},
|
|
@@ -558,70 +552,74 @@ export function registerProjectTools(server) {
|
|
|
558
552
|
const governanceDir = await resolveGovernanceDir(resolvedFrom);
|
|
559
553
|
const projectPath = toProjectPath(governanceDir);
|
|
560
554
|
const markdown = renderToolResponseMarkdown({
|
|
561
|
-
toolName:
|
|
555
|
+
toolName: 'projectLocate',
|
|
562
556
|
sections: [
|
|
563
557
|
summarySection([
|
|
564
558
|
`- resolvedFrom: ${resolvedFrom}`,
|
|
565
559
|
`- projectPath: ${projectPath}`,
|
|
566
560
|
`- governanceDir: ${governanceDir}`,
|
|
567
561
|
]),
|
|
568
|
-
guidanceSection([
|
|
569
|
-
lintSection([
|
|
570
|
-
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}")`),
|
|
571
565
|
],
|
|
572
566
|
});
|
|
573
567
|
return asText(markdown);
|
|
574
568
|
});
|
|
575
|
-
server.registerTool(
|
|
576
|
-
title:
|
|
577
|
-
description:
|
|
569
|
+
server.registerTool('syncViews', {
|
|
570
|
+
title: 'Sync Views',
|
|
571
|
+
description: 'Materialize markdown views from .projitive governance store (tasks.md / roadmap.md)',
|
|
578
572
|
inputSchema: {
|
|
579
573
|
projectPath: z.string(),
|
|
580
|
-
views: z.array(z.enum([
|
|
574
|
+
views: z.array(z.enum(['tasks', 'roadmap'])).optional(),
|
|
581
575
|
force: z.boolean().optional(),
|
|
582
576
|
},
|
|
583
577
|
}, async ({ projectPath, views, force }) => {
|
|
584
578
|
const governanceDir = await resolveGovernanceDir(projectPath);
|
|
585
579
|
const normalizedProjectPath = toProjectPath(governanceDir);
|
|
580
|
+
const tasksViewPath = path.join(governanceDir, 'tasks.md');
|
|
581
|
+
const roadmapViewPath = path.join(governanceDir, 'roadmap.md');
|
|
586
582
|
const dbPath = path.join(governanceDir, PROJECT_MARKER);
|
|
587
583
|
const selectedViews = views && views.length > 0
|
|
588
584
|
? Array.from(new Set(views))
|
|
589
|
-
: [
|
|
585
|
+
: ['tasks', 'roadmap'];
|
|
590
586
|
const forceSync = force === true;
|
|
591
587
|
if (forceSync) {
|
|
592
|
-
if (selectedViews.includes(
|
|
593
|
-
await markMarkdownViewDirty(dbPath,
|
|
588
|
+
if (selectedViews.includes('tasks')) {
|
|
589
|
+
await markMarkdownViewDirty(dbPath, 'tasks_markdown');
|
|
594
590
|
}
|
|
595
|
-
if (selectedViews.includes(
|
|
596
|
-
await markMarkdownViewDirty(dbPath,
|
|
591
|
+
if (selectedViews.includes('roadmap')) {
|
|
592
|
+
await markMarkdownViewDirty(dbPath, 'roadmaps_markdown');
|
|
597
593
|
}
|
|
598
594
|
}
|
|
599
595
|
let taskCount;
|
|
600
596
|
let roadmapCount;
|
|
601
|
-
if (selectedViews.includes(
|
|
597
|
+
if (selectedViews.includes('tasks')) {
|
|
602
598
|
const taskDoc = await loadTasksDocumentWithOptions(governanceDir, forceSync);
|
|
603
599
|
taskCount = taskDoc.tasks.length;
|
|
604
600
|
}
|
|
605
|
-
if (selectedViews.includes(
|
|
601
|
+
if (selectedViews.includes('roadmap')) {
|
|
606
602
|
const roadmapDoc = await loadRoadmapDocumentWithOptions(governanceDir, forceSync);
|
|
607
603
|
roadmapCount = roadmapDoc.milestones.length;
|
|
608
604
|
}
|
|
609
605
|
const markdown = renderToolResponseMarkdown({
|
|
610
|
-
toolName:
|
|
606
|
+
toolName: 'syncViews',
|
|
611
607
|
sections: [
|
|
612
608
|
summarySection([
|
|
613
609
|
`- projectPath: ${normalizedProjectPath}`,
|
|
614
610
|
`- governanceDir: ${governanceDir}`,
|
|
615
|
-
`-
|
|
616
|
-
`-
|
|
611
|
+
`- tasksView: ${tasksViewPath}`,
|
|
612
|
+
`- roadmapView: ${roadmapViewPath}`,
|
|
613
|
+
`- views: ${selectedViews.join(', ')}`,
|
|
614
|
+
`- force: ${forceSync ? 'true' : 'false'}`,
|
|
617
615
|
]),
|
|
618
616
|
evidenceSection([
|
|
619
|
-
...(typeof taskCount ===
|
|
620
|
-
...(typeof roadmapCount ===
|
|
617
|
+
...(typeof taskCount === 'number' ? [`- tasks.md synced | taskCount=${taskCount}`] : []),
|
|
618
|
+
...(typeof roadmapCount === 'number' ? [`- roadmap.md synced | roadmapCount=${roadmapCount}`] : []),
|
|
621
619
|
]),
|
|
622
620
|
guidanceSection([
|
|
623
|
-
|
|
624
|
-
|
|
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.',
|
|
625
623
|
]),
|
|
626
624
|
lintSection([]),
|
|
627
625
|
nextCallSection(`projectContext(projectPath="${normalizedProjectPath}")`),
|
|
@@ -629,9 +627,9 @@ export function registerProjectTools(server) {
|
|
|
629
627
|
});
|
|
630
628
|
return asText(markdown);
|
|
631
629
|
});
|
|
632
|
-
server.registerTool(
|
|
633
|
-
title:
|
|
634
|
-
description:
|
|
630
|
+
server.registerTool('projectContext', {
|
|
631
|
+
title: 'Project Context',
|
|
632
|
+
description: 'Get project-level summary before selecting or executing a task',
|
|
635
633
|
inputSchema: {
|
|
636
634
|
projectPath: z.string(),
|
|
637
635
|
},
|
|
@@ -643,34 +641,36 @@ export function registerProjectTools(server) {
|
|
|
643
641
|
await ensureStore(dbPath);
|
|
644
642
|
const taskStats = await loadTaskStatusStatsFromStore(dbPath);
|
|
645
643
|
const { markdownPath: tasksMarkdownPath, tasks } = await loadTasksDocument(governanceDir);
|
|
646
|
-
const
|
|
644
|
+
const { markdownPath: roadmapMarkdownPath, milestones } = await loadRoadmapDocumentWithOptions(governanceDir, false);
|
|
645
|
+
const roadmapIds = milestones.map((item) => item.id);
|
|
647
646
|
const lintSuggestions = collectTaskLintSuggestions(tasks);
|
|
648
647
|
const markdown = renderToolResponseMarkdown({
|
|
649
|
-
toolName:
|
|
648
|
+
toolName: 'projectContext',
|
|
650
649
|
sections: [
|
|
651
650
|
summarySection([
|
|
652
651
|
`- projectPath: ${normalizedProjectPath}`,
|
|
653
652
|
`- governanceDir: ${governanceDir}`,
|
|
654
653
|
`- tasksView: ${tasksMarkdownPath}`,
|
|
654
|
+
`- roadmapView: ${roadmapMarkdownPath}`,
|
|
655
655
|
`- roadmapIds: ${roadmapIds.length}`,
|
|
656
656
|
]),
|
|
657
657
|
evidenceSection([
|
|
658
|
-
|
|
658
|
+
'### Task Summary',
|
|
659
659
|
`- total: ${taskStats.total}`,
|
|
660
660
|
`- TODO: ${taskStats.todo}`,
|
|
661
661
|
`- IN_PROGRESS: ${taskStats.inProgress}`,
|
|
662
662
|
`- BLOCKED: ${taskStats.blocked}`,
|
|
663
663
|
`- DONE: ${taskStats.done}`,
|
|
664
|
-
|
|
665
|
-
|
|
664
|
+
'',
|
|
665
|
+
'### Artifacts',
|
|
666
666
|
renderArtifactsMarkdown(artifacts),
|
|
667
667
|
]),
|
|
668
668
|
guidanceSection([
|
|
669
|
-
|
|
670
|
-
|
|
669
|
+
'- Start from `taskList` to choose a target task.',
|
|
670
|
+
'- Then call `taskContext` with a task ID to retrieve evidence locations and reading order.',
|
|
671
671
|
]),
|
|
672
672
|
lintSection(lintSuggestions),
|
|
673
|
-
nextCallSection(`taskList(projectPath
|
|
673
|
+
nextCallSection(`taskList(projectPath="${normalizedProjectPath}")`),
|
|
674
674
|
],
|
|
675
675
|
});
|
|
676
676
|
return asText(markdown);
|