@projitive/mcp 2.0.3 → 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 +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/errors.test.js +59 -0
- package/output/source/common/files.js +30 -19
- package/output/source/common/files.test.js +14 -14
- package/output/source/common/index.js +11 -10
- package/output/source/common/linter.js +29 -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 +91 -107
- 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/tool.js +43 -0
- package/output/source/common/types.js +3 -3
- package/output/source/common/utils.js +8 -8
- package/output/source/common/utils.test.js +48 -0
- package/output/source/index.js +16 -16
- package/output/source/index.runtime.test.js +57 -0
- package/output/source/index.test.js +64 -64
- package/output/source/prompts/index.js +3 -3
- package/output/source/prompts/index.test.js +23 -0
- package/output/source/prompts/quickStart.js +96 -96
- package/output/source/prompts/quickStart.test.js +24 -0
- package/output/source/prompts/taskDiscovery.js +184 -184
- package/output/source/prompts/taskDiscovery.test.js +24 -0
- package/output/source/prompts/taskExecution.js +164 -148
- package/output/source/prompts/taskExecution.test.js +27 -0
- package/output/source/resources/designs.js +26 -26
- package/output/source/resources/designs.resources.test.js +52 -0
- package/output/source/resources/designs.test.js +88 -88
- package/output/source/resources/governance.js +19 -19
- package/output/source/resources/governance.test.js +35 -0
- package/output/source/resources/index.js +2 -2
- package/output/source/resources/index.test.js +18 -0
- 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/index.test.js +23 -0
- package/output/source/tools/project.js +330 -377
- package/output/source/tools/project.test.js +308 -175
- package/output/source/tools/roadmap.js +236 -255
- package/output/source/tools/roadmap.test.js +241 -46
- package/output/source/tools/task.js +770 -652
- package/output/source/tools/task.test.js +433 -105
- package/output/source/types.js +28 -22
- package/package.json +8 -2
|
@@ -1,39 +1,31 @@
|
|
|
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,
|
|
6
|
-
import {
|
|
7
|
-
import {
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
const
|
|
11
|
-
const ignoreNames = new Set(["node_modules", ".git", ".next", "dist", "build"]);
|
|
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, loadTaskStatusStatsFromStore, createGovernedTool, getDefaultToolTemplateMarkdown, } from '../common/index.js';
|
|
6
|
+
import { collectTaskLintSuggestions, loadTasksDocument, loadTasksDocumentWithOptions, renderTasksMarkdown } from './task.js';
|
|
7
|
+
import { loadRoadmapDocumentWithOptions, renderRoadmapMarkdown } from './roadmap.js';
|
|
8
|
+
export const PROJECT_MARKER = '.projitive';
|
|
9
|
+
const DEFAULT_GOVERNANCE_DIR = '.projitive';
|
|
10
|
+
const ignoreNames = new Set(['node_modules', '.git', '.next', 'dist', 'build']);
|
|
13
11
|
const MAX_SCAN_DEPTH = 8;
|
|
14
12
|
function normalizePath(inputPath) {
|
|
15
13
|
return inputPath ? path.resolve(inputPath) : process.cwd();
|
|
16
14
|
}
|
|
17
15
|
function normalizeGovernanceDirName(input) {
|
|
18
16
|
const name = input?.trim() || DEFAULT_GOVERNANCE_DIR;
|
|
19
|
-
if (!name) {
|
|
20
|
-
throw new Error("governanceDir cannot be empty");
|
|
21
|
-
}
|
|
22
17
|
if (path.isAbsolute(name)) {
|
|
23
|
-
throw new Error(
|
|
18
|
+
throw new Error('governanceDir must be a relative directory name');
|
|
24
19
|
}
|
|
25
|
-
if (name.includes(
|
|
26
|
-
throw new Error(
|
|
20
|
+
if (name.includes('/') || name.includes('\\')) {
|
|
21
|
+
throw new Error('governanceDir must not contain path separators');
|
|
27
22
|
}
|
|
28
|
-
if (name ===
|
|
29
|
-
throw new Error(
|
|
23
|
+
if (name === '.' || name === '..') {
|
|
24
|
+
throw new Error('governanceDir must be a normal directory name');
|
|
30
25
|
}
|
|
31
26
|
return name;
|
|
32
27
|
}
|
|
33
28
|
function parseDepthFromEnv(rawDepth) {
|
|
34
|
-
if (typeof rawDepth !== "string" || rawDepth.trim().length === 0) {
|
|
35
|
-
return undefined;
|
|
36
|
-
}
|
|
37
29
|
const parsed = Number.parseInt(rawDepth, 10);
|
|
38
30
|
if (!Number.isFinite(parsed)) {
|
|
39
31
|
return undefined;
|
|
@@ -42,7 +34,7 @@ function parseDepthFromEnv(rawDepth) {
|
|
|
42
34
|
}
|
|
43
35
|
function requireEnvVar(name) {
|
|
44
36
|
const value = process.env[name];
|
|
45
|
-
if (typeof value !==
|
|
37
|
+
if (typeof value !== 'string' || value.trim().length === 0) {
|
|
46
38
|
throw new Error(`Missing required environment variable: ${name}`);
|
|
47
39
|
}
|
|
48
40
|
return value.trim();
|
|
@@ -67,47 +59,47 @@ export function resolveScanRoots(inputPaths) {
|
|
|
67
59
|
return normalizedInputPaths;
|
|
68
60
|
}
|
|
69
61
|
const configuredRoots = process.env.PROJITIVE_SCAN_ROOT_PATHS;
|
|
70
|
-
const rootsFromMultiEnv = typeof configuredRoots ===
|
|
62
|
+
const rootsFromMultiEnv = typeof configuredRoots === 'string'
|
|
71
63
|
? normalizeScanRoots(parseScanRoots(configuredRoots))
|
|
72
64
|
: [];
|
|
73
65
|
if (rootsFromMultiEnv.length > 0) {
|
|
74
66
|
return rootsFromMultiEnv;
|
|
75
67
|
}
|
|
76
68
|
const legacyRoot = process.env.PROJITIVE_SCAN_ROOT_PATH;
|
|
77
|
-
const rootsFromLegacyEnv = typeof legacyRoot ===
|
|
69
|
+
const rootsFromLegacyEnv = typeof legacyRoot === 'string'
|
|
78
70
|
? normalizeScanRoots([legacyRoot])
|
|
79
71
|
: [];
|
|
80
72
|
if (rootsFromLegacyEnv.length > 0) {
|
|
81
73
|
return rootsFromLegacyEnv;
|
|
82
74
|
}
|
|
83
|
-
throw new Error(
|
|
75
|
+
throw new Error('Missing required environment variable: PROJITIVE_SCAN_ROOT_PATHS (or legacy PROJITIVE_SCAN_ROOT_PATH)');
|
|
84
76
|
}
|
|
85
77
|
export function resolveScanRoot(inputPath) {
|
|
86
78
|
return resolveScanRoots(inputPath ? [inputPath] : undefined)[0];
|
|
87
79
|
}
|
|
88
80
|
export function resolveScanDepth(inputDepth) {
|
|
89
|
-
const configuredDepthRaw = requireEnvVar(
|
|
81
|
+
const configuredDepthRaw = requireEnvVar('PROJITIVE_SCAN_MAX_DEPTH');
|
|
90
82
|
const configuredDepth = parseDepthFromEnv(configuredDepthRaw);
|
|
91
|
-
if (typeof configuredDepth !==
|
|
92
|
-
throw new Error(
|
|
83
|
+
if (typeof configuredDepth !== 'number') {
|
|
84
|
+
throw new Error('Invalid PROJITIVE_SCAN_MAX_DEPTH: expected integer in range 0-8');
|
|
93
85
|
}
|
|
94
|
-
if (typeof inputDepth ===
|
|
86
|
+
if (typeof inputDepth === 'number') {
|
|
95
87
|
return inputDepth;
|
|
96
88
|
}
|
|
97
89
|
return configuredDepth;
|
|
98
90
|
}
|
|
99
91
|
function renderArtifactsMarkdown(artifacts) {
|
|
100
92
|
const rows = artifacts.map((item) => {
|
|
101
|
-
if (item.kind ===
|
|
102
|
-
const lineText = item.lineCount == null ?
|
|
103
|
-
return `- ${item.exists ?
|
|
93
|
+
if (item.kind === 'file') {
|
|
94
|
+
const lineText = item.lineCount == null ? '-' : String(item.lineCount);
|
|
95
|
+
return `- ${item.exists ? '✅' : '❌'} ${item.name} \n path: ${item.path} \n lineCount: ${lineText}`;
|
|
104
96
|
}
|
|
105
97
|
const nested = (item.markdownFiles ?? [])
|
|
106
98
|
.map((entry) => ` - ${entry.path} (lines: ${entry.lineCount})`)
|
|
107
|
-
.join(
|
|
108
|
-
return `- ${item.exists ?
|
|
99
|
+
.join('\n');
|
|
100
|
+
return `- ${item.exists ? '✅' : '❌'} ${item.name}/ \n path: ${item.path}${nested ? `\n markdownFiles:\n${nested}` : ''}`;
|
|
109
101
|
});
|
|
110
|
-
return rows.join(
|
|
102
|
+
return rows.join('\n');
|
|
111
103
|
}
|
|
112
104
|
async function readTasksSnapshot(governanceDir) {
|
|
113
105
|
const tasksPath = path.join(governanceDir, PROJECT_MARKER);
|
|
@@ -118,8 +110,8 @@ async function readTasksSnapshot(governanceDir) {
|
|
|
118
110
|
lintSuggestions: renderLintSuggestions([
|
|
119
111
|
{
|
|
120
112
|
code: PROJECT_LINT_CODES.TASKS_FILE_MISSING,
|
|
121
|
-
message:
|
|
122
|
-
fixHint:
|
|
113
|
+
message: 'governance store is missing.',
|
|
114
|
+
fixHint: 'Initialize governance tasks structure first.',
|
|
123
115
|
},
|
|
124
116
|
]),
|
|
125
117
|
todo: 0,
|
|
@@ -127,7 +119,7 @@ async function readTasksSnapshot(governanceDir) {
|
|
|
127
119
|
blocked: 0,
|
|
128
120
|
done: 0,
|
|
129
121
|
total: 0,
|
|
130
|
-
latestUpdatedAt:
|
|
122
|
+
latestUpdatedAt: '(unknown)',
|
|
131
123
|
score: 0,
|
|
132
124
|
};
|
|
133
125
|
}
|
|
@@ -141,15 +133,10 @@ async function readTasksSnapshot(governanceDir) {
|
|
|
141
133
|
blocked: stats.blocked,
|
|
142
134
|
done: stats.done,
|
|
143
135
|
total: stats.total,
|
|
144
|
-
latestUpdatedAt: stats.latestUpdatedAt ||
|
|
136
|
+
latestUpdatedAt: stats.latestUpdatedAt || '(unknown)',
|
|
145
137
|
score: stats.inProgress * 2 + stats.todo,
|
|
146
138
|
};
|
|
147
139
|
}
|
|
148
|
-
async function readRoadmapIds(governanceDir) {
|
|
149
|
-
const dbPath = path.join(governanceDir, PROJECT_MARKER);
|
|
150
|
-
await ensureStore(dbPath);
|
|
151
|
-
return loadRoadmapIdsFromStore(dbPath);
|
|
152
|
-
}
|
|
153
140
|
export async function hasProjectMarker(dirPath) {
|
|
154
141
|
const markerPath = path.join(dirPath, PROJECT_MARKER);
|
|
155
142
|
const statResult = await catchIt(fs.stat(markerPath));
|
|
@@ -246,21 +233,21 @@ export async function discoverProjectsAcrossRoots(rootPaths, maxDepth) {
|
|
|
246
233
|
return Array.from(new Set(perRootResults.flat())).sort();
|
|
247
234
|
}
|
|
248
235
|
const DEFAULT_TOOL_TEMPLATE_NAMES = [
|
|
249
|
-
|
|
250
|
-
|
|
251
|
-
|
|
252
|
-
|
|
253
|
-
|
|
254
|
-
|
|
255
|
-
|
|
256
|
-
|
|
257
|
-
|
|
258
|
-
|
|
259
|
-
|
|
260
|
-
|
|
261
|
-
|
|
262
|
-
|
|
263
|
-
|
|
236
|
+
'projectInit',
|
|
237
|
+
'projectScan',
|
|
238
|
+
'projectNext',
|
|
239
|
+
'projectLocate',
|
|
240
|
+
'projectContext',
|
|
241
|
+
'syncViews',
|
|
242
|
+
'taskList',
|
|
243
|
+
'taskNext',
|
|
244
|
+
'taskContext',
|
|
245
|
+
'taskCreate',
|
|
246
|
+
'taskUpdate',
|
|
247
|
+
'roadmapList',
|
|
248
|
+
'roadmapContext',
|
|
249
|
+
'roadmapCreate',
|
|
250
|
+
'roadmapUpdate',
|
|
264
251
|
];
|
|
265
252
|
async function pathExists(targetPath) {
|
|
266
253
|
const accessResult = await catchIt(fs.access(targetPath));
|
|
@@ -269,23 +256,23 @@ async function pathExists(targetPath) {
|
|
|
269
256
|
async function writeTextFile(targetPath, content, force) {
|
|
270
257
|
const exists = await pathExists(targetPath);
|
|
271
258
|
if (exists && !force) {
|
|
272
|
-
return { path: targetPath, action:
|
|
259
|
+
return { path: targetPath, action: 'skipped' };
|
|
273
260
|
}
|
|
274
|
-
await fs.writeFile(targetPath, content,
|
|
275
|
-
return { path: targetPath, action: exists ?
|
|
261
|
+
await fs.writeFile(targetPath, content, 'utf-8');
|
|
262
|
+
return { path: targetPath, action: exists ? 'updated' : 'created' };
|
|
276
263
|
}
|
|
277
264
|
function defaultReadmeMarkdown(governanceDirName) {
|
|
278
265
|
return [
|
|
279
|
-
|
|
280
|
-
|
|
266
|
+
'# Projitive Governance Workspace',
|
|
267
|
+
'',
|
|
281
268
|
`This directory (\`${governanceDirName}/\`) is the governance root for this project.`,
|
|
282
|
-
|
|
283
|
-
|
|
284
|
-
|
|
285
|
-
|
|
286
|
-
|
|
287
|
-
|
|
288
|
-
].join(
|
|
269
|
+
'',
|
|
270
|
+
'## Conventions',
|
|
271
|
+
'- Keep roadmap/task source of truth in .projitive governance store.',
|
|
272
|
+
'- Treat roadmap.md/tasks.md as generated views from governance store.',
|
|
273
|
+
'- Keep IDs stable (TASK-xxxx / ROADMAP-xxxx).',
|
|
274
|
+
'- Update report evidence before status transitions.',
|
|
275
|
+
].join('\n');
|
|
289
276
|
}
|
|
290
277
|
function defaultRoadmapMarkdown(milestones = defaultRoadmapMilestones()) {
|
|
291
278
|
return renderRoadmapMarkdown(milestones);
|
|
@@ -293,51 +280,49 @@ function defaultRoadmapMarkdown(milestones = defaultRoadmapMilestones()) {
|
|
|
293
280
|
function defaultTasksMarkdown(updatedAt = new Date().toISOString()) {
|
|
294
281
|
return renderTasksMarkdown([
|
|
295
282
|
{
|
|
296
|
-
id:
|
|
297
|
-
title:
|
|
298
|
-
status:
|
|
299
|
-
owner:
|
|
300
|
-
summary:
|
|
283
|
+
id: 'TASK-0001',
|
|
284
|
+
title: 'Bootstrap governance workspace',
|
|
285
|
+
status: 'TODO',
|
|
286
|
+
owner: 'unassigned',
|
|
287
|
+
summary: 'Create initial governance artifacts and confirm task execution loop.',
|
|
301
288
|
updatedAt,
|
|
302
289
|
links: [],
|
|
303
|
-
roadmapRefs: [
|
|
290
|
+
roadmapRefs: ['ROADMAP-0001'],
|
|
304
291
|
},
|
|
305
292
|
]);
|
|
306
293
|
}
|
|
307
294
|
function defaultRoadmapMilestones() {
|
|
308
295
|
return [{
|
|
309
|
-
id:
|
|
310
|
-
title:
|
|
311
|
-
status:
|
|
312
|
-
time:
|
|
296
|
+
id: 'ROADMAP-0001',
|
|
297
|
+
title: 'Bootstrap governance baseline',
|
|
298
|
+
status: 'active',
|
|
299
|
+
time: '2026-Q1',
|
|
313
300
|
updatedAt: new Date().toISOString(),
|
|
314
301
|
}];
|
|
315
302
|
}
|
|
316
303
|
function defaultTemplateReadmeMarkdown() {
|
|
317
304
|
return [
|
|
318
|
-
|
|
319
|
-
|
|
320
|
-
|
|
321
|
-
|
|
322
|
-
|
|
323
|
-
|
|
324
|
-
|
|
325
|
-
|
|
326
|
-
|
|
327
|
-
|
|
328
|
-
|
|
329
|
-
|
|
330
|
-
|
|
331
|
-
|
|
332
|
-
|
|
333
|
-
|
|
334
|
-
|
|
335
|
-
|
|
336
|
-
|
|
337
|
-
|
|
338
|
-
|
|
339
|
-
"- {{content}}",
|
|
340
|
-
].join("\n");
|
|
305
|
+
'# Template Guide',
|
|
306
|
+
'',
|
|
307
|
+
'This directory stores response templates (one file per tool).',
|
|
308
|
+
'',
|
|
309
|
+
'How to enable:',
|
|
310
|
+
'- Set env `PROJITIVE_MESSAGE_TEMPLATE_PATH` to a template directory.',
|
|
311
|
+
'- Example: .projitive/templates/tools',
|
|
312
|
+
'',
|
|
313
|
+
'Rule:',
|
|
314
|
+
'- Prefer one template per tool: <toolName>.md (e.g. taskNext.md).',
|
|
315
|
+
'- Template directory mode only loads <toolName>.md files.',
|
|
316
|
+
'- If a tool template file is missing, Projitive will auto-generate that file before rendering.',
|
|
317
|
+
'',
|
|
318
|
+
'Basic Variables:',
|
|
319
|
+
'- {{tool_name}}',
|
|
320
|
+
'- {{summary}}',
|
|
321
|
+
'- {{evidence}}',
|
|
322
|
+
'- {{guidance}}',
|
|
323
|
+
'- {{lint_suggestions}}',
|
|
324
|
+
'- {{next_call}}',
|
|
325
|
+
].join('\n');
|
|
341
326
|
}
|
|
342
327
|
export async function initializeProjectStructure(inputPath, governanceDir, force = false) {
|
|
343
328
|
const projectPath = normalizePath(inputPath);
|
|
@@ -353,15 +338,15 @@ export async function initializeProjectStructure(inputPath, governanceDir, force
|
|
|
353
338
|
const directories = [];
|
|
354
339
|
const requiredDirectories = [
|
|
355
340
|
governancePath,
|
|
356
|
-
path.join(governancePath,
|
|
357
|
-
path.join(governancePath,
|
|
358
|
-
path.join(governancePath,
|
|
359
|
-
path.join(governancePath,
|
|
341
|
+
path.join(governancePath, 'designs'),
|
|
342
|
+
path.join(governancePath, 'reports'),
|
|
343
|
+
path.join(governancePath, 'templates'),
|
|
344
|
+
path.join(governancePath, 'templates', 'tools'),
|
|
360
345
|
];
|
|
361
346
|
for (const dirPath of requiredDirectories) {
|
|
362
347
|
const exists = await pathExists(dirPath);
|
|
363
348
|
await fs.mkdir(dirPath, { recursive: true });
|
|
364
|
-
directories.push({ path: dirPath, action: exists ?
|
|
349
|
+
directories.push({ path: dirPath, action: exists ? 'skipped' : 'created' });
|
|
365
350
|
}
|
|
366
351
|
const markerPath = path.join(governancePath, PROJECT_MARKER);
|
|
367
352
|
const defaultRoadmapData = defaultRoadmapMilestones();
|
|
@@ -372,24 +357,24 @@ export async function initializeProjectStructure(inputPath, governanceDir, force
|
|
|
372
357
|
await replaceRoadmapsInStore(markerPath, defaultRoadmapData);
|
|
373
358
|
await replaceTasksInStore(markerPath, [
|
|
374
359
|
{
|
|
375
|
-
id:
|
|
376
|
-
title:
|
|
377
|
-
status:
|
|
378
|
-
owner:
|
|
379
|
-
summary:
|
|
360
|
+
id: 'TASK-0001',
|
|
361
|
+
title: 'Bootstrap governance workspace',
|
|
362
|
+
status: 'TODO',
|
|
363
|
+
owner: 'unassigned',
|
|
364
|
+
summary: 'Create initial governance artifacts and confirm task execution loop.',
|
|
380
365
|
updatedAt: defaultTaskUpdatedAt,
|
|
381
366
|
links: [],
|
|
382
|
-
roadmapRefs: [
|
|
367
|
+
roadmapRefs: ['ROADMAP-0001'],
|
|
383
368
|
},
|
|
384
369
|
]);
|
|
385
370
|
}
|
|
386
371
|
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,
|
|
372
|
+
writeTextFile(path.join(governancePath, 'README.md'), defaultReadmeMarkdown(governanceDirName), force),
|
|
373
|
+
writeTextFile(path.join(governancePath, 'roadmap.md'), defaultRoadmapMarkdown(defaultRoadmapData), force),
|
|
374
|
+
writeTextFile(path.join(governancePath, 'tasks.md'), defaultTasksMarkdown(defaultTaskUpdatedAt), force),
|
|
375
|
+
writeTextFile(path.join(governancePath, 'templates', 'README.md'), defaultTemplateReadmeMarkdown(), force),
|
|
391
376
|
]);
|
|
392
|
-
const toolTemplateFiles = await Promise.all(DEFAULT_TOOL_TEMPLATE_NAMES.map((toolName) => writeTextFile(path.join(governancePath,
|
|
377
|
+
const toolTemplateFiles = await Promise.all(DEFAULT_TOOL_TEMPLATE_NAMES.map((toolName) => writeTextFile(path.join(governancePath, 'templates', 'tools', `${toolName}.md`), getDefaultToolTemplateMarkdown(toolName), force)));
|
|
393
378
|
const files = [...baseFiles, ...toolTemplateFiles];
|
|
394
379
|
return {
|
|
395
380
|
projectPath,
|
|
@@ -399,280 +384,248 @@ export async function initializeProjectStructure(inputPath, governanceDir, force
|
|
|
399
384
|
};
|
|
400
385
|
}
|
|
401
386
|
export function registerProjectTools(server) {
|
|
402
|
-
server.registerTool(
|
|
403
|
-
|
|
404
|
-
|
|
387
|
+
server.registerTool(...createGovernedTool({
|
|
388
|
+
name: 'projectInit',
|
|
389
|
+
title: 'Project Init',
|
|
390
|
+
description: 'Bootstrap governance files when a project has no .projitive yet (requires projectPath)',
|
|
405
391
|
inputSchema: {
|
|
406
392
|
projectPath: z.string(),
|
|
407
393
|
governanceDir: z.string().optional(),
|
|
408
394
|
force: z.boolean().optional(),
|
|
409
395
|
},
|
|
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
|
-
|
|
440
|
-
|
|
441
|
-
|
|
442
|
-
|
|
443
|
-
|
|
444
|
-
|
|
445
|
-
|
|
446
|
-
|
|
447
|
-
server.registerTool("projectScan", {
|
|
448
|
-
title: "Project Scan",
|
|
449
|
-
description: "Start here when project path is unknown; discover all governance roots",
|
|
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',
|
|
431
|
+
title: 'Project Scan',
|
|
432
|
+
description: 'Start here when project path is unknown; discover all governance roots',
|
|
450
433
|
inputSchema: {},
|
|
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
|
-
|
|
476
|
-
|
|
477
|
-
|
|
478
|
-
|
|
479
|
-
|
|
480
|
-
|
|
481
|
-
return asText(markdown);
|
|
482
|
-
});
|
|
483
|
-
server.registerTool("projectNext", {
|
|
484
|
-
title: "Project Next",
|
|
485
|
-
description: "Rank actionable projects and return the best execution target",
|
|
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',
|
|
462
|
+
title: 'Project Next',
|
|
463
|
+
description: 'Rank actionable projects and return the best execution target',
|
|
486
464
|
inputSchema: {
|
|
487
465
|
limit: z.number().int().min(1).max(50).optional(),
|
|
488
466
|
},
|
|
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
|
-
|
|
537
|
-
|
|
538
|
-
|
|
539
|
-
|
|
540
|
-
|
|
541
|
-
|
|
542
|
-
|
|
543
|
-
|
|
544
|
-
? `projectContext(projectPath=\"${toProjectPath(ranked[0].governanceDir)}\")`
|
|
545
|
-
: undefined),
|
|
546
|
-
],
|
|
547
|
-
});
|
|
548
|
-
return asText(markdown);
|
|
549
|
-
});
|
|
550
|
-
server.registerTool("projectLocate", {
|
|
551
|
-
title: "Project Locate",
|
|
552
|
-
description: "Resolve the nearest governance root from any in-project path",
|
|
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',
|
|
520
|
+
title: 'Project Locate',
|
|
521
|
+
description: 'Resolve the nearest governance root from any in-project path',
|
|
553
522
|
inputSchema: {
|
|
554
523
|
inputPath: z.string(),
|
|
555
524
|
},
|
|
556
|
-
|
|
557
|
-
|
|
558
|
-
|
|
559
|
-
|
|
560
|
-
|
|
561
|
-
|
|
562
|
-
|
|
563
|
-
|
|
564
|
-
|
|
565
|
-
|
|
566
|
-
|
|
567
|
-
|
|
568
|
-
|
|
569
|
-
|
|
570
|
-
|
|
571
|
-
|
|
572
|
-
|
|
573
|
-
|
|
574
|
-
|
|
575
|
-
server.registerTool("syncViews", {
|
|
576
|
-
title: "Sync Views",
|
|
577
|
-
description: "Materialize markdown views from .projitive governance store (tasks.md / roadmap.md)",
|
|
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',
|
|
542
|
+
title: 'Sync Views',
|
|
543
|
+
description: 'Materialize markdown views from .projitive governance store (tasks.md / roadmap.md)',
|
|
578
544
|
inputSchema: {
|
|
579
545
|
projectPath: z.string(),
|
|
580
|
-
views: z.array(z.enum([
|
|
546
|
+
views: z.array(z.enum(['tasks', 'roadmap'])).optional(),
|
|
581
547
|
force: z.boolean().optional(),
|
|
582
548
|
},
|
|
583
|
-
|
|
584
|
-
|
|
585
|
-
|
|
586
|
-
|
|
587
|
-
|
|
588
|
-
? Array.from(new Set(views))
|
|
589
|
-
|
|
590
|
-
|
|
591
|
-
|
|
592
|
-
if (selectedViews.includes(
|
|
593
|
-
await
|
|
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;
|
|
558
|
+
if (selectedViews.includes('tasks')) {
|
|
559
|
+
const taskDoc = await loadTasksDocumentWithOptions(governanceDir, forceSync);
|
|
560
|
+
taskCount = taskDoc.tasks.length;
|
|
594
561
|
}
|
|
595
|
-
if (selectedViews.includes(
|
|
596
|
-
await
|
|
562
|
+
if (selectedViews.includes('roadmap')) {
|
|
563
|
+
const roadmapDoc = await loadRoadmapDocumentWithOptions(governanceDir, forceSync);
|
|
564
|
+
roadmapCount = roadmapDoc.milestones.length;
|
|
597
565
|
}
|
|
598
|
-
|
|
599
|
-
|
|
600
|
-
|
|
601
|
-
|
|
602
|
-
|
|
603
|
-
|
|
604
|
-
|
|
605
|
-
|
|
606
|
-
|
|
607
|
-
|
|
608
|
-
}
|
|
609
|
-
|
|
610
|
-
|
|
611
|
-
|
|
612
|
-
|
|
613
|
-
|
|
614
|
-
|
|
615
|
-
|
|
616
|
-
|
|
617
|
-
|
|
618
|
-
|
|
619
|
-
|
|
620
|
-
|
|
621
|
-
|
|
622
|
-
|
|
623
|
-
"Use this tool after batch updates when you need immediate markdown materialization.",
|
|
624
|
-
"Routine workflows can rely on lazy sync and usually do not require force=true.",
|
|
625
|
-
]),
|
|
626
|
-
lintSection([]),
|
|
627
|
-
nextCallSection(`projectContext(projectPath="${normalizedProjectPath}")`),
|
|
628
|
-
],
|
|
629
|
-
});
|
|
630
|
-
return asText(markdown);
|
|
631
|
-
});
|
|
632
|
-
server.registerTool("projectContext", {
|
|
633
|
-
title: "Project Context",
|
|
634
|
-
description: "Get project-level summary before selecting or executing a task",
|
|
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',
|
|
589
|
+
title: 'Project Context',
|
|
590
|
+
description: 'Get project-level summary before selecting or executing a task',
|
|
635
591
|
inputSchema: {
|
|
636
592
|
projectPath: z.string(),
|
|
637
593
|
},
|
|
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
|
-
|
|
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
|
}
|