roadmapsmith 0.2.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.
package/src/utils.js CHANGED
@@ -1,143 +1,142 @@
1
- 'use strict';
2
-
3
- const path = require('path');
4
-
5
- const STOP_WORDS = new Set([
6
- 'a', 'an', 'and', 'are', 'as', 'at', 'be', 'by', 'for', 'from', 'in', 'into', 'is', 'it', 'of', 'on', 'or', 'that',
7
- 'the', 'to', 'with', 'this', 'these', 'those', 'via', 'per', 'task', 'tasks', 'phase', 'priority'
8
- ]);
9
-
10
- function toPosix(input) {
11
- return input.split(path.sep).join('/');
12
- }
13
-
14
- function slugify(text) {
15
- return String(text || '')
16
- .toLowerCase()
17
- .replace(/[^a-z0-9]+/g, '-')
18
- .replace(/^-+|-+$/g, '')
19
- .replace(/-{2,}/g, '-') || 'task';
20
- }
21
-
22
- function normalizeText(text) {
23
- return String(text || '')
24
- .toLowerCase()
25
- .replace(/[`*_~#>\[\](){}.!?,:;"']/g, ' ')
26
- .replace(/\s+/g, ' ')
27
- .trim();
28
- }
29
-
30
- function tokenize(text) {
31
- return normalizeText(text)
32
- .split(' ')
33
- .filter(Boolean)
34
- .filter((token) => !STOP_WORDS.has(token));
35
- }
36
-
37
- function uniqueBy(items, keyFn) {
38
- const seen = new Set();
39
- const result = [];
40
- for (const item of items) {
41
- const key = keyFn(item);
42
- if (seen.has(key)) {
43
- continue;
44
- }
45
- seen.add(key);
46
- result.push(item);
47
- }
48
- return result;
49
- }
50
-
51
- function similarityScore(left, right) {
52
- const leftTokens = new Set(tokenize(left));
53
- const rightTokens = new Set(tokenize(right));
54
- if (leftTokens.size === 0 || rightTokens.size === 0) {
55
- return 0;
56
- }
57
-
58
- let shared = 0;
59
- for (const token of leftTokens) {
60
- if (rightTokens.has(token)) {
61
- shared += 1;
62
- }
63
- }
64
-
65
- const union = new Set([...leftTokens, ...rightTokens]);
66
- return shared / union.size;
67
- }
68
-
69
- function ensureTrailingNewline(text) {
70
- return text.endsWith('\n') ? text : `${text}\n`;
71
- }
72
-
73
- function escapeRegExp(value) {
74
- return String(value).replace(/[.*+?^${}()|[\]\\]/g, '\\$&');
75
- }
76
-
77
- function parseArgv(argv) {
78
- const flags = {};
79
- const positionals = [];
80
-
81
- for (let i = 0; i < argv.length; i += 1) {
82
- const current = argv[i];
83
- if (!current.startsWith('-')) {
84
- positionals.push(current);
85
- continue;
86
- }
87
-
88
- if (current.startsWith('--')) {
89
- const withoutPrefix = current.slice(2);
90
- const eqIndex = withoutPrefix.indexOf('=');
91
- let key;
92
- let value;
93
-
94
- if (eqIndex >= 0) {
95
- key = withoutPrefix.slice(0, eqIndex);
96
- value = withoutPrefix.slice(eqIndex + 1);
97
- } else {
98
- key = withoutPrefix;
99
- const next = argv[i + 1];
100
- if (next && !next.startsWith('-')) {
101
- value = next;
102
- i += 1;
103
- } else {
104
- value = true;
105
- }
106
- }
107
-
108
- if (Object.prototype.hasOwnProperty.call(flags, key)) {
109
- if (Array.isArray(flags[key])) {
110
- flags[key].push(value);
111
- } else {
112
- flags[key] = [flags[key], value];
113
- }
114
- } else {
115
- flags[key] = value;
116
- }
117
- continue;
118
- }
119
-
120
- const short = current.slice(1);
121
- flags[short] = true;
122
- }
123
-
124
- const command = positionals.length > 0 ? positionals[0] : null;
125
- return {
126
- command,
127
- args: positionals.slice(1),
128
- flags,
129
- positionals
130
- };
131
- }
132
-
133
- module.exports = {
134
- escapeRegExp,
135
- ensureTrailingNewline,
136
- normalizeText,
137
- parseArgv,
138
- similarityScore,
139
- slugify,
140
- toPosix,
141
- tokenize,
142
- uniqueBy
1
+ 'use strict';
2
+
3
+ const path = require('path');
4
+
5
+ const STOP_WORDS = new Set([
6
+ 'a', 'an', 'and', 'are', 'as', 'at', 'be', 'by', 'for', 'from', 'in', 'into', 'is', 'it', 'of', 'on', 'or', 'that',
7
+ 'the', 'to', 'with', 'this', 'these', 'those', 'via', 'per', 'task', 'tasks', 'phase', 'priority'
8
+ ]);
9
+
10
+ function toPosix(input) {
11
+ return input.split(path.sep).join('/');
12
+ }
13
+
14
+ function slugify(text) {
15
+ return String(text || '')
16
+ .toLowerCase()
17
+ .replace(/[^a-z0-9]+/g, '-')
18
+ .replace(/^-+|-+$/g, '')
19
+ .replace(/-{2,}/g, '-') || 'task';
20
+ }
21
+
22
+ function normalizeText(text) {
23
+ return String(text || '')
24
+ .toLowerCase()
25
+ .replace(/[`*_~#>\[\](){}.!?,:;"']/g, ' ')
26
+ .replace(/\s+/g, ' ')
27
+ .trim();
28
+ }
29
+
30
+ function tokenize(text) {
31
+ return normalizeText(text)
32
+ .split(' ')
33
+ .filter(Boolean)
34
+ .filter((token) => !STOP_WORDS.has(token));
35
+ }
36
+
37
+ function uniqueBy(items, keyFn) {
38
+ const seen = new Set();
39
+ const result = [];
40
+ for (const item of items) {
41
+ const key = keyFn(item);
42
+ if (seen.has(key)) {
43
+ continue;
44
+ }
45
+ seen.add(key);
46
+ result.push(item);
47
+ }
48
+ return result;
49
+ }
50
+
51
+ function similarityScore(left, right) {
52
+ const leftTokens = new Set(tokenize(left));
53
+ const rightTokens = new Set(tokenize(right));
54
+ if (leftTokens.size === 0 || rightTokens.size === 0) {
55
+ return 0;
56
+ }
57
+
58
+ let shared = 0;
59
+ for (const token of leftTokens) {
60
+ if (rightTokens.has(token)) {
61
+ shared += 1;
62
+ }
63
+ }
64
+
65
+ const union = new Set([...leftTokens, ...rightTokens]);
66
+ return shared / union.size;
67
+ }
68
+
69
+ function ensureTrailingNewline(text) {
70
+ return text.endsWith('\n') ? text : `${text}\n`;
71
+ }
72
+
73
+ function escapeRegExp(value) {
74
+ return String(value).replace(/[.*+?^${}()|[\]\\]/g, '\\$&');
75
+ }
76
+
77
+ function parseArgv(argv) {
78
+ const flags = {};
79
+ const positionals = [];
80
+
81
+ for (let i = 0; i < argv.length; i += 1) {
82
+ const current = argv[i];
83
+ if (!current.startsWith('-')) {
84
+ positionals.push(current);
85
+ continue;
86
+ }
87
+
88
+ if (current.startsWith('--')) {
89
+ const withoutPrefix = current.slice(2);
90
+ const eqIndex = withoutPrefix.indexOf('=');
91
+ let key;
92
+ let value;
93
+
94
+ if (eqIndex >= 0) {
95
+ key = withoutPrefix.slice(0, eqIndex);
96
+ value = withoutPrefix.slice(eqIndex + 1);
97
+ } else {
98
+ key = withoutPrefix;
99
+ const next = argv[i + 1];
100
+ if (next && !next.startsWith('-')) {
101
+ value = next;
102
+ i += 1;
103
+ } else {
104
+ value = true;
105
+ }
106
+ }
107
+
108
+ if (Object.prototype.hasOwnProperty.call(flags, key)) {
109
+ if (Array.isArray(flags[key])) {
110
+ flags[key].push(value);
111
+ } else {
112
+ flags[key] = [flags[key], value];
113
+ }
114
+ } else {
115
+ flags[key] = value;
116
+ }
117
+ continue;
118
+ }
119
+
120
+ const short = current.slice(1);
121
+ flags[short] = true;
122
+ }
123
+
124
+ const command = positionals.length > 0 ? positionals[0] : null;
125
+ return {
126
+ command,
127
+ args: positionals.slice(1),
128
+ flags,
129
+ positionals
130
+ };
131
+ }
132
+
133
+ module.exports = {
134
+ escapeRegExp,
135
+ ensureTrailingNewline,
136
+ parseArgv,
137
+ similarityScore,
138
+ slugify,
139
+ toPosix,
140
+ tokenize,
141
+ uniqueBy
143
142
  };
@@ -56,6 +56,24 @@ function readFileIndex(projectRoot, files) {
56
56
  return index;
57
57
  }
58
58
 
59
+ const KNOWN_PATH_ROOTS = [
60
+ 'src/', 'lib/', 'bin/', 'test/', 'tests/', 'docs/', 'scripts/',
61
+ 'packages/', 'apps/', 'tools/', '.github/', 'roadmap-skill/'
62
+ ];
63
+
64
+ function hasFileExtension(token) {
65
+ const lastSegment = token.replace(/\\/g, '/').split('/').pop() || '';
66
+ return /\.[A-Za-z0-9]{1,10}$/.test(lastSegment);
67
+ }
68
+
69
+ function isLikelyPath(token) {
70
+ if (/^\.{1,2}\/|^\//.test(token)) return true;
71
+ if (hasFileExtension(token)) return true;
72
+ if (KNOWN_PATH_ROOTS.some((root) => token.startsWith(root))) return true;
73
+ if ((token.match(/\//g) || []).length >= 2) return true;
74
+ return false;
75
+ }
76
+
59
77
  function extractExplicitPaths(text) {
60
78
  const results = new Set();
61
79
  const quoted = String(text).match(/`([^`]+)`/g) || [];
@@ -67,8 +85,9 @@ function extractExplicitPaths(text) {
67
85
  }
68
86
 
69
87
  const pathTokens = String(text).match(/([A-Za-z0-9_.-]+\/[A-Za-z0-9_./-]+)/g) || [];
70
- for (const token of pathTokens) {
71
- results.add(token);
88
+ for (const raw of pathTokens) {
89
+ const token = raw.replace(/[.,;:!?)]+$/, '');
90
+ if (isLikelyPath(token)) results.add(token);
72
91
  }
73
92
 
74
93
  return Array.from(results).sort((left, right) => left.localeCompare(right));
@@ -398,4 +417,4 @@ module.exports = {
398
417
  buildValidationContext,
399
418
  validateTask,
400
419
  validateTasks
401
- };
420
+ };