chainlesschain 0.49.0 → 0.66.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.
Files changed (43) hide show
  1. package/package.json +1 -1
  2. package/src/assets/web-panel/.build-hash +1 -1
  3. package/src/assets/web-panel/assets/{AppLayout-Rvi759IS.js → AppLayout-6SPt_8Y_.js} +1 -1
  4. package/src/assets/web-panel/assets/{Dashboard-DBhFxXYQ.js → Dashboard-Br7kCwKJ.js} +2 -2
  5. package/src/assets/web-panel/assets/Dashboard-CKeMmCoT.css +1 -0
  6. package/src/assets/web-panel/assets/{index-uL0cZ8N_.js → index-tN-8TosE.js} +2 -2
  7. package/src/assets/web-panel/index.html +2 -2
  8. package/src/commands/agent-network.js +785 -0
  9. package/src/commands/automation.js +654 -0
  10. package/src/commands/dao.js +565 -0
  11. package/src/commands/did-v2.js +620 -0
  12. package/src/commands/economy.js +578 -0
  13. package/src/commands/evolution.js +391 -0
  14. package/src/commands/hmemory.js +442 -0
  15. package/src/commands/ipfs.js +392 -0
  16. package/src/commands/multimodal.js +404 -0
  17. package/src/commands/perf.js +433 -0
  18. package/src/commands/pipeline.js +449 -0
  19. package/src/commands/plugin-ecosystem.js +517 -0
  20. package/src/commands/sandbox.js +401 -0
  21. package/src/commands/social.js +311 -0
  22. package/src/commands/sso.js +798 -0
  23. package/src/commands/workflow.js +320 -0
  24. package/src/commands/zkp.js +227 -1
  25. package/src/index.js +27 -0
  26. package/src/lib/agent-economy.js +479 -0
  27. package/src/lib/agent-network.js +1121 -0
  28. package/src/lib/automation-engine.js +948 -0
  29. package/src/lib/dao-governance.js +569 -0
  30. package/src/lib/did-v2-manager.js +1127 -0
  31. package/src/lib/evolution-system.js +453 -0
  32. package/src/lib/hierarchical-memory.js +481 -0
  33. package/src/lib/ipfs-storage.js +575 -0
  34. package/src/lib/multimodal.js +39 -12
  35. package/src/lib/perf-tuning.js +734 -0
  36. package/src/lib/pipeline-orchestrator.js +928 -0
  37. package/src/lib/plugin-ecosystem.js +1109 -0
  38. package/src/lib/sandbox-v2.js +306 -0
  39. package/src/lib/social-graph-analytics.js +707 -0
  40. package/src/lib/sso-manager.js +841 -0
  41. package/src/lib/workflow-engine.js +454 -1
  42. package/src/lib/zkp-engine.js +249 -20
  43. package/src/assets/web-panel/assets/Dashboard-BS-tzGNj.css +0 -1
@@ -0,0 +1,928 @@
1
+ /**
2
+ * Pipeline Orchestrator — CLI port of Phase 26 开发流水线编排
3
+ * (docs/design/modules/26_开发流水线编排.md).
4
+ *
5
+ * Desktop ships a 1,180-line PipelineOrchestrator + DeployAgent +
6
+ * PostDeployMonitor that drive a 7-stage AI development pipeline
7
+ * (requirement → architecture → code-gen → testing → code-review →
8
+ * deploy → monitoring) across 4 project templates (feature / bugfix /
9
+ * refactor / security-audit) with 6 deployment strategies and manual
10
+ * gate approval at the review/deploy boundaries.
11
+ *
12
+ * CLI port is headless and single-process:
13
+ * - Stage *execution* is caller-driven: `completeStage` records the
14
+ * output + artifacts and advances to the next stage. The CLI does
15
+ * NOT itself run the AI requirement parser / code generator / test
16
+ * runner — it's a bookkeeping + state-machine engine.
17
+ * - Deployments are recorded (strategy + status + result) but the CLI
18
+ * does NOT perform real git/docker/npm actions. Rollback flips the
19
+ * stored status only.
20
+ * - Post-deploy monitoring is log-only (append event rows). No real
21
+ * timers or HTTP health checks.
22
+ */
23
+
24
+ import crypto from "crypto";
25
+
26
+ /* ── Constants ───────────────────────────────────────────── */
27
+
28
+ export const STAGE = Object.freeze({
29
+ REQUIREMENT: "requirement",
30
+ ARCHITECTURE: "architecture",
31
+ CODE_GENERATION: "code-generation",
32
+ TESTING: "testing",
33
+ CODE_REVIEW: "code-review",
34
+ DEPLOY: "deploy",
35
+ MONITORING: "monitoring",
36
+ });
37
+
38
+ export const PIPELINE_STATUS = Object.freeze({
39
+ PENDING: "pending",
40
+ RUNNING: "running",
41
+ PAUSED: "paused",
42
+ COMPLETED: "completed",
43
+ FAILED: "failed",
44
+ CANCELLED: "cancelled",
45
+ });
46
+
47
+ export const STAGE_STATUS = Object.freeze({
48
+ PENDING: "pending",
49
+ RUNNING: "running",
50
+ GATE_WAITING: "gate-waiting",
51
+ COMPLETED: "completed",
52
+ FAILED: "failed",
53
+ SKIPPED: "skipped",
54
+ });
55
+
56
+ export const DEPLOY_STRATEGY = Object.freeze({
57
+ GIT_PR: "git-pr",
58
+ DOCKER: "docker",
59
+ NPM_PUBLISH: "npm-publish",
60
+ LOCAL: "local",
61
+ STAGING: "staging",
62
+ CUSTOM: "custom",
63
+ });
64
+
65
+ export const DEPLOY_STATUS = Object.freeze({
66
+ PENDING: "pending",
67
+ RUNNING: "running",
68
+ SUCCEEDED: "succeeded",
69
+ FAILED: "failed",
70
+ ROLLED_BACK: "rolled-back",
71
+ });
72
+
73
+ export const ARTIFACT_TYPE = Object.freeze({
74
+ DOCUMENT: "document",
75
+ CODE: "code",
76
+ REPORT: "report",
77
+ CONFIG: "config",
78
+ DEPLOY_RESULT: "deploy-result",
79
+ });
80
+
81
+ export const GATE_STAGES = Object.freeze(
82
+ new Set([STAGE.CODE_REVIEW, STAGE.DEPLOY]),
83
+ );
84
+
85
+ export const PIPELINE_TEMPLATES = Object.freeze({
86
+ feature: {
87
+ name: "feature",
88
+ description: "完整的新功能开发流水线 (7 stages)",
89
+ stages: [
90
+ STAGE.REQUIREMENT,
91
+ STAGE.ARCHITECTURE,
92
+ STAGE.CODE_GENERATION,
93
+ STAGE.TESTING,
94
+ STAGE.CODE_REVIEW,
95
+ STAGE.DEPLOY,
96
+ STAGE.MONITORING,
97
+ ],
98
+ },
99
+ bugfix: {
100
+ name: "bugfix",
101
+ description: "Bug 修复流水线 (跳过架构+部署+监控)",
102
+ stages: [
103
+ STAGE.REQUIREMENT,
104
+ STAGE.CODE_GENERATION,
105
+ STAGE.TESTING,
106
+ STAGE.CODE_REVIEW,
107
+ ],
108
+ },
109
+ refactor: {
110
+ name: "refactor",
111
+ description: "代码重构流水线 (无需求解析)",
112
+ stages: [
113
+ STAGE.ARCHITECTURE,
114
+ STAGE.CODE_GENERATION,
115
+ STAGE.TESTING,
116
+ STAGE.CODE_REVIEW,
117
+ ],
118
+ },
119
+ "security-audit": {
120
+ name: "security-audit",
121
+ description: "安全审计流水线 (仅代码/测试/审查)",
122
+ stages: [STAGE.CODE_GENERATION, STAGE.TESTING, STAGE.CODE_REVIEW],
123
+ },
124
+ });
125
+
126
+ /* ── Schema ──────────────────────────────────────────────── */
127
+
128
+ export function ensurePipelineTables(db) {
129
+ db.exec(`
130
+ CREATE TABLE IF NOT EXISTS dev_pipelines (
131
+ id TEXT PRIMARY KEY,
132
+ name TEXT,
133
+ template TEXT NOT NULL,
134
+ config TEXT,
135
+ status TEXT NOT NULL,
136
+ current_stage INTEGER DEFAULT 0,
137
+ result TEXT,
138
+ error_message TEXT,
139
+ created_at INTEGER NOT NULL,
140
+ updated_at INTEGER NOT NULL,
141
+ started_at INTEGER,
142
+ completed_at INTEGER
143
+ )
144
+ `);
145
+
146
+ db.exec(`
147
+ CREATE TABLE IF NOT EXISTS dev_pipeline_stages (
148
+ id TEXT PRIMARY KEY,
149
+ pipeline_id TEXT NOT NULL,
150
+ stage_index INTEGER NOT NULL,
151
+ name TEXT NOT NULL,
152
+ status TEXT NOT NULL,
153
+ gate_required INTEGER DEFAULT 0,
154
+ gate_approved INTEGER DEFAULT 0,
155
+ gate_reject_reason TEXT,
156
+ input TEXT,
157
+ output TEXT,
158
+ error_message TEXT,
159
+ started_at INTEGER,
160
+ completed_at INTEGER
161
+ )
162
+ `);
163
+
164
+ db.exec(`
165
+ CREATE TABLE IF NOT EXISTS dev_pipeline_artifacts (
166
+ id TEXT PRIMARY KEY,
167
+ pipeline_id TEXT NOT NULL,
168
+ stage_index INTEGER NOT NULL,
169
+ type TEXT NOT NULL,
170
+ name TEXT NOT NULL,
171
+ content TEXT,
172
+ metadata TEXT,
173
+ created_at INTEGER NOT NULL
174
+ )
175
+ `);
176
+
177
+ db.exec(`
178
+ CREATE TABLE IF NOT EXISTS dev_pipeline_deploys (
179
+ id TEXT PRIMARY KEY,
180
+ pipeline_id TEXT,
181
+ strategy TEXT NOT NULL,
182
+ config TEXT,
183
+ status TEXT NOT NULL,
184
+ result TEXT,
185
+ error_message TEXT,
186
+ rolled_back_at INTEGER,
187
+ rollback_reason TEXT,
188
+ created_at INTEGER NOT NULL,
189
+ completed_at INTEGER
190
+ )
191
+ `);
192
+
193
+ db.exec(`
194
+ CREATE TABLE IF NOT EXISTS dev_pipeline_monitor_events (
195
+ id TEXT PRIMARY KEY,
196
+ deploy_id TEXT NOT NULL,
197
+ event_type TEXT NOT NULL,
198
+ health_status TEXT,
199
+ metrics TEXT,
200
+ created_at INTEGER NOT NULL
201
+ )
202
+ `);
203
+ }
204
+
205
+ /* ── Internals ───────────────────────────────────────────── */
206
+
207
+ const _now = () => Date.now();
208
+ const _uid = (prefix) => `${prefix}-${crypto.randomBytes(6).toString("hex")}`;
209
+
210
+ function _parseJSON(value, fallback) {
211
+ if (!value) return fallback;
212
+ try {
213
+ return JSON.parse(value);
214
+ } catch {
215
+ return fallback;
216
+ }
217
+ }
218
+
219
+ function _rowToPipeline(row) {
220
+ if (!row) return null;
221
+ return {
222
+ id: row.id,
223
+ name: row.name,
224
+ template: row.template,
225
+ config: _parseJSON(row.config, {}),
226
+ status: row.status,
227
+ currentStage: row.current_stage,
228
+ result: _parseJSON(row.result, null),
229
+ errorMessage: row.error_message,
230
+ createdAt: row.created_at,
231
+ updatedAt: row.updated_at,
232
+ startedAt: row.started_at,
233
+ completedAt: row.completed_at,
234
+ };
235
+ }
236
+
237
+ function _rowToStage(row) {
238
+ if (!row) return null;
239
+ return {
240
+ id: row.id,
241
+ pipelineId: row.pipeline_id,
242
+ stageIndex: row.stage_index,
243
+ name: row.name,
244
+ status: row.status,
245
+ gateRequired: !!row.gate_required,
246
+ gateApproved: !!row.gate_approved,
247
+ gateRejectReason: row.gate_reject_reason,
248
+ input: _parseJSON(row.input, null),
249
+ output: _parseJSON(row.output, null),
250
+ errorMessage: row.error_message,
251
+ startedAt: row.started_at,
252
+ completedAt: row.completed_at,
253
+ };
254
+ }
255
+
256
+ function _rowToArtifact(row) {
257
+ if (!row) return null;
258
+ return {
259
+ id: row.id,
260
+ pipelineId: row.pipeline_id,
261
+ stageIndex: row.stage_index,
262
+ type: row.type,
263
+ name: row.name,
264
+ content: row.content,
265
+ metadata: _parseJSON(row.metadata, {}),
266
+ createdAt: row.created_at,
267
+ };
268
+ }
269
+
270
+ function _rowToDeploy(row) {
271
+ if (!row) return null;
272
+ return {
273
+ id: row.id,
274
+ pipelineId: row.pipeline_id,
275
+ strategy: row.strategy,
276
+ config: _parseJSON(row.config, {}),
277
+ status: row.status,
278
+ result: _parseJSON(row.result, null),
279
+ errorMessage: row.error_message,
280
+ rolledBackAt: row.rolled_back_at,
281
+ rollbackReason: row.rollback_reason,
282
+ createdAt: row.created_at,
283
+ completedAt: row.completed_at,
284
+ };
285
+ }
286
+
287
+ function _rowToMonitorEvent(row) {
288
+ if (!row) return null;
289
+ return {
290
+ id: row.id,
291
+ deployId: row.deploy_id,
292
+ eventType: row.event_type,
293
+ healthStatus: row.health_status,
294
+ metrics: _parseJSON(row.metrics, {}),
295
+ createdAt: row.created_at,
296
+ };
297
+ }
298
+
299
+ function _getTemplate(templateName) {
300
+ const tpl = PIPELINE_TEMPLATES[templateName];
301
+ if (!tpl) {
302
+ throw new Error(
303
+ `Unknown template: ${templateName}. Valid: ${Object.keys(PIPELINE_TEMPLATES).join(", ")}`,
304
+ );
305
+ }
306
+ return tpl;
307
+ }
308
+
309
+ function _getPipelineRow(db, pipelineId) {
310
+ return db.prepare("SELECT * FROM dev_pipelines WHERE id = ?").get(pipelineId);
311
+ }
312
+
313
+ function _getStageRow(db, pipelineId, stageIndex) {
314
+ return db
315
+ .prepare(
316
+ "SELECT * FROM dev_pipeline_stages WHERE pipeline_id = ? AND stage_index = ?",
317
+ )
318
+ .get(pipelineId, stageIndex);
319
+ }
320
+
321
+ function _getPipelineStages(db, pipelineId) {
322
+ const rows = db
323
+ .prepare(
324
+ "SELECT * FROM dev_pipeline_stages WHERE pipeline_id = ? ORDER BY stage_index ASC",
325
+ )
326
+ .all(pipelineId);
327
+ return rows.map(_rowToStage);
328
+ }
329
+
330
+ function _updatePipelineFields(db, pipelineId, fields) {
331
+ const sets = [];
332
+ const params = [];
333
+ for (const [col, val] of Object.entries(fields)) {
334
+ sets.push(`${col} = ?`);
335
+ params.push(val);
336
+ }
337
+ sets.push("updated_at = ?");
338
+ params.push(_now());
339
+ params.push(pipelineId);
340
+ db.prepare(`UPDATE dev_pipelines SET ${sets.join(", ")} WHERE id = ?`).run(
341
+ ...params,
342
+ );
343
+ }
344
+
345
+ function _updateStageFields(db, stageId, fields) {
346
+ const sets = [];
347
+ const params = [];
348
+ for (const [col, val] of Object.entries(fields)) {
349
+ sets.push(`${col} = ?`);
350
+ params.push(val);
351
+ }
352
+ params.push(stageId);
353
+ db.prepare(
354
+ `UPDATE dev_pipeline_stages SET ${sets.join(", ")} WHERE id = ?`,
355
+ ).run(...params);
356
+ }
357
+
358
+ /* ── Pipeline lifecycle ──────────────────────────────────── */
359
+
360
+ export function createPipeline(
361
+ db,
362
+ { template, name = null, config = {} } = {},
363
+ ) {
364
+ if (!template) throw new Error("template is required");
365
+ const tpl = _getTemplate(template);
366
+
367
+ const id = _uid("pipe");
368
+ const now = _now();
369
+
370
+ db.prepare(
371
+ `INSERT INTO dev_pipelines (id, name, template, config, status, current_stage, created_at, updated_at) VALUES (?, ?, ?, ?, ?, ?, ?, ?)`,
372
+ ).run(
373
+ id,
374
+ name,
375
+ template,
376
+ JSON.stringify(config || {}),
377
+ PIPELINE_STATUS.PENDING,
378
+ 0,
379
+ now,
380
+ now,
381
+ );
382
+
383
+ tpl.stages.forEach((stageName, idx) => {
384
+ const stageId = _uid("stg");
385
+ const gateRequired = GATE_STAGES.has(stageName) ? 1 : 0;
386
+ db.prepare(
387
+ `INSERT INTO dev_pipeline_stages (id, pipeline_id, stage_index, name, status, gate_required, gate_approved) VALUES (?, ?, ?, ?, ?, ?, ?)`,
388
+ ).run(stageId, id, idx, stageName, STAGE_STATUS.PENDING, gateRequired, 0);
389
+ });
390
+
391
+ return getPipeline(db, id);
392
+ }
393
+
394
+ export function startPipeline(db, pipelineId) {
395
+ const pipeline = _getPipelineRow(db, pipelineId);
396
+ if (!pipeline) throw new Error(`Pipeline not found: ${pipelineId}`);
397
+ if (pipeline.status !== PIPELINE_STATUS.PENDING) {
398
+ throw new Error(
399
+ `Cannot start pipeline in status ${pipeline.status} (must be pending)`,
400
+ );
401
+ }
402
+
403
+ const now = _now();
404
+ _updatePipelineFields(db, pipelineId, {
405
+ status: PIPELINE_STATUS.RUNNING,
406
+ started_at: now,
407
+ });
408
+
409
+ const firstStage = _getStageRow(db, pipelineId, 0);
410
+ if (firstStage) {
411
+ const nextStatus = firstStage.gate_required
412
+ ? STAGE_STATUS.GATE_WAITING
413
+ : STAGE_STATUS.RUNNING;
414
+ _updateStageFields(db, firstStage.id, {
415
+ status: nextStatus,
416
+ started_at: now,
417
+ });
418
+ }
419
+
420
+ return getPipeline(db, pipelineId);
421
+ }
422
+
423
+ export function pausePipeline(db, pipelineId) {
424
+ const pipeline = _getPipelineRow(db, pipelineId);
425
+ if (!pipeline) throw new Error(`Pipeline not found: ${pipelineId}`);
426
+ if (pipeline.status !== PIPELINE_STATUS.RUNNING) {
427
+ throw new Error(
428
+ `Cannot pause pipeline in status ${pipeline.status} (must be running)`,
429
+ );
430
+ }
431
+ _updatePipelineFields(db, pipelineId, { status: PIPELINE_STATUS.PAUSED });
432
+ return getPipeline(db, pipelineId);
433
+ }
434
+
435
+ export function resumePipeline(db, pipelineId) {
436
+ const pipeline = _getPipelineRow(db, pipelineId);
437
+ if (!pipeline) throw new Error(`Pipeline not found: ${pipelineId}`);
438
+ if (pipeline.status !== PIPELINE_STATUS.PAUSED) {
439
+ throw new Error(
440
+ `Cannot resume pipeline in status ${pipeline.status} (must be paused)`,
441
+ );
442
+ }
443
+ _updatePipelineFields(db, pipelineId, { status: PIPELINE_STATUS.RUNNING });
444
+ return getPipeline(db, pipelineId);
445
+ }
446
+
447
+ export function cancelPipeline(db, pipelineId, reason = "cancelled by user") {
448
+ const pipeline = _getPipelineRow(db, pipelineId);
449
+ if (!pipeline) throw new Error(`Pipeline not found: ${pipelineId}`);
450
+ if (
451
+ pipeline.status === PIPELINE_STATUS.COMPLETED ||
452
+ pipeline.status === PIPELINE_STATUS.CANCELLED
453
+ ) {
454
+ throw new Error(`Cannot cancel pipeline in status ${pipeline.status}`);
455
+ }
456
+ const now = _now();
457
+ _updatePipelineFields(db, pipelineId, {
458
+ status: PIPELINE_STATUS.CANCELLED,
459
+ completed_at: now,
460
+ error_message: reason,
461
+ });
462
+ return getPipeline(db, pipelineId);
463
+ }
464
+
465
+ /* ── Stage execution ─────────────────────────────────────── */
466
+
467
+ export function completeStage(
468
+ db,
469
+ pipelineId,
470
+ { output = null, artifacts = [] } = {},
471
+ ) {
472
+ const pipeline = _getPipelineRow(db, pipelineId);
473
+ if (!pipeline) throw new Error(`Pipeline not found: ${pipelineId}`);
474
+ if (pipeline.status !== PIPELINE_STATUS.RUNNING) {
475
+ throw new Error(
476
+ `Cannot complete stage: pipeline is ${pipeline.status} (must be running)`,
477
+ );
478
+ }
479
+
480
+ const current = _getStageRow(db, pipelineId, pipeline.current_stage);
481
+ if (!current) throw new Error("No current stage found");
482
+ if (current.status === STAGE_STATUS.GATE_WAITING) {
483
+ throw new Error(
484
+ `Stage ${current.name} is a gate and awaiting approval — call approveGate first`,
485
+ );
486
+ }
487
+ if (current.status !== STAGE_STATUS.RUNNING) {
488
+ throw new Error(
489
+ `Cannot complete stage in status ${current.status} (must be running)`,
490
+ );
491
+ }
492
+
493
+ const now = _now();
494
+ _updateStageFields(db, current.id, {
495
+ status: STAGE_STATUS.COMPLETED,
496
+ output: output ? JSON.stringify(output) : null,
497
+ completed_at: now,
498
+ });
499
+
500
+ if (artifacts && artifacts.length) {
501
+ for (const art of artifacts) {
502
+ addArtifact(db, pipelineId, pipeline.current_stage, art);
503
+ }
504
+ }
505
+
506
+ // Advance
507
+ const stages = _getPipelineStages(db, pipelineId);
508
+ const nextIdx = pipeline.current_stage + 1;
509
+
510
+ if (nextIdx >= stages.length) {
511
+ _updatePipelineFields(db, pipelineId, {
512
+ status: PIPELINE_STATUS.COMPLETED,
513
+ current_stage: pipeline.current_stage,
514
+ completed_at: now,
515
+ });
516
+ return getPipeline(db, pipelineId);
517
+ }
518
+
519
+ const nextStage = _getStageRow(db, pipelineId, nextIdx);
520
+ const nextStatus = nextStage.gate_required
521
+ ? STAGE_STATUS.GATE_WAITING
522
+ : STAGE_STATUS.RUNNING;
523
+ _updateStageFields(db, nextStage.id, {
524
+ status: nextStatus,
525
+ started_at: now,
526
+ });
527
+ _updatePipelineFields(db, pipelineId, { current_stage: nextIdx });
528
+
529
+ return getPipeline(db, pipelineId);
530
+ }
531
+
532
+ export function failStage(db, pipelineId, errorMessage = "stage failed") {
533
+ const pipeline = _getPipelineRow(db, pipelineId);
534
+ if (!pipeline) throw new Error(`Pipeline not found: ${pipelineId}`);
535
+ const current = _getStageRow(db, pipelineId, pipeline.current_stage);
536
+ if (!current) throw new Error("No current stage found");
537
+
538
+ const now = _now();
539
+ _updateStageFields(db, current.id, {
540
+ status: STAGE_STATUS.FAILED,
541
+ error_message: errorMessage,
542
+ completed_at: now,
543
+ });
544
+ _updatePipelineFields(db, pipelineId, {
545
+ status: PIPELINE_STATUS.FAILED,
546
+ error_message: errorMessage,
547
+ completed_at: now,
548
+ });
549
+ return getPipeline(db, pipelineId);
550
+ }
551
+
552
+ export function retryStage(db, pipelineId, stageIndex) {
553
+ const pipeline = _getPipelineRow(db, pipelineId);
554
+ if (!pipeline) throw new Error(`Pipeline not found: ${pipelineId}`);
555
+
556
+ const stage = _getStageRow(db, pipelineId, stageIndex);
557
+ if (!stage) throw new Error(`Stage ${stageIndex} not found`);
558
+
559
+ const now = _now();
560
+ const nextStatus = stage.gate_required
561
+ ? STAGE_STATUS.GATE_WAITING
562
+ : STAGE_STATUS.RUNNING;
563
+
564
+ _updateStageFields(db, stage.id, {
565
+ status: nextStatus,
566
+ gate_approved: 0,
567
+ gate_reject_reason: null,
568
+ error_message: null,
569
+ output: null,
570
+ started_at: now,
571
+ completed_at: null,
572
+ });
573
+ _updatePipelineFields(db, pipelineId, {
574
+ status: PIPELINE_STATUS.RUNNING,
575
+ current_stage: stageIndex,
576
+ error_message: null,
577
+ completed_at: null,
578
+ });
579
+
580
+ return getPipeline(db, pipelineId);
581
+ }
582
+
583
+ /* ── Gate approval ───────────────────────────────────────── */
584
+
585
+ export function approveGate(db, pipelineId) {
586
+ const pipeline = _getPipelineRow(db, pipelineId);
587
+ if (!pipeline) throw new Error(`Pipeline not found: ${pipelineId}`);
588
+
589
+ const current = _getStageRow(db, pipelineId, pipeline.current_stage);
590
+ if (!current) throw new Error("No current stage found");
591
+ if (!current.gate_required) {
592
+ throw new Error(`Stage ${current.name} has no gate`);
593
+ }
594
+ if (current.status !== STAGE_STATUS.GATE_WAITING) {
595
+ throw new Error(`Gate not waiting — stage is in status ${current.status}`);
596
+ }
597
+
598
+ _updateStageFields(db, current.id, {
599
+ status: STAGE_STATUS.RUNNING,
600
+ gate_approved: 1,
601
+ });
602
+
603
+ return getPipeline(db, pipelineId);
604
+ }
605
+
606
+ export function rejectGate(db, pipelineId, reason = "gate rejected") {
607
+ const pipeline = _getPipelineRow(db, pipelineId);
608
+ if (!pipeline) throw new Error(`Pipeline not found: ${pipelineId}`);
609
+
610
+ const current = _getStageRow(db, pipelineId, pipeline.current_stage);
611
+ if (!current) throw new Error("No current stage found");
612
+ if (!current.gate_required) {
613
+ throw new Error(`Stage ${current.name} has no gate`);
614
+ }
615
+ if (current.status !== STAGE_STATUS.GATE_WAITING) {
616
+ throw new Error(`Gate not waiting — stage is in status ${current.status}`);
617
+ }
618
+
619
+ const now = _now();
620
+ _updateStageFields(db, current.id, {
621
+ status: STAGE_STATUS.FAILED,
622
+ gate_approved: 0,
623
+ gate_reject_reason: reason,
624
+ completed_at: now,
625
+ });
626
+ _updatePipelineFields(db, pipelineId, {
627
+ status: PIPELINE_STATUS.FAILED,
628
+ error_message: `Gate rejected at ${current.name}: ${reason}`,
629
+ completed_at: now,
630
+ });
631
+
632
+ return getPipeline(db, pipelineId);
633
+ }
634
+
635
+ /* ── Artifacts ───────────────────────────────────────────── */
636
+
637
+ export function addArtifact(db, pipelineId, stageIndex, artifact) {
638
+ if (!artifact || !artifact.name) {
639
+ throw new Error("artifact.name is required");
640
+ }
641
+ const id = _uid("art");
642
+ const now = _now();
643
+ db.prepare(
644
+ `INSERT INTO dev_pipeline_artifacts (id, pipeline_id, stage_index, type, name, content, metadata, created_at) VALUES (?, ?, ?, ?, ?, ?, ?, ?)`,
645
+ ).run(
646
+ id,
647
+ pipelineId,
648
+ stageIndex,
649
+ artifact.type || ARTIFACT_TYPE.DOCUMENT,
650
+ artifact.name,
651
+ artifact.content || "",
652
+ JSON.stringify(artifact.metadata || {}),
653
+ now,
654
+ );
655
+ return getArtifact(db, id);
656
+ }
657
+
658
+ export function getArtifact(db, artifactId) {
659
+ const row = db
660
+ .prepare("SELECT * FROM dev_pipeline_artifacts WHERE id = ?")
661
+ .get(artifactId);
662
+ return _rowToArtifact(row);
663
+ }
664
+
665
+ export function listArtifacts(db, pipelineId, stageIndex = null) {
666
+ let rows;
667
+ if (stageIndex != null) {
668
+ rows = db
669
+ .prepare(
670
+ "SELECT * FROM dev_pipeline_artifacts WHERE pipeline_id = ? AND stage_index = ? ORDER BY created_at ASC",
671
+ )
672
+ .all(pipelineId, stageIndex);
673
+ } else {
674
+ rows = db
675
+ .prepare(
676
+ "SELECT * FROM dev_pipeline_artifacts WHERE pipeline_id = ? ORDER BY stage_index ASC, created_at ASC",
677
+ )
678
+ .all(pipelineId);
679
+ }
680
+ return rows.map(_rowToArtifact);
681
+ }
682
+
683
+ /* ── Queries ─────────────────────────────────────────────── */
684
+
685
+ export function getPipeline(db, pipelineId) {
686
+ const row = _getPipelineRow(db, pipelineId);
687
+ if (!row) return null;
688
+ const pipeline = _rowToPipeline(row);
689
+ pipeline.stages = _getPipelineStages(db, pipelineId);
690
+ return pipeline;
691
+ }
692
+
693
+ export function listPipelines(db, { template, status, limit = 100 } = {}) {
694
+ const wheres = [];
695
+ const params = [];
696
+ if (template) {
697
+ wheres.push("template = ?");
698
+ params.push(template);
699
+ }
700
+ if (status) {
701
+ wheres.push("status = ?");
702
+ params.push(status);
703
+ }
704
+ const where = wheres.length ? `WHERE ${wheres.join(" AND ")}` : "";
705
+ params.push(limit);
706
+ const rows = db
707
+ .prepare(
708
+ `SELECT * FROM dev_pipelines ${where} ORDER BY created_at DESC LIMIT ?`,
709
+ )
710
+ .all(...params);
711
+ return rows.map(_rowToPipeline);
712
+ }
713
+
714
+ export function getStage(db, pipelineId, stageIndex) {
715
+ return _rowToStage(_getStageRow(db, pipelineId, stageIndex));
716
+ }
717
+
718
+ export function getTemplates() {
719
+ return Object.values(PIPELINE_TEMPLATES).map((t) => ({
720
+ name: t.name,
721
+ description: t.description,
722
+ stages: [...t.stages],
723
+ gateStages: t.stages.filter((s) => GATE_STAGES.has(s)),
724
+ }));
725
+ }
726
+
727
+ export function getConfig() {
728
+ return {
729
+ stages: Object.values(STAGE),
730
+ pipelineStatuses: Object.values(PIPELINE_STATUS),
731
+ stageStatuses: Object.values(STAGE_STATUS),
732
+ deployStrategies: Object.values(DEPLOY_STRATEGY),
733
+ deployStatuses: Object.values(DEPLOY_STATUS),
734
+ artifactTypes: Object.values(ARTIFACT_TYPE),
735
+ gateStages: [...GATE_STAGES],
736
+ templates: Object.keys(PIPELINE_TEMPLATES),
737
+ };
738
+ }
739
+
740
+ /* ── Deployments ─────────────────────────────────────────── */
741
+
742
+ export function recordDeploy(
743
+ db,
744
+ {
745
+ pipelineId = null,
746
+ strategy,
747
+ config = {},
748
+ result = null,
749
+ status = DEPLOY_STATUS.SUCCEEDED,
750
+ errorMessage = null,
751
+ } = {},
752
+ ) {
753
+ if (!strategy) throw new Error("strategy is required");
754
+ const validStrategies = Object.values(DEPLOY_STRATEGY);
755
+ if (!validStrategies.includes(strategy)) {
756
+ throw new Error(
757
+ `Unknown deploy strategy: ${strategy}. Valid: ${validStrategies.join(", ")}`,
758
+ );
759
+ }
760
+
761
+ const id = _uid("dep");
762
+ const now = _now();
763
+ const completedAt =
764
+ status === DEPLOY_STATUS.PENDING || status === DEPLOY_STATUS.RUNNING
765
+ ? null
766
+ : now;
767
+ db.prepare(
768
+ `INSERT INTO dev_pipeline_deploys (id, pipeline_id, strategy, config, status, result, error_message, created_at, completed_at) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?)`,
769
+ ).run(
770
+ id,
771
+ pipelineId,
772
+ strategy,
773
+ JSON.stringify(config || {}),
774
+ status,
775
+ result ? JSON.stringify(result) : null,
776
+ errorMessage,
777
+ now,
778
+ completedAt,
779
+ );
780
+ return getDeploy(db, id);
781
+ }
782
+
783
+ export function getDeploy(db, deployId) {
784
+ const row = db
785
+ .prepare("SELECT * FROM dev_pipeline_deploys WHERE id = ?")
786
+ .get(deployId);
787
+ return _rowToDeploy(row);
788
+ }
789
+
790
+ export function listDeploys(
791
+ db,
792
+ { pipelineId, strategy, status, limit = 100 } = {},
793
+ ) {
794
+ const wheres = [];
795
+ const params = [];
796
+ if (pipelineId) {
797
+ wheres.push("pipeline_id = ?");
798
+ params.push(pipelineId);
799
+ }
800
+ if (strategy) {
801
+ wheres.push("strategy = ?");
802
+ params.push(strategy);
803
+ }
804
+ if (status) {
805
+ wheres.push("status = ?");
806
+ params.push(status);
807
+ }
808
+ const where = wheres.length ? `WHERE ${wheres.join(" AND ")}` : "";
809
+ params.push(limit);
810
+ const rows = db
811
+ .prepare(
812
+ `SELECT * FROM dev_pipeline_deploys ${where} ORDER BY created_at DESC LIMIT ?`,
813
+ )
814
+ .all(...params);
815
+ return rows.map(_rowToDeploy);
816
+ }
817
+
818
+ export function rollbackDeploy(db, deployId, reason = "rollback requested") {
819
+ const deploy = getDeploy(db, deployId);
820
+ if (!deploy) throw new Error(`Deploy not found: ${deployId}`);
821
+ if (deploy.status === DEPLOY_STATUS.ROLLED_BACK) {
822
+ throw new Error("Deploy already rolled back");
823
+ }
824
+ if (deploy.status !== DEPLOY_STATUS.SUCCEEDED) {
825
+ throw new Error(
826
+ `Cannot roll back deploy in status ${deploy.status} (must be succeeded)`,
827
+ );
828
+ }
829
+ const now = _now();
830
+ db.prepare(
831
+ `UPDATE dev_pipeline_deploys SET status = ?, rolled_back_at = ?, rollback_reason = ? WHERE id = ?`,
832
+ ).run(DEPLOY_STATUS.ROLLED_BACK, now, reason, deployId);
833
+ return getDeploy(db, deployId);
834
+ }
835
+
836
+ /* ── Monitoring ──────────────────────────────────────────── */
837
+
838
+ export function recordMonitorEvent(
839
+ db,
840
+ deployId,
841
+ { eventType = "health-check", healthStatus = "healthy", metrics = {} } = {},
842
+ ) {
843
+ const deploy = getDeploy(db, deployId);
844
+ if (!deploy) throw new Error(`Deploy not found: ${deployId}`);
845
+
846
+ const id = _uid("mon");
847
+ const now = _now();
848
+ db.prepare(
849
+ `INSERT INTO dev_pipeline_monitor_events (id, deploy_id, event_type, health_status, metrics, created_at) VALUES (?, ?, ?, ?, ?, ?)`,
850
+ ).run(id, deployId, eventType, healthStatus, JSON.stringify(metrics), now);
851
+
852
+ const row = db
853
+ .prepare("SELECT * FROM dev_pipeline_monitor_events WHERE id = ?")
854
+ .get(id);
855
+ return _rowToMonitorEvent(row);
856
+ }
857
+
858
+ export function listMonitorEvents(db, deployId, { limit = 100 } = {}) {
859
+ const rows = db
860
+ .prepare(
861
+ "SELECT * FROM dev_pipeline_monitor_events WHERE deploy_id = ? ORDER BY created_at DESC LIMIT ?",
862
+ )
863
+ .all(deployId, limit);
864
+ return rows.map(_rowToMonitorEvent);
865
+ }
866
+
867
+ export function getMonitorStatus(db, deployId) {
868
+ const events = listMonitorEvents(db, deployId, { limit: 1 });
869
+ if (!events.length)
870
+ return { deployId, healthStatus: "unknown", lastEvent: null };
871
+ const latest = events[0];
872
+ return {
873
+ deployId,
874
+ healthStatus: latest.healthStatus,
875
+ lastEvent: latest,
876
+ totalEvents: listMonitorEvents(db, deployId, { limit: 1000 }).length,
877
+ };
878
+ }
879
+
880
+ /* ── Export / Stats ──────────────────────────────────────── */
881
+
882
+ export function exportPipeline(db, pipelineId) {
883
+ const pipeline = getPipeline(db, pipelineId);
884
+ if (!pipeline) return null;
885
+ const artifacts = listArtifacts(db, pipelineId);
886
+ const deploys = listDeploys(db, { pipelineId });
887
+ return {
888
+ exportedAt: _now(),
889
+ pipeline,
890
+ artifacts,
891
+ deploys,
892
+ };
893
+ }
894
+
895
+ export function getStats(db) {
896
+ const pipelines = db.prepare("SELECT * FROM dev_pipelines").all();
897
+ const stages = db.prepare("SELECT * FROM dev_pipeline_stages").all();
898
+ const artifacts = db.prepare("SELECT * FROM dev_pipeline_artifacts").all();
899
+ const deploys = db.prepare("SELECT * FROM dev_pipeline_deploys").all();
900
+
901
+ const byTemplate = {};
902
+ const byStatus = {};
903
+ for (const p of pipelines) {
904
+ byTemplate[p.template] = (byTemplate[p.template] || 0) + 1;
905
+ byStatus[p.status] = (byStatus[p.status] || 0) + 1;
906
+ }
907
+
908
+ const deployByStrategy = {};
909
+ for (const d of deploys) {
910
+ deployByStrategy[d.strategy] = (deployByStrategy[d.strategy] || 0) + 1;
911
+ }
912
+
913
+ const stageByStatus = {};
914
+ for (const s of stages) {
915
+ stageByStatus[s.status] = (stageByStatus[s.status] || 0) + 1;
916
+ }
917
+
918
+ return {
919
+ totalPipelines: pipelines.length,
920
+ pipelinesByTemplate: byTemplate,
921
+ pipelinesByStatus: byStatus,
922
+ totalStages: stages.length,
923
+ stagesByStatus: stageByStatus,
924
+ totalArtifacts: artifacts.length,
925
+ totalDeploys: deploys.length,
926
+ deploysByStrategy: deployByStrategy,
927
+ };
928
+ }