brain-dev 0.1.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/LICENSE +21 -0
- package/README.md +152 -0
- package/agents/brain-checker.md +33 -0
- package/agents/brain-debugger.md +35 -0
- package/agents/brain-executor.md +37 -0
- package/agents/brain-mapper.md +44 -0
- package/agents/brain-planner.md +49 -0
- package/agents/brain-researcher.md +47 -0
- package/agents/brain-synthesizer.md +43 -0
- package/agents/brain-verifier.md +41 -0
- package/bin/brain-tools.cjs +185 -0
- package/bin/lib/adr.cjs +283 -0
- package/bin/lib/agents.cjs +152 -0
- package/bin/lib/anti-patterns.cjs +183 -0
- package/bin/lib/audit.cjs +268 -0
- package/bin/lib/commands/adr.cjs +126 -0
- package/bin/lib/commands/complete.cjs +270 -0
- package/bin/lib/commands/config.cjs +306 -0
- package/bin/lib/commands/discuss.cjs +237 -0
- package/bin/lib/commands/execute.cjs +415 -0
- package/bin/lib/commands/health.cjs +103 -0
- package/bin/lib/commands/map.cjs +101 -0
- package/bin/lib/commands/new-project.cjs +885 -0
- package/bin/lib/commands/pause.cjs +142 -0
- package/bin/lib/commands/phase-manage.cjs +357 -0
- package/bin/lib/commands/plan.cjs +451 -0
- package/bin/lib/commands/progress.cjs +167 -0
- package/bin/lib/commands/quick.cjs +447 -0
- package/bin/lib/commands/resume.cjs +196 -0
- package/bin/lib/commands/storm.cjs +590 -0
- package/bin/lib/commands/verify.cjs +504 -0
- package/bin/lib/commands.cjs +263 -0
- package/bin/lib/complexity.cjs +138 -0
- package/bin/lib/complexity.test.cjs +108 -0
- package/bin/lib/config.cjs +452 -0
- package/bin/lib/core.cjs +62 -0
- package/bin/lib/detect.cjs +603 -0
- package/bin/lib/git.cjs +112 -0
- package/bin/lib/health.cjs +356 -0
- package/bin/lib/init.cjs +310 -0
- package/bin/lib/logger.cjs +100 -0
- package/bin/lib/platform.cjs +58 -0
- package/bin/lib/requirements.cjs +158 -0
- package/bin/lib/roadmap.cjs +228 -0
- package/bin/lib/security.cjs +237 -0
- package/bin/lib/state.cjs +353 -0
- package/bin/lib/templates.cjs +48 -0
- package/bin/templates/advocate.md +182 -0
- package/bin/templates/checkpoint.md +55 -0
- package/bin/templates/debugger.md +148 -0
- package/bin/templates/discuss.md +60 -0
- package/bin/templates/executor.md +201 -0
- package/bin/templates/mapper.md +129 -0
- package/bin/templates/plan-checker.md +134 -0
- package/bin/templates/planner.md +165 -0
- package/bin/templates/researcher.md +78 -0
- package/bin/templates/storm.html +376 -0
- package/bin/templates/synthesis.md +30 -0
- package/bin/templates/verifier.md +181 -0
- package/commands/brain/adr.md +34 -0
- package/commands/brain/complete.md +37 -0
- package/commands/brain/config.md +37 -0
- package/commands/brain/discuss.md +35 -0
- package/commands/brain/execute.md +38 -0
- package/commands/brain/health.md +33 -0
- package/commands/brain/map.md +35 -0
- package/commands/brain/new-project.md +38 -0
- package/commands/brain/pause.md +26 -0
- package/commands/brain/plan.md +38 -0
- package/commands/brain/progress.md +28 -0
- package/commands/brain/quick.md +51 -0
- package/commands/brain/resume.md +28 -0
- package/commands/brain/storm.md +30 -0
- package/commands/brain/verify.md +39 -0
- package/hooks/bootstrap.sh +54 -0
- package/hooks/post-tool-use.sh +45 -0
- package/hooks/statusline.sh +130 -0
- package/package.json +36 -0
|
@@ -0,0 +1,263 @@
|
|
|
1
|
+
'use strict';
|
|
2
|
+
|
|
3
|
+
const path = require('node:path');
|
|
4
|
+
|
|
5
|
+
/**
|
|
6
|
+
* Command registry for all brain-dev CLI commands.
|
|
7
|
+
* Each entry defines name, description, usage, group, implemented status,
|
|
8
|
+
* whether it needs .brain/ state, and optional args documentation.
|
|
9
|
+
*/
|
|
10
|
+
const COMMANDS = [
|
|
11
|
+
// Setup
|
|
12
|
+
{
|
|
13
|
+
name: 'init',
|
|
14
|
+
description: 'Initialize brain in current project',
|
|
15
|
+
usage: 'brain-dev init [--force]',
|
|
16
|
+
group: 'Setup',
|
|
17
|
+
implemented: true,
|
|
18
|
+
needsState: false,
|
|
19
|
+
args: ' --force Wipe and recreate existing .brain/ directory'
|
|
20
|
+
},
|
|
21
|
+
{
|
|
22
|
+
name: 'new-project',
|
|
23
|
+
description: 'Start a new project with guided questioning',
|
|
24
|
+
usage: 'brain-dev new-project [--answers <json>] [--synthesize] [--generate-roadmap] [--finalize]',
|
|
25
|
+
group: 'Setup',
|
|
26
|
+
implemented: true,
|
|
27
|
+
needsState: true,
|
|
28
|
+
args: ' --answers <json> Provide user answers as JSON\n --synthesize Spawn synthesis agent\n --generate-roadmap Generate REQUIREMENTS.md and ROADMAP.md\n --finalize Finalize project setup\n --project-name <name> Project name (used with --finalize)'
|
|
29
|
+
},
|
|
30
|
+
{
|
|
31
|
+
name: 'help',
|
|
32
|
+
description: 'Show all available commands',
|
|
33
|
+
usage: 'brain-dev help [command]',
|
|
34
|
+
group: 'Setup',
|
|
35
|
+
implemented: true,
|
|
36
|
+
needsState: false,
|
|
37
|
+
args: ' [command] Show detailed help for a specific command'
|
|
38
|
+
},
|
|
39
|
+
|
|
40
|
+
// Lifecycle
|
|
41
|
+
{
|
|
42
|
+
name: 'discuss',
|
|
43
|
+
description: 'Capture decisions before planning',
|
|
44
|
+
usage: 'brain-dev discuss [--phase <n>] [--save --decisions <json>]',
|
|
45
|
+
group: 'Lifecycle',
|
|
46
|
+
implemented: true,
|
|
47
|
+
needsState: true,
|
|
48
|
+
args: ' --phase <n> Target phase to discuss (default: current)\n --save Save decisions to CONTEXT.md\n --decisions <json> Decisions JSON (used with --save)'
|
|
49
|
+
},
|
|
50
|
+
{
|
|
51
|
+
name: 'plan',
|
|
52
|
+
description: 'Create execution plans',
|
|
53
|
+
usage: 'brain-dev plan [--phase <n>] [--all]',
|
|
54
|
+
group: 'Lifecycle',
|
|
55
|
+
implemented: true,
|
|
56
|
+
needsState: true,
|
|
57
|
+
args: ' --phase <n> Target phase number to plan\n --all Plan all unplanned phases sequentially'
|
|
58
|
+
},
|
|
59
|
+
{
|
|
60
|
+
name: 'quick',
|
|
61
|
+
description: 'Quick task without full phase ceremony',
|
|
62
|
+
usage: 'brain-dev quick [--full] <description>',
|
|
63
|
+
group: 'Lifecycle',
|
|
64
|
+
implemented: true,
|
|
65
|
+
needsState: true,
|
|
66
|
+
args: ' --full Enable plan checking + verification\n --execute --task N Execute task N\n --verify --task N Verify task N (--full only)\n --complete --task N Complete and commit task N'
|
|
67
|
+
},
|
|
68
|
+
{
|
|
69
|
+
name: 'execute',
|
|
70
|
+
description: 'Execute plans with verification',
|
|
71
|
+
usage: 'brain-dev execute [--plan <id>]',
|
|
72
|
+
group: 'Lifecycle',
|
|
73
|
+
implemented: true,
|
|
74
|
+
needsState: true,
|
|
75
|
+
args: ' --plan <id> Specific plan to execute (e.g. 01-02)'
|
|
76
|
+
},
|
|
77
|
+
{
|
|
78
|
+
name: 'verify',
|
|
79
|
+
description: 'Run verification checks on completed work',
|
|
80
|
+
usage: 'brain-dev verify [--phase <n>]',
|
|
81
|
+
group: 'Lifecycle',
|
|
82
|
+
implemented: true,
|
|
83
|
+
needsState: true,
|
|
84
|
+
args: ' --phase <n> Phase to verify'
|
|
85
|
+
},
|
|
86
|
+
{
|
|
87
|
+
name: 'complete',
|
|
88
|
+
description: 'Mark phase or milestone as complete',
|
|
89
|
+
usage: 'brain-dev complete [--phase <n>]',
|
|
90
|
+
group: 'Lifecycle',
|
|
91
|
+
implemented: true,
|
|
92
|
+
needsState: true,
|
|
93
|
+
args: ' --phase <n> Phase to mark complete'
|
|
94
|
+
},
|
|
95
|
+
{
|
|
96
|
+
name: 'map',
|
|
97
|
+
description: 'Map codebase structure and patterns',
|
|
98
|
+
usage: 'brain-dev map [--focus tech|arch|quality|concerns]',
|
|
99
|
+
group: 'Lifecycle',
|
|
100
|
+
implemented: true,
|
|
101
|
+
needsState: true,
|
|
102
|
+
args: ' --focus <area> Focus on specific area: tech, arch, quality, or concerns'
|
|
103
|
+
},
|
|
104
|
+
{
|
|
105
|
+
name: 'phase',
|
|
106
|
+
description: 'Manage project phases (add, remove, reorder)',
|
|
107
|
+
usage: 'brain-dev phase <add|insert|remove|reorder|list>',
|
|
108
|
+
group: 'Lifecycle',
|
|
109
|
+
implemented: true,
|
|
110
|
+
needsState: true,
|
|
111
|
+
args: ' add Add a new phase\n insert Insert decimal phase (e.g. 2.1)\n remove Remove a phase\n reorder Reorder phases\n list Show all phases (default)'
|
|
112
|
+
},
|
|
113
|
+
|
|
114
|
+
// Session
|
|
115
|
+
{
|
|
116
|
+
name: 'status',
|
|
117
|
+
description: 'Show project state (used by SessionStart hook)',
|
|
118
|
+
usage: 'brain-dev status [--json]',
|
|
119
|
+
group: 'Session',
|
|
120
|
+
implemented: true,
|
|
121
|
+
needsState: true,
|
|
122
|
+
args: ' --json Output raw JSON (for hooks and piping)'
|
|
123
|
+
},
|
|
124
|
+
{
|
|
125
|
+
name: 'progress',
|
|
126
|
+
description: 'Show current progress and status',
|
|
127
|
+
usage: 'brain-dev progress [--verbose]',
|
|
128
|
+
group: 'Session',
|
|
129
|
+
implemented: true,
|
|
130
|
+
needsState: true,
|
|
131
|
+
args: ' --verbose Show full dashboard with all phases'
|
|
132
|
+
},
|
|
133
|
+
{
|
|
134
|
+
name: 'pause',
|
|
135
|
+
description: 'Save session state for later resumption',
|
|
136
|
+
usage: 'brain-dev pause [--note <msg>]',
|
|
137
|
+
group: 'Session',
|
|
138
|
+
implemented: true,
|
|
139
|
+
needsState: true,
|
|
140
|
+
args: ' --note <msg> Add a note about where you stopped'
|
|
141
|
+
},
|
|
142
|
+
{
|
|
143
|
+
name: 'resume',
|
|
144
|
+
description: 'Resume from last saved session state',
|
|
145
|
+
usage: 'brain-dev resume [--session <id>]',
|
|
146
|
+
group: 'Session',
|
|
147
|
+
implemented: true,
|
|
148
|
+
needsState: true,
|
|
149
|
+
args: ' --session <id> Resume a specific session by timestamp ID'
|
|
150
|
+
},
|
|
151
|
+
// Tools
|
|
152
|
+
{
|
|
153
|
+
name: 'storm',
|
|
154
|
+
description: 'Launch visual brainstorming server',
|
|
155
|
+
usage: 'brain-dev storm <topic> [--stop] [--port <n>] [--finalize]',
|
|
156
|
+
group: 'Tools',
|
|
157
|
+
implemented: true,
|
|
158
|
+
needsState: true,
|
|
159
|
+
args: ' <topic> Topic to brainstorm\n --stop Stop running server\n --port <n> Custom port number\n --finalize Generate output.md from fragments'
|
|
160
|
+
},
|
|
161
|
+
{
|
|
162
|
+
name: 'adr',
|
|
163
|
+
description: 'Manage architecture decision records',
|
|
164
|
+
usage: 'brain-dev adr <create|list|search> [options]',
|
|
165
|
+
group: 'Tools',
|
|
166
|
+
implemented: true,
|
|
167
|
+
needsState: true,
|
|
168
|
+
args: ' create Create new ADR interactively\n list [--phase <n>] List ADRs (optionally filtered by phase)\n search <term> Search ADR content'
|
|
169
|
+
},
|
|
170
|
+
|
|
171
|
+
// Config
|
|
172
|
+
{
|
|
173
|
+
name: 'config',
|
|
174
|
+
description: 'Manage brain configuration',
|
|
175
|
+
usage: 'brain-dev config [set|get|list|reset|docs|export|import] [args] [--global]',
|
|
176
|
+
group: 'Config',
|
|
177
|
+
implemented: true,
|
|
178
|
+
needsState: true,
|
|
179
|
+
args: ' set <key> <value> Set a configuration value\n get <key> Get a configuration value\n list [--category X] List all settings grouped by category\n reset [key|category] Reset to defaults\n docs Show full schema reference\n export Export config as portable JSON\n import <file.json> Import config from file\n --global Target ~/.brain/defaults.json'
|
|
180
|
+
},
|
|
181
|
+
|
|
182
|
+
// Meta
|
|
183
|
+
{
|
|
184
|
+
name: 'health',
|
|
185
|
+
description: 'Run health diagnostics and auto-repair safe issues',
|
|
186
|
+
usage: 'brain-dev health [--fix] [--quick] [--json]',
|
|
187
|
+
group: 'Meta',
|
|
188
|
+
implemented: true,
|
|
189
|
+
needsState: false,
|
|
190
|
+
args: ' --fix Enable aggressive repair of hooks and templates\n --quick Run safe checks only (used by bootstrap)\n --json Force JSON output'
|
|
191
|
+
},
|
|
192
|
+
{
|
|
193
|
+
name: 'version',
|
|
194
|
+
description: 'Show brain-dev version',
|
|
195
|
+
usage: 'brain-dev version',
|
|
196
|
+
group: 'Meta',
|
|
197
|
+
implemented: true,
|
|
198
|
+
needsState: false,
|
|
199
|
+
args: ''
|
|
200
|
+
}
|
|
201
|
+
];
|
|
202
|
+
|
|
203
|
+
/**
|
|
204
|
+
* Group order for display purposes.
|
|
205
|
+
*/
|
|
206
|
+
const GROUP_ORDER = ['Setup', 'Lifecycle', 'Session', 'Tools', 'Config', 'Meta'];
|
|
207
|
+
|
|
208
|
+
/**
|
|
209
|
+
* Get formatted help text for a single command.
|
|
210
|
+
* @param {string} name - Command name
|
|
211
|
+
* @returns {string|null} Formatted help text, or null if command not found
|
|
212
|
+
*/
|
|
213
|
+
function getCommandHelp(name) {
|
|
214
|
+
const cmd = COMMANDS.find(c => c.name === name);
|
|
215
|
+
if (!cmd) return null;
|
|
216
|
+
|
|
217
|
+
let text = `${cmd.name} -- ${cmd.description}\n\nUsage: ${cmd.usage}`;
|
|
218
|
+
if (cmd.args) {
|
|
219
|
+
text += `\n\nArguments:\n${cmd.args}`;
|
|
220
|
+
}
|
|
221
|
+
return text;
|
|
222
|
+
}
|
|
223
|
+
|
|
224
|
+
/**
|
|
225
|
+
* Generate full help text with commands grouped by lifecycle stage.
|
|
226
|
+
* @returns {string} Formatted help text
|
|
227
|
+
*/
|
|
228
|
+
function showHelp() {
|
|
229
|
+
const pkg = require(path.join(__dirname, '..', '..', 'package.json'));
|
|
230
|
+
const lines = [`[brain] v${pkg.version} - AI development workflow orchestrator`, ''];
|
|
231
|
+
|
|
232
|
+
for (const group of GROUP_ORDER) {
|
|
233
|
+
const cmds = COMMANDS.filter(c => c.group === group);
|
|
234
|
+
lines.push(`${group}:`);
|
|
235
|
+
for (const cmd of cmds) {
|
|
236
|
+
const padded = cmd.name.padEnd(14);
|
|
237
|
+
lines.push(` ${padded}${cmd.description}`);
|
|
238
|
+
}
|
|
239
|
+
lines.push('');
|
|
240
|
+
}
|
|
241
|
+
|
|
242
|
+
return lines.join('\n').trimEnd();
|
|
243
|
+
}
|
|
244
|
+
|
|
245
|
+
/**
|
|
246
|
+
* Show detailed help for a single unimplemented command.
|
|
247
|
+
* Includes command name, description, usage, args, and status.
|
|
248
|
+
* @param {string} name - Command name
|
|
249
|
+
* @returns {string} Formatted help text
|
|
250
|
+
*/
|
|
251
|
+
function showCommandHelp(name) {
|
|
252
|
+
const cmd = COMMANDS.find(c => c.name === name);
|
|
253
|
+
if (!cmd) return null;
|
|
254
|
+
|
|
255
|
+
let text = `[brain] /brain:${cmd.name} -- ${cmd.description}\n\nUsage: ${cmd.usage}`;
|
|
256
|
+
if (cmd.args) {
|
|
257
|
+
text += `\n${cmd.args}`;
|
|
258
|
+
}
|
|
259
|
+
text += '\n\nStatus: Coming soon.';
|
|
260
|
+
return text;
|
|
261
|
+
}
|
|
262
|
+
|
|
263
|
+
module.exports = { COMMANDS, getCommandHelp, showHelp, showCommandHelp };
|
|
@@ -0,0 +1,138 @@
|
|
|
1
|
+
'use strict';
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* Complexity budget scoring module.
|
|
5
|
+
* Provides weighted scoring for plan complexity, budget checking,
|
|
6
|
+
* and estimation from plan frontmatter.
|
|
7
|
+
*
|
|
8
|
+
* No external dependencies.
|
|
9
|
+
*/
|
|
10
|
+
|
|
11
|
+
const WEIGHTS = {
|
|
12
|
+
fileCount: 1,
|
|
13
|
+
functionCount: 1.5,
|
|
14
|
+
dependencyDepth: 2,
|
|
15
|
+
nestingDepth: 2,
|
|
16
|
+
linesOfCode: 0.5
|
|
17
|
+
};
|
|
18
|
+
|
|
19
|
+
const BUDGETS = {
|
|
20
|
+
scaffolding: 80,
|
|
21
|
+
standard: 60,
|
|
22
|
+
production: 40
|
|
23
|
+
};
|
|
24
|
+
|
|
25
|
+
/**
|
|
26
|
+
* Calculate a weighted complexity score from metrics.
|
|
27
|
+
* @param {object} metrics - { fileCount, functionCount, dependencyDepth, nestingDepth, linesOfCode }
|
|
28
|
+
* @returns {number} Rounded weighted sum
|
|
29
|
+
*/
|
|
30
|
+
function calculateScore(metrics) {
|
|
31
|
+
if (!metrics || typeof metrics !== 'object') return 0;
|
|
32
|
+
|
|
33
|
+
let sum = 0;
|
|
34
|
+
for (const [key, weight] of Object.entries(WEIGHTS)) {
|
|
35
|
+
const value = metrics[key];
|
|
36
|
+
if (typeof value === 'number' && !Number.isNaN(value)) {
|
|
37
|
+
sum += value * weight;
|
|
38
|
+
}
|
|
39
|
+
}
|
|
40
|
+
return Math.round(sum);
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
/**
|
|
44
|
+
* Check whether a score exceeds a budget.
|
|
45
|
+
* @param {number} score - Calculated complexity score
|
|
46
|
+
* @param {number} budget - Budget threshold
|
|
47
|
+
* @returns {object} { exceeded, score, budget, remaining|overage }
|
|
48
|
+
*/
|
|
49
|
+
function checkBudget(score, budget) {
|
|
50
|
+
if (score > budget) {
|
|
51
|
+
return { exceeded: true, score, budget, overage: score - budget };
|
|
52
|
+
}
|
|
53
|
+
return { exceeded: false, score, budget, remaining: budget - score };
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
/**
|
|
57
|
+
* Generate a per-metric breakdown showing how each metric contributes.
|
|
58
|
+
* @param {object} metrics - Same shape as calculateScore input
|
|
59
|
+
* @returns {object} Per-metric { raw, weight, contribution }
|
|
60
|
+
*/
|
|
61
|
+
function generateBreakdown(metrics) {
|
|
62
|
+
if (!metrics || typeof metrics !== 'object') return {};
|
|
63
|
+
|
|
64
|
+
const result = {};
|
|
65
|
+
for (const [key, weight] of Object.entries(WEIGHTS)) {
|
|
66
|
+
const raw = typeof metrics[key] === 'number' ? metrics[key] : 0;
|
|
67
|
+
result[key] = {
|
|
68
|
+
raw,
|
|
69
|
+
weight,
|
|
70
|
+
contribution: raw * weight
|
|
71
|
+
};
|
|
72
|
+
}
|
|
73
|
+
return result;
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
/**
|
|
77
|
+
* Get the default complexity budget for a phase type.
|
|
78
|
+
* @param {string} phaseType - 'scaffolding' | 'standard' | 'production'
|
|
79
|
+
* @returns {number} Budget value
|
|
80
|
+
*/
|
|
81
|
+
function getDefaultBudget(phaseType) {
|
|
82
|
+
return BUDGETS[phaseType] ?? 60;
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
/**
|
|
86
|
+
* Estimate complexity metrics from plan content (frontmatter).
|
|
87
|
+
* Parses files_modified count and task count to produce heuristic estimates.
|
|
88
|
+
* @param {string} planContent - Raw plan file content
|
|
89
|
+
* @returns {object} { metrics, score }
|
|
90
|
+
*/
|
|
91
|
+
function estimateFromPlan(planContent) {
|
|
92
|
+
if (!planContent || typeof planContent !== 'string') {
|
|
93
|
+
return { metrics: { fileCount: 0, functionCount: 0, dependencyDepth: 2, nestingDepth: 2, linesOfCode: 0 }, score: 0 };
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
// Extract frontmatter
|
|
97
|
+
const fmMatch = planContent.match(/^---\n([\s\S]*?)\n---/);
|
|
98
|
+
const frontmatter = fmMatch ? fmMatch[1] : '';
|
|
99
|
+
|
|
100
|
+
// Count files_modified entries (lines starting with " - " after files_modified:)
|
|
101
|
+
let fileCount = 0;
|
|
102
|
+
const fmLines = frontmatter.split('\n');
|
|
103
|
+
let inFilesModified = false;
|
|
104
|
+
for (const line of fmLines) {
|
|
105
|
+
if (line.startsWith('files_modified:')) {
|
|
106
|
+
inFilesModified = true;
|
|
107
|
+
continue;
|
|
108
|
+
}
|
|
109
|
+
if (inFilesModified) {
|
|
110
|
+
if (line.match(/^\s+-\s+/)) {
|
|
111
|
+
fileCount++;
|
|
112
|
+
} else {
|
|
113
|
+
inFilesModified = false;
|
|
114
|
+
}
|
|
115
|
+
}
|
|
116
|
+
}
|
|
117
|
+
|
|
118
|
+
const metrics = {
|
|
119
|
+
fileCount,
|
|
120
|
+
functionCount: fileCount * 3,
|
|
121
|
+
dependencyDepth: 2,
|
|
122
|
+
nestingDepth: 2,
|
|
123
|
+
linesOfCode: fileCount * 80
|
|
124
|
+
};
|
|
125
|
+
|
|
126
|
+
return {
|
|
127
|
+
metrics,
|
|
128
|
+
score: calculateScore(metrics)
|
|
129
|
+
};
|
|
130
|
+
}
|
|
131
|
+
|
|
132
|
+
module.exports = {
|
|
133
|
+
calculateScore,
|
|
134
|
+
checkBudget,
|
|
135
|
+
generateBreakdown,
|
|
136
|
+
getDefaultBudget,
|
|
137
|
+
estimateFromPlan
|
|
138
|
+
};
|
|
@@ -0,0 +1,108 @@
|
|
|
1
|
+
'use strict';
|
|
2
|
+
|
|
3
|
+
const assert = require('node:assert');
|
|
4
|
+
|
|
5
|
+
// Tests for complexity.cjs
|
|
6
|
+
// RED phase: these should fail until implementation exists
|
|
7
|
+
|
|
8
|
+
const c = require('./complexity.cjs');
|
|
9
|
+
|
|
10
|
+
// calculateScore tests
|
|
11
|
+
{
|
|
12
|
+
const score = c.calculateScore({
|
|
13
|
+
fileCount: 5,
|
|
14
|
+
functionCount: 10,
|
|
15
|
+
dependencyDepth: 3,
|
|
16
|
+
nestingDepth: 4,
|
|
17
|
+
linesOfCode: 200
|
|
18
|
+
});
|
|
19
|
+
assert.strictEqual(score, 134, `calculateScore full metrics: expected 134, got ${score}`);
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
{
|
|
23
|
+
const score = c.calculateScore({});
|
|
24
|
+
assert.strictEqual(score, 0, `calculateScore empty: expected 0, got ${score}`);
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
{
|
|
28
|
+
const score = c.calculateScore({ fileCount: 10 });
|
|
29
|
+
assert.strictEqual(score, 10, `calculateScore single metric: expected 10, got ${score}`);
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
// getDefaultBudget tests
|
|
33
|
+
{
|
|
34
|
+
assert.strictEqual(c.getDefaultBudget('scaffolding'), 80);
|
|
35
|
+
assert.strictEqual(c.getDefaultBudget('standard'), 60);
|
|
36
|
+
assert.strictEqual(c.getDefaultBudget('production'), 40);
|
|
37
|
+
assert.strictEqual(c.getDefaultBudget('unknown'), 60);
|
|
38
|
+
assert.strictEqual(c.getDefaultBudget(), 60);
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
// checkBudget tests
|
|
42
|
+
{
|
|
43
|
+
const under = c.checkBudget(50, 60);
|
|
44
|
+
assert.strictEqual(under.exceeded, false);
|
|
45
|
+
assert.strictEqual(under.score, 50);
|
|
46
|
+
assert.strictEqual(under.budget, 60);
|
|
47
|
+
assert.strictEqual(under.remaining, 10);
|
|
48
|
+
assert.strictEqual('overage' in under, false);
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
{
|
|
52
|
+
const over = c.checkBudget(70, 60);
|
|
53
|
+
assert.strictEqual(over.exceeded, true);
|
|
54
|
+
assert.strictEqual(over.score, 70);
|
|
55
|
+
assert.strictEqual(over.budget, 60);
|
|
56
|
+
assert.strictEqual(over.overage, 10);
|
|
57
|
+
assert.strictEqual('remaining' in over, false);
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
{
|
|
61
|
+
const exact = c.checkBudget(60, 60);
|
|
62
|
+
assert.strictEqual(exact.exceeded, false);
|
|
63
|
+
assert.strictEqual(exact.remaining, 0);
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
// generateBreakdown tests
|
|
67
|
+
{
|
|
68
|
+
const breakdown = c.generateBreakdown({
|
|
69
|
+
fileCount: 5,
|
|
70
|
+
functionCount: 10,
|
|
71
|
+
dependencyDepth: 3,
|
|
72
|
+
nestingDepth: 4,
|
|
73
|
+
linesOfCode: 200
|
|
74
|
+
});
|
|
75
|
+
assert.strictEqual(breakdown.fileCount.raw, 5);
|
|
76
|
+
assert.strictEqual(breakdown.fileCount.weight, 1);
|
|
77
|
+
assert.strictEqual(breakdown.fileCount.contribution, 5);
|
|
78
|
+
assert.strictEqual(breakdown.functionCount.contribution, 15);
|
|
79
|
+
assert.strictEqual(breakdown.linesOfCode.contribution, 100);
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
// estimateFromPlan tests
|
|
83
|
+
{
|
|
84
|
+
const planContent = `---
|
|
85
|
+
phase: 01-foundation
|
|
86
|
+
plan: 01
|
|
87
|
+
files_modified:
|
|
88
|
+
- src/a.js
|
|
89
|
+
- src/b.js
|
|
90
|
+
- src/c.js
|
|
91
|
+
---
|
|
92
|
+
|
|
93
|
+
<tasks>
|
|
94
|
+
<task type="auto">Task 1</task>
|
|
95
|
+
<task type="auto">Task 2</task>
|
|
96
|
+
</tasks>`;
|
|
97
|
+
|
|
98
|
+
const est = c.estimateFromPlan(planContent);
|
|
99
|
+
assert.strictEqual(est.metrics.fileCount, 3);
|
|
100
|
+
assert.strictEqual(est.metrics.functionCount, 9); // 3 * 3
|
|
101
|
+
assert.strictEqual(est.metrics.dependencyDepth, 2);
|
|
102
|
+
assert.strictEqual(est.metrics.nestingDepth, 2);
|
|
103
|
+
assert.strictEqual(est.metrics.linesOfCode, 240); // 3 * 80
|
|
104
|
+
assert.strictEqual(typeof est.score, 'number');
|
|
105
|
+
assert.ok(est.score > 0);
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
console.log('All complexity tests passed');
|