@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.
Files changed (40) hide show
  1. package/output/package.json +8 -2
  2. package/output/source/common/artifacts.js +1 -1
  3. package/output/source/common/artifacts.test.js +11 -11
  4. package/output/source/common/errors.js +19 -19
  5. package/output/source/common/files.js +11 -11
  6. package/output/source/common/files.test.js +14 -14
  7. package/output/source/common/index.js +10 -10
  8. package/output/source/common/linter.js +27 -27
  9. package/output/source/common/linter.test.js +9 -9
  10. package/output/source/common/markdown.js +3 -3
  11. package/output/source/common/markdown.test.js +15 -15
  12. package/output/source/common/response.js +74 -74
  13. package/output/source/common/response.test.js +30 -30
  14. package/output/source/common/store.js +40 -40
  15. package/output/source/common/store.test.js +72 -72
  16. package/output/source/common/types.js +3 -3
  17. package/output/source/common/utils.js +8 -8
  18. package/output/source/index.js +16 -16
  19. package/output/source/index.test.js +64 -64
  20. package/output/source/prompts/index.js +3 -3
  21. package/output/source/prompts/quickStart.js +96 -96
  22. package/output/source/prompts/taskDiscovery.js +184 -184
  23. package/output/source/prompts/taskExecution.js +148 -148
  24. package/output/source/resources/designs.js +26 -26
  25. package/output/source/resources/designs.test.js +88 -88
  26. package/output/source/resources/governance.js +19 -19
  27. package/output/source/resources/index.js +2 -2
  28. package/output/source/resources/readme.js +7 -7
  29. package/output/source/resources/readme.test.js +113 -113
  30. package/output/source/resources/reports.js +10 -10
  31. package/output/source/resources/reports.test.js +83 -83
  32. package/output/source/tools/index.js +3 -3
  33. package/output/source/tools/project.js +191 -191
  34. package/output/source/tools/project.test.js +174 -173
  35. package/output/source/tools/roadmap.js +110 -95
  36. package/output/source/tools/roadmap.test.js +54 -46
  37. package/output/source/tools/task.js +305 -277
  38. package/output/source/tools/task.test.js +117 -110
  39. package/output/source/types.js +22 -22
  40. package/package.json +8 -2
@@ -1,61 +1,61 @@
1
- import fs from "node:fs/promises";
2
- import path from "node:path";
3
- import { z } from "zod";
4
- import { candidateFilesFromArtifacts, discoverGovernanceArtifacts, findTextReferences, ensureStore, loadActionableTasksFromStore, loadRoadmapsFromStore, loadTaskStatusStatsFromStore, loadTasksFromStore, replaceTasksInStore, upsertTaskInStore, getStoreVersion, getMarkdownViewState, markMarkdownViewBuilt, } from "../common/index.js";
5
- import { asText, evidenceSection, guidanceSection, lintSection, nextCallSection, renderErrorMarkdown, renderToolResponseMarkdown, summarySection, } from "../common/index.js";
6
- import { TASK_LINT_CODES, renderLintSuggestions } from "../common/index.js";
7
- import { resolveGovernanceDir, resolveScanDepth, resolveScanRoots, discoverProjectsAcrossRoots, toProjectPath } from "./project.js";
8
- import { isValidRoadmapId } from "./roadmap.js";
9
- import { SUB_STATE_PHASES, BLOCKER_TYPES } from "../types.js";
10
- export const ALLOWED_STATUS = ["TODO", "IN_PROGRESS", "BLOCKED", "DONE"];
11
- export const TASK_ID_REGEX = /^TASK-\d{4}$/;
12
- export const TASKS_MARKDOWN_FILE = "tasks.md";
1
+ import fs from 'node:fs/promises';
2
+ import path from 'node:path';
3
+ import { z } from 'zod';
4
+ import { candidateFilesFromArtifacts, discoverGovernanceArtifacts, findTextReferences, ensureStore, loadActionableTasksFromStore, loadRoadmapsFromStore, loadTaskStatusStatsFromStore, loadTasksFromStore, replaceTasksInStore, upsertTaskInStore, getStoreVersion, getMarkdownViewState, markMarkdownViewBuilt, } from '../common/index.js';
5
+ import { asText, evidenceSection, guidanceSection, lintSection, nextCallSection, renderErrorMarkdown, renderToolResponseMarkdown, summarySection, } from '../common/index.js';
6
+ import { TASK_LINT_CODES, renderLintSuggestions } from '../common/index.js';
7
+ import { resolveGovernanceDir, resolveScanDepth, resolveScanRoots, discoverProjectsAcrossRoots, toProjectPath } from './project.js';
8
+ import { isValidRoadmapId } from './roadmap.js';
9
+ import { SUB_STATE_PHASES, BLOCKER_TYPES } from '../types.js';
10
+ export const ALLOWED_STATUS = ['TODO', 'IN_PROGRESS', 'BLOCKED', 'DONE'];
11
+ export const TASK_ID_REGEX = /^TASK-(\d+)$/;
12
+ export const TASKS_MARKDOWN_FILE = 'tasks.md';
13
13
  function appendLintSuggestions(target, suggestions) {
14
14
  target.push(...renderLintSuggestions(suggestions));
15
15
  }
16
16
  function taskStatusGuidance(task) {
17
- if (task.status === "TODO") {
17
+ if (task.status === 'TODO') {
18
18
  return [
19
- "- This task is TODO: confirm scope and set execution plan before edits.",
20
- "- Move to IN_PROGRESS only after owner and initial evidence are ready.",
19
+ '- This task is TODO: confirm scope and set execution plan before edits.',
20
+ '- Move to IN_PROGRESS only after owner and initial evidence are ready.',
21
21
  ];
22
22
  }
23
- if (task.status === "IN_PROGRESS") {
23
+ if (task.status === 'IN_PROGRESS') {
24
24
  return [
25
- "- This task is IN_PROGRESS: prioritize finishing with report/design evidence updates.",
26
- "- Verify references stay consistent before marking DONE.",
25
+ '- This task is IN_PROGRESS: prioritize finishing with report/design evidence updates.',
26
+ '- Verify references stay consistent before marking DONE.',
27
27
  ];
28
28
  }
29
- if (task.status === "BLOCKED") {
29
+ if (task.status === 'BLOCKED') {
30
30
  return [
31
- "- This task is BLOCKED: identify blocker and required unblock condition first.",
32
- "- Reopen only after blocker evidence is documented.",
31
+ '- This task is BLOCKED: identify blocker and required unblock condition first.',
32
+ '- Reopen only after blocker evidence is documented.',
33
33
  ];
34
34
  }
35
35
  return [
36
- "- This task is DONE: only reopen when new requirement changes scope.",
37
- "- Keep report evidence immutable unless correction is required.",
36
+ '- This task is DONE: only reopen when new requirement changes scope.',
37
+ '- Keep report evidence immutable unless correction is required.',
38
38
  ];
39
39
  }
40
40
  const DEFAULT_NO_TASK_DISCOVERY_GUIDANCE = [
41
- "- Recheck project state first: run projectContext and confirm there is truly no TODO/IN_PROGRESS task to execute.",
42
- "- Create new tasks via `taskCreate(...)` (do not edit tasks.md directly).",
43
- "- If all remaining tasks are BLOCKED, create one unblock task with explicit unblock condition and dependency owner.",
44
- "- Start from active roadmap milestones and split into the smallest executable slices with a single done condition each.",
45
- "- Prefer slices that unlock multiple downstream tasks before isolated refactors or low-impact cleanups.",
46
- "- Create TODO tasks only when evidence is clear: each new task must produce at least one report/designs/readme artifact update.",
47
- "- Skip duplicate scope: do not create tasks that overlap existing TODO/IN_PROGRESS/BLOCKED task intent.",
48
- "- Use quality gates for discovery candidates: user value, delivery risk reduction, or measurable throughput improvement.",
49
- "- Keep each discovery round small (1-3 tasks), then rerun taskNext immediately for re-ranking and execution.",
41
+ '- Recheck project state first: run projectContext and confirm there is truly no TODO/IN_PROGRESS task to execute.',
42
+ '- Create new tasks via `taskCreate(...)` (do not edit tasks.md directly).',
43
+ '- If all remaining tasks are BLOCKED, create one unblock task with explicit unblock condition and dependency owner.',
44
+ '- Start from active roadmap milestones and split into the smallest executable slices with a single done condition each.',
45
+ '- Prefer slices that unlock multiple downstream tasks before isolated refactors or low-impact cleanups.',
46
+ '- Create TODO tasks only when evidence is clear: each new task must produce at least one report/designs/readme artifact update.',
47
+ '- Skip duplicate scope: do not create tasks that overlap existing TODO/IN_PROGRESS/BLOCKED task intent.',
48
+ '- Use quality gates for discovery candidates: user value, delivery risk reduction, or measurable throughput improvement.',
49
+ '- Keep each discovery round small (1-3 tasks), then rerun taskNext immediately for re-ranking and execution.',
50
50
  ];
51
51
  const DEFAULT_TASK_CONTEXT_READING_GUIDANCE = [
52
- "- Read governance workspace overview first (README.md / projitive://governance/workspace).",
53
- "- Read roadmap and active milestones (roadmap.md / projitive://governance/roadmap).",
54
- "- Read task view and related task cards (tasks.md / projitive://governance/tasks).",
55
- "- Read design specs and technical decisions under designs/ (architecture, API contracts, constraints).",
56
- "- Read reports/ for latest execution evidence, regressions, and unresolved risks.",
57
- "- Read process guides under templates/docs/project guidelines to align with local governance rules.",
58
- "- If available, read docs/ architecture or migration guides before major structural changes.",
52
+ '- Read governance workspace overview first (README.md / projitive://governance/workspace).',
53
+ '- Read roadmap and active milestones (roadmap.md / projitive://governance/roadmap).',
54
+ '- Read task view and related task cards (tasks.md / projitive://governance/tasks).',
55
+ '- Read design specs and technical decisions under designs/ (architecture, API contracts, constraints).',
56
+ '- Read reports/ for latest execution evidence, regressions, and unresolved risks.',
57
+ '- Read process guides under templates/docs/project guidelines to align with local governance rules.',
58
+ '- If available, read docs/ architecture or migration guides before major structural changes.',
59
59
  ];
60
60
  export async function resolveNoTaskDiscoveryGuidance(governanceDir) {
61
61
  void governanceDir;
@@ -66,7 +66,7 @@ export async function resolveTaskContextReadingGuidance(governanceDir) {
66
66
  return DEFAULT_TASK_CONTEXT_READING_GUIDANCE;
67
67
  }
68
68
  async function readRoadmapIds(governanceDir) {
69
- const dbPath = path.join(governanceDir, ".projitive");
69
+ const dbPath = path.join(governanceDir, '.projitive');
70
70
  try {
71
71
  await ensureStore(dbPath);
72
72
  const milestones = await loadRoadmapsFromStore(dbPath);
@@ -79,16 +79,16 @@ async function readRoadmapIds(governanceDir) {
79
79
  }
80
80
  export function renderTaskSeedTemplate(roadmapRef) {
81
81
  return [
82
- "```markdown",
83
- "## TASK-0001 | TODO | Define initial executable objective",
84
- "- owner: ai-copilot",
85
- "- summary: Convert one roadmap milestone or report gap into an actionable task.",
86
- "- updatedAt: 2026-01-01T00:00:00.000Z",
82
+ '```markdown',
83
+ '## TASK-0001 | TODO | Define initial executable objective',
84
+ '- owner: ai-copilot',
85
+ '- summary: Convert one roadmap milestone or report gap into an actionable task.',
86
+ '- updatedAt: 2026-01-01T00:00:00.000Z',
87
87
  `- roadmapRefs: ${roadmapRef}`,
88
- "- links:",
89
- " - README.md",
90
- " - .projitive/roadmap.md",
91
- "```",
88
+ '- links:',
89
+ ' - README.md',
90
+ ' - .projitive/roadmap.md',
91
+ '```',
92
92
  ];
93
93
  }
94
94
  function isHttpUrl(value) {
@@ -96,9 +96,9 @@ function isHttpUrl(value) {
96
96
  }
97
97
  function isProjectRootRelativePath(value) {
98
98
  return value.length > 0
99
- && !value.startsWith("/")
100
- && !value.startsWith("./")
101
- && !value.startsWith("../")
99
+ && !value.startsWith('/')
100
+ && !value.startsWith('./')
101
+ && !value.startsWith('../')
102
102
  && !/^[A-Za-z]:\//.test(value);
103
103
  }
104
104
  function normalizeTaskLink(link) {
@@ -106,16 +106,16 @@ function normalizeTaskLink(link) {
106
106
  if (trimmed.length === 0 || isHttpUrl(trimmed)) {
107
107
  return trimmed;
108
108
  }
109
- const slashNormalized = trimmed.replace(/\\/g, "/");
110
- const withoutDotPrefix = slashNormalized.replace(/^\.\//, "");
111
- return withoutDotPrefix.replace(/^\/+/, "");
109
+ const slashNormalized = trimmed.replace(/\\/g, '/');
110
+ const withoutDotPrefix = slashNormalized.replace(/^\.\//, '');
111
+ return withoutDotPrefix.replace(/^\/+/, '');
112
112
  }
113
113
  function resolveTaskLinkPath(projectPath, link) {
114
114
  return path.join(projectPath, link);
115
115
  }
116
116
  async function readActionableTaskCandidates(governanceDirs) {
117
117
  const snapshots = await Promise.all(governanceDirs.map(async (governanceDir) => {
118
- const tasksPath = path.join(governanceDir, ".projitive");
118
+ const tasksPath = path.join(governanceDir, '.projitive');
119
119
  await ensureStore(tasksPath);
120
120
  const [stats, actionableTasks] = await Promise.all([
121
121
  loadTaskStatusStatsFromStore(tasksPath),
@@ -125,11 +125,11 @@ async function readActionableTaskCandidates(governanceDirs) {
125
125
  governanceDir,
126
126
  tasks: actionableTasks,
127
127
  projectScore: stats.inProgress * 2 + stats.todo,
128
- projectLatestUpdatedAt: stats.latestUpdatedAt || "(unknown)",
128
+ projectLatestUpdatedAt: stats.latestUpdatedAt || '(unknown)',
129
129
  };
130
130
  }));
131
131
  return snapshots.flatMap((item) => item.tasks
132
- .filter((task) => task.status === "IN_PROGRESS" || task.status === "TODO")
132
+ .filter((task) => task.status === 'IN_PROGRESS' || task.status === 'TODO')
133
133
  .map((task) => ({
134
134
  governanceDir: item.governanceDir,
135
135
  task,
@@ -143,13 +143,13 @@ export function nowIso() {
143
143
  return new Date().toISOString();
144
144
  }
145
145
  export function isValidTaskId(id) {
146
- return TASK_ID_REGEX.test(id);
146
+ return toTaskIdNumericSuffix(id) > 0;
147
147
  }
148
148
  export function taskPriority(status) {
149
- if (status === "IN_PROGRESS") {
149
+ if (status === 'IN_PROGRESS') {
150
150
  return 2;
151
151
  }
152
- if (status === "TODO") {
152
+ if (status === 'TODO') {
153
153
  return 1;
154
154
  }
155
155
  return 0;
@@ -159,11 +159,21 @@ export function toTaskUpdatedAtMs(updatedAt) {
159
159
  return Number.isFinite(timestamp) ? timestamp : 0;
160
160
  }
161
161
  function toTaskIdNumericSuffix(taskId) {
162
- const match = taskId.match(/^(?:TASK-)(\d{4})$/);
162
+ const match = taskId.match(TASK_ID_REGEX);
163
163
  if (!match) {
164
164
  return -1;
165
165
  }
166
- return Number.parseInt(match[1], 10);
166
+ const suffix = Number.parseInt(match[1], 10);
167
+ return Number.isFinite(suffix) ? suffix : -1;
168
+ }
169
+ function nextTaskId(tasks) {
170
+ const maxSuffix = tasks
171
+ .map((item) => toTaskIdNumericSuffix(item.id))
172
+ .filter((value) => value > 0)
173
+ .reduce((max, value) => Math.max(max, value), 0);
174
+ const next = maxSuffix + 1;
175
+ const minWidth = Math.max(4, String(next).length);
176
+ return `TASK-${String(next).padStart(minWidth, '0')}`;
167
177
  }
168
178
  export function sortTasksNewestFirst(tasks) {
169
179
  return [...tasks].sort((a, b) => {
@@ -183,13 +193,13 @@ function normalizeAndSortTasks(tasks) {
183
193
  }
184
194
  function resolveTaskArtifactPaths(governanceDir) {
185
195
  return {
186
- tasksPath: path.join(governanceDir, ".projitive"),
196
+ tasksPath: path.join(governanceDir, '.projitive'),
187
197
  markdownPath: path.join(governanceDir, TASKS_MARKDOWN_FILE),
188
198
  };
189
199
  }
190
200
  async function syncTasksMarkdownView(tasksPath, markdownPath, markdown, force = false) {
191
- const sourceVersion = await getStoreVersion(tasksPath, "tasks");
192
- const viewState = await getMarkdownViewState(tasksPath, "tasks_markdown");
201
+ const sourceVersion = await getStoreVersion(tasksPath, 'tasks');
202
+ const viewState = await getMarkdownViewState(tasksPath, 'tasks_markdown');
193
203
  const markdownExists = await fs.access(markdownPath).then(() => true).catch(() => false);
194
204
  const shouldWrite = force
195
205
  || !markdownExists
@@ -198,8 +208,8 @@ async function syncTasksMarkdownView(tasksPath, markdownPath, markdown, force =
198
208
  if (!shouldWrite) {
199
209
  return;
200
210
  }
201
- await fs.writeFile(markdownPath, markdown, "utf-8");
202
- await markMarkdownViewBuilt(tasksPath, "tasks_markdown", sourceVersion);
211
+ await fs.writeFile(markdownPath, markdown, 'utf-8');
212
+ await markMarkdownViewBuilt(tasksPath, 'tasks_markdown', sourceVersion);
203
213
  }
204
214
  export function rankActionableTaskCandidates(candidates) {
205
215
  return [...candidates].sort((a, b) => {
@@ -225,9 +235,9 @@ export function normalizeTask(task) {
225
235
  const normalized = {
226
236
  id: String(task.id),
227
237
  title: String(task.title),
228
- status: ALLOWED_STATUS.includes(task.status) ? task.status : "TODO",
229
- owner: task.owner ? String(task.owner) : "",
230
- summary: task.summary ? String(task.summary) : "",
238
+ status: ALLOWED_STATUS.includes(task.status) ? task.status : 'TODO',
239
+ owner: task.owner ? String(task.owner) : '',
240
+ summary: task.summary ? String(task.summary) : '',
231
241
  updatedAt: task.updatedAt ? String(task.updatedAt) : nowIso(),
232
242
  links: Array.isArray(task.links)
233
243
  ? Array.from(new Set(task.links
@@ -258,32 +268,32 @@ function collectTaskLintSuggestionItems(tasks) {
258
268
  if (duplicateIds.length > 0) {
259
269
  suggestions.push({
260
270
  code: TASK_LINT_CODES.DUPLICATE_ID,
261
- message: `Duplicate task IDs detected: ${duplicateIds.join(", ")}.`,
262
- fixHint: "Keep task IDs unique in marker block.",
271
+ message: `Duplicate task IDs detected: ${duplicateIds.join(', ')}.`,
272
+ fixHint: 'Keep task IDs unique in marker block.',
263
273
  });
264
274
  }
265
- const inProgressWithoutOwner = tasks.filter((task) => task.status === "IN_PROGRESS" && task.owner.trim().length === 0);
275
+ const inProgressWithoutOwner = tasks.filter((task) => task.status === 'IN_PROGRESS' && task.owner.trim().length === 0);
266
276
  if (inProgressWithoutOwner.length > 0) {
267
277
  suggestions.push({
268
278
  code: TASK_LINT_CODES.IN_PROGRESS_OWNER_EMPTY,
269
279
  message: `${inProgressWithoutOwner.length} IN_PROGRESS task(s) have empty owner.`,
270
- fixHint: "Set owner before continuing execution.",
280
+ fixHint: 'Set owner before continuing execution.',
271
281
  });
272
282
  }
273
- const doneWithoutLinks = tasks.filter((task) => task.status === "DONE" && task.links.length === 0);
283
+ const doneWithoutLinks = tasks.filter((task) => task.status === 'DONE' && task.links.length === 0);
274
284
  if (doneWithoutLinks.length > 0) {
275
285
  suggestions.push({
276
286
  code: TASK_LINT_CODES.DONE_LINKS_MISSING,
277
287
  message: `${doneWithoutLinks.length} DONE task(s) have no links evidence.`,
278
- fixHint: "Add at least one evidence link before keeping DONE.",
288
+ fixHint: 'Add at least one evidence link before keeping DONE.',
279
289
  });
280
290
  }
281
- const blockedWithoutReason = tasks.filter((task) => task.status === "BLOCKED" && task.summary.trim().length === 0);
291
+ const blockedWithoutReason = tasks.filter((task) => task.status === 'BLOCKED' && task.summary.trim().length === 0);
282
292
  if (blockedWithoutReason.length > 0) {
283
293
  suggestions.push({
284
294
  code: TASK_LINT_CODES.BLOCKED_SUMMARY_EMPTY,
285
295
  message: `${blockedWithoutReason.length} BLOCKED task(s) have empty summary.`,
286
- fixHint: "Add blocker reason and unblock condition.",
296
+ fixHint: 'Add blocker reason and unblock condition.',
287
297
  });
288
298
  }
289
299
  const invalidUpdatedAt = tasks.filter((task) => !Number.isFinite(new Date(task.updatedAt).getTime()));
@@ -291,7 +301,7 @@ function collectTaskLintSuggestionItems(tasks) {
291
301
  suggestions.push({
292
302
  code: TASK_LINT_CODES.UPDATED_AT_INVALID,
293
303
  message: `${invalidUpdatedAt.length} task(s) have invalid updatedAt format.`,
294
- fixHint: "Use ISO8601 UTC timestamp.",
304
+ fixHint: 'Use ISO8601 UTC timestamp.',
295
305
  });
296
306
  }
297
307
  const missingRoadmapRefs = tasks.filter((task) => task.roadmapRefs.length === 0);
@@ -299,7 +309,7 @@ function collectTaskLintSuggestionItems(tasks) {
299
309
  suggestions.push({
300
310
  code: TASK_LINT_CODES.ROADMAP_REFS_EMPTY,
301
311
  message: `${missingRoadmapRefs.length} task(s) have empty roadmapRefs.`,
302
- fixHint: "Bind at least one ROADMAP-xxxx when applicable.",
312
+ fixHint: 'Bind at least one ROADMAP-xxxx when applicable.',
303
313
  });
304
314
  }
305
315
  const invalidLinkPathFormat = tasks.filter((task) => task.links.some((link) => {
@@ -310,18 +320,18 @@ function collectTaskLintSuggestionItems(tasks) {
310
320
  suggestions.push({
311
321
  code: TASK_LINT_CODES.LINK_PATH_FORMAT_INVALID,
312
322
  message: `${invalidLinkPathFormat.length} task(s) contain invalid links path format.`,
313
- fixHint: "Use project-root-relative paths without leading slash (for example reports/task-0001.md) or http(s) URL.",
323
+ fixHint: 'Use project-root-relative paths without leading slash (for example reports/task-0001.md) or http(s) URL.',
314
324
  });
315
325
  }
316
326
  // ============================================================================
317
327
  // Spec v1.1.0 - Blocker Categorization Validation
318
328
  // ============================================================================
319
- const blockedWithoutBlocker = tasks.filter((task) => task.status === "BLOCKED" && !task.blocker);
329
+ const blockedWithoutBlocker = tasks.filter((task) => task.status === 'BLOCKED' && !task.blocker);
320
330
  if (blockedWithoutBlocker.length > 0) {
321
331
  suggestions.push({
322
332
  code: TASK_LINT_CODES.BLOCKED_WITHOUT_BLOCKER,
323
333
  message: `${blockedWithoutBlocker.length} BLOCKED task(s) have no blocker metadata.`,
324
- fixHint: "Add structured blocker metadata with type and description.",
334
+ fixHint: 'Add structured blocker metadata with type and description.',
325
335
  });
326
336
  }
327
337
  const blockerTypeInvalid = tasks.filter((task) => task.blocker && !BLOCKER_TYPES.includes(task.blocker.type));
@@ -329,7 +339,7 @@ function collectTaskLintSuggestionItems(tasks) {
329
339
  suggestions.push({
330
340
  code: TASK_LINT_CODES.BLOCKER_TYPE_INVALID,
331
341
  message: `${blockerTypeInvalid.length} task(s) have invalid blocker type.`,
332
- fixHint: `Use one of: ${BLOCKER_TYPES.join(", ")}.`,
342
+ fixHint: `Use one of: ${BLOCKER_TYPES.join(', ')}.`,
333
343
  });
334
344
  }
335
345
  const blockerDescriptionEmpty = tasks.filter((task) => task.blocker && !task.blocker.description?.trim());
@@ -337,18 +347,18 @@ function collectTaskLintSuggestionItems(tasks) {
337
347
  suggestions.push({
338
348
  code: TASK_LINT_CODES.BLOCKER_DESCRIPTION_EMPTY,
339
349
  message: `${blockerDescriptionEmpty.length} task(s) have empty blocker description.`,
340
- fixHint: "Provide a clear description of why the task is blocked.",
350
+ fixHint: 'Provide a clear description of why the task is blocked.',
341
351
  });
342
352
  }
343
353
  // ============================================================================
344
354
  // Spec v1.1.0 - Sub-state Metadata Validation (Optional but Recommended)
345
355
  // ============================================================================
346
- const inProgressWithoutSubState = tasks.filter((task) => task.status === "IN_PROGRESS" && !task.subState);
356
+ const inProgressWithoutSubState = tasks.filter((task) => task.status === 'IN_PROGRESS' && !task.subState);
347
357
  if (inProgressWithoutSubState.length > 0) {
348
358
  suggestions.push({
349
359
  code: TASK_LINT_CODES.IN_PROGRESS_WITHOUT_SUBSTATE,
350
360
  message: `${inProgressWithoutSubState.length} IN_PROGRESS task(s) have no subState metadata.`,
351
- fixHint: "Add optional subState metadata for better progress tracking.",
361
+ fixHint: 'Add optional subState metadata for better progress tracking.',
352
362
  });
353
363
  }
354
364
  const subStatePhaseInvalid = tasks.filter((task) => task.subState?.phase && !SUB_STATE_PHASES.includes(task.subState.phase));
@@ -356,15 +366,15 @@ function collectTaskLintSuggestionItems(tasks) {
356
366
  suggestions.push({
357
367
  code: TASK_LINT_CODES.SUBSTATE_PHASE_INVALID,
358
368
  message: `${subStatePhaseInvalid.length} task(s) have invalid subState phase.`,
359
- fixHint: `Use one of: ${SUB_STATE_PHASES.join(", ")}.`,
369
+ fixHint: `Use one of: ${SUB_STATE_PHASES.join(', ')}.`,
360
370
  });
361
371
  }
362
- const subStateConfidenceInvalid = tasks.filter((task) => typeof task.subState?.confidence === "number" && (task.subState.confidence < 0 || task.subState.confidence > 1));
372
+ const subStateConfidenceInvalid = tasks.filter((task) => typeof task.subState?.confidence === 'number' && (task.subState.confidence < 0 || task.subState.confidence > 1));
363
373
  if (subStateConfidenceInvalid.length > 0) {
364
374
  suggestions.push({
365
375
  code: TASK_LINT_CODES.SUBSTATE_CONFIDENCE_INVALID,
366
376
  message: `${subStateConfidenceInvalid.length} task(s) have invalid confidence score.`,
367
- fixHint: "Confidence must be between 0.0 and 1.0.",
377
+ fixHint: 'Confidence must be between 0.0 and 1.0.',
368
378
  });
369
379
  }
370
380
  return suggestions;
@@ -374,18 +384,18 @@ export function collectTaskLintSuggestions(tasks) {
374
384
  }
375
385
  function collectSingleTaskLintSuggestions(task) {
376
386
  const suggestions = [];
377
- if (task.status === "IN_PROGRESS" && task.owner.trim().length === 0) {
387
+ if (task.status === 'IN_PROGRESS' && task.owner.trim().length === 0) {
378
388
  suggestions.push({
379
389
  code: TASK_LINT_CODES.IN_PROGRESS_OWNER_EMPTY,
380
- message: "Current task is IN_PROGRESS but owner is empty.",
381
- fixHint: "Set owner before continuing execution.",
390
+ message: 'Current task is IN_PROGRESS but owner is empty.',
391
+ fixHint: 'Set owner before continuing execution.',
382
392
  });
383
393
  }
384
- if (task.status === "DONE" && task.links.length === 0) {
394
+ if (task.status === 'DONE' && task.links.length === 0) {
385
395
  suggestions.push({
386
396
  code: TASK_LINT_CODES.DONE_LINKS_MISSING,
387
- message: "Current task is DONE but has no links evidence.",
388
- fixHint: "Add at least one evidence link.",
397
+ message: 'Current task is DONE but has no links evidence.',
398
+ fixHint: 'Add at least one evidence link.',
389
399
  });
390
400
  }
391
401
  const invalidLinkPathFormat = task.links.some((link) => {
@@ -395,77 +405,77 @@ function collectSingleTaskLintSuggestions(task) {
395
405
  if (invalidLinkPathFormat) {
396
406
  suggestions.push({
397
407
  code: TASK_LINT_CODES.LINK_PATH_FORMAT_INVALID,
398
- message: "Current task has invalid links path format.",
399
- fixHint: "Use project-root-relative paths without leading slash (for example reports/task-0001.md) or http(s) URL.",
408
+ message: 'Current task has invalid links path format.',
409
+ fixHint: 'Use project-root-relative paths without leading slash (for example reports/task-0001.md) or http(s) URL.',
400
410
  });
401
411
  }
402
- if (task.status === "BLOCKED" && task.summary.trim().length === 0) {
412
+ if (task.status === 'BLOCKED' && task.summary.trim().length === 0) {
403
413
  suggestions.push({
404
414
  code: TASK_LINT_CODES.BLOCKED_SUMMARY_EMPTY,
405
- message: "Current task is BLOCKED but summary is empty.",
406
- fixHint: "Add blocker reason and unblock condition.",
415
+ message: 'Current task is BLOCKED but summary is empty.',
416
+ fixHint: 'Add blocker reason and unblock condition.',
407
417
  });
408
418
  }
409
419
  if (!Number.isFinite(new Date(task.updatedAt).getTime())) {
410
420
  suggestions.push({
411
421
  code: TASK_LINT_CODES.UPDATED_AT_INVALID,
412
- message: "Current task updatedAt is invalid.",
413
- fixHint: "Use ISO8601 UTC timestamp.",
422
+ message: 'Current task updatedAt is invalid.',
423
+ fixHint: 'Use ISO8601 UTC timestamp.',
414
424
  });
415
425
  }
416
426
  if (task.roadmapRefs.length === 0) {
417
427
  suggestions.push({
418
428
  code: TASK_LINT_CODES.ROADMAP_REFS_EMPTY,
419
- message: "Current task has empty roadmapRefs.",
420
- fixHint: "Bind ROADMAP-xxxx where applicable.",
429
+ message: 'Current task has empty roadmapRefs.',
430
+ fixHint: 'Bind ROADMAP-xxxx where applicable.',
421
431
  });
422
432
  }
423
433
  // ============================================================================
424
434
  // Spec v1.1.0 - Blocker Categorization Validation (Single Task)
425
435
  // ============================================================================
426
- if (task.status === "BLOCKED" && !task.blocker) {
436
+ if (task.status === 'BLOCKED' && !task.blocker) {
427
437
  suggestions.push({
428
438
  code: TASK_LINT_CODES.BLOCKED_WITHOUT_BLOCKER,
429
- message: "Current task is BLOCKED but has no blocker metadata.",
430
- fixHint: "Add structured blocker metadata with type and description.",
439
+ message: 'Current task is BLOCKED but has no blocker metadata.',
440
+ fixHint: 'Add structured blocker metadata with type and description.',
431
441
  });
432
442
  }
433
443
  if (task.blocker && !BLOCKER_TYPES.includes(task.blocker.type)) {
434
444
  suggestions.push({
435
445
  code: TASK_LINT_CODES.BLOCKER_TYPE_INVALID,
436
446
  message: `Current task has invalid blocker type: ${task.blocker.type}.`,
437
- fixHint: `Use one of: ${BLOCKER_TYPES.join(", ")}.`,
447
+ fixHint: `Use one of: ${BLOCKER_TYPES.join(', ')}.`,
438
448
  });
439
449
  }
440
450
  if (task.blocker && !task.blocker.description?.trim()) {
441
451
  suggestions.push({
442
452
  code: TASK_LINT_CODES.BLOCKER_DESCRIPTION_EMPTY,
443
- message: "Current task has empty blocker description.",
444
- fixHint: "Provide a clear description of why the task is blocked.",
453
+ message: 'Current task has empty blocker description.',
454
+ fixHint: 'Provide a clear description of why the task is blocked.',
445
455
  });
446
456
  }
447
457
  // ============================================================================
448
458
  // Spec v1.1.0 - Sub-state Metadata Validation (Single Task, Optional)
449
459
  // ============================================================================
450
- if (task.status === "IN_PROGRESS" && !task.subState) {
460
+ if (task.status === 'IN_PROGRESS' && !task.subState) {
451
461
  suggestions.push({
452
462
  code: TASK_LINT_CODES.IN_PROGRESS_WITHOUT_SUBSTATE,
453
- message: "Current task is IN_PROGRESS but has no subState metadata.",
454
- fixHint: "Add optional subState metadata for better progress tracking.",
463
+ message: 'Current task is IN_PROGRESS but has no subState metadata.',
464
+ fixHint: 'Add optional subState metadata for better progress tracking.',
455
465
  });
456
466
  }
457
467
  if (task.subState?.phase && !SUB_STATE_PHASES.includes(task.subState.phase)) {
458
468
  suggestions.push({
459
469
  code: TASK_LINT_CODES.SUBSTATE_PHASE_INVALID,
460
470
  message: `Current task has invalid subState phase: ${task.subState.phase}.`,
461
- fixHint: `Use one of: ${SUB_STATE_PHASES.join(", ")}.`,
471
+ fixHint: `Use one of: ${SUB_STATE_PHASES.join(', ')}.`,
462
472
  });
463
473
  }
464
- if (typeof task.subState?.confidence === "number" && (task.subState.confidence < 0 || task.subState.confidence > 1)) {
474
+ if (typeof task.subState?.confidence === 'number' && (task.subState.confidence < 0 || task.subState.confidence > 1)) {
465
475
  suggestions.push({
466
476
  code: TASK_LINT_CODES.SUBSTATE_CONFIDENCE_INVALID,
467
477
  message: `Current task has invalid confidence score: ${task.subState.confidence}.`,
468
- fixHint: "Confidence must be between 0.0 and 1.0.",
478
+ fixHint: 'Confidence must be between 0.0 and 1.0.',
469
479
  });
470
480
  }
471
481
  return renderLintSuggestions(suggestions);
@@ -485,7 +495,7 @@ async function collectTaskFileLintSuggestions(governanceDir, task) {
485
495
  suggestions.push({
486
496
  code: TASK_LINT_CODES.LINK_PATH_FORMAT_INVALID,
487
497
  message: `Link path should be project-root-relative without leading slash: ${normalized}.`,
488
- fixHint: "Use path/from/project/root format.",
498
+ fixHint: 'Use path/from/project/root format.',
489
499
  });
490
500
  continue;
491
501
  }
@@ -502,25 +512,25 @@ async function collectTaskFileLintSuggestions(governanceDir, task) {
502
512
  }
503
513
  export function renderTasksMarkdown(tasks) {
504
514
  const sections = sortTasksNewestFirst(tasks).map((task) => {
505
- const roadmapRefs = task.roadmapRefs.length > 0 ? task.roadmapRefs.join(", ") : "(none)";
515
+ const roadmapRefs = task.roadmapRefs.length > 0 ? task.roadmapRefs.join(', ') : '(none)';
506
516
  const links = task.links.length > 0
507
- ? ["- links:", ...task.links.map((link) => ` - ${link}`)]
508
- : ["- links:", " - (none)"];
517
+ ? ['- links:', ...task.links.map((link) => ` - ${link}`)]
518
+ : ['- links:', ' - (none)'];
509
519
  const lines = [
510
520
  `## ${task.id} | ${task.status} | ${task.title}`,
511
- `- owner: ${task.owner || "(none)"}`,
512
- `- summary: ${task.summary || "(none)"}`,
521
+ `- owner: ${task.owner || '(none)'}`,
522
+ `- summary: ${task.summary || '(none)'}`,
513
523
  `- updatedAt: ${task.updatedAt}`,
514
524
  `- roadmapRefs: ${roadmapRefs}`,
515
525
  ...links,
516
526
  ];
517
527
  // Add subState for IN_PROGRESS tasks (Spec v1.1.0)
518
- if (task.subState && task.status === "IN_PROGRESS") {
519
- lines.push(`- subState:`);
528
+ if (task.subState && task.status === 'IN_PROGRESS') {
529
+ lines.push('- subState:');
520
530
  if (task.subState.phase) {
521
531
  lines.push(` - phase: ${task.subState.phase}`);
522
532
  }
523
- if (typeof task.subState.confidence === "number") {
533
+ if (typeof task.subState.confidence === 'number') {
524
534
  lines.push(` - confidence: ${task.subState.confidence}`);
525
535
  }
526
536
  if (task.subState.estimatedCompletion) {
@@ -528,8 +538,8 @@ export function renderTasksMarkdown(tasks) {
528
538
  }
529
539
  }
530
540
  // Add blocker for BLOCKED tasks (Spec v1.1.0)
531
- if (task.blocker && task.status === "BLOCKED") {
532
- lines.push(`- blocker:`);
541
+ if (task.blocker && task.status === 'BLOCKED') {
542
+ lines.push('- blocker:');
533
543
  lines.push(` - type: ${task.blocker.type}`);
534
544
  lines.push(` - description: ${task.blocker.description}`);
535
545
  if (task.blocker.blockingEntity) {
@@ -542,16 +552,20 @@ export function renderTasksMarkdown(tasks) {
542
552
  lines.push(` - escalationPath: ${task.blocker.escalationPath}`);
543
553
  }
544
554
  }
545
- return lines.join("\n");
555
+ return lines.join('\n');
546
556
  });
547
557
  return [
548
- "# Tasks",
549
- "",
550
- "This file is generated from .projitive governance store by Projitive MCP. Manual edits will be overwritten.",
551
- "",
552
- ...(sections.length > 0 ? sections : ["(no tasks)"]),
553
- "",
554
- ].join("\n");
558
+ '# Tasks',
559
+ '',
560
+ 'Projitive is an AI-first project governance framework for tasks, roadmaps, reports, and designs.',
561
+ 'Author: yinxulai',
562
+ 'Repository: https://github.com/yinxulai/projitive',
563
+ 'Do not edit this file manually. This file is automatically generated by Projitive.',
564
+ 'This file is generated from .projitive governance store by Projitive MCP. Manual edits will be overwritten.',
565
+ '',
566
+ ...(sections.length > 0 ? sections : ['(no tasks)']),
567
+ '',
568
+ ].join('\n');
555
569
  }
556
570
  export async function ensureTasksFile(inputPath) {
557
571
  const governanceDir = await resolveGovernanceDir(inputPath);
@@ -588,26 +602,27 @@ export function validateTransition(from, to) {
588
602
  return true;
589
603
  }
590
604
  const allowed = {
591
- TODO: new Set(["IN_PROGRESS", "BLOCKED"]),
592
- IN_PROGRESS: new Set(["BLOCKED", "DONE"]),
593
- BLOCKED: new Set(["IN_PROGRESS", "TODO"]),
605
+ TODO: new Set(['IN_PROGRESS', 'BLOCKED']),
606
+ IN_PROGRESS: new Set(['BLOCKED', 'DONE']),
607
+ BLOCKED: new Set(['IN_PROGRESS', 'TODO']),
594
608
  DONE: new Set(),
595
609
  };
596
610
  return allowed[from].has(to);
597
611
  }
598
612
  export function registerTaskTools(server) {
599
- server.registerTool("taskList", {
600
- title: "Task List",
601
- description: "List tasks for a known project and optionally filter by status",
613
+ server.registerTool('taskList', {
614
+ title: 'Task List',
615
+ description: 'List tasks for a known project and optionally filter by status',
602
616
  inputSchema: {
603
617
  projectPath: z.string(),
604
- status: z.enum(["TODO", "IN_PROGRESS", "BLOCKED", "DONE"]).optional(),
618
+ status: z.enum(['TODO', 'IN_PROGRESS', 'BLOCKED', 'DONE']).optional(),
605
619
  limit: z.number().int().min(1).max(200).optional(),
606
620
  },
607
621
  }, async ({ projectPath, status, limit }) => {
608
622
  const governanceDir = await resolveGovernanceDir(projectPath);
609
623
  const normalizedProjectPath = toProjectPath(governanceDir);
610
- const { tasks } = await loadTasksDocument(governanceDir);
624
+ const { tasks, markdownPath: tasksViewPath } = await loadTasksDocument(governanceDir);
625
+ const roadmapViewPath = path.join(governanceDir, 'roadmap.md');
611
626
  const filtered = tasks
612
627
  .filter((task) => (status ? task.status === status : true))
613
628
  .slice(0, limit ?? 100);
@@ -617,52 +632,54 @@ export function registerTaskTools(server) {
617
632
  {
618
633
  code: TASK_LINT_CODES.FILTER_EMPTY,
619
634
  message: `No tasks matched status=${status}.`,
620
- fixHint: "Confirm status values or update task states.",
635
+ fixHint: 'Confirm status values or update task states.',
621
636
  },
622
637
  ]);
623
638
  }
624
639
  const nextTaskId = filtered[0]?.id;
625
640
  const markdown = renderToolResponseMarkdown({
626
- toolName: "taskList",
641
+ toolName: 'taskList',
627
642
  sections: [
628
643
  summarySection([
629
644
  `- projectPath: ${normalizedProjectPath}`,
630
645
  `- governanceDir: ${governanceDir}`,
631
- `- filter.status: ${status ?? "(none)"}`,
646
+ `- tasksView: ${tasksViewPath}`,
647
+ `- roadmapView: ${roadmapViewPath}`,
648
+ `- filter.status: ${status ?? '(none)'}`,
632
649
  `- returned: ${filtered.length}`,
633
650
  ]),
634
651
  evidenceSection([
635
- "- tasks:",
636
- ...filtered.map((task) => `- ${task.id} | ${task.status} | ${task.title} | owner=${task.owner || ""} | updatedAt=${task.updatedAt}`),
652
+ '- tasks:',
653
+ ...filtered.map((task) => `- ${task.id} | ${task.status} | ${task.title} | owner=${task.owner || ''} | updatedAt=${task.updatedAt}`),
637
654
  ]),
638
- guidanceSection(["- Pick one task ID and call `taskContext`."]),
655
+ guidanceSection(['- Pick one task ID and call `taskContext`.']),
639
656
  lintSection(lintSuggestions),
640
657
  nextCallSection(nextTaskId
641
- ? `taskContext(projectPath=\"${toProjectPath(governanceDir)}\", taskId=\"${nextTaskId}\")`
658
+ ? `taskContext(projectPath="${toProjectPath(governanceDir)}", taskId="${nextTaskId}")`
642
659
  : undefined),
643
660
  ],
644
661
  });
645
662
  return asText(markdown);
646
663
  });
647
- server.registerTool("taskCreate", {
648
- title: "Task Create",
649
- description: "Create a new task in governance store with stable TASK-xxxx ID",
664
+ server.registerTool('taskCreate', {
665
+ title: 'Task Create',
666
+ description: 'Create a new task in governance store with stable TASK-<number> ID',
650
667
  inputSchema: {
651
668
  projectPath: z.string(),
652
- taskId: z.string(),
669
+ taskId: z.string().optional(),
653
670
  title: z.string(),
654
- status: z.enum(["TODO", "IN_PROGRESS", "BLOCKED", "DONE"]).optional(),
671
+ status: z.enum(['TODO', 'IN_PROGRESS', 'BLOCKED', 'DONE']).optional(),
655
672
  owner: z.string().optional(),
656
673
  summary: z.string().optional(),
657
674
  roadmapRefs: z.array(z.string()).optional(),
658
675
  links: z.array(z.string()).optional(),
659
676
  subState: z.object({
660
- phase: z.enum(["discovery", "design", "implementation", "testing"]).optional(),
677
+ phase: z.enum(['discovery', 'design', 'implementation', 'testing']).optional(),
661
678
  confidence: z.number().min(0).max(1).optional(),
662
679
  estimatedCompletion: z.string().optional(),
663
680
  }).optional(),
664
681
  blocker: z.object({
665
- type: z.enum(["internal_dependency", "external_dependency", "resource", "approval"]),
682
+ type: z.enum(['internal_dependency', 'external_dependency', 'resource', 'approval']),
666
683
  description: z.string(),
667
684
  blockingEntity: z.string().optional(),
668
685
  unblockCondition: z.string().optional(),
@@ -670,26 +687,28 @@ export function registerTaskTools(server) {
670
687
  }).optional(),
671
688
  },
672
689
  }, async ({ projectPath, taskId, title, status, owner, summary, roadmapRefs, links, subState, blocker }) => {
673
- if (!isValidTaskId(taskId)) {
690
+ if (taskId && !isValidTaskId(taskId)) {
674
691
  return {
675
- ...asText(renderErrorMarkdown("taskCreate", `Invalid task ID format: ${taskId}`, ["expected format: TASK-0001", "retry with a valid task ID"], `taskCreate(projectPath=\"${projectPath}\", taskId=\"TASK-0001\", title=\"Define executable objective\")`)),
692
+ ...asText(renderErrorMarkdown('taskCreate', `Invalid task ID format: ${taskId}`, ['expected format: TASK-1 or TASK-0001', 'omit taskId to auto-generate next ID'], `taskCreate(projectPath="${projectPath}", title="Define executable objective")`)),
676
693
  isError: true,
677
694
  };
678
695
  }
679
696
  const governanceDir = await resolveGovernanceDir(projectPath);
680
697
  const normalizedProjectPath = toProjectPath(governanceDir);
681
- const { tasksPath, tasks } = await loadTasksDocument(governanceDir);
682
- const duplicated = tasks.some((item) => item.id === taskId);
698
+ const { tasksPath, tasks, markdownPath: tasksViewPath } = await loadTasksDocument(governanceDir);
699
+ const roadmapViewPath = path.join(governanceDir, 'roadmap.md');
700
+ const finalTaskId = taskId ?? nextTaskId(tasks);
701
+ const duplicated = tasks.some((item) => item.id === finalTaskId);
683
702
  if (duplicated) {
684
703
  return {
685
- ...asText(renderErrorMarkdown("taskCreate", `Task already exists: ${taskId}`, ["task IDs must be unique", "use taskUpdate for existing tasks"], `taskUpdate(projectPath=\"${normalizedProjectPath}\", taskId=\"${taskId}\", updates={...})`)),
704
+ ...asText(renderErrorMarkdown('taskCreate', `Task already exists: ${finalTaskId}`, ['task IDs must be unique', 'use taskUpdate for existing tasks'], `taskUpdate(projectPath="${normalizedProjectPath}", taskId="${finalTaskId}", updates={...})`)),
686
705
  isError: true,
687
706
  };
688
707
  }
689
708
  const createdTask = normalizeTask({
690
- id: taskId,
709
+ id: finalTaskId,
691
710
  title,
692
- status: status ?? "TODO",
711
+ status: status ?? 'TODO',
693
712
  owner,
694
713
  summary,
695
714
  roadmapRefs,
@@ -705,36 +724,38 @@ export function registerTaskTools(server) {
705
724
  ...(await collectTaskFileLintSuggestions(governanceDir, createdTask)),
706
725
  ];
707
726
  const markdown = renderToolResponseMarkdown({
708
- toolName: "taskCreate",
727
+ toolName: 'taskCreate',
709
728
  sections: [
710
729
  summarySection([
711
730
  `- projectPath: ${normalizedProjectPath}`,
712
731
  `- governanceDir: ${governanceDir}`,
732
+ `- tasksView: ${tasksViewPath}`,
733
+ `- roadmapView: ${roadmapViewPath}`,
713
734
  `- taskId: ${createdTask.id}`,
714
735
  `- status: ${createdTask.status}`,
715
- `- owner: ${createdTask.owner || "(none)"}`,
736
+ `- owner: ${createdTask.owner || '(none)'}`,
716
737
  `- updatedAt: ${createdTask.updatedAt}`,
717
738
  ]),
718
739
  evidenceSection([
719
- "### Created Task",
740
+ '### Created Task',
720
741
  `- ${createdTask.id} | ${createdTask.status} | ${createdTask.title}`,
721
- `- summary: ${createdTask.summary || "(none)"}`,
722
- `- roadmapRefs: ${createdTask.roadmapRefs.join(", ") || "(none)"}`,
723
- `- links: ${createdTask.links.join(", ") || "(none)"}`,
742
+ `- summary: ${createdTask.summary || '(none)'}`,
743
+ `- roadmapRefs: ${createdTask.roadmapRefs.join(', ') || '(none)'}`,
744
+ `- links: ${createdTask.links.join(', ') || '(none)'}`,
724
745
  ]),
725
746
  guidanceSection([
726
- "Task created in governance store successfully and tasks.md has been synced.",
727
- "Run taskContext to verify references and lint guidance.",
747
+ 'Task created in governance store successfully and tasks.md has been synced.',
748
+ 'Run taskContext to verify references and lint guidance.',
728
749
  ]),
729
750
  lintSection(lintSuggestions),
730
- nextCallSection(`taskContext(projectPath=\"${normalizedProjectPath}\", taskId=\"${createdTask.id}\")`),
751
+ nextCallSection(`taskContext(projectPath="${normalizedProjectPath}", taskId="${createdTask.id}")`),
731
752
  ],
732
753
  });
733
754
  return asText(markdown);
734
755
  });
735
- server.registerTool("taskNext", {
736
- title: "Task Next",
737
- description: "Start here to auto-select the highest-priority actionable task",
756
+ server.registerTool('taskNext', {
757
+ title: 'Task Next',
758
+ description: 'Start here to auto-select the highest-priority actionable task',
738
759
  inputSchema: {
739
760
  limit: z.number().int().min(1).max(20).optional(),
740
761
  },
@@ -745,7 +766,7 @@ export function registerTaskTools(server) {
745
766
  const rankedCandidates = rankActionableTaskCandidates(await readActionableTaskCandidates(projects));
746
767
  if (rankedCandidates.length === 0) {
747
768
  const projectSnapshots = await Promise.all(projects.map(async (governanceDir) => {
748
- const tasksPath = path.join(governanceDir, ".projitive");
769
+ const tasksPath = path.join(governanceDir, '.projitive');
749
770
  await ensureStore(tasksPath);
750
771
  const stats = await loadTaskStatusStatsFromStore(tasksPath);
751
772
  const roadmapIds = await readRoadmapIds(governanceDir);
@@ -760,47 +781,47 @@ export function registerTaskTools(server) {
760
781
  };
761
782
  }));
762
783
  const preferredProject = projectSnapshots[0];
763
- const preferredRoadmapRef = preferredProject?.roadmapIds[0] ?? "ROADMAP-0001";
784
+ const preferredRoadmapRef = preferredProject?.roadmapIds[0] ?? 'ROADMAP-0001';
764
785
  const noTaskDiscoveryGuidance = await resolveNoTaskDiscoveryGuidance(preferredProject?.governanceDir);
765
786
  const markdown = renderToolResponseMarkdown({
766
- toolName: "taskNext",
787
+ toolName: 'taskNext',
767
788
  sections: [
768
789
  summarySection([
769
- `- rootPaths: ${roots.join(", ")}`,
790
+ `- rootPaths: ${roots.join(', ')}`,
770
791
  `- rootCount: ${roots.length}`,
771
792
  `- maxDepth: ${depth}`,
772
793
  `- matchedProjects: ${projects.length}`,
773
- "- actionableTasks: 0",
794
+ '- actionableTasks: 0',
774
795
  ]),
775
796
  evidenceSection([
776
- "### Project Snapshots",
797
+ '### Project Snapshots',
777
798
  ...(projectSnapshots.length > 0
778
- ? projectSnapshots.map((item, index) => `${index + 1}. ${toProjectPath(item.governanceDir)} | total=${item.total} | todo=${item.todo} | in_progress=${item.inProgress} | blocked=${item.blocked} | done=${item.done} | roadmapIds=${item.roadmapIds.join(", ") || "(none)"}`)
779
- : ["- (none)"]),
780
- "",
781
- "### Seed Task Template",
799
+ ? projectSnapshots.map((item, index) => `${index + 1}. ${toProjectPath(item.governanceDir)} | total=${item.total} | todo=${item.todo} | in_progress=${item.inProgress} | blocked=${item.blocked} | done=${item.done} | roadmapIds=${item.roadmapIds.join(', ') || '(none)'}`)
800
+ : ['- (none)']),
801
+ '',
802
+ '### Seed Task Template',
782
803
  ...renderTaskSeedTemplate(preferredRoadmapRef),
783
804
  ]),
784
805
  guidanceSection([
785
- "- No TODO/IN_PROGRESS task is available.",
786
- "- Create 1-3 new TODO tasks using `taskCreate(...)` from active roadmap slices.",
787
- "- Use no-task discovery checklist below to proactively find and create meaningful TODO tasks.",
788
- "- If roadmap has active milestones, analyze milestone intent and split into 1-3 executable TODO tasks.",
789
- "",
790
- "### No-Task Discovery Checklist",
806
+ '- No TODO/IN_PROGRESS task is available.',
807
+ '- Create 1-3 new TODO tasks using `taskCreate(...)` from active roadmap slices.',
808
+ '- Use no-task discovery checklist below to proactively find and create meaningful TODO tasks.',
809
+ '- If roadmap has active milestones, analyze milestone intent and split into 1-3 executable TODO tasks.',
810
+ '',
811
+ '### No-Task Discovery Checklist',
791
812
  ...noTaskDiscoveryGuidance,
792
- "",
793
- "- If no tasks exist, derive 1-3 TODO tasks from roadmap milestones, README scope, or unresolved report gaps.",
794
- "- If only BLOCKED/DONE tasks exist, reopen one blocked item or create a follow-up TODO task.",
795
- "- After creating tasks, rerun `taskNext` to re-rank actionable work.",
813
+ '',
814
+ '- If no tasks exist, derive 1-3 TODO tasks from roadmap milestones, README scope, or unresolved report gaps.',
815
+ '- If only BLOCKED/DONE tasks exist, reopen one blocked item or create a follow-up TODO task.',
816
+ '- After creating tasks, rerun `taskNext` to re-rank actionable work.',
796
817
  ]),
797
818
  lintSection([
798
- "- No actionable tasks found. Verify task statuses and required fields in .projitive task table.",
799
- "- Ensure each new task has stable TASK-xxxx ID and at least one roadmapRefs item.",
819
+ '- No actionable tasks found. Verify task statuses and required fields in .projitive task table.',
820
+ '- Ensure each new task has stable TASK-<number> ID and at least one roadmapRefs item.',
800
821
  ]),
801
822
  nextCallSection(preferredProject
802
- ? `taskCreate(projectPath=\"${toProjectPath(preferredProject.governanceDir)}\", taskId=\"TASK-0001\", title=\"Create first executable slice\", roadmapRefs=[\"${preferredRoadmapRef}\"], summary=\"Derived from active roadmap milestone\")`
803
- : "projectScan()"),
823
+ ? `taskCreate(projectPath="${toProjectPath(preferredProject.governanceDir)}", title="Create first executable slice", roadmapRefs=["${preferredRoadmapRef}"], summary="Derived from active roadmap milestone")`
824
+ : 'projectScan()'),
804
825
  ],
805
826
  });
806
827
  return asText(markdown);
@@ -816,10 +837,10 @@ export function registerTaskTools(server) {
816
837
  const suggestedReadOrder = [selectedTaskDocument.markdownPath, ...relatedArtifacts.filter((item) => item !== selectedTaskDocument.markdownPath)];
817
838
  const candidateLimit = limit ?? 5;
818
839
  const markdown = renderToolResponseMarkdown({
819
- toolName: "taskNext",
840
+ toolName: 'taskNext',
820
841
  sections: [
821
842
  summarySection([
822
- `- rootPaths: ${roots.join(", ")}`,
843
+ `- rootPaths: ${roots.join(', ')}`,
823
844
  `- rootCount: ${roots.length}`,
824
845
  `- maxDepth: ${depth}`,
825
846
  `- matchedProjects: ${projects.length}`,
@@ -829,48 +850,48 @@ export function registerTaskTools(server) {
829
850
  `- selectedTaskStatus: ${selected.task.status}`,
830
851
  ]),
831
852
  evidenceSection([
832
- "### Selected Task",
853
+ '### Selected Task',
833
854
  `- id: ${selected.task.id}`,
834
855
  `- title: ${selected.task.title}`,
835
- `- owner: ${selected.task.owner || "(none)"}`,
856
+ `- owner: ${selected.task.owner || '(none)'}`,
836
857
  `- updatedAt: ${selected.task.updatedAt}`,
837
- `- roadmapRefs: ${selected.task.roadmapRefs.join(", ") || "(none)"}`,
858
+ `- roadmapRefs: ${selected.task.roadmapRefs.join(', ') || '(none)'}`,
838
859
  `- taskLocation: ${taskLocation ? `${taskLocation.filePath}#L${taskLocation.line}` : selectedTaskDocument.markdownPath}`,
839
- "",
840
- "### Top Candidates",
860
+ '',
861
+ '### Top Candidates',
841
862
  ...rankedCandidates
842
863
  .slice(0, candidateLimit)
843
864
  .map((item, index) => `${index + 1}. ${item.task.id} | ${item.task.status} | ${item.task.title} | projectPath=${toProjectPath(item.governanceDir)} | projectScore=${item.projectScore} | latest=${item.projectLatestUpdatedAt}`),
844
- "",
845
- "### Selection Reason",
846
- "- Rank rule: projectScore DESC -> taskPriority DESC -> taskUpdatedAt DESC.",
865
+ '',
866
+ '### Selection Reason',
867
+ '- Rank rule: projectScore DESC -> taskPriority DESC -> taskUpdatedAt DESC.',
847
868
  `- Selected candidate scores: projectScore=${selected.projectScore}, taskPriority=${selected.taskPriority}, taskUpdatedAtMs=${selected.taskUpdatedAtMs}.`,
848
- "",
849
- "### Related Artifacts",
850
- ...(relatedArtifacts.length > 0 ? relatedArtifacts.map((file) => `- ${file}`) : ["- (none)"]),
851
- "",
852
- "### Reference Locations",
869
+ '',
870
+ '### Related Artifacts',
871
+ ...(relatedArtifacts.length > 0 ? relatedArtifacts.map((file) => `- ${file}`) : ['- (none)']),
872
+ '',
873
+ '### Reference Locations',
853
874
  ...(referenceLocations.length > 0
854
875
  ? referenceLocations.map((item) => `- ${item.filePath}#L${item.line}: ${item.text}`)
855
- : ["- (none)"]),
856
- "",
857
- "### Suggested Read Order",
876
+ : ['- (none)']),
877
+ '',
878
+ '### Suggested Read Order',
858
879
  ...suggestedReadOrder.map((item, index) => `${index + 1}. ${item}`),
859
880
  ]),
860
881
  guidanceSection([
861
- "- Start immediately with Suggested Read Order and execute the selected task.",
862
- "- Update markdown artifacts directly while keeping TASK/ROADMAP IDs unchanged.",
863
- "- Re-run `taskContext` for the selectedTaskId after edits to verify evidence consistency.",
882
+ '- Start immediately with Suggested Read Order and execute the selected task.',
883
+ '- Update markdown artifacts directly while keeping TASK/ROADMAP IDs unchanged.',
884
+ '- Re-run `taskContext` for the selectedTaskId after edits to verify evidence consistency.',
864
885
  ]),
865
886
  lintSection(lintSuggestions),
866
- nextCallSection(`taskContext(projectPath=\"${toProjectPath(selected.governanceDir)}\", taskId=\"${selected.task.id}\")`),
887
+ nextCallSection(`taskContext(projectPath="${toProjectPath(selected.governanceDir)}", taskId="${selected.task.id}")`),
867
888
  ],
868
889
  });
869
890
  return asText(markdown);
870
891
  });
871
- server.registerTool("taskContext", {
872
- title: "Task Context",
873
- description: "Get deep context, evidence links, and read order for one task",
892
+ server.registerTool('taskContext', {
893
+ title: 'Task Context',
894
+ description: 'Get deep context, evidence links, and read order for one task',
874
895
  inputSchema: {
875
896
  projectPath: z.string(),
876
897
  taskId: z.string(),
@@ -878,17 +899,18 @@ export function registerTaskTools(server) {
878
899
  }, async ({ projectPath, taskId }) => {
879
900
  if (!isValidTaskId(taskId)) {
880
901
  return {
881
- ...asText(renderErrorMarkdown("taskContext", `Invalid task ID format: ${taskId}`, ["expected format: TASK-0001", "retry with a valid task ID"], `taskContext(projectPath=\"${projectPath}\", taskId=\"TASK-0001\")`)),
902
+ ...asText(renderErrorMarkdown('taskContext', `Invalid task ID format: ${taskId}`, ['expected format: TASK-1 or TASK-0001', 'retry with a valid task ID'], `taskContext(projectPath="${projectPath}", taskId="TASK-0001")`)),
882
903
  isError: true,
883
904
  };
884
905
  }
885
906
  const governanceDir = await resolveGovernanceDir(projectPath);
886
907
  const normalizedProjectPath = toProjectPath(governanceDir);
887
- const { markdownPath, tasks, markdown: tasksMarkdown } = await loadTasksDocument(governanceDir);
908
+ const { markdownPath, tasks } = await loadTasksDocument(governanceDir);
909
+ const roadmapViewPath = path.join(governanceDir, 'roadmap.md');
888
910
  const task = tasks.find((item) => item.id === taskId);
889
911
  if (!task) {
890
912
  return {
891
- ...asText(renderErrorMarkdown("taskContext", `Task not found: ${taskId}`, ["run `taskList` to discover available IDs", "retry with an existing task ID"], `taskList(projectPath=\"${toProjectPath(governanceDir)}\")`)),
913
+ ...asText(renderErrorMarkdown('taskContext', `Task not found: ${taskId}`, ['run `taskList` to discover available IDs', 'retry with an existing task ID'], `taskList(projectPath="${toProjectPath(governanceDir)}")`)),
892
914
  isError: true,
893
915
  };
894
916
  }
@@ -907,21 +929,23 @@ export function registerTaskTools(server) {
907
929
  const summaryLines = [
908
930
  `- projectPath: ${normalizedProjectPath}`,
909
931
  `- governanceDir: ${governanceDir}`,
932
+ `- tasksView: ${markdownPath}`,
933
+ `- roadmapView: ${roadmapViewPath}`,
910
934
  `- taskId: ${task.id}`,
911
935
  `- title: ${task.title}`,
912
936
  `- status: ${task.status}`,
913
937
  `- owner: ${task.owner}`,
914
938
  `- updatedAt: ${task.updatedAt}`,
915
- `- roadmapRefs: ${task.roadmapRefs.join(", ") || "(none)"}`,
939
+ `- roadmapRefs: ${task.roadmapRefs.join(', ') || '(none)'}`,
916
940
  `- taskLocation: ${taskLocation ? `${taskLocation.filePath}#L${taskLocation.line}` : markdownPath}`,
917
941
  ];
918
942
  // Add subState info for IN_PROGRESS tasks (v1.1.0)
919
- if (task.subState && task.status === "IN_PROGRESS") {
920
- summaryLines.push(`- subState:`);
943
+ if (task.subState && task.status === 'IN_PROGRESS') {
944
+ summaryLines.push('- subState:');
921
945
  if (task.subState.phase) {
922
946
  summaryLines.push(` - phase: ${task.subState.phase}`);
923
947
  }
924
- if (typeof task.subState.confidence === "number") {
948
+ if (typeof task.subState.confidence === 'number') {
925
949
  summaryLines.push(` - confidence: ${task.subState.confidence}`);
926
950
  }
927
951
  if (task.subState.estimatedCompletion) {
@@ -929,8 +953,8 @@ export function registerTaskTools(server) {
929
953
  }
930
954
  }
931
955
  // Add blocker info for BLOCKED tasks (v1.1.0)
932
- if (task.blocker && task.status === "BLOCKED") {
933
- summaryLines.push(`- blocker:`);
956
+ if (task.blocker && task.status === 'BLOCKED') {
957
+ summaryLines.push('- blocker:');
934
958
  summaryLines.push(` - type: ${task.blocker.type}`);
935
959
  summaryLines.push(` - description: ${task.blocker.description}`);
936
960
  if (task.blocker.blockingEntity) {
@@ -944,58 +968,58 @@ export function registerTaskTools(server) {
944
968
  }
945
969
  }
946
970
  const coreMarkdown = renderToolResponseMarkdown({
947
- toolName: "taskContext",
971
+ toolName: 'taskContext',
948
972
  sections: [
949
973
  summarySection(summaryLines),
950
974
  evidenceSection([
951
- "### Related Artifacts",
952
- ...(relatedArtifacts.length > 0 ? relatedArtifacts.map((file) => `- ${file}`) : ["- (none)"]),
953
- "",
954
- "### Reference Locations",
975
+ '### Related Artifacts',
976
+ ...(relatedArtifacts.length > 0 ? relatedArtifacts.map((file) => `- ${file}`) : ['- (none)']),
977
+ '',
978
+ '### Reference Locations',
955
979
  ...(referenceLocations.length > 0
956
980
  ? referenceLocations.map((item) => `- ${item.filePath}#L${item.line}: ${item.text}`)
957
- : ["- (none)"]),
958
- "",
959
- "### Suggested Read Order",
981
+ : ['- (none)']),
982
+ '',
983
+ '### Suggested Read Order',
960
984
  ...suggestedReadOrder.map((item, index) => `${index + 1}. ${item}`),
961
985
  ]),
962
986
  guidanceSection([
963
- "- Read the files in Suggested Read Order.",
964
- "",
965
- "### Recommended Context Reading",
987
+ '- Read the files in Suggested Read Order.',
988
+ '',
989
+ '### Recommended Context Reading',
966
990
  ...contextReadingGuidance,
967
- "",
968
- "- Verify whether current status and evidence are consistent.",
991
+ '',
992
+ '- Verify whether current status and evidence are consistent.',
969
993
  ...taskStatusGuidance(task),
970
- "- If updates are needed, use tool writes for governance store (`taskUpdate` / `roadmapUpdate`) and keep TASK IDs unchanged.",
971
- "- After editing, re-run `taskContext` to verify references and context consistency.",
994
+ '- If updates are needed, use tool writes for governance store (`taskUpdate` / `roadmapUpdate`) and keep TASK IDs unchanged.',
995
+ '- After editing, re-run `taskContext` to verify references and context consistency.',
972
996
  ]),
973
997
  lintSection(lintSuggestions),
974
- nextCallSection(`taskContext(projectPath=\"${toProjectPath(governanceDir)}\", taskId=\"${task.id}\")`),
998
+ nextCallSection(`taskContext(projectPath="${toProjectPath(governanceDir)}", taskId="${task.id}")`),
975
999
  ],
976
1000
  });
977
1001
  return asText(coreMarkdown);
978
1002
  });
979
1003
  // taskUpdate tool - Update task fields including subState and blocker (Spec v1.1.0)
980
- server.registerTool("taskUpdate", {
981
- title: "Task Update",
982
- description: "Update task fields including status, owner, summary, subState, and blocker metadata",
1004
+ server.registerTool('taskUpdate', {
1005
+ title: 'Task Update',
1006
+ description: 'Update task fields including status, owner, summary, subState, and blocker metadata',
983
1007
  inputSchema: {
984
1008
  projectPath: z.string(),
985
1009
  taskId: z.string(),
986
1010
  updates: z.object({
987
- status: z.enum(["TODO", "IN_PROGRESS", "BLOCKED", "DONE"]).optional(),
1011
+ status: z.enum(['TODO', 'IN_PROGRESS', 'BLOCKED', 'DONE']).optional(),
988
1012
  owner: z.string().optional(),
989
1013
  summary: z.string().optional(),
990
1014
  roadmapRefs: z.array(z.string()).optional(),
991
1015
  links: z.array(z.string()).optional(),
992
1016
  subState: z.object({
993
- phase: z.enum(["discovery", "design", "implementation", "testing"]).optional(),
1017
+ phase: z.enum(['discovery', 'design', 'implementation', 'testing']).optional(),
994
1018
  confidence: z.number().min(0).max(1).optional(),
995
1019
  estimatedCompletion: z.string().optional(),
996
1020
  }).optional(),
997
1021
  blocker: z.object({
998
- type: z.enum(["internal_dependency", "external_dependency", "resource", "approval"]),
1022
+ type: z.enum(['internal_dependency', 'external_dependency', 'resource', 'approval']),
999
1023
  description: z.string(),
1000
1024
  blockingEntity: z.string().optional(),
1001
1025
  unblockCondition: z.string().optional(),
@@ -1006,17 +1030,19 @@ export function registerTaskTools(server) {
1006
1030
  }, async ({ projectPath, taskId, updates }) => {
1007
1031
  if (!isValidTaskId(taskId)) {
1008
1032
  return {
1009
- ...asText(renderErrorMarkdown("taskUpdate", `Invalid task ID format: ${taskId}`, ["expected format: TASK-0001", "retry with a valid task ID"], `taskUpdate(projectPath=\"${projectPath}\", taskId=\"TASK-0001\", updates={...})`)),
1033
+ ...asText(renderErrorMarkdown('taskUpdate', `Invalid task ID format: ${taskId}`, ['expected format: TASK-1 or TASK-0001', 'retry with a valid task ID'], `taskUpdate(projectPath="${projectPath}", taskId="TASK-0001", updates={...})`)),
1010
1034
  isError: true,
1011
1035
  };
1012
1036
  }
1013
1037
  const governanceDir = await resolveGovernanceDir(projectPath);
1014
1038
  const normalizedProjectPath = toProjectPath(governanceDir);
1015
1039
  const { tasksPath, tasks } = await loadTasksDocument(governanceDir);
1040
+ const tasksViewPath = path.join(governanceDir, TASKS_MARKDOWN_FILE);
1041
+ const roadmapViewPath = path.join(governanceDir, 'roadmap.md');
1016
1042
  const taskIndex = tasks.findIndex((item) => item.id === taskId);
1017
1043
  if (taskIndex === -1) {
1018
1044
  return {
1019
- ...asText(renderErrorMarkdown("taskUpdate", `Task not found: ${taskId}`, ["run `taskList` to discover available IDs", "retry with an existing task ID"], `taskList(projectPath=\"${toProjectPath(governanceDir)}\")`)),
1045
+ ...asText(renderErrorMarkdown('taskUpdate', `Task not found: ${taskId}`, ['run `taskList` to discover available IDs', 'retry with an existing task ID'], `taskList(projectPath="${toProjectPath(governanceDir)}")`)),
1020
1046
  isError: true,
1021
1047
  };
1022
1048
  }
@@ -1025,7 +1051,7 @@ export function registerTaskTools(server) {
1025
1051
  // Validate status transition
1026
1052
  if (updates.status && !validateTransition(originalStatus, updates.status)) {
1027
1053
  return {
1028
- ...asText(renderErrorMarkdown("taskUpdate", `Invalid status transition: ${originalStatus} -> ${updates.status}`, ["use `validateTransition` to check allowed transitions", "provide evidence when transitioning to DONE"], `taskContext(projectPath=\"${toProjectPath(governanceDir)}\", taskId=\"${taskId}\")`)),
1054
+ ...asText(renderErrorMarkdown('taskUpdate', `Invalid status transition: ${originalStatus} -> ${updates.status}`, ['use `validateTransition` to check allowed transitions', 'provide evidence when transitioning to DONE'], `taskContext(projectPath="${toProjectPath(governanceDir)}", taskId="${taskId}")`)),
1029
1055
  isError: true,
1030
1056
  };
1031
1057
  }
@@ -1079,22 +1105,24 @@ export function registerTaskTools(server) {
1079
1105
  const updateSummary = [
1080
1106
  `- projectPath: ${normalizedProjectPath}`,
1081
1107
  `- governanceDir: ${governanceDir}`,
1108
+ `- tasksView: ${tasksViewPath}`,
1109
+ `- roadmapView: ${roadmapViewPath}`,
1082
1110
  `- taskId: ${taskId}`,
1083
1111
  `- originalStatus: ${originalStatus}`,
1084
1112
  `- newStatus: ${task.status}`,
1085
1113
  `- updatedAt: ${task.updatedAt}`,
1086
1114
  ];
1087
1115
  if (task.subState) {
1088
- updateSummary.push(`- subState:`);
1116
+ updateSummary.push('- subState:');
1089
1117
  if (task.subState.phase)
1090
1118
  updateSummary.push(` - phase: ${task.subState.phase}`);
1091
- if (typeof task.subState.confidence === "number")
1119
+ if (typeof task.subState.confidence === 'number')
1092
1120
  updateSummary.push(` - confidence: ${task.subState.confidence}`);
1093
1121
  if (task.subState.estimatedCompletion)
1094
1122
  updateSummary.push(` - estimatedCompletion: ${task.subState.estimatedCompletion}`);
1095
1123
  }
1096
1124
  if (task.blocker) {
1097
- updateSummary.push(`- blocker:`);
1125
+ updateSummary.push('- blocker:');
1098
1126
  updateSummary.push(` - type: ${task.blocker.type}`);
1099
1127
  updateSummary.push(` - description: ${task.blocker.description}`);
1100
1128
  if (task.blocker.blockingEntity)
@@ -1105,32 +1133,32 @@ export function registerTaskTools(server) {
1105
1133
  updateSummary.push(` - escalationPath: ${task.blocker.escalationPath}`);
1106
1134
  }
1107
1135
  const markdown = renderToolResponseMarkdown({
1108
- toolName: "taskUpdate",
1136
+ toolName: 'taskUpdate',
1109
1137
  sections: [
1110
1138
  summarySection(updateSummary),
1111
1139
  evidenceSection([
1112
- "### Updated Task",
1140
+ '### Updated Task',
1113
1141
  `- ${task.id} | ${task.status} | ${task.title}`,
1114
- `- owner: ${task.owner || "(none)"}`,
1115
- `- summary: ${task.summary || "(none)"}`,
1116
- "",
1117
- "### Update Details",
1142
+ `- owner: ${task.owner || '(none)'}`,
1143
+ `- summary: ${task.summary || '(none)'}`,
1144
+ '',
1145
+ '### Update Details',
1118
1146
  ...(updates.status ? [`- status: ${originalStatus} → ${updates.status}`] : []),
1119
1147
  ...(updates.owner !== undefined ? [`- owner: ${updates.owner}`] : []),
1120
1148
  ...(updates.summary !== undefined ? [`- summary: ${updates.summary}`] : []),
1121
- ...(updates.roadmapRefs ? [`- roadmapRefs: ${updates.roadmapRefs.join(", ")}`] : []),
1122
- ...(updates.links ? [`- links: ${updates.links.join(", ")}`] : []),
1149
+ ...(updates.roadmapRefs ? [`- roadmapRefs: ${updates.roadmapRefs.join(', ')}`] : []),
1150
+ ...(updates.links ? [`- links: ${updates.links.join(', ')}`] : []),
1123
1151
  ...(updates.subState ? [`- subState: ${JSON.stringify(updates.subState)}`] : []),
1124
1152
  ...(updates.blocker ? [`- blocker: ${JSON.stringify(updates.blocker)}`] : []),
1125
1153
  ]),
1126
1154
  guidanceSection([
1127
- "Task updated successfully and tasks.md has been synced. Run `taskContext` to verify the changes.",
1128
- "If status changed to DONE, ensure evidence links are added.",
1129
- "If subState or blocker were updated, verify the metadata is correct.",
1130
- ".projitive governance store is source of truth; tasks.md is a generated view and may be overwritten.",
1155
+ 'Task updated successfully and tasks.md has been synced. Run `taskContext` to verify the changes.',
1156
+ 'If status changed to DONE, ensure evidence links are added.',
1157
+ 'If subState or blocker were updated, verify the metadata is correct.',
1158
+ '.projitive governance store is source of truth; tasks.md is a generated view and may be overwritten.',
1131
1159
  ]),
1132
1160
  lintSection([]),
1133
- nextCallSection(`taskContext(projectPath=\"${toProjectPath(governanceDir)}\", taskId=\"${taskId}\")`),
1161
+ nextCallSection(`taskContext(projectPath="${toProjectPath(governanceDir)}", taskId="${taskId}")`),
1134
1162
  ],
1135
1163
  });
1136
1164
  return asText(markdown);