@projitive/mcp 2.1.1 → 2.1.3

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.
@@ -1,14 +1,16 @@
1
1
  import fs from 'node:fs/promises';
2
+ import os from 'node:os';
2
3
  import path from 'node:path';
3
4
  import process from 'node:process';
4
5
  import { z } from 'zod';
5
- import { discoverGovernanceArtifacts, catchIt, PROJECT_LINT_CODES, renderLintSuggestions, ensureStore, replaceRoadmapsInStore, replaceTasksInStore, loadTaskStatusStatsFromStore, createGovernedTool, getDefaultToolTemplateMarkdown, } from '../common/index.js';
6
- import { collectTaskLintSuggestions, loadTasksDocument, loadTasksDocumentWithOptions, renderTasksMarkdown } from './task.js';
6
+ import { candidateFilesFromArtifacts, discoverGovernanceArtifacts, catchIt, PROJECT_LINT_CODES, renderLintSuggestions, ensureStore, loadRoadmapsFromStore, loadTasksFromStore, replaceRoadmapsInStore, replaceTasksInStore, loadTaskStatusStatsFromStore, upsertRoadmapInStore, upsertTaskInStore, createGovernedTool, getDefaultToolTemplateMarkdown, } from '../common/index.js';
7
+ import { collectProjectContextDocsLintSuggestions, collectTaskLintSuggestions, CORE_ARCHITECTURE_DOC_FILE, CORE_CODE_STYLE_DOC_FILE, CORE_UI_STYLE_DOC_FILE, inspectProjectContextDocsFromArtifacts, loadTasksDocument, loadTasksDocumentWithOptions, renderTasksMarkdown, } from './task.js';
7
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']);
11
12
  const MAX_SCAN_DEPTH = 8;
13
+ const DEFAULT_SCAN_DEPTH = 3;
12
14
  function normalizePath(inputPath) {
13
15
  return inputPath ? path.resolve(inputPath) : process.cwd();
14
16
  }
@@ -32,13 +34,6 @@ function parseDepthFromEnv(rawDepth) {
32
34
  }
33
35
  return Math.min(MAX_SCAN_DEPTH, Math.max(0, parsed));
34
36
  }
35
- function requireEnvVar(name) {
36
- const value = process.env[name];
37
- if (typeof value !== 'string' || value.trim().length === 0) {
38
- throw new Error(`Missing required environment variable: ${name}`);
39
- }
40
- return value.trim();
41
- }
42
37
  function normalizeScanRoots(rootPaths) {
43
38
  const normalized = rootPaths
44
39
  .map((entry) => entry.trim())
@@ -72,21 +67,24 @@ export function resolveScanRoots(inputPaths) {
72
67
  if (rootsFromLegacyEnv.length > 0) {
73
68
  return rootsFromLegacyEnv;
74
69
  }
75
- throw new Error('Missing required environment variable: PROJITIVE_SCAN_ROOT_PATHS (or legacy PROJITIVE_SCAN_ROOT_PATH)');
70
+ return [os.homedir()];
76
71
  }
77
72
  export function resolveScanRoot(inputPath) {
78
73
  return resolveScanRoots(inputPath ? [inputPath] : undefined)[0];
79
74
  }
80
75
  export function resolveScanDepth(inputDepth) {
81
- const configuredDepthRaw = requireEnvVar('PROJITIVE_SCAN_MAX_DEPTH');
82
- const configuredDepth = parseDepthFromEnv(configuredDepthRaw);
83
- if (typeof configuredDepth !== 'number') {
84
- throw new Error('Invalid PROJITIVE_SCAN_MAX_DEPTH: expected integer in range 0-8');
85
- }
86
76
  if (typeof inputDepth === 'number') {
87
77
  return inputDepth;
88
78
  }
89
- return configuredDepth;
79
+ const configuredDepthRaw = process.env.PROJITIVE_SCAN_MAX_DEPTH;
80
+ if (typeof configuredDepthRaw === 'string' && configuredDepthRaw.trim().length > 0) {
81
+ const configuredDepth = parseDepthFromEnv(configuredDepthRaw);
82
+ if (typeof configuredDepth !== 'number') {
83
+ throw new Error('Invalid PROJITIVE_SCAN_MAX_DEPTH: expected integer in range 0-8');
84
+ }
85
+ return configuredDepth;
86
+ }
87
+ return DEFAULT_SCAN_DEPTH;
90
88
  }
91
89
  function renderArtifactsMarkdown(artifacts) {
92
90
  const rows = artifacts.map((item) => {
@@ -272,34 +270,261 @@ function defaultReadmeMarkdown(governanceDirName) {
272
270
  '- Treat roadmap.md/tasks.md as generated views from governance store.',
273
271
  '- Keep IDs stable (TASK-xxxx / ROADMAP-xxxx).',
274
272
  '- Update report evidence before status transitions.',
273
+ '- Maintain core docs under designs/core/: architecture.md, code-style.md, ui-style.md.',
274
+ '- After each task completion, review whether architecture, code conventions, or UI rules changed and update the matching core docs.',
275
275
  ].join('\n');
276
276
  }
277
277
  function defaultRoadmapMarkdown(milestones = defaultRoadmapMilestones()) {
278
278
  return renderRoadmapMarkdown(milestones);
279
279
  }
280
280
  function defaultTasksMarkdown(updatedAt = new Date().toISOString()) {
281
- return renderTasksMarkdown([
281
+ return renderTasksMarkdown(defaultBootstrapTasks(updatedAt));
282
+ }
283
+ function defaultBootstrapTaskBlueprints() {
284
+ return [
282
285
  {
283
- id: 'TASK-0001',
284
- title: 'Bootstrap governance workspace',
285
- status: 'TODO',
286
- owner: 'unassigned',
287
- summary: 'Create initial governance artifacts and confirm task execution loop.',
288
- updatedAt,
289
- links: [],
290
- roadmapRefs: ['ROADMAP-0001'],
286
+ title: 'Initialize project architecture document',
287
+ summary: `Establish system context, boundaries, modules, and integration flows in ${CORE_ARCHITECTURE_DOC_FILE}.`,
288
+ links: [CORE_ARCHITECTURE_DOC_FILE],
291
289
  },
292
- ]);
290
+ {
291
+ title: 'Initialize code style document',
292
+ summary: `Capture naming, structure, testing, and review conventions in ${CORE_CODE_STYLE_DOC_FILE}.`,
293
+ links: [CORE_CODE_STYLE_DOC_FILE],
294
+ },
295
+ {
296
+ title: 'Initialize UI style document',
297
+ summary: `Capture visual language, tokens, accessibility, and interaction rules in ${CORE_UI_STYLE_DOC_FILE}.`,
298
+ links: [CORE_UI_STYLE_DOC_FILE],
299
+ },
300
+ ];
293
301
  }
294
302
  function defaultRoadmapMilestones() {
295
303
  return [{
296
304
  id: 'ROADMAP-0001',
297
- title: 'Bootstrap governance baseline',
305
+ title: 'Bootstrap governance and core docs baseline',
298
306
  status: 'active',
299
307
  time: '2026-Q1',
300
308
  updatedAt: new Date().toISOString(),
301
309
  }];
302
310
  }
311
+ function defaultBootstrapTasks(updatedAt = new Date().toISOString()) {
312
+ return defaultBootstrapTaskBlueprints().map((blueprint, index) => ({
313
+ id: `TASK-${String(index + 1).padStart(4, '0')}`,
314
+ title: blueprint.title,
315
+ status: 'TODO',
316
+ owner: 'unassigned',
317
+ summary: blueprint.summary,
318
+ updatedAt,
319
+ links: blueprint.links,
320
+ roadmapRefs: ['ROADMAP-0001'],
321
+ }));
322
+ }
323
+ function nextGeneratedTaskId(taskIds) {
324
+ const maxSuffix = taskIds
325
+ .map((taskId) => /^TASK-(\d+)$/.exec(taskId)?.[1])
326
+ .map((value) => Number.parseInt(value ?? '-1', 10))
327
+ .filter((value) => Number.isFinite(value) && value > 0)
328
+ .reduce((max, value) => Math.max(max, value), 0);
329
+ const next = maxSuffix + 1;
330
+ return `TASK-${String(next).padStart(Math.max(4, String(next).length), '0')}`;
331
+ }
332
+ function buildBackfillTask(blueprint, taskId, roadmapRef) {
333
+ return {
334
+ id: taskId,
335
+ title: blueprint.title,
336
+ status: 'TODO',
337
+ owner: 'unassigned',
338
+ summary: blueprint.summary,
339
+ updatedAt: new Date().toISOString(),
340
+ links: blueprint.links,
341
+ roadmapRefs: [roadmapRef],
342
+ };
343
+ }
344
+ async function inspectProjectInitMissingState(governancePath) {
345
+ const requiredDirectories = [
346
+ governancePath,
347
+ path.join(governancePath, 'designs'),
348
+ path.join(governancePath, 'designs', 'core'),
349
+ path.join(governancePath, 'designs', 'research'),
350
+ path.join(governancePath, 'reports'),
351
+ path.join(governancePath, 'templates'),
352
+ path.join(governancePath, 'templates', 'tools'),
353
+ ];
354
+ const requiredFiles = [
355
+ path.join(governancePath, 'README.md'),
356
+ path.join(governancePath, 'roadmap.md'),
357
+ path.join(governancePath, 'tasks.md'),
358
+ path.join(governancePath, CORE_ARCHITECTURE_DOC_FILE),
359
+ path.join(governancePath, CORE_CODE_STYLE_DOC_FILE),
360
+ path.join(governancePath, CORE_UI_STYLE_DOC_FILE),
361
+ path.join(governancePath, 'templates', 'README.md'),
362
+ ...DEFAULT_TOOL_TEMPLATE_NAMES.map((toolName) => path.join(governancePath, 'templates', 'tools', `${toolName}.md`)),
363
+ ];
364
+ const missingDirectories = (await Promise.all(requiredDirectories.map(async (dirPath) => (await pathExists(dirPath)) ? undefined : dirPath)))
365
+ .filter((item) => item != null);
366
+ const missingFiles = (await Promise.all(requiredFiles.map(async (filePath) => (await pathExists(filePath)) ? undefined : filePath)))
367
+ .filter((item) => item != null);
368
+ const markerPath = path.join(governancePath, PROJECT_MARKER);
369
+ const markerMissing = !(await pathExists(markerPath));
370
+ if (markerMissing) {
371
+ return {
372
+ markerMissing,
373
+ missingDirectories,
374
+ missingFiles,
375
+ missingBootstrapTaskTitles: defaultBootstrapTaskBlueprints().map((item) => item.title),
376
+ missingBootstrapRoadmap: true,
377
+ };
378
+ }
379
+ await ensureStore(markerPath);
380
+ const [tasks, roadmaps] = await Promise.all([
381
+ loadTasksFromStore(markerPath),
382
+ loadRoadmapsFromStore(markerPath),
383
+ ]);
384
+ const missingBootstrapTaskTitles = defaultBootstrapTaskBlueprints()
385
+ .filter((blueprint) => !tasks.some((task) => task.title === blueprint.title || blueprint.links.some((link) => task.links.includes(link))))
386
+ .map((item) => item.title);
387
+ return {
388
+ markerMissing,
389
+ missingDirectories,
390
+ missingFiles,
391
+ missingBootstrapTaskTitles,
392
+ missingBootstrapRoadmap: roadmaps.length === 0,
393
+ };
394
+ }
395
+ async function backfillBootstrapTasks(markerPath) {
396
+ await ensureStore(markerPath);
397
+ const [tasks, roadmaps] = await Promise.all([
398
+ loadTasksFromStore(markerPath),
399
+ loadRoadmapsFromStore(markerPath),
400
+ ]);
401
+ let createdBootstrapRoadmapId;
402
+ let roadmapRef = roadmaps[0]?.id;
403
+ if (!roadmapRef) {
404
+ const milestone = defaultRoadmapMilestones()[0];
405
+ await upsertRoadmapInStore(markerPath, milestone);
406
+ createdBootstrapRoadmapId = milestone.id;
407
+ roadmapRef = milestone.id;
408
+ }
409
+ const mutableTasks = [...tasks];
410
+ const createdBootstrapTaskIds = [];
411
+ for (const blueprint of defaultBootstrapTaskBlueprints()) {
412
+ const exists = mutableTasks.some((task) => task.title === blueprint.title || blueprint.links.some((link) => task.links.includes(link)));
413
+ if (exists) {
414
+ continue;
415
+ }
416
+ const nextTaskId = nextGeneratedTaskId(mutableTasks.map((task) => task.id));
417
+ const task = buildBackfillTask(blueprint, nextTaskId, roadmapRef);
418
+ await upsertTaskInStore(markerPath, task);
419
+ mutableTasks.push(task);
420
+ createdBootstrapTaskIds.push(nextTaskId);
421
+ }
422
+ return { createdBootstrapTaskIds, createdBootstrapRoadmapId };
423
+ }
424
+ function defaultProjectArchitectureMarkdown() {
425
+ return [
426
+ '# Project Architecture',
427
+ '',
428
+ '## Mission and Scope',
429
+ '- Describe the product or repository purpose.',
430
+ '- Define the operational boundary of this project.',
431
+ '',
432
+ '## System Boundaries',
433
+ '- List primary inputs, outputs, external integrations, and ownership boundaries.',
434
+ '',
435
+ '## Modules and Responsibilities',
436
+ '- Document the major modules, packages, or services and their responsibilities.',
437
+ '',
438
+ '## Key Flows',
439
+ '- Summarize the highest-value runtime and maintenance flows.',
440
+ '',
441
+ '## Change Triggers',
442
+ '- Update this document when tasks change architecture boundaries, data flow, or integration contracts.',
443
+ ].join('\n');
444
+ }
445
+ function defaultCodeStyleMarkdown() {
446
+ return [
447
+ '# Code Style',
448
+ '',
449
+ '## Core Principles',
450
+ '- Document the repository coding principles and non-negotiable constraints.',
451
+ '',
452
+ '## Naming and Structure',
453
+ '- Define naming rules, module boundaries, and file organization expectations.',
454
+ '',
455
+ '## Testing and Validation',
456
+ '- Record expectations for unit tests, integration tests, and verification commands.',
457
+ '',
458
+ '## Review Checklist',
459
+ '- List the code quality checks every completed task should re-evaluate.',
460
+ '',
461
+ '## Change Triggers',
462
+ '- Update this document when tasks establish or revise reusable engineering conventions.',
463
+ ].join('\n');
464
+ }
465
+ function defaultUiStyleMarkdown() {
466
+ return [
467
+ '# UI Style',
468
+ '',
469
+ '## Visual Language',
470
+ '- Describe the intended product tone, typography, spacing, and visual hierarchy.',
471
+ '',
472
+ '## Components and Patterns',
473
+ '- Record reusable UI patterns, interaction rules, and component expectations.',
474
+ '',
475
+ '## Accessibility and Quality',
476
+ '- Document accessibility expectations, responsiveness rules, and UX quality bars.',
477
+ '',
478
+ '## Design Tokens',
479
+ '- Capture colors, spacing, motion, and other token-level guidance if applicable.',
480
+ '',
481
+ '## Change Triggers',
482
+ '- Update this document when tasks change UI behavior, interaction rules, or visual consistency guidance.',
483
+ ].join('\n');
484
+ }
485
+ function toRelativeGovernancePath(governanceDir, targetPath) {
486
+ const relative = path.relative(governanceDir, targetPath).replace(/\\/g, '/');
487
+ return relative.length > 0 ? relative : '.';
488
+ }
489
+ function classifyProjectInitMissing(initialized) {
490
+ const coreDocFiles = new Set([
491
+ CORE_ARCHITECTURE_DOC_FILE,
492
+ CORE_CODE_STYLE_DOC_FILE,
493
+ CORE_UI_STYLE_DOC_FILE,
494
+ ]);
495
+ const relativeMissingFiles = initialized.missingBeforeInit.missingFiles
496
+ .map((item) => toRelativeGovernancePath(initialized.governanceDir, item));
497
+ const coreDocs = relativeMissingFiles.filter((item) => coreDocFiles.has(item));
498
+ const templates = relativeMissingFiles.filter((item) => item === 'templates/README.md' || item.startsWith('templates/tools/'));
499
+ const otherFiles = relativeMissingFiles.filter((item) => !coreDocFiles.has(item) && !(item === 'templates/README.md' || item.startsWith('templates/tools/')));
500
+ return {
501
+ coreDocs,
502
+ templates,
503
+ bootstrapTasks: initialized.missingBeforeInit.missingBootstrapTaskTitles,
504
+ otherFiles,
505
+ };
506
+ }
507
+ function renderProjectInitRepairSummary(initialized) {
508
+ const classified = classifyProjectInitMissing(initialized);
509
+ return [
510
+ '### Repair Summary (Missing Before Init)',
511
+ '- core docs:',
512
+ ` - count: ${classified.coreDocs.length}`,
513
+ ...(classified.coreDocs.length > 0 ? classified.coreDocs.map((item) => ` - ${item}`) : [' - (none)']),
514
+ '- templates:',
515
+ ` - count: ${classified.templates.length}`,
516
+ ...(classified.templates.length > 0 ? classified.templates.map((item) => ` - ${item}`) : [' - (none)']),
517
+ '- bootstrap tasks:',
518
+ ` - count: ${classified.bootstrapTasks.length}`,
519
+ ...(classified.bootstrapTasks.length > 0 ? classified.bootstrapTasks.map((item) => ` - ${item}`) : [' - (none)']),
520
+ '- other required files:',
521
+ ` - count: ${classified.otherFiles.length}`,
522
+ ...(classified.otherFiles.length > 0 ? classified.otherFiles.map((item) => ` - ${item}`) : [' - (none)']),
523
+ '- remediation actions:',
524
+ ` - created bootstrap tasks: ${initialized.remediation.createdBootstrapTaskIds.length}`,
525
+ ` - created bootstrap roadmap: ${initialized.remediation.createdBootstrapRoadmapId ?? '(none)'}`,
526
+ ];
527
+ }
303
528
  function defaultTemplateReadmeMarkdown() {
304
529
  return [
305
530
  '# Template Guide',
@@ -335,10 +560,13 @@ export async function initializeProjectStructure(inputPath, governanceDir, force
335
560
  throw new Error(`projectPath must be a directory: ${projectPath}`);
336
561
  }
337
562
  const governancePath = path.join(projectPath, governanceDirName);
563
+ const missingBeforeInit = await inspectProjectInitMissingState(governancePath);
338
564
  const directories = [];
339
565
  const requiredDirectories = [
340
566
  governancePath,
341
567
  path.join(governancePath, 'designs'),
568
+ path.join(governancePath, 'designs', 'core'),
569
+ path.join(governancePath, 'designs', 'research'),
342
570
  path.join(governancePath, 'reports'),
343
571
  path.join(governancePath, 'templates'),
344
572
  path.join(governancePath, 'templates', 'tools'),
@@ -353,25 +581,21 @@ export async function initializeProjectStructure(inputPath, governanceDir, force
353
581
  const defaultTaskUpdatedAt = new Date().toISOString();
354
582
  const markerExists = await pathExists(markerPath);
355
583
  await ensureStore(markerPath);
584
+ let remediation = { createdBootstrapTaskIds: [] };
356
585
  if (force || !markerExists) {
357
586
  await replaceRoadmapsInStore(markerPath, defaultRoadmapData);
358
- await replaceTasksInStore(markerPath, [
359
- {
360
- id: 'TASK-0001',
361
- title: 'Bootstrap governance workspace',
362
- status: 'TODO',
363
- owner: 'unassigned',
364
- summary: 'Create initial governance artifacts and confirm task execution loop.',
365
- updatedAt: defaultTaskUpdatedAt,
366
- links: [],
367
- roadmapRefs: ['ROADMAP-0001'],
368
- },
369
- ]);
587
+ await replaceTasksInStore(markerPath, defaultBootstrapTasks(defaultTaskUpdatedAt));
588
+ }
589
+ else {
590
+ remediation = await backfillBootstrapTasks(markerPath);
370
591
  }
371
592
  const baseFiles = await Promise.all([
372
593
  writeTextFile(path.join(governancePath, 'README.md'), defaultReadmeMarkdown(governanceDirName), force),
373
594
  writeTextFile(path.join(governancePath, 'roadmap.md'), defaultRoadmapMarkdown(defaultRoadmapData), force),
374
595
  writeTextFile(path.join(governancePath, 'tasks.md'), defaultTasksMarkdown(defaultTaskUpdatedAt), force),
596
+ writeTextFile(path.join(governancePath, CORE_ARCHITECTURE_DOC_FILE), defaultProjectArchitectureMarkdown(), force),
597
+ writeTextFile(path.join(governancePath, CORE_CODE_STYLE_DOC_FILE), defaultCodeStyleMarkdown(), force),
598
+ writeTextFile(path.join(governancePath, CORE_UI_STYLE_DOC_FILE), defaultUiStyleMarkdown(), force),
375
599
  writeTextFile(path.join(governancePath, 'templates', 'README.md'), defaultTemplateReadmeMarkdown(), force),
376
600
  ]);
377
601
  const toolTemplateFiles = await Promise.all(DEFAULT_TOOL_TEMPLATE_NAMES.map((toolName) => writeTextFile(path.join(governancePath, 'templates', 'tools', `${toolName}.md`), getDefaultToolTemplateMarkdown(toolName), force)));
@@ -381,6 +605,8 @@ export async function initializeProjectStructure(inputPath, governanceDir, force
381
605
  governanceDir: governancePath,
382
606
  directories,
383
607
  files,
608
+ missingBeforeInit,
609
+ remediation,
384
610
  };
385
611
  }
386
612
  export function registerProjectTools(server) {
@@ -411,17 +637,45 @@ export function registerProjectTools(server) {
411
637
  `- createdFiles: ${filesByAction.created.length}`,
412
638
  `- updatedFiles: ${filesByAction.updated.length}`,
413
639
  `- skippedFiles: ${filesByAction.skipped.length}`,
640
+ `- missingDirectoriesBeforeInit: ${initialized.missingBeforeInit.missingDirectories.length}`,
641
+ `- missingFilesBeforeInit: ${initialized.missingBeforeInit.missingFiles.length}`,
642
+ `- missingBootstrapTasksBeforeInit: ${initialized.missingBeforeInit.missingBootstrapTaskTitles.length}`,
643
+ `- missingBootstrapRoadmapBeforeInit: ${initialized.missingBeforeInit.missingBootstrapRoadmap ? 'true' : 'false'}`,
644
+ `- createdBootstrapTasks: ${initialized.remediation.createdBootstrapTaskIds.length}`,
645
+ `- createdBootstrapRoadmap: ${initialized.remediation.createdBootstrapRoadmapId ?? '(none)'}`,
414
646
  '- directories:',
415
647
  ...initialized.directories.map((item) => ` - ${item.action}: ${item.path}`),
416
648
  '- files:',
417
649
  ...initialized.files.map((item) => ` - ${item.action}: ${item.path}`),
650
+ ...(initialized.missingBeforeInit.missingDirectories.length > 0
651
+ ? ['- missingDirectoriesBeforeInit.list:', ...initialized.missingBeforeInit.missingDirectories.map((item) => ` - ${item}`)]
652
+ : []),
653
+ ...(initialized.missingBeforeInit.missingFiles.length > 0
654
+ ? ['- missingFilesBeforeInit.list:', ...initialized.missingBeforeInit.missingFiles.map((item) => ` - ${item}`)]
655
+ : []),
656
+ ...(initialized.missingBeforeInit.missingBootstrapTaskTitles.length > 0
657
+ ? ['- missingBootstrapTasksBeforeInit.list:', ...initialized.missingBeforeInit.missingBootstrapTaskTitles.map((item) => ` - ${item}`)]
658
+ : []),
659
+ ...(initialized.remediation.createdBootstrapTaskIds.length > 0
660
+ ? ['- createdBootstrapTaskIds.list:', ...initialized.remediation.createdBootstrapTaskIds.map((item) => ` - ${item}`)]
661
+ : []),
662
+ '',
663
+ ...renderProjectInitRepairSummary(initialized),
418
664
  ],
419
- guidance: () => [
665
+ guidance: ({ initialized }) => [
666
+ ...(initialized.missingBeforeInit.markerMissing
667
+ ? ['- Governance root was missing before init. This call performed a full bootstrap.']
668
+ : ['- Governance root already existed. This call inspected the current initialization state and backfilled missing artifacts where possible.']),
669
+ ...(initialized.missingBeforeInit.missingFiles.length > 0 || initialized.missingBeforeInit.missingDirectories.length > 0 || initialized.missingBeforeInit.missingBootstrapTaskTitles.length > 0 || initialized.missingBeforeInit.missingBootstrapRoadmap
670
+ ? ['- This project was partially initialized. Review the created files and bootstrap tasks, then complete any placeholder content.']
671
+ : ['- No initialization gaps were detected. Use force=true only when you intentionally want to overwrite templates/files.']),
420
672
  '- If files were skipped and you want to overwrite templates, rerun with force=true.',
421
673
  '- Continue with projectContext and taskList for execution.',
674
+ '- Start with the three bootstrap TODO tasks for architecture, code style, and UI style docs.',
422
675
  ],
423
676
  suggestions: () => [
424
677
  '- After init, fill owner/roadmapRefs/links in .projitive task table before marking DONE.',
678
+ '- Keep designs/core/architecture.md, designs/core/code-style.md, and designs/core/ui-style.md in sync with completed work.',
425
679
  '- Keep task source-of-truth inside .projitive governance store.',
426
680
  ],
427
681
  nextCall: ({ initialized }) => `projectContext(projectPath="${initialized.projectPath}")`,
@@ -595,20 +849,24 @@ export function registerProjectTools(server) {
595
849
  const governanceDir = await resolveGovernanceDir(projectPath);
596
850
  const normalizedProjectPath = toProjectPath(governanceDir);
597
851
  const artifacts = await discoverGovernanceArtifacts(governanceDir);
852
+ const projectContextDocsState = inspectProjectContextDocsFromArtifacts(candidateFilesFromArtifacts(artifacts));
598
853
  const dbPath = path.join(governanceDir, PROJECT_MARKER);
599
854
  await ensureStore(dbPath);
600
855
  const taskStats = await loadTaskStatusStatsFromStore(dbPath);
601
856
  const { markdownPath: tasksMarkdownPath, tasks } = await loadTasksDocument(governanceDir);
602
857
  const { markdownPath: roadmapMarkdownPath, milestones } = await loadRoadmapDocumentWithOptions(governanceDir, false);
603
858
  const roadmapIds = milestones.map((item) => item.id);
604
- return { normalizedProjectPath, governanceDir, tasksMarkdownPath, roadmapMarkdownPath, roadmapIds, taskStats, artifacts, tasks };
859
+ return { normalizedProjectPath, governanceDir, tasksMarkdownPath, roadmapMarkdownPath, roadmapIds, taskStats, artifacts, tasks, projectContextDocsState };
605
860
  },
606
- summary: ({ normalizedProjectPath, governanceDir, tasksMarkdownPath, roadmapMarkdownPath, roadmapIds }) => [
861
+ summary: ({ normalizedProjectPath, governanceDir, tasksMarkdownPath, roadmapMarkdownPath, roadmapIds, projectContextDocsState }) => [
607
862
  `- projectPath: ${normalizedProjectPath}`,
608
863
  `- governanceDir: ${governanceDir}`,
609
864
  `- tasksView: ${tasksMarkdownPath}`,
610
865
  `- roadmapView: ${roadmapMarkdownPath}`,
611
866
  `- roadmapIds: ${roadmapIds.length}`,
867
+ `- projectArchitectureDocsStatus: ${projectContextDocsState.missingArchitectureDocs ? 'MISSING' : 'READY'}`,
868
+ `- codeStyleDocsStatus: ${projectContextDocsState.missingCodeStyleDocs ? 'MISSING' : 'READY'}`,
869
+ `- uiStyleDocsStatus: ${projectContextDocsState.missingUiStyleDocs ? 'MISSING' : 'READY'}`,
612
870
  ],
613
871
  evidence: ({ taskStats, artifacts }) => [
614
872
  '### Task Summary',
@@ -621,11 +879,24 @@ export function registerProjectTools(server) {
621
879
  '### Artifacts',
622
880
  renderArtifactsMarkdown(artifacts),
623
881
  ],
624
- guidance: () => [
882
+ guidance: ({ normalizedProjectPath, projectContextDocsState }) => [
883
+ ...(!projectContextDocsState.ready
884
+ ? [
885
+ '- Project-level governance gate is NOT satisfied because required core docs are missing.',
886
+ `- Rerun projectInit to repair missing governance artifacts and bootstrap tasks: projectInit(projectPath="${normalizedProjectPath}")`,
887
+ '- After projectInit backfills missing files/tasks, update any placeholder content in the created docs.',
888
+ ]
889
+ : []),
890
+ '- Governance state must be changed via tools; do not directly edit tasks.md/roadmap.md generated views.',
625
891
  '- Start from `taskList` to choose a target task.',
626
892
  '- Then call `taskContext` with a task ID to retrieve evidence locations and reading order.',
627
893
  ],
628
- suggestions: ({ tasks }) => collectTaskLintSuggestions(tasks),
629
- nextCall: ({ normalizedProjectPath }) => `taskList(projectPath="${normalizedProjectPath}")`,
894
+ suggestions: ({ tasks, projectContextDocsState }) => [
895
+ ...collectTaskLintSuggestions(tasks),
896
+ ...renderLintSuggestions(collectProjectContextDocsLintSuggestions(projectContextDocsState)),
897
+ ],
898
+ nextCall: ({ normalizedProjectPath, projectContextDocsState }) => projectContextDocsState.ready
899
+ ? `taskList(projectPath="${normalizedProjectPath}")`
900
+ : `projectInit(projectPath="${normalizedProjectPath}")`,
630
901
  }));
631
902
  }
@@ -1,8 +1,9 @@
1
1
  import fs from 'node:fs/promises';
2
- import os from 'node:os';
2
+ import os, { homedir } from 'node:os';
3
3
  import path from 'node:path';
4
4
  import { afterEach, describe, expect, it, vi } from 'vitest';
5
5
  import { discoverProjects, discoverProjectsAcrossRoots, hasProjectMarker, initializeProjectStructure, resolveGovernanceDir, resolveScanRoots, resolveScanRoot, resolveScanDepth, toProjectPath, registerProjectTools } from './project.js';
6
+ import { loadTasksFromStore, replaceTasksInStore } from '../common/store.js';
6
7
  const tempPaths = [];
7
8
  async function createTempDir() {
8
9
  const dir = await fs.mkdtemp(path.join(os.tmpdir(), 'projitive-mcp-test-'));
@@ -222,10 +223,15 @@ describe('projitive module', () => {
222
223
  path.join(root, '.projitive', 'README.md'),
223
224
  path.join(root, '.projitive', 'roadmap.md'),
224
225
  path.join(root, '.projitive', 'tasks.md'),
226
+ path.join(root, '.projitive', 'designs', 'core', 'architecture.md'),
227
+ path.join(root, '.projitive', 'designs', 'core', 'code-style.md'),
228
+ path.join(root, '.projitive', 'designs', 'core', 'ui-style.md'),
225
229
  path.join(root, '.projitive', 'templates', 'README.md'),
226
230
  path.join(root, '.projitive', 'templates', 'tools', 'taskNext.md'),
227
231
  path.join(root, '.projitive', 'templates', 'tools', 'taskUpdate.md'),
228
232
  path.join(root, '.projitive', 'designs'),
233
+ path.join(root, '.projitive', 'designs', 'core'),
234
+ path.join(root, '.projitive', 'designs', 'research'),
229
235
  path.join(root, '.projitive', 'reports'),
230
236
  path.join(root, '.projitive', 'templates'),
231
237
  path.join(root, '.projitive', 'templates', 'tools'),
@@ -278,6 +284,25 @@ describe('projitive module', () => {
278
284
  expect(readmeContent).toBe('custom-content');
279
285
  expect(initialized.files.find((item) => item.path === readmePath)?.action).toBe('skipped');
280
286
  });
287
+ it('backfills missing core docs and bootstrap tasks when rerunning projectInit on partial governance', async () => {
288
+ const root = await createTempDir();
289
+ const governanceDir = path.join(root, '.projitive');
290
+ await initializeProjectStructure(root);
291
+ await fs.rm(path.join(governanceDir, 'designs', 'core', 'ui-style.md'));
292
+ const dbPath = path.join(governanceDir, '.projitive');
293
+ const existingTasks = await loadTasksFromStore(dbPath);
294
+ const uiTask = existingTasks.find((task) => task.title === 'Initialize UI style document');
295
+ expect(uiTask).toBeTruthy();
296
+ const filteredTasks = existingTasks.filter((task) => task.title !== 'Initialize UI style document');
297
+ await replaceTasksInStore(dbPath, filteredTasks);
298
+ const initialized = await initializeProjectStructure(root);
299
+ expect(await fs.access(path.join(governanceDir, 'designs', 'core', 'ui-style.md')).then(() => true).catch(() => false)).toBe(true);
300
+ expect(initialized.missingBeforeInit.missingFiles.some((item) => item.endsWith('designs/core/ui-style.md'))).toBe(true);
301
+ expect(initialized.missingBeforeInit.missingBootstrapTaskTitles).toContain('Initialize UI style document');
302
+ expect(initialized.remediation.createdBootstrapTaskIds).toHaveLength(1);
303
+ const reloadedTasks = await loadTasksFromStore(dbPath);
304
+ expect(reloadedTasks.some((task) => task.title === 'Initialize UI style document')).toBe(true);
305
+ });
281
306
  it('creates all required subdirectories', async () => {
282
307
  const root = await createTempDir();
283
308
  const initialized = await initializeProjectStructure(root);
@@ -327,9 +352,9 @@ describe('projitive module', () => {
327
352
  expect(resolveScanRoots()).toHaveLength(1);
328
353
  vi.unstubAllEnvs();
329
354
  });
330
- it('throws error when no root environment variables are configured', () => {
355
+ it('falls back to home directory when no env vars configured', () => {
331
356
  vi.unstubAllEnvs();
332
- expect(() => resolveScanRoots()).toThrow('Missing required environment variable: PROJITIVE_SCAN_ROOT_PATHS');
357
+ expect(resolveScanRoots()).toEqual([homedir()]);
333
358
  });
334
359
  });
335
360
  describe('resolveScanDepth', () => {
@@ -357,9 +382,9 @@ describe('projitive module', () => {
357
382
  expect(() => resolveScanDepth()).toThrow('Invalid PROJITIVE_SCAN_MAX_DEPTH');
358
383
  vi.unstubAllEnvs();
359
384
  });
360
- it('throws when PROJITIVE_SCAN_MAX_DEPTH env var is missing', () => {
385
+ it('falls back to default depth 3 when env var is missing', () => {
361
386
  vi.unstubAllEnvs();
362
- expect(() => resolveScanDepth()).toThrow('Missing required environment variable: PROJITIVE_SCAN_MAX_DEPTH');
387
+ expect(resolveScanDepth()).toBe(3);
363
388
  });
364
389
  });
365
390
  describe('resolveScanRoot', () => {
@@ -443,6 +468,13 @@ describe('projitive module', () => {
443
468
  expect(result.isError).toBeUndefined();
444
469
  expect(result.content[0].text).toContain('governanceDir:');
445
470
  expect(result.content[0].text).toContain('createdFiles:');
471
+ expect(result.content[0].text).toContain('designs/core/architecture.md');
472
+ expect(result.content[0].text).toContain('designs/core/code-style.md');
473
+ expect(result.content[0].text).toContain('designs/core/ui-style.md');
474
+ expect(result.content[0].text).toContain('Repair Summary (Missing Before Init)');
475
+ expect(result.content[0].text).toContain('- core docs:');
476
+ expect(result.content[0].text).toContain('- templates:');
477
+ expect(result.content[0].text).toContain('- bootstrap tasks:');
446
478
  });
447
479
  it('projectLocate resolves governance dir from any inner path', async () => {
448
480
  const root = await createTempDir();
@@ -484,6 +516,19 @@ describe('projitive module', () => {
484
516
  expect(result.content[0].text).toContain('Task Summary');
485
517
  expect(result.content[0].text).toContain('Artifacts');
486
518
  });
519
+ it('projectContext suggests rerunning projectInit when required core docs are missing', async () => {
520
+ const root = await createTempDir();
521
+ await initializeProjectStructure(root);
522
+ await fs.rm(path.join(root, '.projitive', 'designs', 'core', 'code-style.md'));
523
+ const mockServer = { registerTool: vi.fn() };
524
+ registerProjectTools(mockServer);
525
+ const projectContext = getProjectToolHandler(mockServer, 'projectContext');
526
+ const result = await projectContext({ projectPath: root });
527
+ expect(result.isError).toBeUndefined();
528
+ expect(result.content[0].text).toContain('codeStyleDocsStatus: MISSING');
529
+ expect(result.content[0].text).toContain('projectInit(projectPath="');
530
+ expect(result.content[0].text).toContain('PROJECT_CODE_STYLE_DOC_MISSING');
531
+ });
487
532
  it('syncViews materializes both tasks and roadmap markdown views', async () => {
488
533
  const root = await createTempDir();
489
534
  await initializeProjectStructure(root);