roadmapsmith 0.4.0 → 0.5.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -2,7 +2,7 @@
2
2
 
3
3
  const fs = require('fs');
4
4
  const path = require('path');
5
- const { walkFiles, detectLanguages, detectTestFrameworks } = require('../io');
5
+ const { walkFiles, detectLanguages, detectTestFrameworks, detectWorkspaces } = require('../io');
6
6
  const { createRoadmapModel, PHASE_ORDER } = require('../model');
7
7
  const { slugify, ensureTrailingNewline } = require('../utils');
8
8
  const { parseRoadmap, upsertManagedBlock } = require('../parser');
@@ -10,21 +10,52 @@ const { findBestTaskMatch, dedupeTasks } = require('../match');
10
10
  const { collectPluginContributions } = require('../config');
11
11
  const { renderBody } = require('../renderer');
12
12
 
13
+ const IMPL_PATTERN_RE = /[/|]TODO|TODO[|/]|[/|]FIXME|FIXME[|/]/;
14
+ const COMMENT_TODO_RE = /(?:\/\/|#|\*\s*).*\b(?:TODO|FIXME)\b/;
15
+
16
+ function isTodoMarker(line) {
17
+ return COMMENT_TODO_RE.test(line) && !IMPL_PATTERN_RE.test(line);
18
+ }
19
+
20
+ const GENERIC_MODULE_NAMES = new Set(['index', 'main', 'utils', 'common', 'helpers', 'types', 'constants', 'model']);
21
+
13
22
  function detectModules(files) {
14
23
  const modules = new Set();
15
- const roots = ['src/', 'apps/', 'packages/', 'lib/', 'cmd/', 'internal/'];
24
+ const rootPrefixes = ['src/', 'apps/', 'packages/', 'lib/', 'cmd/', 'internal/'];
16
25
 
17
26
  for (const file of files) {
18
- const root = roots.find((candidate) => file.startsWith(candidate));
19
- if (!root) {
20
- continue;
27
+ let relative;
28
+
29
+ const directRoot = rootPrefixes.find((r) => file.startsWith(r));
30
+ if (directRoot) {
31
+ relative = file.slice(directRoot.length);
32
+ } else {
33
+ let found = false;
34
+ for (const r of rootPrefixes) {
35
+ const idx = file.indexOf('/' + r);
36
+ if (idx !== -1) {
37
+ // Only accept nested prefix when it appears within the first two path segments
38
+ // (e.g. "wrapper/src/..." is fine; "a/b/c/src/..." is too deep and likely a fixture or dependency)
39
+ if (file.slice(0, idx).split('/').length > 2) continue;
40
+ relative = file.slice(idx + 1 + r.length);
41
+ found = true;
42
+ break;
43
+ }
44
+ }
45
+ if (!found) continue;
21
46
  }
22
- const relative = file.slice(root.length);
47
+
23
48
  const first = relative.split('/')[0];
24
- if (!first || first.includes('.')) {
25
- continue;
49
+ if (!first) continue;
50
+
51
+ if (first.includes('.')) {
52
+ const name = first.slice(0, first.lastIndexOf('.'));
53
+ if (name && !GENERIC_MODULE_NAMES.has(name)) {
54
+ modules.add(name);
55
+ }
56
+ } else {
57
+ modules.add(first);
26
58
  }
27
- modules.add(first);
28
59
  }
29
60
 
30
61
  return Array.from(modules).sort((left, right) => left.localeCompare(right));
@@ -58,7 +89,7 @@ function collectTodoHints(projectRoot, files) {
58
89
 
59
90
  const lines = content.split(/\r?\n/);
60
91
  for (let i = 0; i < lines.length; i += 1) {
61
- if (/TODO|FIXME/i.test(lines[i])) {
92
+ if (isTodoMarker(lines[i])) {
62
93
  hints.push({
63
94
  file,
64
95
  line: i + 1,
@@ -89,7 +120,7 @@ function collectCodeTodoHints(projectRoot, files) {
89
120
 
90
121
  const lines = content.split(/\r?\n/);
91
122
  for (let i = 0; i < lines.length; i += 1) {
92
- if (/TODO|FIXME/i.test(lines[i])) {
123
+ if (isTodoMarker(lines[i])) {
93
124
  hints.push({
94
125
  file,
95
126
  line: i + 1,
@@ -113,6 +144,7 @@ function scanProject(projectRoot) {
113
144
  const commands = detectCommands(files);
114
145
  const todos = collectTodoHints(projectRoot, files);
115
146
  const codeTodos = collectCodeTodoHints(projectRoot, files);
147
+ const workspaces = detectWorkspaces(projectRoot, files);
116
148
 
117
149
  const implementedFiles = files.filter((file) => /\.(js|ts|tsx|py|go|rs|java|kt|cs)$/.test(file));
118
150
  const testFiles = files.filter((file) => /(^|\/)(__tests__|tests)\//.test(file) || /\.test\.|\.spec\.|_test\.go$/.test(file));
@@ -126,6 +158,7 @@ function scanProject(projectRoot) {
126
158
  commands,
127
159
  todos,
128
160
  codeTodos,
161
+ workspaces,
129
162
  implementedCount: implementedFiles.length,
130
163
  testCount: testFiles.length
131
164
  };
@@ -272,97 +305,6 @@ function groupByPhase(tasks) {
272
305
  return groups;
273
306
  }
274
307
 
275
- function taskLine(task) {
276
- return `- [${task.checked ? 'x' : ' '}] ${task.text} <!-- rs:task=${task.id} -->`;
277
- }
278
-
279
- function checkedState(model, id) {
280
- return Boolean(model.checkedById && model.checkedById[id]);
281
- }
282
-
283
- function renderManagedBody(model) {
284
- const lines = [];
285
-
286
- lines.push('# Project Roadmap');
287
- lines.push('');
288
- lines.push('## Product North Star');
289
- lines.push(model.northStar);
290
- lines.push('');
291
-
292
- lines.push('## Current State');
293
- lines.push(`- Implemented surface: ${model.currentState.implementedSummary}`);
294
- lines.push(`- TODO surface: ${model.currentState.todoSummary}`);
295
- lines.push(`- Detected stacks: ${model.currentState.stackSummary}`);
296
- lines.push('');
297
-
298
- lines.push('## Phased Roadmap');
299
- lines.push('');
300
- lines.push('### Phase P0 (Critical)');
301
- for (const task of model.phases.P0) {
302
- lines.push(taskLine(task));
303
- }
304
- lines.push('');
305
- lines.push('### Phase P1 (Important)');
306
- for (const task of model.phases.P1) {
307
- lines.push(taskLine(task));
308
- }
309
- lines.push('');
310
- lines.push('### Phase P2 (Optimization)');
311
- for (const task of model.phases.P2) {
312
- lines.push(taskLine(task));
313
- }
314
- lines.push('');
315
-
316
- lines.push('## Release Milestones');
317
- for (const milestone of model.milestones) {
318
- const id = `milestone-${slugify(milestone.version)}`;
319
- lines.push(`- [${checkedState(model, id) ? 'x' : ' '}] ${milestone.version}: ${milestone.goal} <!-- rs:task=${id} -->`);
320
- }
321
- lines.push('');
322
-
323
- lines.push('## Command/Module Breakdown');
324
- if (model.commandBreakdown.length === 0) {
325
- const id = 'identify-command-module-boundaries';
326
- lines.push(`- [${checkedState(model, id) ? 'x' : ' '}] Identify command/module boundaries for the next increment <!-- rs:task=${id} -->`);
327
- } else {
328
- for (const item of model.commandBreakdown) {
329
- const id = `module-${slugify(item)}`;
330
- lines.push(`- [${checkedState(model, id) ? 'x' : ' '}] ${item} <!-- rs:task=${id} -->`);
331
- }
332
- }
333
- lines.push('');
334
-
335
- lines.push('## Exit Criteria Per Phase');
336
- for (const item of model.exitCriteria) {
337
- const id = `exit-${slugify(item)}`;
338
- lines.push(`- [${checkedState(model, id) ? 'x' : ' '}] ${item} <!-- rs:task=${id} -->`);
339
- }
340
- lines.push('');
341
-
342
- for (const section of model.customSections) {
343
- lines.push(`## ${section.title}`);
344
- for (const line of section.items) {
345
- lines.push(line);
346
- }
347
- lines.push('');
348
- }
349
-
350
- lines.push('## Risks and Anti-goals');
351
- lines.push('### Risks');
352
- for (const risk of model.risks) {
353
- const id = `risk-${slugify(risk)}`;
354
- lines.push(`- [${checkedState(model, id) ? 'x' : ' '}] ${risk} <!-- rs:task=${id} -->`);
355
- }
356
- lines.push('');
357
- lines.push('### Anti-goals');
358
- for (const antiGoal of model.antiGoals) {
359
- const id = `anti-goal-${slugify(antiGoal)}`;
360
- lines.push(`- [${checkedState(model, id) ? 'x' : ' '}] ${antiGoal} <!-- rs:task=${id} -->`);
361
- }
362
-
363
- return ensureTrailingNewline(lines.join('\n')).trimEnd();
364
- }
365
-
366
308
  function inferProjectName(projectRoot) {
367
309
  const pkgPath = path.join(projectRoot, 'package.json');
368
310
  try {
@@ -488,6 +430,7 @@ function createModel(scan, tasks, config, customSections, checkedById) {
488
430
  implemented,
489
431
  scaffold,
490
432
  knownLimitations,
433
+ workspaces: scan.workspaces || [],
491
434
  implementedSummary: `${scan.implementedCount} implementation files detected`,
492
435
  todoSummary: `${scan.todos.length} TODO/FIXME markers detected`,
493
436
  stackSummary: scan.languages.length > 0 ? scan.languages.join(', ') : 'No language-specific stack detected'
@@ -615,6 +558,5 @@ function generateRoadmapDocument(options) {
615
558
 
616
559
  module.exports = {
617
560
  generateRoadmapDocument,
618
- renderManagedBody,
619
561
  scanProject
620
562
  };
package/src/index.js CHANGED
@@ -1,8 +1,11 @@
1
- 'use strict';
2
-
3
- module.exports = {
4
- generator: require('./generator'),
5
- parser: require('./parser'),
6
- sync: require('./sync'),
7
- validator: require('./validator')
1
+ 'use strict';
2
+
3
+ module.exports = {
4
+ generator: require('./generator'),
5
+ parser: require('./parser'),
6
+ sync: require('./sync'),
7
+ validator: require('./validator'),
8
+ config: require('./config'),
9
+ model: require('./model'),
10
+ renderer: require('./renderer')
8
11
  };