complexity-cli 1.0.1 → 1.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/README.md +6 -0
- package/dist/index.js +487 -46
- package/package.json +10 -6
- package/dist/commands/add.d.ts +0 -4
- package/dist/commands/add.d.ts.map +0 -1
- package/dist/commands/add.js +0 -109
- package/dist/commands/add.js.map +0 -1
- package/dist/commands/init.d.ts +0 -5
- package/dist/commands/init.d.ts.map +0 -1
- package/dist/commands/init.js +0 -85
- package/dist/commands/init.js.map +0 -1
- package/dist/commands/list.d.ts +0 -2
- package/dist/commands/list.d.ts.map +0 -1
- package/dist/commands/list.js +0 -58
- package/dist/commands/list.js.map +0 -1
- package/dist/commands/prompt.d.ts +0 -2
- package/dist/commands/prompt.d.ts.map +0 -1
- package/dist/commands/prompt.js +0 -38
- package/dist/commands/prompt.js.map +0 -1
- package/dist/commands/remove.d.ts +0 -4
- package/dist/commands/remove.d.ts.map +0 -1
- package/dist/commands/remove.js +0 -86
- package/dist/commands/remove.js.map +0 -1
- package/dist/commands/update.d.ts +0 -6
- package/dist/commands/update.d.ts.map +0 -1
- package/dist/commands/update.js +0 -125
- package/dist/commands/update.js.map +0 -1
- package/dist/core/generator.d.ts +0 -5
- package/dist/core/generator.d.ts.map +0 -1
- package/dist/core/generator.js +0 -96
- package/dist/core/generator.js.map +0 -1
- package/dist/core/parser.d.ts +0 -4
- package/dist/core/parser.d.ts.map +0 -1
- package/dist/core/parser.js +0 -144
- package/dist/core/parser.js.map +0 -1
- package/dist/core/validator.d.ts +0 -6
- package/dist/core/validator.d.ts.map +0 -1
- package/dist/core/validator.js +0 -22
- package/dist/core/validator.js.map +0 -1
- package/dist/index.d.ts +0 -3
- package/dist/index.d.ts.map +0 -1
- package/dist/index.js.map +0 -1
- package/dist/utils/error-helpers.d.ts +0 -4
- package/dist/utils/error-helpers.d.ts.map +0 -1
- package/dist/utils/error-helpers.js +0 -26
- package/dist/utils/error-helpers.js.map +0 -1
- package/dist/utils/prompts.d.ts +0 -3
- package/dist/utils/prompts.d.ts.map +0 -1
- package/dist/utils/prompts.js +0 -37
- package/dist/utils/prompts.js.map +0 -1
- package/dist/utils/types.d.ts +0 -17
- package/dist/utils/types.d.ts.map +0 -1
- package/dist/utils/types.js +0 -3
- package/dist/utils/types.js.map +0 -1
package/README.md
CHANGED
|
@@ -1,5 +1,7 @@
|
|
|
1
1
|
# complexity-cli
|
|
2
2
|
|
|
3
|
+
[](https://www.npmjs.com/package/complexity-cli)
|
|
4
|
+
|
|
3
5
|
A CLI tool for managing project complexity and knowledge maps. Perfect for
|
|
4
6
|
tracking what developers need to know to work on your project, with AI-friendly
|
|
5
7
|
workflows.
|
|
@@ -114,6 +116,10 @@ This outputs a comprehensive prompt that instructs an AI to:
|
|
|
114
116
|
- Use the `complexity` CLI to maintain consistency
|
|
115
117
|
- Consider all relevant technologies and concepts
|
|
116
118
|
|
|
119
|
+
> Note: I really recommend using a larger, thinking AI model like Claude Opus or
|
|
120
|
+
> GPT 5.x. This tool's duty isn't the analysis of the project. But, rather the
|
|
121
|
+
> maintenance of a simple markdown document. Bigger model, better time.
|
|
122
|
+
|
|
117
123
|
## Features
|
|
118
124
|
|
|
119
125
|
- **Simple CLI**: Easy-to-use commands for managing complexity documentation
|
package/dist/index.js
CHANGED
|
@@ -1,48 +1,489 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
const
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
2
|
+
import { Command } from "commander";
|
|
3
|
+
import path from "node:path";
|
|
4
|
+
import { fileURLToPath } from "node:url";
|
|
5
|
+
import * as fs from "fs";
|
|
6
|
+
import * as path$1 from "path";
|
|
7
|
+
import chalk from "chalk";
|
|
8
|
+
import inquirer from "inquirer";
|
|
9
|
+
|
|
10
|
+
//#region node_modules/tsdown/esm-shims.js
|
|
11
|
+
const getFilename = () => fileURLToPath(import.meta.url);
|
|
12
|
+
const getDirname = () => path.dirname(getFilename());
|
|
13
|
+
const __dirname = /* @__PURE__ */ getDirname();
|
|
14
|
+
|
|
15
|
+
//#endregion
|
|
16
|
+
//#region src/utils/prompts.ts
|
|
17
|
+
async function confirmAction(message) {
|
|
18
|
+
const { confirmed } = await inquirer.prompt([{
|
|
19
|
+
type: "confirm",
|
|
20
|
+
name: "confirmed",
|
|
21
|
+
message,
|
|
22
|
+
default: false
|
|
23
|
+
}]);
|
|
24
|
+
return confirmed;
|
|
25
|
+
}
|
|
26
|
+
async function promptForInput(message, defaultValue) {
|
|
27
|
+
const { value } = await inquirer.prompt([{
|
|
28
|
+
type: "input",
|
|
29
|
+
name: "value",
|
|
30
|
+
message,
|
|
31
|
+
default: defaultValue,
|
|
32
|
+
validate: (input) => {
|
|
33
|
+
if (!input || input.trim() === "") return "This field is required";
|
|
34
|
+
return true;
|
|
35
|
+
}
|
|
36
|
+
}]);
|
|
37
|
+
return value;
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
//#endregion
|
|
41
|
+
//#region src/utils/error-helpers.ts
|
|
42
|
+
function exitWithError(message, hint) {
|
|
43
|
+
console.error(chalk.red(`Error: ${message}`));
|
|
44
|
+
if (hint) console.error(chalk.yellow(hint));
|
|
45
|
+
process.exit(1);
|
|
46
|
+
}
|
|
47
|
+
function exitWithUsageError(message, usageExample) {
|
|
48
|
+
console.error(chalk.red(`Error: ${message}`));
|
|
49
|
+
console.error(chalk.yellow(`Usage: ${usageExample}`));
|
|
50
|
+
process.exit(1);
|
|
51
|
+
}
|
|
52
|
+
function exitCancelled() {
|
|
53
|
+
console.log(chalk.yellow("Cancelled."));
|
|
54
|
+
process.exit(0);
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
//#endregion
|
|
58
|
+
//#region src/commands/init.ts
|
|
59
|
+
const TEMPLATE_FILE = path$1.join(__dirname, "../Complexity.template.md");
|
|
60
|
+
const OUTPUT_FILE = "COMPLEXITY.md";
|
|
61
|
+
function getOutputPath() {
|
|
62
|
+
return path$1.join(process.cwd(), OUTPUT_FILE);
|
|
63
|
+
}
|
|
64
|
+
function validateFileDoesNotExist(outputPath, force) {
|
|
65
|
+
if (fs.existsSync(outputPath) && !force) exitWithError(`${OUTPUT_FILE} already exists in this directory.`, "Use --force to overwrite it.");
|
|
66
|
+
}
|
|
67
|
+
async function getProjectName(providedName) {
|
|
68
|
+
if (providedName) return providedName;
|
|
69
|
+
return promptForInput("Enter project name:", path$1.basename(process.cwd()));
|
|
70
|
+
}
|
|
71
|
+
function readTemplateFile() {
|
|
72
|
+
if (!fs.existsSync(TEMPLATE_FILE)) exitWithError(`Template file not found at ${TEMPLATE_FILE}`);
|
|
73
|
+
return fs.readFileSync(TEMPLATE_FILE, "utf-8");
|
|
74
|
+
}
|
|
75
|
+
function initializeTemplateContent(template, projectName) {
|
|
76
|
+
return template.replace(/\{Project Name\}/g, projectName).replace(/\{X\}/g, "0").replace(/\{Y\}/g, "0").replace(/\{A\}/g, "0").replace(/\{B\}/g, "0").replace(/\{C\}/g, "0");
|
|
77
|
+
}
|
|
78
|
+
async function initCommand(options) {
|
|
79
|
+
const outputPath = getOutputPath();
|
|
80
|
+
validateFileDoesNotExist(outputPath, options.force ?? false);
|
|
81
|
+
const projectName = await getProjectName(options.name);
|
|
82
|
+
const content = initializeTemplateContent(readTemplateFile(), projectName);
|
|
83
|
+
fs.writeFileSync(outputPath, content, "utf-8");
|
|
84
|
+
console.log(chalk.green(`✓ Created ${OUTPUT_FILE} for project: ${projectName}`));
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
//#endregion
|
|
88
|
+
//#region src/core/parser.ts
|
|
89
|
+
const COMPLEXITY_FILE = "COMPLEXITY.md";
|
|
90
|
+
const BLANK_AREA$2 = "___";
|
|
91
|
+
const DEFAULT_PROJECT_NAME = "Project Name";
|
|
92
|
+
function extractProjectName(lines) {
|
|
93
|
+
const projectNameLine = lines[3];
|
|
94
|
+
if (!projectNameLine) return DEFAULT_PROJECT_NAME;
|
|
95
|
+
const match = projectNameLine.match(/A guide to what you need to know to work on (.+?), broken/);
|
|
96
|
+
return match ? match[1] : DEFAULT_PROJECT_NAME;
|
|
97
|
+
}
|
|
98
|
+
function findTableStartIndex(lines) {
|
|
99
|
+
for (let i = 0; i < lines.length; i++) if (lines[i].trim().startsWith("| Topic | Area | Criticality")) return i + 2;
|
|
100
|
+
return -1;
|
|
101
|
+
}
|
|
102
|
+
function isValidCriticality(criticality) {
|
|
103
|
+
return !isNaN(criticality) && criticality >= 1 && criticality <= 3;
|
|
104
|
+
}
|
|
105
|
+
function parseTableRow(line) {
|
|
106
|
+
const parts = line.split("|").map((p) => p.trim()).filter((p) => p);
|
|
107
|
+
if (parts.length !== 3) return null;
|
|
108
|
+
const [topic, area, criticalityStr] = parts;
|
|
109
|
+
if (!topic || topic === "Topic") return null;
|
|
110
|
+
const criticality = parseInt(criticalityStr);
|
|
111
|
+
if (!isValidCriticality(criticality)) return null;
|
|
112
|
+
return {
|
|
113
|
+
topic,
|
|
114
|
+
area: area || BLANK_AREA$2,
|
|
115
|
+
criticality
|
|
116
|
+
};
|
|
117
|
+
}
|
|
118
|
+
function parseTableRows(lines, startIndex) {
|
|
119
|
+
if (startIndex === -1) return [];
|
|
120
|
+
const concepts = [];
|
|
121
|
+
for (let i = startIndex; i < lines.length; i++) {
|
|
122
|
+
const line = lines[i].trim();
|
|
123
|
+
if (!line.startsWith("|")) break;
|
|
124
|
+
const concept = parseTableRow(line);
|
|
125
|
+
if (concept) concepts.push(concept);
|
|
126
|
+
}
|
|
127
|
+
return concepts;
|
|
128
|
+
}
|
|
129
|
+
function parseComplexityFile(filePath) {
|
|
130
|
+
if (!fs.existsSync(filePath)) throw new Error(`COMPLEXITY.md not found at: ${filePath}`);
|
|
131
|
+
const lines = fs.readFileSync(filePath, "utf-8").split("\n");
|
|
132
|
+
return {
|
|
133
|
+
projectName: extractProjectName(lines),
|
|
134
|
+
concepts: parseTableRows(lines, findTableStartIndex(lines))
|
|
135
|
+
};
|
|
136
|
+
}
|
|
137
|
+
function findGitRoot(startDir) {
|
|
138
|
+
let searchDir = startDir;
|
|
139
|
+
const filesystemRoot = path$1.parse(searchDir).root;
|
|
140
|
+
while (searchDir !== filesystemRoot) {
|
|
141
|
+
if (fs.existsSync(path$1.join(searchDir, ".git"))) return searchDir;
|
|
142
|
+
searchDir = path$1.dirname(searchDir);
|
|
143
|
+
}
|
|
144
|
+
return null;
|
|
145
|
+
}
|
|
146
|
+
function searchUpwardForFile(startDir, filename, stopAtDir) {
|
|
147
|
+
let currentDir = startDir;
|
|
148
|
+
while (true) {
|
|
149
|
+
const filePath = path$1.join(currentDir, filename);
|
|
150
|
+
if (fs.existsSync(filePath)) return filePath;
|
|
151
|
+
if (currentDir === stopAtDir) break;
|
|
152
|
+
const parentDir = path$1.dirname(currentDir);
|
|
153
|
+
if (parentDir === currentDir) break;
|
|
154
|
+
currentDir = parentDir;
|
|
155
|
+
}
|
|
156
|
+
return null;
|
|
157
|
+
}
|
|
158
|
+
function findComplexityFile(startDir = process.cwd()) {
|
|
159
|
+
const resolvedStartDir = path$1.resolve(startDir);
|
|
160
|
+
return searchUpwardForFile(resolvedStartDir, COMPLEXITY_FILE, findGitRoot(resolvedStartDir) || path$1.parse(resolvedStartDir).root);
|
|
161
|
+
}
|
|
162
|
+
|
|
163
|
+
//#endregion
|
|
164
|
+
//#region src/core/generator.ts
|
|
165
|
+
function calculateStats(concepts) {
|
|
166
|
+
const uniqueAreas = new Set(concepts.map((c) => c.area).filter((a) => a && a !== "___"));
|
|
167
|
+
return {
|
|
168
|
+
totalConcepts: concepts.length,
|
|
169
|
+
totalAreas: uniqueAreas.size,
|
|
170
|
+
criticalCount: concepts.filter((c) => c.criticality === 3).length,
|
|
171
|
+
importantCount: concepts.filter((c) => c.criticality === 2).length,
|
|
172
|
+
situationalCount: concepts.filter((c) => c.criticality === 1).length
|
|
173
|
+
};
|
|
174
|
+
}
|
|
175
|
+
function sortConcepts(concepts) {
|
|
176
|
+
return [...concepts].sort((a, b) => {
|
|
177
|
+
if (a.criticality !== b.criticality) return b.criticality - a.criticality;
|
|
178
|
+
return a.topic.localeCompare(b.topic);
|
|
179
|
+
});
|
|
180
|
+
}
|
|
181
|
+
function generateMarkdown(projectName, concepts) {
|
|
182
|
+
const stats = calculateStats(concepts);
|
|
183
|
+
const sorted = sortConcepts(concepts);
|
|
184
|
+
const critical = sorted.filter((c) => c.criticality === 3);
|
|
185
|
+
const important = sorted.filter((c) => c.criticality === 2);
|
|
186
|
+
const situational = sorted.filter((c) => c.criticality === 1);
|
|
187
|
+
const lines = [];
|
|
188
|
+
lines.push("# Project Complexity & Knowledge Map");
|
|
189
|
+
lines.push("");
|
|
190
|
+
lines.push(`<!-- Replace {Project Name} with your project name -->`);
|
|
191
|
+
lines.push(`A guide to what you need to know to work on ${projectName}, broken into three tiers.`);
|
|
192
|
+
lines.push("");
|
|
193
|
+
lines.push(`> **${stats.totalConcepts} technologies/concepts** across ${stats.totalAreas} areas`);
|
|
194
|
+
lines.push(`> **${stats.criticalCount} critical**`);
|
|
195
|
+
lines.push(`> **${stats.importantCount} important**`);
|
|
196
|
+
lines.push(`> **${stats.situationalCount} situational**`);
|
|
197
|
+
lines.push("");
|
|
198
|
+
lines.push("---");
|
|
199
|
+
lines.push("");
|
|
200
|
+
lines.push("## **What you NEED to know to do any meaningful work**");
|
|
201
|
+
lines.push("");
|
|
202
|
+
if (critical.length > 0) critical.forEach((c) => lines.push(`- ${c.topic}`));
|
|
203
|
+
else lines.push("<!-- Criticality 3 items from the table below -->");
|
|
204
|
+
lines.push("");
|
|
205
|
+
lines.push("## **What you SHOULD know to be very helpful**");
|
|
206
|
+
lines.push("");
|
|
207
|
+
if (important.length > 0) important.forEach((c) => lines.push(`- ${c.topic}`));
|
|
208
|
+
else lines.push("<!-- Everything above, plus Criticality 2 items -->");
|
|
209
|
+
lines.push("");
|
|
210
|
+
lines.push("## **What you should EVENTUALLY learn for specific areas**");
|
|
211
|
+
lines.push("");
|
|
212
|
+
if (situational.length > 0) situational.forEach((c) => lines.push(`- ${c.topic}`));
|
|
213
|
+
else lines.push("<!-- Criticality 1 items -->");
|
|
214
|
+
lines.push("");
|
|
215
|
+
lines.push("---");
|
|
216
|
+
lines.push("");
|
|
217
|
+
lines.push("## Full Reference");
|
|
218
|
+
lines.push("");
|
|
219
|
+
lines.push("<!--");
|
|
220
|
+
lines.push(" Criticality scale:");
|
|
221
|
+
lines.push(" 3 = Can't do meaningful work without it");
|
|
222
|
+
lines.push(" 2 = Will encounter regularly; gaps will slow you down");
|
|
223
|
+
lines.push(" 1 = Comes up occasionally or is abstracted away enough to learn on the job");
|
|
224
|
+
lines.push("-->");
|
|
225
|
+
lines.push("");
|
|
226
|
+
lines.push("| Topic | Area | Criticality (1-3) |");
|
|
227
|
+
lines.push("|---|---|---|");
|
|
228
|
+
if (sorted.length > 0) sorted.forEach((c) => {
|
|
229
|
+
lines.push(`| ${c.topic} | ${c.area} | ${c.criticality} |`);
|
|
230
|
+
});
|
|
231
|
+
else lines.push("| | | |");
|
|
232
|
+
lines.push("");
|
|
233
|
+
return lines.join("\n");
|
|
234
|
+
}
|
|
235
|
+
|
|
236
|
+
//#endregion
|
|
237
|
+
//#region src/core/validator.ts
|
|
238
|
+
function isDuplicate(concept, existingConcepts) {
|
|
239
|
+
return existingConcepts.some((c) => c.topic.toLowerCase() === concept.toLowerCase());
|
|
240
|
+
}
|
|
241
|
+
function isValidLevel(level) {
|
|
242
|
+
const num = parseInt(level);
|
|
243
|
+
return !isNaN(num) && num >= 1 && num <= 3;
|
|
244
|
+
}
|
|
245
|
+
function isExistingArea(area, existingConcepts) {
|
|
246
|
+
if (!area || area === "___") return true;
|
|
247
|
+
return existingConcepts.some((c) => c.area.toLowerCase() === area.toLowerCase());
|
|
248
|
+
}
|
|
249
|
+
function findConcept(conceptName, concepts) {
|
|
250
|
+
return concepts.find((c) => c.topic.toLowerCase() === conceptName.toLowerCase());
|
|
251
|
+
}
|
|
252
|
+
|
|
253
|
+
//#endregion
|
|
254
|
+
//#region src/commands/add.ts
|
|
255
|
+
const BLANK_AREA$1 = "___";
|
|
256
|
+
function getLevelInput(levelArg, options) {
|
|
257
|
+
const levelInput = options.level || levelArg;
|
|
258
|
+
if (!levelInput) exitWithUsageError("Criticality level is required.", "complexity add <concept> <level> [area]\n or: complexity add <concept> --level <level> [area]");
|
|
259
|
+
return levelInput;
|
|
260
|
+
}
|
|
261
|
+
function validateLevel(levelInput) {
|
|
262
|
+
if (!isValidLevel(levelInput)) exitWithError("Level must be 1, 2, or 3.");
|
|
263
|
+
return parseInt(levelInput);
|
|
264
|
+
}
|
|
265
|
+
function requireComplexityFile$3() {
|
|
266
|
+
const filePath = findComplexityFile();
|
|
267
|
+
if (!filePath) exitWithError("COMPLEXITY.md not found.", "Run \"complexity init\" to create one.");
|
|
268
|
+
return filePath;
|
|
269
|
+
}
|
|
270
|
+
function validateConceptDoesNotExist(concept, existingConcepts) {
|
|
271
|
+
if (isDuplicate(concept, existingConcepts)) exitWithError(`Concept "${concept}" already exists.`, "Use \"complexity update\" to modify it.");
|
|
272
|
+
}
|
|
273
|
+
async function confirmNewAreaIfNeeded$1(area, existingConcepts) {
|
|
274
|
+
if (area === BLANK_AREA$1) return;
|
|
275
|
+
if (isExistingArea(area, existingConcepts)) return;
|
|
276
|
+
if (!await confirmAction(`No pre-existing area "${area}" found. Would you still like to add it?`)) exitCancelled();
|
|
277
|
+
}
|
|
278
|
+
function createConcept(concept, level, area) {
|
|
279
|
+
return {
|
|
280
|
+
topic: concept,
|
|
281
|
+
area,
|
|
282
|
+
criticality: level
|
|
283
|
+
};
|
|
284
|
+
}
|
|
285
|
+
function saveComplexityFile$2(filePath, projectName, concepts) {
|
|
286
|
+
const markdown = generateMarkdown(projectName, concepts);
|
|
287
|
+
fs.writeFileSync(filePath, markdown, "utf-8");
|
|
288
|
+
}
|
|
289
|
+
async function addCommand(concept, levelArg, areaArg, options) {
|
|
290
|
+
const level = validateLevel(getLevelInput(levelArg, options));
|
|
291
|
+
const area = areaArg || BLANK_AREA$1;
|
|
292
|
+
const filePath = requireComplexityFile$3();
|
|
293
|
+
const doc = parseComplexityFile(filePath);
|
|
294
|
+
validateConceptDoesNotExist(concept, doc.concepts);
|
|
295
|
+
await confirmNewAreaIfNeeded$1(area, doc.concepts);
|
|
296
|
+
const newConcept = createConcept(concept, level, area);
|
|
297
|
+
const updatedConcepts = [...doc.concepts, newConcept];
|
|
298
|
+
saveComplexityFile$2(filePath, doc.projectName, updatedConcepts);
|
|
299
|
+
console.log(chalk.green(`✓ Added "${concept}" (Level ${level}, Area: ${area})`));
|
|
300
|
+
}
|
|
301
|
+
|
|
302
|
+
//#endregion
|
|
303
|
+
//#region src/commands/remove.ts
|
|
304
|
+
function requireComplexityFile$2() {
|
|
305
|
+
const filePath = findComplexityFile();
|
|
306
|
+
if (!filePath) exitWithError("COMPLEXITY.md not found.", "Run \"complexity init\" to create one.");
|
|
307
|
+
return filePath;
|
|
308
|
+
}
|
|
309
|
+
function requireConceptExists$1(conceptName, concepts) {
|
|
310
|
+
const existing = findConcept(conceptName, concepts);
|
|
311
|
+
if (!existing) exitWithError(`Concept "${conceptName}" not found.`);
|
|
312
|
+
return existing;
|
|
313
|
+
}
|
|
314
|
+
async function confirmRemovalIfNeeded(concept, force) {
|
|
315
|
+
if (force) return;
|
|
316
|
+
if (!await confirmAction(`Remove "${concept.topic}" (Level ${concept.criticality}, Area: ${concept.area})?`)) exitCancelled();
|
|
317
|
+
}
|
|
318
|
+
function removeConcept(conceptName, concepts) {
|
|
319
|
+
return concepts.filter((c) => c.topic.toLowerCase() !== conceptName.toLowerCase());
|
|
320
|
+
}
|
|
321
|
+
function saveComplexityFile$1(filePath, projectName, concepts) {
|
|
322
|
+
const markdown = generateMarkdown(projectName, concepts);
|
|
323
|
+
fs.writeFileSync(filePath, markdown, "utf-8");
|
|
324
|
+
}
|
|
325
|
+
async function removeCommand(concept, options) {
|
|
326
|
+
const filePath = requireComplexityFile$2();
|
|
327
|
+
const doc = parseComplexityFile(filePath);
|
|
328
|
+
const existing = requireConceptExists$1(concept, doc.concepts);
|
|
329
|
+
await confirmRemovalIfNeeded(existing, options.force ?? false);
|
|
330
|
+
const updatedConcepts = removeConcept(concept, doc.concepts);
|
|
331
|
+
saveComplexityFile$1(filePath, doc.projectName, updatedConcepts);
|
|
332
|
+
console.log(chalk.green(`✓ Removed "${existing.topic}"`));
|
|
333
|
+
}
|
|
334
|
+
|
|
335
|
+
//#endregion
|
|
336
|
+
//#region src/commands/update.ts
|
|
337
|
+
const BLANK_AREA = "___";
|
|
338
|
+
function validateAtLeastOneOptionProvided(options) {
|
|
339
|
+
if (!options.level && !options.area && !options.name) exitWithUsageError("At least one option (--level, --area, or --name) is required.", "complexity update <concept> [--level <level>] [--area <area>] [--name <new-name>]");
|
|
340
|
+
}
|
|
341
|
+
function requireComplexityFile$1() {
|
|
342
|
+
const filePath = findComplexityFile();
|
|
343
|
+
if (!filePath) exitWithError("COMPLEXITY.md not found.", "Run \"complexity init\" to create one.");
|
|
344
|
+
return filePath;
|
|
345
|
+
}
|
|
346
|
+
function requireConceptExists(conceptName, concepts) {
|
|
347
|
+
const existing = findConcept(conceptName, concepts);
|
|
348
|
+
if (!existing) exitWithError(`Concept "${conceptName}" not found.`);
|
|
349
|
+
return existing;
|
|
350
|
+
}
|
|
351
|
+
function validateLevelIfProvided(level) {
|
|
352
|
+
if (!level) return;
|
|
353
|
+
if (!isValidLevel(level)) exitWithError("Level must be 1, 2, or 3.");
|
|
354
|
+
}
|
|
355
|
+
async function confirmNewAreaIfNeeded(area, existingConcepts) {
|
|
356
|
+
if (!area || area === BLANK_AREA) return;
|
|
357
|
+
if (isExistingArea(area, existingConcepts)) return;
|
|
358
|
+
if (!await confirmAction(`No pre-existing area "${area}" found. Would you still like to use it?`)) exitCancelled();
|
|
359
|
+
}
|
|
360
|
+
function createUpdatedConcept(existing, options) {
|
|
361
|
+
return {
|
|
362
|
+
topic: options.name || existing.topic,
|
|
363
|
+
area: options.area !== void 0 ? options.area : existing.area,
|
|
364
|
+
criticality: options.level ? parseInt(options.level) : existing.criticality
|
|
365
|
+
};
|
|
366
|
+
}
|
|
367
|
+
function replaceConceptInList(conceptName, updatedConcept, concepts) {
|
|
368
|
+
return concepts.map((c) => c.topic.toLowerCase() === conceptName.toLowerCase() ? updatedConcept : c);
|
|
369
|
+
}
|
|
370
|
+
function saveComplexityFile(filePath, projectName, concepts) {
|
|
371
|
+
const markdown = generateMarkdown(projectName, concepts);
|
|
372
|
+
fs.writeFileSync(filePath, markdown, "utf-8");
|
|
373
|
+
}
|
|
374
|
+
function displayUpdateSummary(conceptName, updatedConcept, options) {
|
|
375
|
+
console.log(chalk.green(`✓ Updated "${conceptName}"`));
|
|
376
|
+
if (options.name) console.log(chalk.gray(` → Name: ${updatedConcept.topic}`));
|
|
377
|
+
if (options.level) console.log(chalk.gray(` → Level: ${updatedConcept.criticality}`));
|
|
378
|
+
if (options.area !== void 0) console.log(chalk.gray(` → Area: ${updatedConcept.area}`));
|
|
379
|
+
}
|
|
380
|
+
async function updateCommand(concept, options) {
|
|
381
|
+
validateAtLeastOneOptionProvided(options);
|
|
382
|
+
validateLevelIfProvided(options.level);
|
|
383
|
+
const filePath = requireComplexityFile$1();
|
|
384
|
+
const doc = parseComplexityFile(filePath);
|
|
385
|
+
const existing = requireConceptExists(concept, doc.concepts);
|
|
386
|
+
await confirmNewAreaIfNeeded(options.area, doc.concepts);
|
|
387
|
+
const updatedConcept = createUpdatedConcept(existing, options);
|
|
388
|
+
const updatedConcepts = replaceConceptInList(concept, updatedConcept, doc.concepts);
|
|
389
|
+
saveComplexityFile(filePath, doc.projectName, updatedConcepts);
|
|
390
|
+
displayUpdateSummary(concept, updatedConcept, options);
|
|
391
|
+
}
|
|
392
|
+
|
|
393
|
+
//#endregion
|
|
394
|
+
//#region src/commands/list.ts
|
|
395
|
+
function requireComplexityFile() {
|
|
396
|
+
const filePath = findComplexityFile();
|
|
397
|
+
if (!filePath) exitWithError("COMPLEXITY.md not found.", "Run \"complexity init\" to create one.");
|
|
398
|
+
return filePath;
|
|
399
|
+
}
|
|
400
|
+
function displayEmptyState(filePath) {
|
|
401
|
+
console.log(chalk.yellow("No concepts found in COMPLEXITY.md"));
|
|
402
|
+
console.log(chalk.gray(`File location: ${filePath}`));
|
|
403
|
+
}
|
|
404
|
+
function displayHeader(projectName, filePath, totalConcepts) {
|
|
405
|
+
console.log(chalk.bold(`\nProject: ${projectName}`));
|
|
406
|
+
console.log(chalk.gray(`File: ${filePath}`));
|
|
407
|
+
console.log(chalk.gray(`Total concepts: ${totalConcepts}\n`));
|
|
408
|
+
}
|
|
409
|
+
function displayConceptGroup(title, concepts, color) {
|
|
410
|
+
if (concepts.length === 0) return;
|
|
411
|
+
console.log(color(title));
|
|
412
|
+
concepts.forEach((concept) => {
|
|
413
|
+
console.log(` ${chalk.bold(concept.topic)} ${chalk.gray(`[${concept.area}]`)}`);
|
|
414
|
+
});
|
|
415
|
+
console.log("");
|
|
416
|
+
}
|
|
417
|
+
function groupConceptsByCriticality(concepts) {
|
|
418
|
+
return {
|
|
419
|
+
critical: concepts.filter((c) => c.criticality === 3),
|
|
420
|
+
important: concepts.filter((c) => c.criticality === 2),
|
|
421
|
+
situational: concepts.filter((c) => c.criticality === 1)
|
|
422
|
+
};
|
|
423
|
+
}
|
|
424
|
+
function listCommand() {
|
|
425
|
+
const filePath = requireComplexityFile();
|
|
426
|
+
const doc = parseComplexityFile(filePath);
|
|
427
|
+
const sorted = sortConcepts(doc.concepts);
|
|
428
|
+
if (sorted.length === 0) {
|
|
429
|
+
displayEmptyState(filePath);
|
|
430
|
+
return;
|
|
431
|
+
}
|
|
432
|
+
displayHeader(doc.projectName, filePath, sorted.length);
|
|
433
|
+
const groups = groupConceptsByCriticality(sorted);
|
|
434
|
+
displayConceptGroup("Critical (Level 3):", groups.critical, chalk.red.bold);
|
|
435
|
+
displayConceptGroup("Important (Level 2):", groups.important, chalk.yellow.bold);
|
|
436
|
+
displayConceptGroup("Situational (Level 1):", groups.situational, chalk.green.bold);
|
|
437
|
+
}
|
|
438
|
+
|
|
439
|
+
//#endregion
|
|
440
|
+
//#region src/commands/prompt.ts
|
|
441
|
+
function promptCommand() {
|
|
442
|
+
const prompt = `
|
|
443
|
+
I'd like you to analyze this project and create a comprehensive complexity map. This map should list everything someone needs to know to take full ownership of this software project.
|
|
444
|
+
|
|
445
|
+
For each technology, concept, or skill:
|
|
446
|
+
- Rate its criticality from 1-3:
|
|
447
|
+
* 3 = Can't do meaningful work without it
|
|
448
|
+
* 2 = Will encounter regularly; gaps will slow you down
|
|
449
|
+
* 1 = Comes up occasionally or is abstracted away
|
|
450
|
+
|
|
451
|
+
- Categorize it by area (e.g., backend, frontend, infrastructure, devops, etc.)
|
|
452
|
+
|
|
453
|
+
Use the following CLI commands to maintain the COMPLEXITY.md file:
|
|
454
|
+
- \`complexity add <concept> <level> [area]\` - Add a new technology
|
|
455
|
+
- \`complexity update <concept> --level <level> --area <area>\` - Update existing entry
|
|
456
|
+
- \`complexity remove <concept>\` - Remove an entry
|
|
457
|
+
- \`complexity list\` - View current entries
|
|
458
|
+
|
|
459
|
+
Be thorough and accurate. Consider:
|
|
460
|
+
- Programming languages and frameworks
|
|
461
|
+
- Databases and data stores
|
|
462
|
+
- DevOps and infrastructure tools
|
|
463
|
+
- Testing frameworks and methodologies
|
|
464
|
+
- Build tools and package managers
|
|
465
|
+
- Domain-specific knowledge
|
|
466
|
+
- Architectural patterns and design principles
|
|
467
|
+
- Third-party services and APIs
|
|
468
|
+
|
|
469
|
+
Start by reviewing the existing entries with \`complexity list\`, then add, update, or remove entries as needed to ensure completeness and accuracy.
|
|
470
|
+
|
|
471
|
+
Do not update the COMPLEXITY.md file manually.
|
|
472
|
+
`.trim();
|
|
473
|
+
console.log(prompt);
|
|
474
|
+
}
|
|
475
|
+
|
|
476
|
+
//#endregion
|
|
477
|
+
//#region src/index.ts
|
|
478
|
+
const program = new Command();
|
|
479
|
+
program.name("complexity").description("A CLI tool for managing project complexity and knowledge maps").version("1.0.0");
|
|
480
|
+
program.command("init").description("Initialize a COMPLEXITY.md file in the current directory").option("-n, --name <name>", "Project name").option("-f, --force", "Overwrite existing file").action(initCommand);
|
|
481
|
+
program.command("add <concept> [level] [area]").description("Add a concept to the complexity map").option("-l, --level <level>", "Criticality level (1-3)").action(addCommand);
|
|
482
|
+
program.command("remove <concept>").description("Remove a concept from the complexity map").option("-f, --force", "Skip confirmation").action(removeCommand);
|
|
483
|
+
program.command("update <concept>").description("Update an existing concept").option("-l, --level <level>", "New criticality level (1-3)").option("-a, --area <area>", "New area").option("-n, --name <name>", "New concept name").action(updateCommand);
|
|
484
|
+
program.command("list").description("List all concepts in the complexity map").action(listCommand);
|
|
485
|
+
program.command("prompt").description("Display the AI prompt for complexity analysis").action(promptCommand);
|
|
47
486
|
program.parse();
|
|
48
|
-
|
|
487
|
+
|
|
488
|
+
//#endregion
|
|
489
|
+
export { };
|
package/package.json
CHANGED
|
@@ -1,14 +1,17 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "complexity-cli",
|
|
3
|
-
"version": "1.0
|
|
3
|
+
"version": "1.1.0",
|
|
4
4
|
"description": "A CLI tool for managing project complexity and knowledge maps",
|
|
5
|
+
"type": "module",
|
|
5
6
|
"main": "dist/index.js",
|
|
6
7
|
"bin": {
|
|
7
8
|
"complexity": "dist/index.js"
|
|
8
9
|
},
|
|
9
10
|
"scripts": {
|
|
10
|
-
"build": "
|
|
11
|
-
"dev": "
|
|
11
|
+
"build": "tsdown",
|
|
12
|
+
"dev": "tsdown --watch",
|
|
13
|
+
"typecheck": "tsc --noEmit",
|
|
14
|
+
"link": "npm run build && npm link",
|
|
12
15
|
"prepublishOnly": "npm run build"
|
|
13
16
|
},
|
|
14
17
|
"keywords": [
|
|
@@ -37,13 +40,14 @@
|
|
|
37
40
|
"LICENSE"
|
|
38
41
|
],
|
|
39
42
|
"dependencies": {
|
|
43
|
+
"chalk": "^4.1.2",
|
|
40
44
|
"commander": "^12.0.0",
|
|
41
|
-
"inquirer": "^9.2.0"
|
|
42
|
-
"chalk": "^4.1.2"
|
|
45
|
+
"inquirer": "^9.2.0"
|
|
43
46
|
},
|
|
44
47
|
"devDependencies": {
|
|
45
|
-
"@types/node": "^20.0.0",
|
|
46
48
|
"@types/inquirer": "^9.0.0",
|
|
49
|
+
"@types/node": "^20.0.0",
|
|
50
|
+
"tsdown": "^0.20.3",
|
|
47
51
|
"typescript": "^5.3.0"
|
|
48
52
|
},
|
|
49
53
|
"engines": {
|
package/dist/commands/add.d.ts
DELETED
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"file":"add.d.ts","sourceRoot":"","sources":["../../src/commands/add.ts"],"names":[],"mappings":"AA8EA,wBAAsB,UAAU,CAC9B,OAAO,EAAE,MAAM,EACf,QAAQ,EAAE,MAAM,GAAG,SAAS,EAC5B,OAAO,EAAE,MAAM,GAAG,SAAS,EAC3B,OAAO,EAAE;IAAE,KAAK,CAAC,EAAE,MAAM,CAAA;CAAE,iBAkB5B"}
|