chainlesschain 0.47.8 → 0.49.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/bin/chainlesschain.js +0 -0
- package/package.json +10 -8
- package/src/assets/web-panel/.build-hash +1 -1
- package/src/assets/web-panel/assets/{AppLayout-6SPt_8Y_.js → AppLayout-Rvi759IS.js} +1 -1
- package/src/assets/web-panel/assets/Dashboard-BS-tzGNj.css +1 -0
- package/src/assets/web-panel/assets/{Dashboard-Br7kCwKJ.js → Dashboard-DBhFxXYQ.js} +2 -2
- package/src/assets/web-panel/assets/{index-tN-8TosE.js → index-uL0cZ8N_.js} +2 -2
- package/src/assets/web-panel/index.html +2 -2
- package/src/commands/activitypub.js +533 -0
- package/src/commands/codegen.js +303 -0
- package/src/commands/collab.js +482 -0
- package/src/commands/compliance.js +597 -6
- package/src/commands/crosschain.js +382 -0
- package/src/commands/dbevo.js +388 -0
- package/src/commands/dev.js +411 -0
- package/src/commands/federation.js +427 -0
- package/src/commands/fusion.js +332 -0
- package/src/commands/governance.js +505 -0
- package/src/commands/hardening.js +110 -0
- package/src/commands/incentive.js +373 -0
- package/src/commands/inference.js +304 -0
- package/src/commands/infra.js +361 -0
- package/src/commands/kg.js +371 -0
- package/src/commands/marketplace.js +326 -0
- package/src/commands/matrix.js +283 -0
- package/src/commands/mcp.js +441 -18
- package/src/commands/nlprog.js +329 -0
- package/src/commands/nostr.js +196 -7
- package/src/commands/ops.js +408 -0
- package/src/commands/perception.js +385 -0
- package/src/commands/pqc.js +34 -0
- package/src/commands/privacy.js +345 -0
- package/src/commands/quantization.js +280 -0
- package/src/commands/recommend.js +336 -0
- package/src/commands/reputation.js +349 -0
- package/src/commands/runtime.js +500 -0
- package/src/commands/sla.js +352 -0
- package/src/commands/social.js +265 -0
- package/src/commands/stress.js +252 -0
- package/src/commands/tech.js +268 -0
- package/src/commands/tenant.js +576 -0
- package/src/commands/trust.js +366 -0
- package/src/harness/mcp-client.js +330 -54
- package/src/index.js +114 -0
- package/src/lib/activitypub-bridge.js +623 -0
- package/src/lib/aiops.js +523 -0
- package/src/lib/autonomous-developer.js +524 -0
- package/src/lib/code-agent.js +442 -0
- package/src/lib/collaboration-governance.js +556 -0
- package/src/lib/community-governance.js +649 -0
- package/src/lib/compliance-framework-reporter.js +600 -0
- package/src/lib/content-recommendation.js +600 -0
- package/src/lib/cross-chain.js +669 -0
- package/src/lib/dbevo.js +669 -0
- package/src/lib/decentral-infra.js +445 -0
- package/src/lib/federation-hardening.js +587 -0
- package/src/lib/hardening-manager.js +409 -0
- package/src/lib/inference-network.js +407 -0
- package/src/lib/knowledge-graph.js +530 -0
- package/src/lib/matrix-bridge.js +252 -0
- package/src/lib/mcp-client.js +3 -0
- package/src/lib/mcp-registry.js +347 -0
- package/src/lib/mcp-scaffold.js +385 -0
- package/src/lib/multimodal.js +698 -0
- package/src/lib/nl-programming.js +595 -0
- package/src/lib/nostr-bridge.js +214 -38
- package/src/lib/perception.js +500 -0
- package/src/lib/pqc-manager.js +141 -9
- package/src/lib/privacy-computing.js +575 -0
- package/src/lib/protocol-fusion.js +535 -0
- package/src/lib/quantization.js +362 -0
- package/src/lib/reputation-optimizer.js +509 -0
- package/src/lib/skill-marketplace.js +397 -0
- package/src/lib/sla-manager.js +484 -0
- package/src/lib/social-graph.js +408 -0
- package/src/lib/stix-parser.js +167 -0
- package/src/lib/stress-tester.js +383 -0
- package/src/lib/tech-learning-engine.js +651 -0
- package/src/lib/tenant-saas.js +831 -0
- package/src/lib/threat-intel.js +268 -0
- package/src/lib/token-incentive.js +513 -0
- package/src/lib/topic-classifier.js +400 -0
- package/src/lib/trust-security.js +473 -0
- package/src/lib/ueba.js +403 -0
- package/src/lib/universal-runtime.js +771 -0
- package/src/assets/web-panel/assets/Dashboard-CKeMmCoT.css +0 -1
|
@@ -0,0 +1,252 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Stress commands
|
|
3
|
+
* chainlesschain stress run|list|show|analyze|plan|stop|levels
|
|
4
|
+
*/
|
|
5
|
+
|
|
6
|
+
import chalk from "chalk";
|
|
7
|
+
import { logger } from "../lib/logger.js";
|
|
8
|
+
import { bootstrap, shutdown } from "../runtime/bootstrap.js";
|
|
9
|
+
import {
|
|
10
|
+
ensureStressTables,
|
|
11
|
+
startStressTest,
|
|
12
|
+
stopStressTest,
|
|
13
|
+
getTestResults,
|
|
14
|
+
listTestHistory,
|
|
15
|
+
analyzeBottlenecks,
|
|
16
|
+
generateCapacityPlan,
|
|
17
|
+
listLoadLevels,
|
|
18
|
+
} from "../lib/stress-tester.js";
|
|
19
|
+
|
|
20
|
+
function _dbFromCtx(ctx) {
|
|
21
|
+
if (!ctx.db) {
|
|
22
|
+
logger.error("Database not available");
|
|
23
|
+
process.exit(1);
|
|
24
|
+
}
|
|
25
|
+
const db = ctx.db.getDatabase();
|
|
26
|
+
ensureStressTables(db);
|
|
27
|
+
return db;
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
export function registerStressCommand(program) {
|
|
31
|
+
const stress = program
|
|
32
|
+
.command("stress")
|
|
33
|
+
.description(
|
|
34
|
+
"Federation stress testing — load simulation, bottleneck & capacity planning",
|
|
35
|
+
);
|
|
36
|
+
|
|
37
|
+
stress
|
|
38
|
+
.command("run")
|
|
39
|
+
.description("Run a stress test at a given load level")
|
|
40
|
+
.option(
|
|
41
|
+
"-l, --level <level>",
|
|
42
|
+
"Load level (light|medium|heavy|extreme)",
|
|
43
|
+
"medium",
|
|
44
|
+
)
|
|
45
|
+
.option("-c, --concurrency <n>", "Override concurrency", parseInt)
|
|
46
|
+
.option("-r, --rps <n>", "Override requests per second", parseInt)
|
|
47
|
+
.option("-d, --duration <ms>", "Override duration in ms", parseInt)
|
|
48
|
+
.option("--json", "Output as JSON")
|
|
49
|
+
.action(async (options) => {
|
|
50
|
+
try {
|
|
51
|
+
const ctx = await bootstrap({ verbose: program.opts().verbose });
|
|
52
|
+
const db = _dbFromCtx(ctx);
|
|
53
|
+
const run = startStressTest(db, {
|
|
54
|
+
level: options.level,
|
|
55
|
+
concurrency: options.concurrency,
|
|
56
|
+
requestsPerSecond: options.rps,
|
|
57
|
+
duration: options.duration,
|
|
58
|
+
});
|
|
59
|
+
if (options.json) {
|
|
60
|
+
console.log(JSON.stringify(run, null, 2));
|
|
61
|
+
} else {
|
|
62
|
+
logger.success(`Stress test ${run.status}`);
|
|
63
|
+
logger.log(
|
|
64
|
+
` ${chalk.bold("ID:")} ${chalk.cyan(run.testId)}`,
|
|
65
|
+
);
|
|
66
|
+
logger.log(` ${chalk.bold("Level:")} ${run.loadLevel}`);
|
|
67
|
+
logger.log(
|
|
68
|
+
` ${chalk.bold("Load:")} ${run.concurrency} concurrent, ${run.requestsPerSecond} rps, ${run.duration}ms`,
|
|
69
|
+
);
|
|
70
|
+
const m = run.result;
|
|
71
|
+
logger.log(` ${chalk.bold("TPS:")} ${m.tps}`);
|
|
72
|
+
logger.log(
|
|
73
|
+
` ${chalk.bold("Latency:")} p50=${m.p50ResponseTime}ms p95=${m.p95ResponseTime}ms p99=${m.p99ResponseTime}ms`,
|
|
74
|
+
);
|
|
75
|
+
logger.log(
|
|
76
|
+
` ${chalk.bold("Error rate:")} ${(m.errorRate * 100).toFixed(2)}%`,
|
|
77
|
+
);
|
|
78
|
+
if (m.bottlenecks.length) {
|
|
79
|
+
logger.log(chalk.yellow(` Bottlenecks:`));
|
|
80
|
+
for (const b of m.bottlenecks) {
|
|
81
|
+
logger.log(
|
|
82
|
+
` ${chalk.yellow("•")} ${b.kind} (${b.severity}): ${b.detail}`,
|
|
83
|
+
);
|
|
84
|
+
}
|
|
85
|
+
}
|
|
86
|
+
}
|
|
87
|
+
await shutdown();
|
|
88
|
+
} catch (err) {
|
|
89
|
+
logger.error(`Failed: ${err.message}`);
|
|
90
|
+
process.exit(1);
|
|
91
|
+
}
|
|
92
|
+
});
|
|
93
|
+
|
|
94
|
+
stress
|
|
95
|
+
.command("list")
|
|
96
|
+
.description("List stress test history")
|
|
97
|
+
.option("-l, --level <level>", "Filter by load level")
|
|
98
|
+
.option(
|
|
99
|
+
"-s, --status <status>",
|
|
100
|
+
"Filter by status (running|complete|stopped)",
|
|
101
|
+
)
|
|
102
|
+
.option("--limit <n>", "Maximum entries", parseInt, 10)
|
|
103
|
+
.option("--json", "Output as JSON")
|
|
104
|
+
.action(async (options) => {
|
|
105
|
+
try {
|
|
106
|
+
const ctx = await bootstrap({ verbose: program.opts().verbose });
|
|
107
|
+
_dbFromCtx(ctx);
|
|
108
|
+
const rows = listTestHistory({
|
|
109
|
+
level: options.level,
|
|
110
|
+
status: options.status,
|
|
111
|
+
limit: options.limit,
|
|
112
|
+
});
|
|
113
|
+
if (options.json) {
|
|
114
|
+
console.log(JSON.stringify(rows, null, 2));
|
|
115
|
+
} else if (rows.length === 0) {
|
|
116
|
+
logger.info("No stress tests recorded.");
|
|
117
|
+
} else {
|
|
118
|
+
for (const r of rows) {
|
|
119
|
+
logger.log(
|
|
120
|
+
` ${chalk.cyan(r.testId.slice(0, 8))} ${r.loadLevel.padEnd(8)} [${r.status}] c=${r.concurrency} rps=${r.requestsPerSecond}`,
|
|
121
|
+
);
|
|
122
|
+
}
|
|
123
|
+
}
|
|
124
|
+
await shutdown();
|
|
125
|
+
} catch (err) {
|
|
126
|
+
logger.error(`Failed: ${err.message}`);
|
|
127
|
+
process.exit(1);
|
|
128
|
+
}
|
|
129
|
+
});
|
|
130
|
+
|
|
131
|
+
stress
|
|
132
|
+
.command("show <test-id>")
|
|
133
|
+
.description("Show full results for a stress test")
|
|
134
|
+
.option("--json", "Output as JSON")
|
|
135
|
+
.action(async (testId, options) => {
|
|
136
|
+
try {
|
|
137
|
+
const ctx = await bootstrap({ verbose: program.opts().verbose });
|
|
138
|
+
_dbFromCtx(ctx);
|
|
139
|
+
const r = getTestResults(testId);
|
|
140
|
+
if (options.json) {
|
|
141
|
+
console.log(JSON.stringify(r, null, 2));
|
|
142
|
+
} else {
|
|
143
|
+
logger.log(` ${chalk.bold("ID:")} ${chalk.cyan(r.testId)}`);
|
|
144
|
+
logger.log(` ${chalk.bold("Level:")} ${r.loadLevel}`);
|
|
145
|
+
logger.log(` ${chalk.bold("Status:")} ${r.status}`);
|
|
146
|
+
if (r.result) {
|
|
147
|
+
const m = r.result;
|
|
148
|
+
logger.log(` ${chalk.bold("TPS:")} ${m.tps}`);
|
|
149
|
+
logger.log(
|
|
150
|
+
` ${chalk.bold("Latency:")} p50=${m.p50ResponseTime}ms p95=${m.p95ResponseTime}ms p99=${m.p99ResponseTime}ms`,
|
|
151
|
+
);
|
|
152
|
+
logger.log(
|
|
153
|
+
` ${chalk.bold("Error rate:")} ${(m.errorRate * 100).toFixed(2)}%`,
|
|
154
|
+
);
|
|
155
|
+
}
|
|
156
|
+
}
|
|
157
|
+
await shutdown();
|
|
158
|
+
} catch (err) {
|
|
159
|
+
logger.error(`Failed: ${err.message}`);
|
|
160
|
+
process.exit(1);
|
|
161
|
+
}
|
|
162
|
+
});
|
|
163
|
+
|
|
164
|
+
stress
|
|
165
|
+
.command("analyze <test-id>")
|
|
166
|
+
.description("Analyze bottlenecks for a stress test")
|
|
167
|
+
.option("--json", "Output as JSON")
|
|
168
|
+
.action(async (testId, options) => {
|
|
169
|
+
try {
|
|
170
|
+
const ctx = await bootstrap({ verbose: program.opts().verbose });
|
|
171
|
+
_dbFromCtx(ctx);
|
|
172
|
+
const r = analyzeBottlenecks(testId);
|
|
173
|
+
if (options.json) {
|
|
174
|
+
console.log(JSON.stringify(r, null, 2));
|
|
175
|
+
} else {
|
|
176
|
+
logger.log(` ${chalk.bold("Summary:")} ${r.summary}`);
|
|
177
|
+
for (const b of r.bottlenecks) {
|
|
178
|
+
logger.log(
|
|
179
|
+
` ${chalk.yellow(b.kind)} (${b.severity}): ${b.detail}`,
|
|
180
|
+
);
|
|
181
|
+
}
|
|
182
|
+
}
|
|
183
|
+
await shutdown();
|
|
184
|
+
} catch (err) {
|
|
185
|
+
logger.error(`Failed: ${err.message}`);
|
|
186
|
+
process.exit(1);
|
|
187
|
+
}
|
|
188
|
+
});
|
|
189
|
+
|
|
190
|
+
stress
|
|
191
|
+
.command("plan <test-id>")
|
|
192
|
+
.description("Generate capacity plan recommendations")
|
|
193
|
+
.option("--json", "Output as JSON")
|
|
194
|
+
.action(async (testId, options) => {
|
|
195
|
+
try {
|
|
196
|
+
const ctx = await bootstrap({ verbose: program.opts().verbose });
|
|
197
|
+
_dbFromCtx(ctx);
|
|
198
|
+
const plan = generateCapacityPlan(testId);
|
|
199
|
+
if (options.json) {
|
|
200
|
+
console.log(JSON.stringify(plan, null, 2));
|
|
201
|
+
} else {
|
|
202
|
+
logger.log(` ${chalk.bold("Level:")} ${plan.loadLevel}`);
|
|
203
|
+
logger.log(
|
|
204
|
+
` ${chalk.bold("Target/Got:")} ${plan.targetRps} rps → ${plan.realizedTps} rps`,
|
|
205
|
+
);
|
|
206
|
+
logger.log(
|
|
207
|
+
` ${chalk.bold("Scale:")} ${plan.scale}× (${plan.headroom})`,
|
|
208
|
+
);
|
|
209
|
+
for (const rec of plan.recommendations) {
|
|
210
|
+
logger.log(` ${chalk.green("→")} ${rec}`);
|
|
211
|
+
}
|
|
212
|
+
}
|
|
213
|
+
await shutdown();
|
|
214
|
+
} catch (err) {
|
|
215
|
+
logger.error(`Failed: ${err.message}`);
|
|
216
|
+
process.exit(1);
|
|
217
|
+
}
|
|
218
|
+
});
|
|
219
|
+
|
|
220
|
+
stress
|
|
221
|
+
.command("stop <test-id>")
|
|
222
|
+
.description("Mark a running stress test as stopped")
|
|
223
|
+
.action(async (testId) => {
|
|
224
|
+
try {
|
|
225
|
+
const ctx = await bootstrap({ verbose: program.opts().verbose });
|
|
226
|
+
_dbFromCtx(ctx);
|
|
227
|
+
const r = stopStressTest(testId);
|
|
228
|
+
logger.success(`Test ${r.testId.slice(0, 8)} → ${r.status}`);
|
|
229
|
+
await shutdown();
|
|
230
|
+
} catch (err) {
|
|
231
|
+
logger.error(`Failed: ${err.message}`);
|
|
232
|
+
process.exit(1);
|
|
233
|
+
}
|
|
234
|
+
});
|
|
235
|
+
|
|
236
|
+
stress
|
|
237
|
+
.command("levels")
|
|
238
|
+
.description("List built-in load levels")
|
|
239
|
+
.option("--json", "Output as JSON")
|
|
240
|
+
.action((options) => {
|
|
241
|
+
const levels = listLoadLevels();
|
|
242
|
+
if (options.json) {
|
|
243
|
+
console.log(JSON.stringify(levels, null, 2));
|
|
244
|
+
} else {
|
|
245
|
+
for (const l of levels) {
|
|
246
|
+
logger.log(
|
|
247
|
+
` ${chalk.cyan(l.name.padEnd(8))} concurrency=${l.concurrency} rps=${l.requestsPerSecond} duration=${l.duration}ms`,
|
|
248
|
+
);
|
|
249
|
+
}
|
|
250
|
+
}
|
|
251
|
+
});
|
|
252
|
+
}
|
|
@@ -0,0 +1,268 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Tech Learning Engine commands (Phase 62)
|
|
3
|
+
* chainlesschain tech analyze|profile|detect|practice|practices|recommend|types
|
|
4
|
+
*/
|
|
5
|
+
|
|
6
|
+
import chalk from "chalk";
|
|
7
|
+
import { logger } from "../lib/logger.js";
|
|
8
|
+
import { bootstrap, shutdown } from "../runtime/bootstrap.js";
|
|
9
|
+
import {
|
|
10
|
+
ensureTechLearningTables,
|
|
11
|
+
analyzeTechStack,
|
|
12
|
+
getProfile,
|
|
13
|
+
detectAntiPatterns,
|
|
14
|
+
recordPractice,
|
|
15
|
+
listPractices,
|
|
16
|
+
getRecommendations,
|
|
17
|
+
TECH_TYPES,
|
|
18
|
+
PRACTICE_LEVELS,
|
|
19
|
+
ANTI_PATTERNS,
|
|
20
|
+
} from "../lib/tech-learning-engine.js";
|
|
21
|
+
|
|
22
|
+
function _dbFromCtx(ctx) {
|
|
23
|
+
if (!ctx.db) {
|
|
24
|
+
logger.error("Database not available");
|
|
25
|
+
process.exit(1);
|
|
26
|
+
}
|
|
27
|
+
const db = ctx.db.getDatabase();
|
|
28
|
+
ensureTechLearningTables(db);
|
|
29
|
+
return db;
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
export function registerTechCommand(program) {
|
|
33
|
+
const tech = program
|
|
34
|
+
.command("tech")
|
|
35
|
+
.description(
|
|
36
|
+
"Tech Learning Engine — stack analysis, anti-pattern detection, practice store",
|
|
37
|
+
);
|
|
38
|
+
|
|
39
|
+
tech
|
|
40
|
+
.command("types")
|
|
41
|
+
.description("List known tech types, practice levels, anti-patterns")
|
|
42
|
+
.option("--json", "Output as JSON")
|
|
43
|
+
.action((options) => {
|
|
44
|
+
const payload = {
|
|
45
|
+
techTypes: Object.values(TECH_TYPES),
|
|
46
|
+
practiceLevels: Object.values(PRACTICE_LEVELS),
|
|
47
|
+
antiPatterns: Object.values(ANTI_PATTERNS),
|
|
48
|
+
};
|
|
49
|
+
if (options.json) {
|
|
50
|
+
console.log(JSON.stringify(payload, null, 2));
|
|
51
|
+
} else {
|
|
52
|
+
logger.log(
|
|
53
|
+
` ${chalk.bold("Tech types:")} ${payload.techTypes.join(", ")}`,
|
|
54
|
+
);
|
|
55
|
+
logger.log(
|
|
56
|
+
` ${chalk.bold("Practice levels:")} ${payload.practiceLevels.join(", ")}`,
|
|
57
|
+
);
|
|
58
|
+
logger.log(
|
|
59
|
+
` ${chalk.bold("Anti-patterns:")} ${payload.antiPatterns.join(", ")}`,
|
|
60
|
+
);
|
|
61
|
+
}
|
|
62
|
+
});
|
|
63
|
+
|
|
64
|
+
tech
|
|
65
|
+
.command("analyze [path]")
|
|
66
|
+
.description("Analyze tech stack of a project directory")
|
|
67
|
+
.option("--json", "Output as JSON")
|
|
68
|
+
.action(async (projectPath, options) => {
|
|
69
|
+
try {
|
|
70
|
+
const ctx = await bootstrap({ verbose: program.opts().verbose });
|
|
71
|
+
const db = _dbFromCtx(ctx);
|
|
72
|
+
const profile = analyzeTechStack(db, projectPath || process.cwd());
|
|
73
|
+
if (options.json) {
|
|
74
|
+
console.log(JSON.stringify(profile, null, 2));
|
|
75
|
+
} else {
|
|
76
|
+
logger.success("Tech stack analyzed");
|
|
77
|
+
logger.log(` ${chalk.bold("Path:")} ${profile.projectPath}`);
|
|
78
|
+
logger.log(
|
|
79
|
+
` ${chalk.bold("Languages:")} ${profile.languages.join(", ") || "(none)"}`,
|
|
80
|
+
);
|
|
81
|
+
logger.log(
|
|
82
|
+
` ${chalk.bold("Frameworks:")} ${profile.frameworks.join(", ") || "(none)"}`,
|
|
83
|
+
);
|
|
84
|
+
logger.log(
|
|
85
|
+
` ${chalk.bold("Databases:")} ${profile.databases.join(", ") || "(none)"}`,
|
|
86
|
+
);
|
|
87
|
+
logger.log(
|
|
88
|
+
` ${chalk.bold("Tools:")} ${profile.tools.join(", ") || "(none)"}`,
|
|
89
|
+
);
|
|
90
|
+
logger.log(
|
|
91
|
+
` ${chalk.bold("Total deps:")} ${profile.totalDependencies}`,
|
|
92
|
+
);
|
|
93
|
+
}
|
|
94
|
+
await shutdown();
|
|
95
|
+
} catch (err) {
|
|
96
|
+
logger.error(`Failed: ${err.message}`);
|
|
97
|
+
process.exit(1);
|
|
98
|
+
}
|
|
99
|
+
});
|
|
100
|
+
|
|
101
|
+
tech
|
|
102
|
+
.command("profile [path]")
|
|
103
|
+
.description("Show the last analyzed profile for a path")
|
|
104
|
+
.option("--json", "Output as JSON")
|
|
105
|
+
.action(async (projectPath, options) => {
|
|
106
|
+
try {
|
|
107
|
+
const ctx = await bootstrap({ verbose: program.opts().verbose });
|
|
108
|
+
_dbFromCtx(ctx);
|
|
109
|
+
const profile = getProfile(projectPath || process.cwd());
|
|
110
|
+
if (!profile) {
|
|
111
|
+
logger.info("No profile cached. Run `tech analyze` first.");
|
|
112
|
+
} else if (options.json) {
|
|
113
|
+
console.log(JSON.stringify(profile, null, 2));
|
|
114
|
+
} else {
|
|
115
|
+
logger.log(
|
|
116
|
+
` ${chalk.bold("Languages:")} ${profile.languages.join(", ")}`,
|
|
117
|
+
);
|
|
118
|
+
logger.log(
|
|
119
|
+
` ${chalk.bold("Frameworks:")} ${profile.frameworks.join(", ") || "(none)"}`,
|
|
120
|
+
);
|
|
121
|
+
logger.log(
|
|
122
|
+
` ${chalk.bold("Libraries:")} ${profile.libraries.length}`,
|
|
123
|
+
);
|
|
124
|
+
}
|
|
125
|
+
await shutdown();
|
|
126
|
+
} catch (err) {
|
|
127
|
+
logger.error(`Failed: ${err.message}`);
|
|
128
|
+
process.exit(1);
|
|
129
|
+
}
|
|
130
|
+
});
|
|
131
|
+
|
|
132
|
+
tech
|
|
133
|
+
.command("detect <file>")
|
|
134
|
+
.description("Detect anti-patterns in a single file")
|
|
135
|
+
.option("--json", "Output as JSON")
|
|
136
|
+
.action(async (file, options) => {
|
|
137
|
+
try {
|
|
138
|
+
const r = detectAntiPatterns(file);
|
|
139
|
+
if (options.json) {
|
|
140
|
+
console.log(JSON.stringify(r, null, 2));
|
|
141
|
+
} else if (r.totalFindings === 0) {
|
|
142
|
+
logger.success(
|
|
143
|
+
`No anti-patterns detected (${r.lines} lines, ${r.functionCount} funcs)`,
|
|
144
|
+
);
|
|
145
|
+
} else {
|
|
146
|
+
logger.warn(`${r.totalFindings} anti-pattern finding(s):`);
|
|
147
|
+
for (const f of r.findings) {
|
|
148
|
+
logger.log(
|
|
149
|
+
` ${chalk.yellow(f.type.padEnd(22))} [${f.severity}] ${f.detail}`,
|
|
150
|
+
);
|
|
151
|
+
}
|
|
152
|
+
}
|
|
153
|
+
} catch (err) {
|
|
154
|
+
logger.error(`Failed: ${err.message}`);
|
|
155
|
+
process.exit(1);
|
|
156
|
+
}
|
|
157
|
+
});
|
|
158
|
+
|
|
159
|
+
tech
|
|
160
|
+
.command("practice <tech-type> <tech-name> <pattern> <level>")
|
|
161
|
+
.description(
|
|
162
|
+
"Record a learned practice (tech-type: language|framework|library|database|tool|pattern)",
|
|
163
|
+
)
|
|
164
|
+
.option("-d, --description <text>", "Description", "")
|
|
165
|
+
.option("-s, --score <n>", "Score 0..1", parseFloat, 0)
|
|
166
|
+
.option("--source <tag>", "Source tag", "manual")
|
|
167
|
+
.option("--json", "Output as JSON")
|
|
168
|
+
.action(async (techType, techName, patternName, level, options) => {
|
|
169
|
+
try {
|
|
170
|
+
const ctx = await bootstrap({ verbose: program.opts().verbose });
|
|
171
|
+
const db = _dbFromCtx(ctx);
|
|
172
|
+
const p = recordPractice(db, {
|
|
173
|
+
techType,
|
|
174
|
+
techName,
|
|
175
|
+
patternName,
|
|
176
|
+
level,
|
|
177
|
+
description: options.description,
|
|
178
|
+
score: options.score,
|
|
179
|
+
source: options.source,
|
|
180
|
+
});
|
|
181
|
+
if (options.json) {
|
|
182
|
+
console.log(JSON.stringify(p, null, 2));
|
|
183
|
+
} else {
|
|
184
|
+
logger.success("Practice recorded");
|
|
185
|
+
logger.log(
|
|
186
|
+
` ${chalk.bold("ID:")} ${chalk.cyan(p.practiceId.slice(0, 8))}`,
|
|
187
|
+
);
|
|
188
|
+
logger.log(` ${chalk.bold("Tech:")} ${p.techType}/${p.techName}`);
|
|
189
|
+
logger.log(` ${chalk.bold("Pattern:")} ${p.patternName}`);
|
|
190
|
+
logger.log(` ${chalk.bold("Level:")} ${p.level}`);
|
|
191
|
+
}
|
|
192
|
+
await shutdown();
|
|
193
|
+
} catch (err) {
|
|
194
|
+
logger.error(`Failed: ${err.message}`);
|
|
195
|
+
process.exit(1);
|
|
196
|
+
}
|
|
197
|
+
});
|
|
198
|
+
|
|
199
|
+
tech
|
|
200
|
+
.command("practices")
|
|
201
|
+
.description("List recorded practices")
|
|
202
|
+
.option("-t, --type <type>", "Filter by tech type")
|
|
203
|
+
.option("-n, --name <name>", "Filter by tech name")
|
|
204
|
+
.option("-l, --level <level>", "Filter by level")
|
|
205
|
+
.option("--limit <n>", "Maximum entries", parseInt, 50)
|
|
206
|
+
.option("--json", "Output as JSON")
|
|
207
|
+
.action(async (options) => {
|
|
208
|
+
try {
|
|
209
|
+
const ctx = await bootstrap({ verbose: program.opts().verbose });
|
|
210
|
+
_dbFromCtx(ctx);
|
|
211
|
+
const rows = listPractices({
|
|
212
|
+
techType: options.type,
|
|
213
|
+
techName: options.name,
|
|
214
|
+
level: options.level,
|
|
215
|
+
limit: options.limit,
|
|
216
|
+
});
|
|
217
|
+
if (options.json) {
|
|
218
|
+
console.log(JSON.stringify(rows, null, 2));
|
|
219
|
+
} else if (rows.length === 0) {
|
|
220
|
+
logger.info("No practices recorded.");
|
|
221
|
+
} else {
|
|
222
|
+
for (const p of rows) {
|
|
223
|
+
logger.log(
|
|
224
|
+
` ${chalk.cyan(p.practiceId.slice(0, 8))} ${p.techType.padEnd(10)} ${p.techName.padEnd(14)} [${p.level.padEnd(12)}] ${p.patternName}`,
|
|
225
|
+
);
|
|
226
|
+
}
|
|
227
|
+
}
|
|
228
|
+
await shutdown();
|
|
229
|
+
} catch (err) {
|
|
230
|
+
logger.error(`Failed: ${err.message}`);
|
|
231
|
+
process.exit(1);
|
|
232
|
+
}
|
|
233
|
+
});
|
|
234
|
+
|
|
235
|
+
tech
|
|
236
|
+
.command("recommend")
|
|
237
|
+
.description("Recommend practices based on the analyzed stack")
|
|
238
|
+
.option("--limit <n>", "Maximum entries", parseInt, 20)
|
|
239
|
+
.option("--json", "Output as JSON")
|
|
240
|
+
.action(async (options) => {
|
|
241
|
+
try {
|
|
242
|
+
const ctx = await bootstrap({ verbose: program.opts().verbose });
|
|
243
|
+
_dbFromCtx(ctx);
|
|
244
|
+
const r = getRecommendations({ limit: options.limit });
|
|
245
|
+
if (options.json) {
|
|
246
|
+
console.log(JSON.stringify(r, null, 2));
|
|
247
|
+
} else if (r.recommendations.length === 0) {
|
|
248
|
+
logger.info(
|
|
249
|
+
r.message ||
|
|
250
|
+
`${r.totalPractices} practice(s) stored; 0 match the analyzed stack.`,
|
|
251
|
+
);
|
|
252
|
+
} else {
|
|
253
|
+
logger.log(
|
|
254
|
+
` ${chalk.bold("Matches:")} ${r.totalMatches}/${r.totalPractices}`,
|
|
255
|
+
);
|
|
256
|
+
for (const p of r.recommendations) {
|
|
257
|
+
logger.log(
|
|
258
|
+
` ${chalk.cyan(p.techName.padEnd(14))} [${p.level.padEnd(12)}] ${p.patternName}`,
|
|
259
|
+
);
|
|
260
|
+
}
|
|
261
|
+
}
|
|
262
|
+
await shutdown();
|
|
263
|
+
} catch (err) {
|
|
264
|
+
logger.error(`Failed: ${err.message}`);
|
|
265
|
+
process.exit(1);
|
|
266
|
+
}
|
|
267
|
+
});
|
|
268
|
+
}
|