chainlesschain 0.47.9 → 0.51.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 +1 -1
- 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/codegen.js +303 -0
- package/src/commands/collab.js +482 -0
- 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/ipfs.js +392 -0
- package/src/commands/kg.js +371 -0
- package/src/commands/marketplace.js +326 -0
- package/src/commands/mcp.js +97 -18
- package/src/commands/multimodal.js +404 -0
- package/src/commands/nlprog.js +329 -0
- 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/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 +118 -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/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/ipfs-storage.js +575 -0
- package/src/lib/knowledge-graph.js +530 -0
- package/src/lib/mcp-client.js +3 -0
- package/src/lib/multimodal.js +725 -0
- package/src/lib/nl-programming.js +595 -0
- 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/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/token-incentive.js +513 -0
- package/src/lib/trust-security.js +473 -0
- package/src/lib/universal-runtime.js +771 -0
- package/src/assets/web-panel/assets/Dashboard-CKeMmCoT.css +0 -1
|
@@ -0,0 +1,524 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Autonomous Developer — CLI port of Phase 63 autonomous-developer system
|
|
3
|
+
* (docs/design/modules/35_自主开发者系统.md).
|
|
4
|
+
*
|
|
5
|
+
* The Desktop build drives an L2 autonomous-developer loop backed by a live
|
|
6
|
+
* LLMSession + techLearningEngine: requirement → design → implementation →
|
|
7
|
+
* testing → review → deployment, with auto-generated code / tests / ADRs.
|
|
8
|
+
* The CLI can't host a long-running LLM loop, so this port ships the
|
|
9
|
+
* tractable scaffolding:
|
|
10
|
+
*
|
|
11
|
+
* - Dev session lifecycle (start / advance phase / pause / resume /
|
|
12
|
+
* complete / fail) with SQLite persistence.
|
|
13
|
+
* - Architecture Decision Record (ADR) store with status workflow
|
|
14
|
+
* (proposed / accepted / deprecated / superseded).
|
|
15
|
+
* - reviewCode(file): wraps Phase 62 detectAntiPatterns into a
|
|
16
|
+
* session-scoped review record (score + findings).
|
|
17
|
+
* - Refactoring-type + autonomy-level + phase catalogs.
|
|
18
|
+
*
|
|
19
|
+
* Auto-codegen, auto-test-gen and LLM-driven requirement parsing are
|
|
20
|
+
* explicitly out of scope — they belong in Desktop + LLM-hosted paths.
|
|
21
|
+
*/
|
|
22
|
+
|
|
23
|
+
import crypto from "crypto";
|
|
24
|
+
import { detectAntiPatterns as _detectAntiPatterns } from "./tech-learning-engine.js";
|
|
25
|
+
|
|
26
|
+
/* ── Constants ─────────────────────────────────────────────── */
|
|
27
|
+
|
|
28
|
+
export const AUTONOMY_LEVELS = Object.freeze({
|
|
29
|
+
L0: {
|
|
30
|
+
level: 0,
|
|
31
|
+
name: "Suggest Only",
|
|
32
|
+
description: "Suggestions only; all actions need human approval",
|
|
33
|
+
capabilities: ["suggest", "analyze"],
|
|
34
|
+
},
|
|
35
|
+
L1: {
|
|
36
|
+
level: 1,
|
|
37
|
+
name: "Simple Tasks",
|
|
38
|
+
description: "Auto-execute simple tasks",
|
|
39
|
+
capabilities: ["suggest", "analyze", "simple_code_gen"],
|
|
40
|
+
},
|
|
41
|
+
L2: {
|
|
42
|
+
level: 2,
|
|
43
|
+
name: "Medium Complexity",
|
|
44
|
+
description: "Auto-execute medium-complexity tasks (default)",
|
|
45
|
+
capabilities: ["suggest", "analyze", "code_gen", "refactor", "test_gen"],
|
|
46
|
+
},
|
|
47
|
+
L3: {
|
|
48
|
+
level: 3,
|
|
49
|
+
name: "Complex Tasks",
|
|
50
|
+
description: "Auto-execute complex tasks",
|
|
51
|
+
capabilities: ["full_feature_dev", "architecture_design"],
|
|
52
|
+
},
|
|
53
|
+
L4: {
|
|
54
|
+
level: 4,
|
|
55
|
+
name: "Full Autonomy",
|
|
56
|
+
description: "Full autonomous development",
|
|
57
|
+
capabilities: ["project_planning", "team_collaboration"],
|
|
58
|
+
},
|
|
59
|
+
});
|
|
60
|
+
|
|
61
|
+
export const DEV_PHASES = Object.freeze({
|
|
62
|
+
REQUIREMENT_ANALYSIS: "requirement_analysis",
|
|
63
|
+
DESIGN: "design",
|
|
64
|
+
IMPLEMENTATION: "implementation",
|
|
65
|
+
TESTING: "testing",
|
|
66
|
+
REVIEW: "review",
|
|
67
|
+
DEPLOYMENT: "deployment",
|
|
68
|
+
});
|
|
69
|
+
|
|
70
|
+
const PHASE_ORDER = Object.freeze([
|
|
71
|
+
DEV_PHASES.REQUIREMENT_ANALYSIS,
|
|
72
|
+
DEV_PHASES.DESIGN,
|
|
73
|
+
DEV_PHASES.IMPLEMENTATION,
|
|
74
|
+
DEV_PHASES.TESTING,
|
|
75
|
+
DEV_PHASES.REVIEW,
|
|
76
|
+
DEV_PHASES.DEPLOYMENT,
|
|
77
|
+
]);
|
|
78
|
+
|
|
79
|
+
export const SESSION_STATUS = Object.freeze({
|
|
80
|
+
ACTIVE: "active",
|
|
81
|
+
PAUSED: "paused",
|
|
82
|
+
COMPLETED: "completed",
|
|
83
|
+
FAILED: "failed",
|
|
84
|
+
});
|
|
85
|
+
|
|
86
|
+
export const ADR_STATUS = Object.freeze({
|
|
87
|
+
PROPOSED: "proposed",
|
|
88
|
+
ACCEPTED: "accepted",
|
|
89
|
+
DEPRECATED: "deprecated",
|
|
90
|
+
SUPERSEDED: "superseded",
|
|
91
|
+
});
|
|
92
|
+
|
|
93
|
+
export const REFACTORING_TYPES = Object.freeze({
|
|
94
|
+
EXTRACT_METHOD: "extract_method",
|
|
95
|
+
RENAME_VARIABLE: "rename_variable",
|
|
96
|
+
SIMPLIFY_LOGIC: "simplify_logic",
|
|
97
|
+
INLINE_VARIABLE: "inline_variable",
|
|
98
|
+
EXTRACT_CLASS: "extract_class",
|
|
99
|
+
MOVE_METHOD: "move_method",
|
|
100
|
+
});
|
|
101
|
+
|
|
102
|
+
const VALID_PHASES = new Set(Object.values(DEV_PHASES));
|
|
103
|
+
const VALID_STATUS = new Set(Object.values(SESSION_STATUS));
|
|
104
|
+
const VALID_ADR_STATUS = new Set(Object.values(ADR_STATUS));
|
|
105
|
+
const VALID_AUTONOMY = new Set([0, 1, 2, 3, 4]);
|
|
106
|
+
|
|
107
|
+
/* ── State ─────────────────────────────────────────────────── */
|
|
108
|
+
|
|
109
|
+
const _sessions = new Map();
|
|
110
|
+
const _adrs = new Map();
|
|
111
|
+
let _seq = 0;
|
|
112
|
+
|
|
113
|
+
/* ── Schema ────────────────────────────────────────────────── */
|
|
114
|
+
|
|
115
|
+
export function ensureAutonomousDevTables(db) {
|
|
116
|
+
if (!db) return;
|
|
117
|
+
db.exec(`
|
|
118
|
+
CREATE TABLE IF NOT EXISTS dev_sessions (
|
|
119
|
+
session_id TEXT PRIMARY KEY,
|
|
120
|
+
requirement TEXT NOT NULL,
|
|
121
|
+
current_phase TEXT NOT NULL,
|
|
122
|
+
status TEXT DEFAULT 'active',
|
|
123
|
+
autonomy_level INTEGER DEFAULT 2,
|
|
124
|
+
code_changes TEXT,
|
|
125
|
+
test_results TEXT,
|
|
126
|
+
review_feedback TEXT,
|
|
127
|
+
started_at INTEGER NOT NULL,
|
|
128
|
+
completed_at INTEGER,
|
|
129
|
+
created_by TEXT,
|
|
130
|
+
updated_at INTEGER NOT NULL
|
|
131
|
+
)
|
|
132
|
+
`);
|
|
133
|
+
db.exec(`
|
|
134
|
+
CREATE TABLE IF NOT EXISTS architecture_decisions (
|
|
135
|
+
adr_id TEXT PRIMARY KEY,
|
|
136
|
+
session_id TEXT NOT NULL,
|
|
137
|
+
title TEXT NOT NULL,
|
|
138
|
+
context TEXT NOT NULL,
|
|
139
|
+
decision TEXT NOT NULL,
|
|
140
|
+
consequences TEXT,
|
|
141
|
+
alternatives TEXT,
|
|
142
|
+
status TEXT DEFAULT 'accepted',
|
|
143
|
+
created_at INTEGER NOT NULL
|
|
144
|
+
)
|
|
145
|
+
`);
|
|
146
|
+
db.exec(
|
|
147
|
+
`CREATE INDEX IF NOT EXISTS idx_dev_sessions_status ON dev_sessions(status)`,
|
|
148
|
+
);
|
|
149
|
+
db.exec(
|
|
150
|
+
`CREATE INDEX IF NOT EXISTS idx_architecture_decisions_session ON architecture_decisions(session_id)`,
|
|
151
|
+
);
|
|
152
|
+
}
|
|
153
|
+
|
|
154
|
+
/* ── Catalogs ──────────────────────────────────────────────── */
|
|
155
|
+
|
|
156
|
+
export function listAutonomyLevels() {
|
|
157
|
+
return Object.values(AUTONOMY_LEVELS);
|
|
158
|
+
}
|
|
159
|
+
|
|
160
|
+
export function listPhases() {
|
|
161
|
+
return [...PHASE_ORDER];
|
|
162
|
+
}
|
|
163
|
+
|
|
164
|
+
export function listRefactoringTypes() {
|
|
165
|
+
return Object.values(REFACTORING_TYPES);
|
|
166
|
+
}
|
|
167
|
+
|
|
168
|
+
/* ── Sessions ──────────────────────────────────────────────── */
|
|
169
|
+
|
|
170
|
+
function _strip(row) {
|
|
171
|
+
const { _seq: _omit, ...rest } = row;
|
|
172
|
+
void _omit;
|
|
173
|
+
return rest;
|
|
174
|
+
}
|
|
175
|
+
|
|
176
|
+
export function startDevSession(db, config = {}) {
|
|
177
|
+
const requirement = String(config.requirement || "").trim();
|
|
178
|
+
if (!requirement) throw new Error("requirement is required");
|
|
179
|
+
|
|
180
|
+
const autonomyLevel =
|
|
181
|
+
config.autonomyLevel == null ? 2 : Number(config.autonomyLevel);
|
|
182
|
+
if (!VALID_AUTONOMY.has(autonomyLevel)) {
|
|
183
|
+
throw new Error(
|
|
184
|
+
`Invalid autonomy level: ${config.autonomyLevel} (must be 0..4)`,
|
|
185
|
+
);
|
|
186
|
+
}
|
|
187
|
+
|
|
188
|
+
const now = Number(config.now ?? Date.now());
|
|
189
|
+
const sessionId = config.sessionId || crypto.randomUUID();
|
|
190
|
+
const session = {
|
|
191
|
+
sessionId,
|
|
192
|
+
requirement,
|
|
193
|
+
currentPhase: DEV_PHASES.REQUIREMENT_ANALYSIS,
|
|
194
|
+
status: SESSION_STATUS.ACTIVE,
|
|
195
|
+
autonomyLevel,
|
|
196
|
+
codeChanges: [],
|
|
197
|
+
testResults: null,
|
|
198
|
+
reviewFeedback: null,
|
|
199
|
+
startedAt: now,
|
|
200
|
+
completedAt: null,
|
|
201
|
+
createdBy: config.createdBy || null,
|
|
202
|
+
updatedAt: now,
|
|
203
|
+
_seq: ++_seq,
|
|
204
|
+
};
|
|
205
|
+
_sessions.set(sessionId, session);
|
|
206
|
+
|
|
207
|
+
if (db) {
|
|
208
|
+
db.prepare(
|
|
209
|
+
`INSERT INTO dev_sessions (session_id, requirement, current_phase, status, autonomy_level, code_changes, test_results, review_feedback, started_at, completed_at, created_by, updated_at)
|
|
210
|
+
VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)`,
|
|
211
|
+
).run(
|
|
212
|
+
sessionId,
|
|
213
|
+
requirement,
|
|
214
|
+
session.currentPhase,
|
|
215
|
+
session.status,
|
|
216
|
+
autonomyLevel,
|
|
217
|
+
JSON.stringify(session.codeChanges),
|
|
218
|
+
null,
|
|
219
|
+
null,
|
|
220
|
+
now,
|
|
221
|
+
null,
|
|
222
|
+
session.createdBy,
|
|
223
|
+
now,
|
|
224
|
+
);
|
|
225
|
+
}
|
|
226
|
+
|
|
227
|
+
return _strip(session);
|
|
228
|
+
}
|
|
229
|
+
|
|
230
|
+
export function getSession(sessionId) {
|
|
231
|
+
const s = _sessions.get(sessionId);
|
|
232
|
+
return s ? _strip(s) : null;
|
|
233
|
+
}
|
|
234
|
+
|
|
235
|
+
export function listSessions(opts = {}) {
|
|
236
|
+
let rows = [..._sessions.values()];
|
|
237
|
+
if (opts.status) {
|
|
238
|
+
const st = String(opts.status).toLowerCase();
|
|
239
|
+
rows = rows.filter((s) => s.status === st);
|
|
240
|
+
}
|
|
241
|
+
if (opts.phase) {
|
|
242
|
+
const p = String(opts.phase).toLowerCase();
|
|
243
|
+
rows = rows.filter((s) => s.currentPhase === p);
|
|
244
|
+
}
|
|
245
|
+
rows.sort((a, b) => b.startedAt - a.startedAt || b._seq - a._seq);
|
|
246
|
+
const limit = opts.limit || 50;
|
|
247
|
+
return rows.slice(0, limit).map(_strip);
|
|
248
|
+
}
|
|
249
|
+
|
|
250
|
+
function _mustGet(sessionId) {
|
|
251
|
+
const s = _sessions.get(sessionId);
|
|
252
|
+
if (!s) throw new Error(`Session not found: ${sessionId}`);
|
|
253
|
+
return s;
|
|
254
|
+
}
|
|
255
|
+
|
|
256
|
+
function _persistUpdate(db, sessionId, fields) {
|
|
257
|
+
if (!db) return;
|
|
258
|
+
const setClauses = Object.keys(fields)
|
|
259
|
+
.map((k) => `${k} = ?`)
|
|
260
|
+
.join(", ");
|
|
261
|
+
const values = Object.values(fields).map((v) =>
|
|
262
|
+
v && typeof v === "object" ? JSON.stringify(v) : v,
|
|
263
|
+
);
|
|
264
|
+
db.prepare(`UPDATE dev_sessions SET ${setClauses} WHERE session_id = ?`).run(
|
|
265
|
+
...values,
|
|
266
|
+
sessionId,
|
|
267
|
+
);
|
|
268
|
+
}
|
|
269
|
+
|
|
270
|
+
export function advancePhase(db, sessionId, nextPhase) {
|
|
271
|
+
const session = _mustGet(sessionId);
|
|
272
|
+
if (session.status !== SESSION_STATUS.ACTIVE) {
|
|
273
|
+
throw new Error(
|
|
274
|
+
`Cannot advance phase on non-active session (status=${session.status})`,
|
|
275
|
+
);
|
|
276
|
+
}
|
|
277
|
+
const target = String(nextPhase || "").toLowerCase();
|
|
278
|
+
if (!VALID_PHASES.has(target)) {
|
|
279
|
+
throw new Error(
|
|
280
|
+
`Invalid phase: ${nextPhase} (known: ${[...VALID_PHASES].join("/")})`,
|
|
281
|
+
);
|
|
282
|
+
}
|
|
283
|
+
const now = Date.now();
|
|
284
|
+
session.currentPhase = target;
|
|
285
|
+
session.updatedAt = now;
|
|
286
|
+
_persistUpdate(db, sessionId, {
|
|
287
|
+
current_phase: target,
|
|
288
|
+
updated_at: now,
|
|
289
|
+
});
|
|
290
|
+
return _strip(session);
|
|
291
|
+
}
|
|
292
|
+
|
|
293
|
+
export function pauseSession(db, sessionId) {
|
|
294
|
+
const session = _mustGet(sessionId);
|
|
295
|
+
if (session.status !== SESSION_STATUS.ACTIVE) {
|
|
296
|
+
throw new Error(
|
|
297
|
+
`Cannot pause non-active session (status=${session.status})`,
|
|
298
|
+
);
|
|
299
|
+
}
|
|
300
|
+
const now = Date.now();
|
|
301
|
+
session.status = SESSION_STATUS.PAUSED;
|
|
302
|
+
session.updatedAt = now;
|
|
303
|
+
_persistUpdate(db, sessionId, {
|
|
304
|
+
status: SESSION_STATUS.PAUSED,
|
|
305
|
+
updated_at: now,
|
|
306
|
+
});
|
|
307
|
+
return _strip(session);
|
|
308
|
+
}
|
|
309
|
+
|
|
310
|
+
export function resumeSession(db, sessionId) {
|
|
311
|
+
const session = _mustGet(sessionId);
|
|
312
|
+
if (session.status !== SESSION_STATUS.PAUSED) {
|
|
313
|
+
throw new Error(
|
|
314
|
+
`Cannot resume non-paused session (status=${session.status})`,
|
|
315
|
+
);
|
|
316
|
+
}
|
|
317
|
+
const now = Date.now();
|
|
318
|
+
session.status = SESSION_STATUS.ACTIVE;
|
|
319
|
+
session.updatedAt = now;
|
|
320
|
+
_persistUpdate(db, sessionId, {
|
|
321
|
+
status: SESSION_STATUS.ACTIVE,
|
|
322
|
+
updated_at: now,
|
|
323
|
+
});
|
|
324
|
+
return _strip(session);
|
|
325
|
+
}
|
|
326
|
+
|
|
327
|
+
export function completeSession(db, sessionId) {
|
|
328
|
+
const session = _mustGet(sessionId);
|
|
329
|
+
if (
|
|
330
|
+
session.status === SESSION_STATUS.COMPLETED ||
|
|
331
|
+
session.status === SESSION_STATUS.FAILED
|
|
332
|
+
) {
|
|
333
|
+
throw new Error(`Session already terminal (status=${session.status})`);
|
|
334
|
+
}
|
|
335
|
+
const now = Date.now();
|
|
336
|
+
session.status = SESSION_STATUS.COMPLETED;
|
|
337
|
+
session.completedAt = now;
|
|
338
|
+
session.updatedAt = now;
|
|
339
|
+
_persistUpdate(db, sessionId, {
|
|
340
|
+
status: SESSION_STATUS.COMPLETED,
|
|
341
|
+
completed_at: now,
|
|
342
|
+
updated_at: now,
|
|
343
|
+
});
|
|
344
|
+
return _strip(session);
|
|
345
|
+
}
|
|
346
|
+
|
|
347
|
+
export function failSession(db, sessionId, reason) {
|
|
348
|
+
const session = _mustGet(sessionId);
|
|
349
|
+
if (
|
|
350
|
+
session.status === SESSION_STATUS.COMPLETED ||
|
|
351
|
+
session.status === SESSION_STATUS.FAILED
|
|
352
|
+
) {
|
|
353
|
+
throw new Error(`Session already terminal (status=${session.status})`);
|
|
354
|
+
}
|
|
355
|
+
const now = Date.now();
|
|
356
|
+
const feedback = session.reviewFeedback || {};
|
|
357
|
+
if (reason) feedback.failureReason = reason;
|
|
358
|
+
session.reviewFeedback = feedback;
|
|
359
|
+
session.status = SESSION_STATUS.FAILED;
|
|
360
|
+
session.completedAt = now;
|
|
361
|
+
session.updatedAt = now;
|
|
362
|
+
_persistUpdate(db, sessionId, {
|
|
363
|
+
status: SESSION_STATUS.FAILED,
|
|
364
|
+
review_feedback: feedback,
|
|
365
|
+
completed_at: now,
|
|
366
|
+
updated_at: now,
|
|
367
|
+
});
|
|
368
|
+
return _strip(session);
|
|
369
|
+
}
|
|
370
|
+
|
|
371
|
+
/* ── Code review ───────────────────────────────────────────── */
|
|
372
|
+
|
|
373
|
+
export function reviewCode(filePath, opts = {}) {
|
|
374
|
+
const report = _detectAntiPatterns(filePath, opts);
|
|
375
|
+
|
|
376
|
+
// score: 1.0 clean, penalize high/medium findings.
|
|
377
|
+
let score = 1.0;
|
|
378
|
+
for (const f of report.findings) {
|
|
379
|
+
score -= f.severity === "high" ? 0.25 : 0.1;
|
|
380
|
+
}
|
|
381
|
+
if (score < 0) score = 0;
|
|
382
|
+
|
|
383
|
+
let grade;
|
|
384
|
+
if (score >= 0.9) grade = "A";
|
|
385
|
+
else if (score >= 0.75) grade = "B";
|
|
386
|
+
else if (score >= 0.6) grade = "C";
|
|
387
|
+
else if (score >= 0.4) grade = "D";
|
|
388
|
+
else grade = "F";
|
|
389
|
+
|
|
390
|
+
const result = {
|
|
391
|
+
filePath: report.filePath,
|
|
392
|
+
lines: report.lines,
|
|
393
|
+
functionCount: report.functionCount,
|
|
394
|
+
totalFindings: report.totalFindings,
|
|
395
|
+
findings: report.findings,
|
|
396
|
+
score: Number(score.toFixed(3)),
|
|
397
|
+
grade,
|
|
398
|
+
passed: score >= (opts.minScore ?? 0.7),
|
|
399
|
+
};
|
|
400
|
+
|
|
401
|
+
// Optional: attach review feedback to a session.
|
|
402
|
+
if (opts.sessionId) {
|
|
403
|
+
const session = _sessions.get(opts.sessionId);
|
|
404
|
+
if (session) {
|
|
405
|
+
const feedback = session.reviewFeedback || { reviews: [] };
|
|
406
|
+
if (!Array.isArray(feedback.reviews)) feedback.reviews = [];
|
|
407
|
+
feedback.reviews.push(result);
|
|
408
|
+
session.reviewFeedback = feedback;
|
|
409
|
+
session.updatedAt = Date.now();
|
|
410
|
+
if (opts.db) {
|
|
411
|
+
_persistUpdate(opts.db, opts.sessionId, {
|
|
412
|
+
review_feedback: feedback,
|
|
413
|
+
updated_at: session.updatedAt,
|
|
414
|
+
});
|
|
415
|
+
}
|
|
416
|
+
}
|
|
417
|
+
}
|
|
418
|
+
|
|
419
|
+
return result;
|
|
420
|
+
}
|
|
421
|
+
|
|
422
|
+
/* ── ADRs ──────────────────────────────────────────────────── */
|
|
423
|
+
|
|
424
|
+
export function recordADR(db, config = {}) {
|
|
425
|
+
const sessionId = String(config.sessionId || "").trim();
|
|
426
|
+
if (!sessionId) throw new Error("sessionId is required");
|
|
427
|
+
if (!_sessions.has(sessionId)) {
|
|
428
|
+
throw new Error(`Session not found: ${sessionId}`);
|
|
429
|
+
}
|
|
430
|
+
const title = String(config.title || "").trim();
|
|
431
|
+
const context = String(config.context || "").trim();
|
|
432
|
+
const decision = String(config.decision || "").trim();
|
|
433
|
+
if (!title) throw new Error("title is required");
|
|
434
|
+
if (!context) throw new Error("context is required");
|
|
435
|
+
if (!decision) throw new Error("decision is required");
|
|
436
|
+
|
|
437
|
+
const status = String(config.status || ADR_STATUS.ACCEPTED).toLowerCase();
|
|
438
|
+
if (!VALID_ADR_STATUS.has(status)) {
|
|
439
|
+
throw new Error(
|
|
440
|
+
`Invalid ADR status: ${config.status} (known: ${[...VALID_ADR_STATUS].join("/")})`,
|
|
441
|
+
);
|
|
442
|
+
}
|
|
443
|
+
|
|
444
|
+
const now = Number(config.now ?? Date.now());
|
|
445
|
+
const adrId = config.adrId || crypto.randomUUID();
|
|
446
|
+
const adr = {
|
|
447
|
+
adrId,
|
|
448
|
+
sessionId,
|
|
449
|
+
title,
|
|
450
|
+
context,
|
|
451
|
+
decision,
|
|
452
|
+
consequences: config.consequences || "",
|
|
453
|
+
alternatives: Array.isArray(config.alternatives) ? config.alternatives : [],
|
|
454
|
+
status,
|
|
455
|
+
createdAt: now,
|
|
456
|
+
_seq: ++_seq,
|
|
457
|
+
};
|
|
458
|
+
_adrs.set(adrId, adr);
|
|
459
|
+
|
|
460
|
+
if (db) {
|
|
461
|
+
db.prepare(
|
|
462
|
+
`INSERT INTO architecture_decisions (adr_id, session_id, title, context, decision, consequences, alternatives, status, created_at)
|
|
463
|
+
VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?)`,
|
|
464
|
+
).run(
|
|
465
|
+
adrId,
|
|
466
|
+
sessionId,
|
|
467
|
+
title,
|
|
468
|
+
context,
|
|
469
|
+
decision,
|
|
470
|
+
adr.consequences,
|
|
471
|
+
JSON.stringify(adr.alternatives),
|
|
472
|
+
status,
|
|
473
|
+
now,
|
|
474
|
+
);
|
|
475
|
+
}
|
|
476
|
+
|
|
477
|
+
return _strip(adr);
|
|
478
|
+
}
|
|
479
|
+
|
|
480
|
+
export function listADRs(opts = {}) {
|
|
481
|
+
let rows = [..._adrs.values()];
|
|
482
|
+
if (opts.sessionId) {
|
|
483
|
+
rows = rows.filter((a) => a.sessionId === opts.sessionId);
|
|
484
|
+
}
|
|
485
|
+
if (opts.status) {
|
|
486
|
+
const s = String(opts.status).toLowerCase();
|
|
487
|
+
rows = rows.filter((a) => a.status === s);
|
|
488
|
+
}
|
|
489
|
+
rows.sort((a, b) => a.createdAt - b.createdAt || a._seq - b._seq);
|
|
490
|
+
const limit = opts.limit || 50;
|
|
491
|
+
return rows.slice(0, limit).map(_strip);
|
|
492
|
+
}
|
|
493
|
+
|
|
494
|
+
export function renderADR(adr) {
|
|
495
|
+
const alts =
|
|
496
|
+
Array.isArray(adr.alternatives) && adr.alternatives.length
|
|
497
|
+
? adr.alternatives.map((a) => `- ${a}`).join("\n")
|
|
498
|
+
: "(none)";
|
|
499
|
+
return `# ADR: ${adr.title}
|
|
500
|
+
|
|
501
|
+
## Status
|
|
502
|
+
${adr.status}
|
|
503
|
+
|
|
504
|
+
## Context
|
|
505
|
+
${adr.context}
|
|
506
|
+
|
|
507
|
+
## Decision
|
|
508
|
+
${adr.decision}
|
|
509
|
+
|
|
510
|
+
## Consequences
|
|
511
|
+
${adr.consequences || "(none)"}
|
|
512
|
+
|
|
513
|
+
## Alternatives
|
|
514
|
+
${alts}
|
|
515
|
+
`;
|
|
516
|
+
}
|
|
517
|
+
|
|
518
|
+
/* ── Reset (tests) ─────────────────────────────────────────── */
|
|
519
|
+
|
|
520
|
+
export function _resetState() {
|
|
521
|
+
_sessions.clear();
|
|
522
|
+
_adrs.clear();
|
|
523
|
+
_seq = 0;
|
|
524
|
+
}
|