roadmapsmith 0.9.3 → 0.9.5
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +20 -0
- package/bin/cli.js +254 -254
- package/package.json +56 -56
- package/src/config.js +219 -219
- package/src/generator/index.js +614 -614
- package/src/index.js +11 -11
- package/src/io.js +264 -264
- package/src/match.js +86 -86
- package/src/model.js +33 -33
- package/src/parser/index.js +100 -100
- package/src/renderer/professional.js +544 -544
- package/src/sync/index.js +1 -1
- package/src/templates/index.js +1 -1
- package/src/utils.js +142 -142
- package/src/validator/index.js +727 -641
- package/templates/roadmap.template.md +1 -1
package/src/config.js
CHANGED
|
@@ -1,219 +1,219 @@
|
|
|
1
|
-
'use strict';
|
|
2
|
-
|
|
3
|
-
const fs = require('fs');
|
|
4
|
-
const path = require('path');
|
|
5
|
-
const { readTextIfExists } = require('./io');
|
|
6
|
-
|
|
7
|
-
const DEFAULT_CONFIG = {
|
|
8
|
-
roadmapFile: './ROADMAP.md',
|
|
9
|
-
agentsFile: './AGENTS.md',
|
|
10
|
-
roadmapProfile: 'compact',
|
|
11
|
-
taskMatchers: [],
|
|
12
|
-
validators: [],
|
|
13
|
-
customSections: [],
|
|
14
|
-
plugins: [],
|
|
15
|
-
product: {
|
|
16
|
-
name: '',
|
|
17
|
-
northStar: '',
|
|
18
|
-
positioning: '',
|
|
19
|
-
primaryUser: '',
|
|
20
|
-
targetOutcome: '',
|
|
21
|
-
antiGoals: [],
|
|
22
|
-
risks: [],
|
|
23
|
-
successCriteria: [],
|
|
24
|
-
steps: [],
|
|
25
|
-
phases: []
|
|
26
|
-
},
|
|
27
|
-
validation: {
|
|
28
|
-
minimumConfidence: 'low'
|
|
29
|
-
},
|
|
30
|
-
milestones: [
|
|
31
|
-
{ version: 'v0.1', goal: 'Foundation baseline complete' },
|
|
32
|
-
{ version: 'v0.2', goal: 'Core feature coverage stabilized' },
|
|
33
|
-
{ version: 'v0.3', goal: 'Release candidate hardening complete' },
|
|
34
|
-
{ version: 'v1.0', goal: 'Production readiness exit criteria met' }
|
|
35
|
-
],
|
|
36
|
-
phaseTemplates: {
|
|
37
|
-
P0: [
|
|
38
|
-
'Stabilize project baseline and unblock high-risk delivery paths',
|
|
39
|
-
'Implement critical tasks required for milestone v0.1'
|
|
40
|
-
],
|
|
41
|
-
P1: [
|
|
42
|
-
'Expand feature completeness and improve reliability',
|
|
43
|
-
'Reduce operational risk before v0.3'
|
|
44
|
-
],
|
|
45
|
-
P2: [
|
|
46
|
-
'Complete final hardening and release readiness for v1.0',
|
|
47
|
-
'Close non-critical backlog aligned to anti-goals'
|
|
48
|
-
]
|
|
49
|
-
}
|
|
50
|
-
};
|
|
51
|
-
|
|
52
|
-
function safeParseJson(content, filePath) {
|
|
53
|
-
try {
|
|
54
|
-
return JSON.parse(content);
|
|
55
|
-
} catch (error) {
|
|
56
|
-
throw new Error(`Invalid JSON in ${filePath}: ${error.message}`);
|
|
57
|
-
}
|
|
58
|
-
}
|
|
59
|
-
|
|
60
|
-
function mergeConfig(userConfig) {
|
|
61
|
-
return {
|
|
62
|
-
...DEFAULT_CONFIG,
|
|
63
|
-
...userConfig,
|
|
64
|
-
taskMatchers: Array.isArray(userConfig.taskMatchers) ? userConfig.taskMatchers : DEFAULT_CONFIG.taskMatchers,
|
|
65
|
-
validators: Array.isArray(userConfig.validators) ? userConfig.validators : DEFAULT_CONFIG.validators,
|
|
66
|
-
customSections: Array.isArray(userConfig.customSections) ? userConfig.customSections : DEFAULT_CONFIG.customSections,
|
|
67
|
-
plugins: Array.isArray(userConfig.plugins) ? userConfig.plugins : DEFAULT_CONFIG.plugins,
|
|
68
|
-
milestones: Array.isArray(userConfig.milestones) ? userConfig.milestones : DEFAULT_CONFIG.milestones,
|
|
69
|
-
phaseTemplates: {
|
|
70
|
-
...DEFAULT_CONFIG.phaseTemplates,
|
|
71
|
-
...((userConfig && userConfig.phaseTemplates) || {})
|
|
72
|
-
},
|
|
73
|
-
product: {
|
|
74
|
-
...DEFAULT_CONFIG.product,
|
|
75
|
-
...((userConfig && userConfig.product) || {}),
|
|
76
|
-
phases: (userConfig && userConfig.product && Array.isArray(userConfig.product.phases))
|
|
77
|
-
? userConfig.product.phases
|
|
78
|
-
: DEFAULT_CONFIG.product.phases
|
|
79
|
-
},
|
|
80
|
-
validation: {
|
|
81
|
-
...DEFAULT_CONFIG.validation,
|
|
82
|
-
...((userConfig && userConfig.validation) || {})
|
|
83
|
-
}
|
|
84
|
-
};
|
|
85
|
-
}
|
|
86
|
-
|
|
87
|
-
function loadConfig(options = {}) {
|
|
88
|
-
const projectRoot = path.resolve(options.projectRoot || process.cwd());
|
|
89
|
-
const resolvedConfigPath = options.configPath
|
|
90
|
-
? path.resolve(projectRoot, String(options.configPath))
|
|
91
|
-
: path.resolve(projectRoot, 'roadmap-skill.config.json');
|
|
92
|
-
|
|
93
|
-
const content = readTextIfExists(resolvedConfigPath);
|
|
94
|
-
let userConfig = {};
|
|
95
|
-
if (!content) {
|
|
96
|
-
const merged = mergeConfig(userConfig);
|
|
97
|
-
Object.defineProperty(merged, '__roadmapFileExplicit', {
|
|
98
|
-
value: false,
|
|
99
|
-
enumerable: false,
|
|
100
|
-
configurable: false,
|
|
101
|
-
writable: false
|
|
102
|
-
});
|
|
103
|
-
return merged;
|
|
104
|
-
}
|
|
105
|
-
|
|
106
|
-
userConfig = safeParseJson(content, resolvedConfigPath);
|
|
107
|
-
const merged = mergeConfig(userConfig);
|
|
108
|
-
Object.defineProperty(merged, '__roadmapFileExplicit', {
|
|
109
|
-
value: Object.prototype.hasOwnProperty.call(userConfig, 'roadmapFile'),
|
|
110
|
-
enumerable: false,
|
|
111
|
-
configurable: false,
|
|
112
|
-
writable: false
|
|
113
|
-
});
|
|
114
|
-
return merged;
|
|
115
|
-
}
|
|
116
|
-
|
|
117
|
-
function resolveRoadmapFile(projectRoot, config, overridePath) {
|
|
118
|
-
if (overridePath) {
|
|
119
|
-
return path.resolve(projectRoot, overridePath);
|
|
120
|
-
}
|
|
121
|
-
|
|
122
|
-
const configuredRoadmapFile =
|
|
123
|
-
config && typeof config.roadmapFile === 'string' ? config.roadmapFile.trim() : '';
|
|
124
|
-
const hasExplicitRoadmapFile = Boolean(config && config.__roadmapFileExplicit);
|
|
125
|
-
const hasCustomConfigRoadmapFile =
|
|
126
|
-
configuredRoadmapFile.length > 0 &&
|
|
127
|
-
(hasExplicitRoadmapFile || configuredRoadmapFile !== DEFAULT_CONFIG.roadmapFile);
|
|
128
|
-
|
|
129
|
-
if (hasCustomConfigRoadmapFile) {
|
|
130
|
-
return path.resolve(projectRoot, configuredRoadmapFile);
|
|
131
|
-
}
|
|
132
|
-
|
|
133
|
-
let rootEntries = null;
|
|
134
|
-
try {
|
|
135
|
-
rootEntries = fs.readdirSync(projectRoot);
|
|
136
|
-
} catch {
|
|
137
|
-
rootEntries = null;
|
|
138
|
-
}
|
|
139
|
-
|
|
140
|
-
const canonicalRoadmapPath = path.resolve(projectRoot, DEFAULT_CONFIG.roadmapFile);
|
|
141
|
-
const legacyRoadmapPath = path.resolve(projectRoot, './roadmap.md');
|
|
142
|
-
|
|
143
|
-
const hasCanonicalRoadmap = Array.isArray(rootEntries)
|
|
144
|
-
? rootEntries.includes('ROADMAP.md')
|
|
145
|
-
: readTextIfExists(canonicalRoadmapPath) != null;
|
|
146
|
-
const hasLegacyRoadmap = Array.isArray(rootEntries)
|
|
147
|
-
? rootEntries.includes('roadmap.md')
|
|
148
|
-
: readTextIfExists(legacyRoadmapPath) != null;
|
|
149
|
-
|
|
150
|
-
if (hasCanonicalRoadmap) {
|
|
151
|
-
return canonicalRoadmapPath;
|
|
152
|
-
}
|
|
153
|
-
if (hasLegacyRoadmap) {
|
|
154
|
-
return legacyRoadmapPath;
|
|
155
|
-
}
|
|
156
|
-
return canonicalRoadmapPath;
|
|
157
|
-
}
|
|
158
|
-
|
|
159
|
-
function resolveAgentsFile(projectRoot, config, overridePath) {
|
|
160
|
-
if (overridePath) {
|
|
161
|
-
return path.resolve(projectRoot, overridePath);
|
|
162
|
-
}
|
|
163
|
-
|
|
164
|
-
const agentsPath = path.resolve(projectRoot, config.agentsFile || './AGENTS.md');
|
|
165
|
-
const claudePath = path.resolve(projectRoot, './CLAUDE.md');
|
|
166
|
-
|
|
167
|
-
if (readTextIfExists(agentsPath) != null) {
|
|
168
|
-
return agentsPath;
|
|
169
|
-
}
|
|
170
|
-
if (readTextIfExists(claudePath) != null) {
|
|
171
|
-
return claudePath;
|
|
172
|
-
}
|
|
173
|
-
return agentsPath;
|
|
174
|
-
}
|
|
175
|
-
|
|
176
|
-
function loadPlugins(projectRoot, pluginEntries) {
|
|
177
|
-
const plugins = [];
|
|
178
|
-
for (const entry of pluginEntries || []) {
|
|
179
|
-
const pluginPath = path.resolve(projectRoot, entry);
|
|
180
|
-
const pluginModule = require(pluginPath);
|
|
181
|
-
plugins.push({
|
|
182
|
-
name: path.basename(pluginPath),
|
|
183
|
-
path: pluginPath,
|
|
184
|
-
module: pluginModule
|
|
185
|
-
});
|
|
186
|
-
}
|
|
187
|
-
return plugins;
|
|
188
|
-
}
|
|
189
|
-
|
|
190
|
-
function collectPluginContributions(plugins, hookName, context) {
|
|
191
|
-
const contributions = [];
|
|
192
|
-
for (const plugin of plugins || []) {
|
|
193
|
-
const hook = plugin.module && plugin.module[hookName];
|
|
194
|
-
if (typeof hook !== 'function') {
|
|
195
|
-
continue;
|
|
196
|
-
}
|
|
197
|
-
let result;
|
|
198
|
-
try {
|
|
199
|
-
result = hook(context);
|
|
200
|
-
} catch (err) {
|
|
201
|
-
throw new Error(`Plugin "${plugin.name}" failed in hook "${hookName}": ${err.message}`);
|
|
202
|
-
}
|
|
203
|
-
if (Array.isArray(result)) {
|
|
204
|
-
for (const item of result) {
|
|
205
|
-
contributions.push(item);
|
|
206
|
-
}
|
|
207
|
-
}
|
|
208
|
-
}
|
|
209
|
-
return contributions;
|
|
210
|
-
}
|
|
211
|
-
|
|
212
|
-
module.exports = {
|
|
213
|
-
DEFAULT_CONFIG,
|
|
214
|
-
collectPluginContributions,
|
|
215
|
-
loadConfig,
|
|
216
|
-
loadPlugins,
|
|
217
|
-
resolveAgentsFile,
|
|
218
|
-
resolveRoadmapFile
|
|
219
|
-
};
|
|
1
|
+
'use strict';
|
|
2
|
+
|
|
3
|
+
const fs = require('fs');
|
|
4
|
+
const path = require('path');
|
|
5
|
+
const { readTextIfExists } = require('./io');
|
|
6
|
+
|
|
7
|
+
const DEFAULT_CONFIG = {
|
|
8
|
+
roadmapFile: './ROADMAP.md',
|
|
9
|
+
agentsFile: './AGENTS.md',
|
|
10
|
+
roadmapProfile: 'compact',
|
|
11
|
+
taskMatchers: [],
|
|
12
|
+
validators: [],
|
|
13
|
+
customSections: [],
|
|
14
|
+
plugins: [],
|
|
15
|
+
product: {
|
|
16
|
+
name: '',
|
|
17
|
+
northStar: '',
|
|
18
|
+
positioning: '',
|
|
19
|
+
primaryUser: '',
|
|
20
|
+
targetOutcome: '',
|
|
21
|
+
antiGoals: [],
|
|
22
|
+
risks: [],
|
|
23
|
+
successCriteria: [],
|
|
24
|
+
steps: [],
|
|
25
|
+
phases: []
|
|
26
|
+
},
|
|
27
|
+
validation: {
|
|
28
|
+
minimumConfidence: 'low'
|
|
29
|
+
},
|
|
30
|
+
milestones: [
|
|
31
|
+
{ version: 'v0.1', goal: 'Foundation baseline complete' },
|
|
32
|
+
{ version: 'v0.2', goal: 'Core feature coverage stabilized' },
|
|
33
|
+
{ version: 'v0.3', goal: 'Release candidate hardening complete' },
|
|
34
|
+
{ version: 'v1.0', goal: 'Production readiness exit criteria met' }
|
|
35
|
+
],
|
|
36
|
+
phaseTemplates: {
|
|
37
|
+
P0: [
|
|
38
|
+
'Stabilize project baseline and unblock high-risk delivery paths',
|
|
39
|
+
'Implement critical tasks required for milestone v0.1'
|
|
40
|
+
],
|
|
41
|
+
P1: [
|
|
42
|
+
'Expand feature completeness and improve reliability',
|
|
43
|
+
'Reduce operational risk before v0.3'
|
|
44
|
+
],
|
|
45
|
+
P2: [
|
|
46
|
+
'Complete final hardening and release readiness for v1.0',
|
|
47
|
+
'Close non-critical backlog aligned to anti-goals'
|
|
48
|
+
]
|
|
49
|
+
}
|
|
50
|
+
};
|
|
51
|
+
|
|
52
|
+
function safeParseJson(content, filePath) {
|
|
53
|
+
try {
|
|
54
|
+
return JSON.parse(content);
|
|
55
|
+
} catch (error) {
|
|
56
|
+
throw new Error(`Invalid JSON in ${filePath}: ${error.message}`);
|
|
57
|
+
}
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
function mergeConfig(userConfig) {
|
|
61
|
+
return {
|
|
62
|
+
...DEFAULT_CONFIG,
|
|
63
|
+
...userConfig,
|
|
64
|
+
taskMatchers: Array.isArray(userConfig.taskMatchers) ? userConfig.taskMatchers : DEFAULT_CONFIG.taskMatchers,
|
|
65
|
+
validators: Array.isArray(userConfig.validators) ? userConfig.validators : DEFAULT_CONFIG.validators,
|
|
66
|
+
customSections: Array.isArray(userConfig.customSections) ? userConfig.customSections : DEFAULT_CONFIG.customSections,
|
|
67
|
+
plugins: Array.isArray(userConfig.plugins) ? userConfig.plugins : DEFAULT_CONFIG.plugins,
|
|
68
|
+
milestones: Array.isArray(userConfig.milestones) ? userConfig.milestones : DEFAULT_CONFIG.milestones,
|
|
69
|
+
phaseTemplates: {
|
|
70
|
+
...DEFAULT_CONFIG.phaseTemplates,
|
|
71
|
+
...((userConfig && userConfig.phaseTemplates) || {})
|
|
72
|
+
},
|
|
73
|
+
product: {
|
|
74
|
+
...DEFAULT_CONFIG.product,
|
|
75
|
+
...((userConfig && userConfig.product) || {}),
|
|
76
|
+
phases: (userConfig && userConfig.product && Array.isArray(userConfig.product.phases))
|
|
77
|
+
? userConfig.product.phases
|
|
78
|
+
: DEFAULT_CONFIG.product.phases
|
|
79
|
+
},
|
|
80
|
+
validation: {
|
|
81
|
+
...DEFAULT_CONFIG.validation,
|
|
82
|
+
...((userConfig && userConfig.validation) || {})
|
|
83
|
+
}
|
|
84
|
+
};
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
function loadConfig(options = {}) {
|
|
88
|
+
const projectRoot = path.resolve(options.projectRoot || process.cwd());
|
|
89
|
+
const resolvedConfigPath = options.configPath
|
|
90
|
+
? path.resolve(projectRoot, String(options.configPath))
|
|
91
|
+
: path.resolve(projectRoot, 'roadmap-skill.config.json');
|
|
92
|
+
|
|
93
|
+
const content = readTextIfExists(resolvedConfigPath);
|
|
94
|
+
let userConfig = {};
|
|
95
|
+
if (!content) {
|
|
96
|
+
const merged = mergeConfig(userConfig);
|
|
97
|
+
Object.defineProperty(merged, '__roadmapFileExplicit', {
|
|
98
|
+
value: false,
|
|
99
|
+
enumerable: false,
|
|
100
|
+
configurable: false,
|
|
101
|
+
writable: false
|
|
102
|
+
});
|
|
103
|
+
return merged;
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
userConfig = safeParseJson(content, resolvedConfigPath);
|
|
107
|
+
const merged = mergeConfig(userConfig);
|
|
108
|
+
Object.defineProperty(merged, '__roadmapFileExplicit', {
|
|
109
|
+
value: Object.prototype.hasOwnProperty.call(userConfig, 'roadmapFile'),
|
|
110
|
+
enumerable: false,
|
|
111
|
+
configurable: false,
|
|
112
|
+
writable: false
|
|
113
|
+
});
|
|
114
|
+
return merged;
|
|
115
|
+
}
|
|
116
|
+
|
|
117
|
+
function resolveRoadmapFile(projectRoot, config, overridePath) {
|
|
118
|
+
if (overridePath) {
|
|
119
|
+
return path.resolve(projectRoot, overridePath);
|
|
120
|
+
}
|
|
121
|
+
|
|
122
|
+
const configuredRoadmapFile =
|
|
123
|
+
config && typeof config.roadmapFile === 'string' ? config.roadmapFile.trim() : '';
|
|
124
|
+
const hasExplicitRoadmapFile = Boolean(config && config.__roadmapFileExplicit);
|
|
125
|
+
const hasCustomConfigRoadmapFile =
|
|
126
|
+
configuredRoadmapFile.length > 0 &&
|
|
127
|
+
(hasExplicitRoadmapFile || configuredRoadmapFile !== DEFAULT_CONFIG.roadmapFile);
|
|
128
|
+
|
|
129
|
+
if (hasCustomConfigRoadmapFile) {
|
|
130
|
+
return path.resolve(projectRoot, configuredRoadmapFile);
|
|
131
|
+
}
|
|
132
|
+
|
|
133
|
+
let rootEntries = null;
|
|
134
|
+
try {
|
|
135
|
+
rootEntries = fs.readdirSync(projectRoot);
|
|
136
|
+
} catch {
|
|
137
|
+
rootEntries = null;
|
|
138
|
+
}
|
|
139
|
+
|
|
140
|
+
const canonicalRoadmapPath = path.resolve(projectRoot, DEFAULT_CONFIG.roadmapFile);
|
|
141
|
+
const legacyRoadmapPath = path.resolve(projectRoot, './roadmap.md');
|
|
142
|
+
|
|
143
|
+
const hasCanonicalRoadmap = Array.isArray(rootEntries)
|
|
144
|
+
? rootEntries.includes('ROADMAP.md')
|
|
145
|
+
: readTextIfExists(canonicalRoadmapPath) != null;
|
|
146
|
+
const hasLegacyRoadmap = Array.isArray(rootEntries)
|
|
147
|
+
? rootEntries.includes('roadmap.md')
|
|
148
|
+
: readTextIfExists(legacyRoadmapPath) != null;
|
|
149
|
+
|
|
150
|
+
if (hasCanonicalRoadmap) {
|
|
151
|
+
return canonicalRoadmapPath;
|
|
152
|
+
}
|
|
153
|
+
if (hasLegacyRoadmap) {
|
|
154
|
+
return legacyRoadmapPath;
|
|
155
|
+
}
|
|
156
|
+
return canonicalRoadmapPath;
|
|
157
|
+
}
|
|
158
|
+
|
|
159
|
+
function resolveAgentsFile(projectRoot, config, overridePath) {
|
|
160
|
+
if (overridePath) {
|
|
161
|
+
return path.resolve(projectRoot, overridePath);
|
|
162
|
+
}
|
|
163
|
+
|
|
164
|
+
const agentsPath = path.resolve(projectRoot, config.agentsFile || './AGENTS.md');
|
|
165
|
+
const claudePath = path.resolve(projectRoot, './CLAUDE.md');
|
|
166
|
+
|
|
167
|
+
if (readTextIfExists(agentsPath) != null) {
|
|
168
|
+
return agentsPath;
|
|
169
|
+
}
|
|
170
|
+
if (readTextIfExists(claudePath) != null) {
|
|
171
|
+
return claudePath;
|
|
172
|
+
}
|
|
173
|
+
return agentsPath;
|
|
174
|
+
}
|
|
175
|
+
|
|
176
|
+
function loadPlugins(projectRoot, pluginEntries) {
|
|
177
|
+
const plugins = [];
|
|
178
|
+
for (const entry of pluginEntries || []) {
|
|
179
|
+
const pluginPath = path.resolve(projectRoot, entry);
|
|
180
|
+
const pluginModule = require(pluginPath);
|
|
181
|
+
plugins.push({
|
|
182
|
+
name: path.basename(pluginPath),
|
|
183
|
+
path: pluginPath,
|
|
184
|
+
module: pluginModule
|
|
185
|
+
});
|
|
186
|
+
}
|
|
187
|
+
return plugins;
|
|
188
|
+
}
|
|
189
|
+
|
|
190
|
+
function collectPluginContributions(plugins, hookName, context) {
|
|
191
|
+
const contributions = [];
|
|
192
|
+
for (const plugin of plugins || []) {
|
|
193
|
+
const hook = plugin.module && plugin.module[hookName];
|
|
194
|
+
if (typeof hook !== 'function') {
|
|
195
|
+
continue;
|
|
196
|
+
}
|
|
197
|
+
let result;
|
|
198
|
+
try {
|
|
199
|
+
result = hook(context);
|
|
200
|
+
} catch (err) {
|
|
201
|
+
throw new Error(`Plugin "${plugin.name}" failed in hook "${hookName}": ${err.message}`);
|
|
202
|
+
}
|
|
203
|
+
if (Array.isArray(result)) {
|
|
204
|
+
for (const item of result) {
|
|
205
|
+
contributions.push(item);
|
|
206
|
+
}
|
|
207
|
+
}
|
|
208
|
+
}
|
|
209
|
+
return contributions;
|
|
210
|
+
}
|
|
211
|
+
|
|
212
|
+
module.exports = {
|
|
213
|
+
DEFAULT_CONFIG,
|
|
214
|
+
collectPluginContributions,
|
|
215
|
+
loadConfig,
|
|
216
|
+
loadPlugins,
|
|
217
|
+
resolveAgentsFile,
|
|
218
|
+
resolveRoadmapFile
|
|
219
|
+
};
|