@projitive/mcp 1.2.0 → 2.0.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +114 -423
- package/output/package.json +4 -1
- package/output/source/common/files.js +1 -1
- package/output/source/common/index.js +1 -0
- package/output/source/common/linter.js +1 -0
- package/output/source/common/migrations/runner.js +68 -0
- package/output/source/common/migrations/steps.js +55 -0
- package/output/source/common/migrations/types.js +1 -0
- package/output/source/common/response.js +147 -1
- package/output/source/common/store.js +623 -0
- package/output/source/common/store.test.js +164 -0
- package/output/source/index.js +1 -1
- package/output/source/prompts/quickStart.js +33 -7
- package/output/source/prompts/taskDiscovery.js +23 -9
- package/output/source/prompts/taskExecution.js +18 -8
- package/output/source/resources/governance.js +2 -2
- package/output/source/resources/readme.test.js +2 -2
- package/output/source/tools/project.js +206 -111
- package/output/source/tools/project.test.js +6 -3
- package/output/source/tools/roadmap.js +166 -16
- package/output/source/tools/roadmap.test.js +19 -55
- package/output/source/tools/task.js +206 -374
- package/output/source/tools/task.test.js +84 -388
- package/output/source/types.js +1 -9
- package/package.json +4 -1
|
@@ -2,9 +2,10 @@ import fs from "node:fs/promises";
|
|
|
2
2
|
import path from "node:path";
|
|
3
3
|
import process from "node:process";
|
|
4
4
|
import { z } from "zod";
|
|
5
|
-
import { discoverGovernanceArtifacts, catchIt, PROJECT_LINT_CODES, renderLintSuggestions } from "../common/index.js";
|
|
6
|
-
import { asText, evidenceSection, guidanceSection, lintSection, nextCallSection, renderToolResponseMarkdown, summarySection, } from "../common/index.js";
|
|
7
|
-
import { collectTaskLintSuggestions, loadTasksDocument } from "./task.js";
|
|
5
|
+
import { discoverGovernanceArtifacts, catchIt, PROJECT_LINT_CODES, renderLintSuggestions, ensureStore, replaceRoadmapsInStore, replaceTasksInStore, markMarkdownViewDirty, loadTaskStatusStatsFromStore, loadRoadmapIdsFromStore, } 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";
|
|
8
9
|
export const PROJECT_MARKER = ".projitive";
|
|
9
10
|
const DEFAULT_GOVERNANCE_DIR = ".projitive";
|
|
10
11
|
const ignoreNames = new Set(["node_modules", ".git", ".next", "dist", "build"]);
|
|
@@ -109,49 +110,45 @@ function renderArtifactsMarkdown(artifacts) {
|
|
|
109
110
|
return rows.join("\n");
|
|
110
111
|
}
|
|
111
112
|
async function readTasksSnapshot(governanceDir) {
|
|
112
|
-
const tasksPath = path.join(governanceDir,
|
|
113
|
-
const
|
|
114
|
-
if (
|
|
113
|
+
const tasksPath = path.join(governanceDir, PROJECT_MARKER);
|
|
114
|
+
const exists = await fs.access(tasksPath).then(() => true).catch(() => false);
|
|
115
|
+
if (!exists) {
|
|
115
116
|
return {
|
|
116
|
-
tasksPath,
|
|
117
117
|
exists: false,
|
|
118
|
-
tasks: [],
|
|
119
118
|
lintSuggestions: renderLintSuggestions([
|
|
120
119
|
{
|
|
121
120
|
code: PROJECT_LINT_CODES.TASKS_FILE_MISSING,
|
|
122
|
-
message: "
|
|
121
|
+
message: "governance store is missing.",
|
|
123
122
|
fixHint: "Initialize governance tasks structure first.",
|
|
124
123
|
},
|
|
125
124
|
]),
|
|
125
|
+
todo: 0,
|
|
126
|
+
inProgress: 0,
|
|
127
|
+
blocked: 0,
|
|
128
|
+
done: 0,
|
|
129
|
+
total: 0,
|
|
130
|
+
latestUpdatedAt: "(unknown)",
|
|
131
|
+
score: 0,
|
|
126
132
|
};
|
|
127
133
|
}
|
|
128
|
-
|
|
129
|
-
const
|
|
130
|
-
return {
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
}
|
|
141
|
-
function actionableScore(tasks) {
|
|
142
|
-
return tasks.filter((task) => task.status === "IN_PROGRESS").length * 2
|
|
143
|
-
+ tasks.filter((task) => task.status === "TODO").length;
|
|
134
|
+
await ensureStore(tasksPath);
|
|
135
|
+
const stats = await loadTaskStatusStatsFromStore(tasksPath);
|
|
136
|
+
return {
|
|
137
|
+
exists: true,
|
|
138
|
+
lintSuggestions: [],
|
|
139
|
+
todo: stats.todo,
|
|
140
|
+
inProgress: stats.inProgress,
|
|
141
|
+
blocked: stats.blocked,
|
|
142
|
+
done: stats.done,
|
|
143
|
+
total: stats.total,
|
|
144
|
+
latestUpdatedAt: stats.latestUpdatedAt || "(unknown)",
|
|
145
|
+
score: stats.inProgress * 2 + stats.todo,
|
|
146
|
+
};
|
|
144
147
|
}
|
|
145
148
|
async function readRoadmapIds(governanceDir) {
|
|
146
|
-
const
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
const matches = markdown.match(/ROADMAP-\d{4}/g) ?? [];
|
|
150
|
-
return Array.from(new Set(matches));
|
|
151
|
-
}
|
|
152
|
-
catch {
|
|
153
|
-
return [];
|
|
154
|
-
}
|
|
149
|
+
const dbPath = path.join(governanceDir, PROJECT_MARKER);
|
|
150
|
+
await ensureStore(dbPath);
|
|
151
|
+
return loadRoadmapIdsFromStore(dbPath);
|
|
155
152
|
}
|
|
156
153
|
export async function hasProjectMarker(dirPath) {
|
|
157
154
|
const markerPath = path.join(dirPath, PROJECT_MARKER);
|
|
@@ -248,6 +245,21 @@ export async function discoverProjectsAcrossRoots(rootPaths, maxDepth) {
|
|
|
248
245
|
const perRootResults = await Promise.all(rootPaths.map((rootPath) => discoverProjects(rootPath, maxDepth)));
|
|
249
246
|
return Array.from(new Set(perRootResults.flat())).sort();
|
|
250
247
|
}
|
|
248
|
+
const DEFAULT_TOOL_TEMPLATE_NAMES = [
|
|
249
|
+
"projectInit",
|
|
250
|
+
"projectScan",
|
|
251
|
+
"projectNext",
|
|
252
|
+
"projectLocate",
|
|
253
|
+
"projectContext",
|
|
254
|
+
"syncViews",
|
|
255
|
+
"taskList",
|
|
256
|
+
"taskNext",
|
|
257
|
+
"taskContext",
|
|
258
|
+
"taskUpdate",
|
|
259
|
+
"roadmapList",
|
|
260
|
+
"roadmapContext",
|
|
261
|
+
"roadmapUpdate",
|
|
262
|
+
];
|
|
251
263
|
async function pathExists(targetPath) {
|
|
252
264
|
const accessResult = await catchIt(fs.access(targetPath));
|
|
253
265
|
return !accessResult.isError();
|
|
@@ -267,51 +279,62 @@ function defaultReadmeMarkdown(governanceDirName) {
|
|
|
267
279
|
`This directory (\`${governanceDirName}/\`) is the governance root for this project.`,
|
|
268
280
|
"",
|
|
269
281
|
"## Conventions",
|
|
270
|
-
"- Keep roadmap/task
|
|
282
|
+
"- Keep roadmap/task source of truth in .projitive sqlite tables.",
|
|
283
|
+
"- Treat roadmap.md/tasks.md as generated views from sqlite.",
|
|
271
284
|
"- Keep IDs stable (TASK-xxxx / ROADMAP-xxxx).",
|
|
272
285
|
"- Update report evidence before status transitions.",
|
|
273
286
|
].join("\n");
|
|
274
287
|
}
|
|
275
|
-
function defaultRoadmapMarkdown() {
|
|
276
|
-
return
|
|
277
|
-
"# Roadmap",
|
|
278
|
-
"",
|
|
279
|
-
"## Active Milestones",
|
|
280
|
-
"- [ ] ROADMAP-0001: Bootstrap governance baseline (time: 2026-Q1)",
|
|
281
|
-
].join("\n");
|
|
288
|
+
function defaultRoadmapMarkdown(milestones = defaultRoadmapMilestones()) {
|
|
289
|
+
return renderRoadmapMarkdown(milestones);
|
|
282
290
|
}
|
|
283
|
-
function defaultTasksMarkdown() {
|
|
284
|
-
|
|
285
|
-
|
|
286
|
-
|
|
287
|
-
|
|
288
|
-
|
|
289
|
-
|
|
290
|
-
|
|
291
|
-
|
|
292
|
-
|
|
293
|
-
|
|
294
|
-
|
|
295
|
-
|
|
296
|
-
|
|
297
|
-
|
|
291
|
+
function defaultTasksMarkdown(updatedAt = new Date().toISOString()) {
|
|
292
|
+
return renderTasksMarkdown([
|
|
293
|
+
{
|
|
294
|
+
id: "TASK-0001",
|
|
295
|
+
title: "Bootstrap governance workspace",
|
|
296
|
+
status: "TODO",
|
|
297
|
+
owner: "unassigned",
|
|
298
|
+
summary: "Create initial governance artifacts and confirm task execution loop.",
|
|
299
|
+
updatedAt,
|
|
300
|
+
links: [],
|
|
301
|
+
roadmapRefs: ["ROADMAP-0001"],
|
|
302
|
+
},
|
|
303
|
+
]);
|
|
304
|
+
}
|
|
305
|
+
function defaultRoadmapMilestones() {
|
|
306
|
+
return [{
|
|
307
|
+
id: "ROADMAP-0001",
|
|
308
|
+
title: "Bootstrap governance baseline",
|
|
309
|
+
status: "active",
|
|
310
|
+
time: "2026-Q1",
|
|
311
|
+
updatedAt: new Date().toISOString(),
|
|
312
|
+
}];
|
|
298
313
|
}
|
|
299
|
-
function
|
|
314
|
+
function defaultTemplateReadmeMarkdown() {
|
|
300
315
|
return [
|
|
301
|
-
"
|
|
302
|
-
"
|
|
316
|
+
"# Template Guide",
|
|
317
|
+
"",
|
|
318
|
+
"This directory stores response templates (one file per tool).",
|
|
303
319
|
"",
|
|
304
|
-
"
|
|
305
|
-
"-
|
|
306
|
-
"-
|
|
307
|
-
"- Check development/testing workflow bottlenecks; create tasks for reliability and speed improvements.",
|
|
308
|
-
"- Check TODO/FIXME/HACK comments; turn feasible items into governed tasks.",
|
|
309
|
-
"- Check dependency/security hygiene and stale tooling; create tasks where upgrades are justified.",
|
|
320
|
+
"How to enable:",
|
|
321
|
+
"- Set env `PROJITIVE_MESSAGE_TEMPLATE_PATH` to a template directory.",
|
|
322
|
+
"- Example: .projitive/templates/tools",
|
|
310
323
|
"",
|
|
311
|
-
"
|
|
312
|
-
"-
|
|
313
|
-
"-
|
|
314
|
-
"-
|
|
324
|
+
"Rule:",
|
|
325
|
+
"- Prefer one template per tool: <toolName>.md (e.g. taskNext.md).",
|
|
326
|
+
"- Template directory mode only loads <toolName>.md files.",
|
|
327
|
+
"- If a tool template file is missing, Projitive will auto-generate that file before rendering.",
|
|
328
|
+
"- Include {{content}} to render original tool output.",
|
|
329
|
+
"- If {{content}} is missing, original output is appended after template text.",
|
|
330
|
+
"",
|
|
331
|
+
"Basic Variables:",
|
|
332
|
+
"- {{tool_name}}",
|
|
333
|
+
"- {{summary}}",
|
|
334
|
+
"- {{evidence}}",
|
|
335
|
+
"- {{guidance}}",
|
|
336
|
+
"- {{next_call}}",
|
|
337
|
+
"- {{content}}",
|
|
315
338
|
].join("\n");
|
|
316
339
|
}
|
|
317
340
|
export async function initializeProjectStructure(inputPath, governanceDir, force = false) {
|
|
@@ -326,24 +349,49 @@ export async function initializeProjectStructure(inputPath, governanceDir, force
|
|
|
326
349
|
}
|
|
327
350
|
const governancePath = path.join(projectPath, governanceDirName);
|
|
328
351
|
const directories = [];
|
|
329
|
-
const requiredDirectories = [
|
|
352
|
+
const requiredDirectories = [
|
|
353
|
+
governancePath,
|
|
354
|
+
path.join(governancePath, "designs"),
|
|
355
|
+
path.join(governancePath, "reports"),
|
|
356
|
+
path.join(governancePath, "templates"),
|
|
357
|
+
path.join(governancePath, "templates", "tools"),
|
|
358
|
+
];
|
|
330
359
|
for (const dirPath of requiredDirectories) {
|
|
331
360
|
const exists = await pathExists(dirPath);
|
|
332
361
|
await fs.mkdir(dirPath, { recursive: true });
|
|
333
362
|
directories.push({ path: dirPath, action: exists ? "skipped" : "created" });
|
|
334
363
|
}
|
|
335
364
|
const markerPath = path.join(governancePath, PROJECT_MARKER);
|
|
336
|
-
const
|
|
337
|
-
|
|
365
|
+
const defaultRoadmapData = defaultRoadmapMilestones();
|
|
366
|
+
const defaultTaskUpdatedAt = new Date().toISOString();
|
|
367
|
+
const markerExists = await pathExists(markerPath);
|
|
368
|
+
await ensureStore(markerPath);
|
|
369
|
+
if (force || !markerExists) {
|
|
370
|
+
await replaceRoadmapsInStore(markerPath, defaultRoadmapData);
|
|
371
|
+
await replaceTasksInStore(markerPath, [
|
|
372
|
+
{
|
|
373
|
+
id: "TASK-0001",
|
|
374
|
+
title: "Bootstrap governance workspace",
|
|
375
|
+
status: "TODO",
|
|
376
|
+
owner: "unassigned",
|
|
377
|
+
summary: "Create initial governance artifacts and confirm task execution loop.",
|
|
378
|
+
updatedAt: defaultTaskUpdatedAt,
|
|
379
|
+
links: [],
|
|
380
|
+
roadmapRefs: ["ROADMAP-0001"],
|
|
381
|
+
},
|
|
382
|
+
]);
|
|
383
|
+
}
|
|
384
|
+
const baseFiles = await Promise.all([
|
|
338
385
|
writeTextFile(path.join(governancePath, "README.md"), defaultReadmeMarkdown(governanceDirName), force),
|
|
339
|
-
writeTextFile(path.join(governancePath, "roadmap.md"), defaultRoadmapMarkdown(), force),
|
|
340
|
-
writeTextFile(path.join(governancePath, "tasks.md"), defaultTasksMarkdown(), force),
|
|
341
|
-
writeTextFile(path.join(governancePath, "
|
|
386
|
+
writeTextFile(path.join(governancePath, "roadmap.md"), defaultRoadmapMarkdown(defaultRoadmapData), force),
|
|
387
|
+
writeTextFile(path.join(governancePath, "tasks.md"), defaultTasksMarkdown(defaultTaskUpdatedAt), force),
|
|
388
|
+
writeTextFile(path.join(governancePath, "templates", "README.md"), defaultTemplateReadmeMarkdown(), force),
|
|
342
389
|
]);
|
|
390
|
+
const toolTemplateFiles = await Promise.all(DEFAULT_TOOL_TEMPLATE_NAMES.map((toolName) => writeTextFile(path.join(governancePath, "templates", "tools", `${toolName}.md`), getDefaultToolTemplateMarkdown(toolName), force)));
|
|
391
|
+
const files = [...baseFiles, ...toolTemplateFiles];
|
|
343
392
|
return {
|
|
344
393
|
projectPath,
|
|
345
394
|
governanceDir: governancePath,
|
|
346
|
-
markerPath,
|
|
347
395
|
directories,
|
|
348
396
|
files,
|
|
349
397
|
};
|
|
@@ -370,7 +418,6 @@ export function registerProjectTools(server) {
|
|
|
370
418
|
summarySection([
|
|
371
419
|
`- projectPath: ${initialized.projectPath}`,
|
|
372
420
|
`- governanceDir: ${initialized.governanceDir}`,
|
|
373
|
-
`- markerPath: ${initialized.markerPath}`,
|
|
374
421
|
`- force: ${force === true ? "true" : "false"}`,
|
|
375
422
|
]),
|
|
376
423
|
evidenceSection([
|
|
@@ -387,8 +434,8 @@ export function registerProjectTools(server) {
|
|
|
387
434
|
"- Continue with projectContext and taskList for execution.",
|
|
388
435
|
]),
|
|
389
436
|
lintSection([
|
|
390
|
-
"- After init, fill owner/roadmapRefs/links in
|
|
391
|
-
"- Keep task source-of-truth inside
|
|
437
|
+
"- After init, fill owner/roadmapRefs/links in .projitive task table before marking DONE.",
|
|
438
|
+
"- Keep task source-of-truth inside sqlite tables.",
|
|
392
439
|
]),
|
|
393
440
|
nextCallSection(`projectContext(projectPath=\"${initialized.projectPath}\")`),
|
|
394
441
|
],
|
|
@@ -442,23 +489,18 @@ export function registerProjectTools(server) {
|
|
|
442
489
|
const projects = await discoverProjectsAcrossRoots(roots, depth);
|
|
443
490
|
const snapshots = await Promise.all(projects.map(async (governanceDir) => {
|
|
444
491
|
const snapshot = await readTasksSnapshot(governanceDir);
|
|
445
|
-
const
|
|
446
|
-
const todo = snapshot.tasks.filter((task) => task.status === "TODO").length;
|
|
447
|
-
const blocked = snapshot.tasks.filter((task) => task.status === "BLOCKED").length;
|
|
448
|
-
const done = snapshot.tasks.filter((task) => task.status === "DONE").length;
|
|
449
|
-
const actionable = inProgress + todo;
|
|
492
|
+
const actionable = snapshot.inProgress + snapshot.todo;
|
|
450
493
|
return {
|
|
451
494
|
governanceDir,
|
|
452
|
-
tasksPath: snapshot.tasksPath,
|
|
453
495
|
tasksExists: snapshot.exists,
|
|
454
496
|
lintSuggestions: snapshot.lintSuggestions,
|
|
455
|
-
inProgress,
|
|
456
|
-
todo,
|
|
457
|
-
blocked,
|
|
458
|
-
done,
|
|
497
|
+
inProgress: snapshot.inProgress,
|
|
498
|
+
todo: snapshot.todo,
|
|
499
|
+
blocked: snapshot.blocked,
|
|
500
|
+
done: snapshot.done,
|
|
459
501
|
actionable,
|
|
460
|
-
latestUpdatedAt:
|
|
461
|
-
score:
|
|
502
|
+
latestUpdatedAt: snapshot.latestUpdatedAt,
|
|
503
|
+
score: snapshot.score,
|
|
462
504
|
};
|
|
463
505
|
}));
|
|
464
506
|
const ranked = snapshots
|
|
@@ -470,6 +512,10 @@ export function registerProjectTools(server) {
|
|
|
470
512
|
return b.latestUpdatedAt.localeCompare(a.latestUpdatedAt);
|
|
471
513
|
})
|
|
472
514
|
.slice(0, limit ?? 10);
|
|
515
|
+
if (ranked[0]) {
|
|
516
|
+
const topDoc = await loadTasksDocument(ranked[0].governanceDir);
|
|
517
|
+
ranked[0].lintSuggestions = collectTaskLintSuggestions(topDoc.tasks);
|
|
518
|
+
}
|
|
473
519
|
const markdown = renderToolResponseMarkdown({
|
|
474
520
|
toolName: "projectNext",
|
|
475
521
|
sections: [
|
|
@@ -483,12 +529,12 @@ export function registerProjectTools(server) {
|
|
|
483
529
|
]),
|
|
484
530
|
evidenceSection([
|
|
485
531
|
"- rankedProjects:",
|
|
486
|
-
...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}
|
|
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 ? "" : " | store=missing"}`),
|
|
487
533
|
]),
|
|
488
534
|
guidanceSection([
|
|
489
535
|
"- Pick top 1 project and call `projectContext` with its projectPath.",
|
|
490
536
|
"- Then call `taskList` and `taskContext` to continue execution.",
|
|
491
|
-
"- If
|
|
537
|
+
"- If governance store is missing, initialize governance before task-level operations.",
|
|
492
538
|
]),
|
|
493
539
|
lintSection(ranked[0]?.lintSuggestions ?? []),
|
|
494
540
|
nextCallSection(ranked[0]
|
|
@@ -508,7 +554,6 @@ export function registerProjectTools(server) {
|
|
|
508
554
|
const resolvedFrom = normalizePath(inputPath);
|
|
509
555
|
const governanceDir = await resolveGovernanceDir(resolvedFrom);
|
|
510
556
|
const projectPath = toProjectPath(governanceDir);
|
|
511
|
-
const markerPath = path.join(governanceDir, ".projitive");
|
|
512
557
|
const markdown = renderToolResponseMarkdown({
|
|
513
558
|
toolName: "projectLocate",
|
|
514
559
|
sections: [
|
|
@@ -516,7 +561,6 @@ export function registerProjectTools(server) {
|
|
|
516
561
|
`- resolvedFrom: ${resolvedFrom}`,
|
|
517
562
|
`- projectPath: ${projectPath}`,
|
|
518
563
|
`- governanceDir: ${governanceDir}`,
|
|
519
|
-
`- markerPath: ${markerPath}`,
|
|
520
564
|
]),
|
|
521
565
|
guidanceSection(["- Call `projectContext` with this projectPath to get task and roadmap summaries."]),
|
|
522
566
|
lintSection(["- Run `projectContext` to get governance/module lint suggestions for this project."]),
|
|
@@ -525,6 +569,61 @@ export function registerProjectTools(server) {
|
|
|
525
569
|
});
|
|
526
570
|
return asText(markdown);
|
|
527
571
|
});
|
|
572
|
+
server.registerTool("syncViews", {
|
|
573
|
+
title: "Sync Views",
|
|
574
|
+
description: "Materialize markdown views from .projitive sqlite tables (tasks.md / roadmap.md)",
|
|
575
|
+
inputSchema: {
|
|
576
|
+
projectPath: z.string(),
|
|
577
|
+
views: z.array(z.enum(["tasks", "roadmap"])).optional(),
|
|
578
|
+
force: z.boolean().optional(),
|
|
579
|
+
},
|
|
580
|
+
}, async ({ projectPath, views, force }) => {
|
|
581
|
+
const governanceDir = await resolveGovernanceDir(projectPath);
|
|
582
|
+
const dbPath = path.join(governanceDir, PROJECT_MARKER);
|
|
583
|
+
const selectedViews = views && views.length > 0
|
|
584
|
+
? Array.from(new Set(views))
|
|
585
|
+
: ["tasks", "roadmap"];
|
|
586
|
+
const forceSync = force === true;
|
|
587
|
+
if (forceSync) {
|
|
588
|
+
if (selectedViews.includes("tasks")) {
|
|
589
|
+
await markMarkdownViewDirty(dbPath, "tasks_markdown");
|
|
590
|
+
}
|
|
591
|
+
if (selectedViews.includes("roadmap")) {
|
|
592
|
+
await markMarkdownViewDirty(dbPath, "roadmaps_markdown");
|
|
593
|
+
}
|
|
594
|
+
}
|
|
595
|
+
let taskCount;
|
|
596
|
+
let roadmapCount;
|
|
597
|
+
if (selectedViews.includes("tasks")) {
|
|
598
|
+
const taskDoc = await loadTasksDocumentWithOptions(governanceDir, forceSync);
|
|
599
|
+
taskCount = taskDoc.tasks.length;
|
|
600
|
+
}
|
|
601
|
+
if (selectedViews.includes("roadmap")) {
|
|
602
|
+
const roadmapDoc = await loadRoadmapDocumentWithOptions(governanceDir, forceSync);
|
|
603
|
+
roadmapCount = roadmapDoc.milestones.length;
|
|
604
|
+
}
|
|
605
|
+
const markdown = renderToolResponseMarkdown({
|
|
606
|
+
toolName: "syncViews",
|
|
607
|
+
sections: [
|
|
608
|
+
summarySection([
|
|
609
|
+
`- governanceDir: ${governanceDir}`,
|
|
610
|
+
`- views: ${selectedViews.join(", ")}`,
|
|
611
|
+
`- force: ${forceSync ? "true" : "false"}`,
|
|
612
|
+
]),
|
|
613
|
+
evidenceSection([
|
|
614
|
+
...(typeof taskCount === "number" ? [`- tasks.md synced | taskCount=${taskCount}`] : []),
|
|
615
|
+
...(typeof roadmapCount === "number" ? [`- roadmap.md synced | roadmapCount=${roadmapCount}`] : []),
|
|
616
|
+
]),
|
|
617
|
+
guidanceSection([
|
|
618
|
+
"Use this tool after batch updates when you need immediate markdown materialization.",
|
|
619
|
+
"Routine workflows can rely on lazy sync and usually do not require force=true.",
|
|
620
|
+
]),
|
|
621
|
+
lintSection([]),
|
|
622
|
+
nextCallSection(`projectContext(projectPath="${toProjectPath(governanceDir)}")`),
|
|
623
|
+
],
|
|
624
|
+
});
|
|
625
|
+
return asText(markdown);
|
|
626
|
+
});
|
|
528
627
|
server.registerTool("projectContext", {
|
|
529
628
|
title: "Project Context",
|
|
530
629
|
description: "Get project-level summary before selecting or executing a task",
|
|
@@ -535,32 +634,28 @@ export function registerProjectTools(server) {
|
|
|
535
634
|
const governanceDir = await resolveGovernanceDir(projectPath);
|
|
536
635
|
const normalizedProjectPath = toProjectPath(governanceDir);
|
|
537
636
|
const artifacts = await discoverGovernanceArtifacts(governanceDir);
|
|
538
|
-
const
|
|
637
|
+
const dbPath = path.join(governanceDir, PROJECT_MARKER);
|
|
638
|
+
await ensureStore(dbPath);
|
|
639
|
+
const taskStats = await loadTaskStatusStatsFromStore(dbPath);
|
|
640
|
+
const { markdownPath: tasksMarkdownPath, tasks } = await loadTasksDocument(governanceDir);
|
|
539
641
|
const roadmapIds = await readRoadmapIds(governanceDir);
|
|
540
|
-
const lintSuggestions = collectTaskLintSuggestions(tasks
|
|
541
|
-
const taskSummary = {
|
|
542
|
-
total: tasks.length,
|
|
543
|
-
TODO: tasks.filter((task) => task.status === "TODO").length,
|
|
544
|
-
IN_PROGRESS: tasks.filter((task) => task.status === "IN_PROGRESS").length,
|
|
545
|
-
BLOCKED: tasks.filter((task) => task.status === "BLOCKED").length,
|
|
546
|
-
DONE: tasks.filter((task) => task.status === "DONE").length,
|
|
547
|
-
};
|
|
642
|
+
const lintSuggestions = collectTaskLintSuggestions(tasks);
|
|
548
643
|
const markdown = renderToolResponseMarkdown({
|
|
549
644
|
toolName: "projectContext",
|
|
550
645
|
sections: [
|
|
551
646
|
summarySection([
|
|
552
647
|
`- projectPath: ${normalizedProjectPath}`,
|
|
553
648
|
`- governanceDir: ${governanceDir}`,
|
|
554
|
-
`-
|
|
649
|
+
`- tasksView: ${tasksMarkdownPath}`,
|
|
555
650
|
`- roadmapIds: ${roadmapIds.length}`,
|
|
556
651
|
]),
|
|
557
652
|
evidenceSection([
|
|
558
653
|
"### Task Summary",
|
|
559
|
-
`- total: ${
|
|
560
|
-
`- TODO: ${
|
|
561
|
-
`- IN_PROGRESS: ${
|
|
562
|
-
`- BLOCKED: ${
|
|
563
|
-
`- DONE: ${
|
|
654
|
+
`- total: ${taskStats.total}`,
|
|
655
|
+
`- TODO: ${taskStats.todo}`,
|
|
656
|
+
`- IN_PROGRESS: ${taskStats.inProgress}`,
|
|
657
|
+
`- BLOCKED: ${taskStats.blocked}`,
|
|
658
|
+
`- DONE: ${taskStats.done}`,
|
|
564
659
|
"",
|
|
565
660
|
"### Artifacts",
|
|
566
661
|
renderArtifactsMarkdown(artifacts),
|
|
@@ -206,10 +206,13 @@ describe("projitive module", () => {
|
|
|
206
206
|
path.join(root, ".projitive", "README.md"),
|
|
207
207
|
path.join(root, ".projitive", "roadmap.md"),
|
|
208
208
|
path.join(root, ".projitive", "tasks.md"),
|
|
209
|
-
path.join(root, ".projitive", "
|
|
209
|
+
path.join(root, ".projitive", "templates", "README.md"),
|
|
210
|
+
path.join(root, ".projitive", "templates", "tools", "taskNext.md"),
|
|
211
|
+
path.join(root, ".projitive", "templates", "tools", "taskUpdate.md"),
|
|
210
212
|
path.join(root, ".projitive", "designs"),
|
|
211
213
|
path.join(root, ".projitive", "reports"),
|
|
212
|
-
path.join(root, ".projitive", "
|
|
214
|
+
path.join(root, ".projitive", "templates"),
|
|
215
|
+
path.join(root, ".projitive", "templates", "tools"),
|
|
213
216
|
];
|
|
214
217
|
await Promise.all(expectedPaths.map(async (targetPath) => {
|
|
215
218
|
await expect(fs.access(targetPath)).resolves.toBeUndefined();
|
|
@@ -266,7 +269,7 @@ describe("projitive module", () => {
|
|
|
266
269
|
const initialized = await initializeProjectStructure(root);
|
|
267
270
|
expect(initialized.directories.some(d => d.path.includes("designs"))).toBe(true);
|
|
268
271
|
expect(initialized.directories.some(d => d.path.includes("reports"))).toBe(true);
|
|
269
|
-
expect(initialized.directories.some(d => d.path.includes("
|
|
272
|
+
expect(initialized.directories.some(d => d.path.includes("templates"))).toBe(true);
|
|
270
273
|
});
|
|
271
274
|
});
|
|
272
275
|
describe("utility functions", () => {
|