scene-capability-engine 3.6.3 → 3.6.5
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/CHANGELOG.md +57 -0
- package/README.md +9 -0
- package/README.zh.md +9 -0
- package/bin/scene-capability-engine.js +31 -1
- package/docs/command-reference.md +56 -0
- package/lib/collab/agent-registry.js +38 -1
- package/lib/commands/session.js +60 -2
- package/lib/commands/state.js +210 -0
- package/lib/gitignore/gitignore-detector.js +2 -1
- package/lib/gitignore/layered-rules-template.js +1 -0
- package/lib/runtime/project-timeline.js +202 -17
- package/lib/runtime/session-store.js +167 -14
- package/lib/state/sce-state-store.js +1280 -0
- package/lib/state/state-migration-manager.js +850 -0
- package/lib/steering/compliance-error-reporter.js +6 -0
- package/lib/steering/steering-compliance-checker.js +43 -8
- package/package.json +2 -1
|
@@ -21,6 +21,31 @@ function normalizeInteger(value, fallback = 0) {
|
|
|
21
21
|
return parsed;
|
|
22
22
|
}
|
|
23
23
|
|
|
24
|
+
function normalizeBooleanValue(value, fallback = false) {
|
|
25
|
+
if (typeof value === 'boolean') {
|
|
26
|
+
return value;
|
|
27
|
+
}
|
|
28
|
+
const normalized = normalizeString(`${value || ''}`).toLowerCase();
|
|
29
|
+
if (!normalized) {
|
|
30
|
+
return fallback;
|
|
31
|
+
}
|
|
32
|
+
if (['1', 'true', 'yes', 'y', 'on'].includes(normalized)) {
|
|
33
|
+
return true;
|
|
34
|
+
}
|
|
35
|
+
if (['0', 'false', 'no', 'n', 'off'].includes(normalized)) {
|
|
36
|
+
return false;
|
|
37
|
+
}
|
|
38
|
+
return fallback;
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
function normalizeNonNegativeInteger(value, fallback = 0) {
|
|
42
|
+
const parsed = Number.parseInt(`${value}`, 10);
|
|
43
|
+
if (!Number.isFinite(parsed) || parsed < 0) {
|
|
44
|
+
return fallback;
|
|
45
|
+
}
|
|
46
|
+
return parsed;
|
|
47
|
+
}
|
|
48
|
+
|
|
24
49
|
function parseJsonSafe(value, fallback) {
|
|
25
50
|
if (typeof value !== 'string' || !value.trim()) {
|
|
26
51
|
return fallback;
|
|
@@ -105,6 +130,14 @@ class SceStateStore {
|
|
|
105
130
|
specs: {},
|
|
106
131
|
tasks: {},
|
|
107
132
|
refs: {},
|
|
133
|
+
timeline_snapshots: {},
|
|
134
|
+
scene_session_cycles: {},
|
|
135
|
+
agent_runtime: {},
|
|
136
|
+
errorbook_entry_index: {},
|
|
137
|
+
errorbook_incident_index: {},
|
|
138
|
+
governance_spec_scene_override: {},
|
|
139
|
+
governance_scene_index: {},
|
|
140
|
+
migration_records: {},
|
|
108
141
|
auth_leases: {},
|
|
109
142
|
auth_events: [],
|
|
110
143
|
sequences: {
|
|
@@ -238,6 +271,142 @@ class SceStateStore {
|
|
|
238
271
|
|
|
239
272
|
CREATE INDEX IF NOT EXISTS idx_auth_event_stream_ts
|
|
240
273
|
ON auth_event_stream(event_timestamp);
|
|
274
|
+
|
|
275
|
+
CREATE TABLE IF NOT EXISTS timeline_snapshot_registry (
|
|
276
|
+
snapshot_id TEXT PRIMARY KEY,
|
|
277
|
+
created_at TEXT NOT NULL,
|
|
278
|
+
trigger TEXT,
|
|
279
|
+
event TEXT,
|
|
280
|
+
summary TEXT,
|
|
281
|
+
scene_id TEXT,
|
|
282
|
+
session_id TEXT,
|
|
283
|
+
command TEXT,
|
|
284
|
+
file_count INTEGER,
|
|
285
|
+
total_bytes INTEGER,
|
|
286
|
+
snapshot_path TEXT,
|
|
287
|
+
git_json TEXT,
|
|
288
|
+
source TEXT,
|
|
289
|
+
updated_at TEXT NOT NULL
|
|
290
|
+
);
|
|
291
|
+
|
|
292
|
+
CREATE INDEX IF NOT EXISTS idx_timeline_snapshot_registry_created
|
|
293
|
+
ON timeline_snapshot_registry(created_at DESC);
|
|
294
|
+
|
|
295
|
+
CREATE TABLE IF NOT EXISTS scene_session_cycle_registry (
|
|
296
|
+
scene_id TEXT NOT NULL,
|
|
297
|
+
cycle INTEGER NOT NULL,
|
|
298
|
+
session_id TEXT NOT NULL,
|
|
299
|
+
status TEXT,
|
|
300
|
+
started_at TEXT,
|
|
301
|
+
completed_at TEXT,
|
|
302
|
+
source TEXT,
|
|
303
|
+
updated_at TEXT NOT NULL,
|
|
304
|
+
PRIMARY KEY (scene_id, cycle)
|
|
305
|
+
);
|
|
306
|
+
|
|
307
|
+
CREATE INDEX IF NOT EXISTS idx_scene_session_cycle_registry_session
|
|
308
|
+
ON scene_session_cycle_registry(session_id);
|
|
309
|
+
|
|
310
|
+
CREATE TABLE IF NOT EXISTS agent_runtime_registry (
|
|
311
|
+
agent_id TEXT PRIMARY KEY,
|
|
312
|
+
machine_id TEXT,
|
|
313
|
+
instance_index INTEGER,
|
|
314
|
+
hostname TEXT,
|
|
315
|
+
registered_at TEXT,
|
|
316
|
+
last_heartbeat TEXT,
|
|
317
|
+
status TEXT,
|
|
318
|
+
current_task_json TEXT,
|
|
319
|
+
source TEXT,
|
|
320
|
+
updated_at TEXT NOT NULL
|
|
321
|
+
);
|
|
322
|
+
|
|
323
|
+
CREATE INDEX IF NOT EXISTS idx_agent_runtime_registry_status
|
|
324
|
+
ON agent_runtime_registry(status);
|
|
325
|
+
|
|
326
|
+
CREATE TABLE IF NOT EXISTS state_migration_registry (
|
|
327
|
+
migration_id TEXT PRIMARY KEY,
|
|
328
|
+
component_id TEXT NOT NULL,
|
|
329
|
+
source_path TEXT,
|
|
330
|
+
mode TEXT NOT NULL,
|
|
331
|
+
status TEXT NOT NULL,
|
|
332
|
+
metrics_json TEXT,
|
|
333
|
+
detail_json TEXT,
|
|
334
|
+
started_at TEXT NOT NULL,
|
|
335
|
+
completed_at TEXT,
|
|
336
|
+
updated_at TEXT NOT NULL
|
|
337
|
+
);
|
|
338
|
+
|
|
339
|
+
CREATE INDEX IF NOT EXISTS idx_state_migration_registry_component_started
|
|
340
|
+
ON state_migration_registry(component_id, started_at DESC);
|
|
341
|
+
|
|
342
|
+
CREATE TABLE IF NOT EXISTS errorbook_entry_index_registry (
|
|
343
|
+
entry_id TEXT PRIMARY KEY,
|
|
344
|
+
fingerprint TEXT,
|
|
345
|
+
title TEXT,
|
|
346
|
+
status TEXT,
|
|
347
|
+
quality_score INTEGER,
|
|
348
|
+
tags_json TEXT,
|
|
349
|
+
ontology_tags_json TEXT,
|
|
350
|
+
temporary_mitigation_active INTEGER,
|
|
351
|
+
temporary_mitigation_deadline_at TEXT,
|
|
352
|
+
occurrences INTEGER,
|
|
353
|
+
created_at TEXT,
|
|
354
|
+
updated_at TEXT,
|
|
355
|
+
source TEXT,
|
|
356
|
+
indexed_at TEXT NOT NULL
|
|
357
|
+
);
|
|
358
|
+
|
|
359
|
+
CREATE INDEX IF NOT EXISTS idx_errorbook_entry_index_registry_status_updated
|
|
360
|
+
ON errorbook_entry_index_registry(status, updated_at DESC);
|
|
361
|
+
|
|
362
|
+
CREATE TABLE IF NOT EXISTS errorbook_incident_index_registry (
|
|
363
|
+
incident_id TEXT PRIMARY KEY,
|
|
364
|
+
fingerprint TEXT,
|
|
365
|
+
title TEXT,
|
|
366
|
+
symptom TEXT,
|
|
367
|
+
state TEXT,
|
|
368
|
+
attempt_count INTEGER,
|
|
369
|
+
created_at TEXT,
|
|
370
|
+
updated_at TEXT,
|
|
371
|
+
last_attempt_at TEXT,
|
|
372
|
+
resolved_at TEXT,
|
|
373
|
+
linked_entry_id TEXT,
|
|
374
|
+
source TEXT,
|
|
375
|
+
indexed_at TEXT NOT NULL
|
|
376
|
+
);
|
|
377
|
+
|
|
378
|
+
CREATE INDEX IF NOT EXISTS idx_errorbook_incident_index_registry_state_updated
|
|
379
|
+
ON errorbook_incident_index_registry(state, updated_at DESC);
|
|
380
|
+
|
|
381
|
+
CREATE TABLE IF NOT EXISTS governance_spec_scene_override_registry (
|
|
382
|
+
spec_id TEXT PRIMARY KEY,
|
|
383
|
+
scene_id TEXT NOT NULL,
|
|
384
|
+
source TEXT,
|
|
385
|
+
rule_id TEXT,
|
|
386
|
+
updated_at TEXT,
|
|
387
|
+
indexed_at TEXT NOT NULL
|
|
388
|
+
);
|
|
389
|
+
|
|
390
|
+
CREATE INDEX IF NOT EXISTS idx_governance_spec_scene_override_registry_scene
|
|
391
|
+
ON governance_spec_scene_override_registry(scene_id, updated_at DESC);
|
|
392
|
+
|
|
393
|
+
CREATE TABLE IF NOT EXISTS governance_scene_index_registry (
|
|
394
|
+
scene_id TEXT PRIMARY KEY,
|
|
395
|
+
total_specs INTEGER,
|
|
396
|
+
active_specs INTEGER,
|
|
397
|
+
completed_specs INTEGER,
|
|
398
|
+
stale_specs INTEGER,
|
|
399
|
+
spec_ids_json TEXT,
|
|
400
|
+
active_spec_ids_json TEXT,
|
|
401
|
+
stale_spec_ids_json TEXT,
|
|
402
|
+
generated_at TEXT,
|
|
403
|
+
scene_filter TEXT,
|
|
404
|
+
source TEXT,
|
|
405
|
+
indexed_at TEXT NOT NULL
|
|
406
|
+
);
|
|
407
|
+
|
|
408
|
+
CREATE INDEX IF NOT EXISTS idx_governance_scene_index_registry_counts
|
|
409
|
+
ON governance_scene_index_registry(total_specs DESC, active_specs DESC);
|
|
241
410
|
`);
|
|
242
411
|
}
|
|
243
412
|
|
|
@@ -358,6 +527,157 @@ class SceStateStore {
|
|
|
358
527
|
};
|
|
359
528
|
}
|
|
360
529
|
|
|
530
|
+
_mapTimelineSnapshotRow(row) {
|
|
531
|
+
if (!row) {
|
|
532
|
+
return null;
|
|
533
|
+
}
|
|
534
|
+
return {
|
|
535
|
+
snapshot_id: normalizeString(row.snapshot_id),
|
|
536
|
+
created_at: normalizeIsoTimestamp(row.created_at) || null,
|
|
537
|
+
trigger: normalizeString(row.trigger) || null,
|
|
538
|
+
event: normalizeString(row.event) || null,
|
|
539
|
+
summary: normalizeString(row.summary) || null,
|
|
540
|
+
scene_id: normalizeString(row.scene_id) || null,
|
|
541
|
+
session_id: normalizeString(row.session_id) || null,
|
|
542
|
+
command: normalizeString(row.command) || null,
|
|
543
|
+
file_count: normalizeNonNegativeInteger(row.file_count, 0),
|
|
544
|
+
total_bytes: normalizeNonNegativeInteger(row.total_bytes, 0),
|
|
545
|
+
snapshot_path: normalizeString(row.snapshot_path) || null,
|
|
546
|
+
git: parseJsonSafe(row.git_json, {}) || {},
|
|
547
|
+
source: normalizeString(row.source) || null,
|
|
548
|
+
updated_at: normalizeIsoTimestamp(row.updated_at) || null
|
|
549
|
+
};
|
|
550
|
+
}
|
|
551
|
+
|
|
552
|
+
_mapSceneSessionCycleRow(row) {
|
|
553
|
+
if (!row) {
|
|
554
|
+
return null;
|
|
555
|
+
}
|
|
556
|
+
return {
|
|
557
|
+
scene_id: normalizeString(row.scene_id),
|
|
558
|
+
cycle: normalizeNonNegativeInteger(row.cycle, 0),
|
|
559
|
+
session_id: normalizeString(row.session_id),
|
|
560
|
+
status: normalizeString(row.status) || null,
|
|
561
|
+
started_at: normalizeIsoTimestamp(row.started_at) || null,
|
|
562
|
+
completed_at: normalizeIsoTimestamp(row.completed_at) || null,
|
|
563
|
+
source: normalizeString(row.source) || null,
|
|
564
|
+
updated_at: normalizeIsoTimestamp(row.updated_at) || null
|
|
565
|
+
};
|
|
566
|
+
}
|
|
567
|
+
|
|
568
|
+
_mapAgentRuntimeRow(row) {
|
|
569
|
+
if (!row) {
|
|
570
|
+
return null;
|
|
571
|
+
}
|
|
572
|
+
return {
|
|
573
|
+
agent_id: normalizeString(row.agent_id),
|
|
574
|
+
machine_id: normalizeString(row.machine_id) || null,
|
|
575
|
+
instance_index: normalizeNonNegativeInteger(row.instance_index, 0),
|
|
576
|
+
hostname: normalizeString(row.hostname) || null,
|
|
577
|
+
registered_at: normalizeIsoTimestamp(row.registered_at) || null,
|
|
578
|
+
last_heartbeat: normalizeIsoTimestamp(row.last_heartbeat) || null,
|
|
579
|
+
status: normalizeString(row.status) || null,
|
|
580
|
+
current_task: parseJsonSafe(row.current_task_json, null),
|
|
581
|
+
source: normalizeString(row.source) || null,
|
|
582
|
+
updated_at: normalizeIsoTimestamp(row.updated_at) || null
|
|
583
|
+
};
|
|
584
|
+
}
|
|
585
|
+
|
|
586
|
+
_mapStateMigrationRow(row) {
|
|
587
|
+
if (!row) {
|
|
588
|
+
return null;
|
|
589
|
+
}
|
|
590
|
+
return {
|
|
591
|
+
migration_id: normalizeString(row.migration_id),
|
|
592
|
+
component_id: normalizeString(row.component_id),
|
|
593
|
+
source_path: normalizeString(row.source_path) || null,
|
|
594
|
+
mode: normalizeString(row.mode) || null,
|
|
595
|
+
status: normalizeString(row.status) || null,
|
|
596
|
+
metrics: parseJsonSafe(row.metrics_json, {}) || {},
|
|
597
|
+
detail: parseJsonSafe(row.detail_json, {}) || {},
|
|
598
|
+
started_at: normalizeIsoTimestamp(row.started_at) || null,
|
|
599
|
+
completed_at: normalizeIsoTimestamp(row.completed_at) || null,
|
|
600
|
+
updated_at: normalizeIsoTimestamp(row.updated_at) || null
|
|
601
|
+
};
|
|
602
|
+
}
|
|
603
|
+
|
|
604
|
+
_mapErrorbookEntryIndexRow(row) {
|
|
605
|
+
if (!row) {
|
|
606
|
+
return null;
|
|
607
|
+
}
|
|
608
|
+
return {
|
|
609
|
+
entry_id: normalizeString(row.entry_id),
|
|
610
|
+
fingerprint: normalizeString(row.fingerprint) || null,
|
|
611
|
+
title: normalizeString(row.title) || null,
|
|
612
|
+
status: normalizeString(row.status) || null,
|
|
613
|
+
quality_score: normalizeNonNegativeInteger(row.quality_score, 0),
|
|
614
|
+
tags: normalizeStringArray(parseJsonSafe(row.tags_json, []), []),
|
|
615
|
+
ontology_tags: normalizeStringArray(parseJsonSafe(row.ontology_tags_json, []), []),
|
|
616
|
+
temporary_mitigation_active: Number(row.temporary_mitigation_active) === 1,
|
|
617
|
+
temporary_mitigation_deadline_at: normalizeIsoTimestamp(row.temporary_mitigation_deadline_at, '') || null,
|
|
618
|
+
occurrences: normalizeNonNegativeInteger(row.occurrences, 0),
|
|
619
|
+
created_at: normalizeIsoTimestamp(row.created_at, '') || null,
|
|
620
|
+
updated_at: normalizeIsoTimestamp(row.updated_at, '') || null,
|
|
621
|
+
source: normalizeString(row.source) || null,
|
|
622
|
+
indexed_at: normalizeIsoTimestamp(row.indexed_at, '') || null
|
|
623
|
+
};
|
|
624
|
+
}
|
|
625
|
+
|
|
626
|
+
_mapErrorbookIncidentIndexRow(row) {
|
|
627
|
+
if (!row) {
|
|
628
|
+
return null;
|
|
629
|
+
}
|
|
630
|
+
return {
|
|
631
|
+
incident_id: normalizeString(row.incident_id),
|
|
632
|
+
fingerprint: normalizeString(row.fingerprint) || null,
|
|
633
|
+
title: normalizeString(row.title) || null,
|
|
634
|
+
symptom: normalizeString(row.symptom) || null,
|
|
635
|
+
state: normalizeString(row.state) || null,
|
|
636
|
+
attempt_count: normalizeNonNegativeInteger(row.attempt_count, 0),
|
|
637
|
+
created_at: normalizeIsoTimestamp(row.created_at, '') || null,
|
|
638
|
+
updated_at: normalizeIsoTimestamp(row.updated_at, '') || null,
|
|
639
|
+
last_attempt_at: normalizeIsoTimestamp(row.last_attempt_at, '') || null,
|
|
640
|
+
resolved_at: normalizeIsoTimestamp(row.resolved_at, '') || null,
|
|
641
|
+
linked_entry_id: normalizeString(row.linked_entry_id) || null,
|
|
642
|
+
source: normalizeString(row.source) || null,
|
|
643
|
+
indexed_at: normalizeIsoTimestamp(row.indexed_at, '') || null
|
|
644
|
+
};
|
|
645
|
+
}
|
|
646
|
+
|
|
647
|
+
_mapGovernanceSpecSceneOverrideRow(row) {
|
|
648
|
+
if (!row) {
|
|
649
|
+
return null;
|
|
650
|
+
}
|
|
651
|
+
return {
|
|
652
|
+
spec_id: normalizeString(row.spec_id),
|
|
653
|
+
scene_id: normalizeString(row.scene_id),
|
|
654
|
+
source: normalizeString(row.source) || null,
|
|
655
|
+
rule_id: normalizeString(row.rule_id) || null,
|
|
656
|
+
updated_at: normalizeIsoTimestamp(row.updated_at, '') || null,
|
|
657
|
+
indexed_at: normalizeIsoTimestamp(row.indexed_at, '') || null
|
|
658
|
+
};
|
|
659
|
+
}
|
|
660
|
+
|
|
661
|
+
_mapGovernanceSceneIndexRow(row) {
|
|
662
|
+
if (!row) {
|
|
663
|
+
return null;
|
|
664
|
+
}
|
|
665
|
+
return {
|
|
666
|
+
scene_id: normalizeString(row.scene_id),
|
|
667
|
+
total_specs: normalizeNonNegativeInteger(row.total_specs, 0),
|
|
668
|
+
active_specs: normalizeNonNegativeInteger(row.active_specs, 0),
|
|
669
|
+
completed_specs: normalizeNonNegativeInteger(row.completed_specs, 0),
|
|
670
|
+
stale_specs: normalizeNonNegativeInteger(row.stale_specs, 0),
|
|
671
|
+
spec_ids: normalizeStringArray(parseJsonSafe(row.spec_ids_json, []), []),
|
|
672
|
+
active_spec_ids: normalizeStringArray(parseJsonSafe(row.active_spec_ids_json, []), []),
|
|
673
|
+
stale_spec_ids: normalizeStringArray(parseJsonSafe(row.stale_spec_ids_json, []), []),
|
|
674
|
+
generated_at: normalizeIsoTimestamp(row.generated_at, '') || null,
|
|
675
|
+
scene_filter: normalizeString(row.scene_filter) || null,
|
|
676
|
+
source: normalizeString(row.source) || null,
|
|
677
|
+
indexed_at: normalizeIsoTimestamp(row.indexed_at, '') || null
|
|
678
|
+
};
|
|
679
|
+
}
|
|
680
|
+
|
|
361
681
|
async resolveOrCreateTaskRef(options = {}) {
|
|
362
682
|
const sceneId = normalizeString(options.sceneId);
|
|
363
683
|
const specId = normalizeString(options.specId);
|
|
@@ -885,6 +1205,966 @@ class SceStateStore {
|
|
|
885
1205
|
.filter(Boolean);
|
|
886
1206
|
}
|
|
887
1207
|
|
|
1208
|
+
async upsertTimelineSnapshotIndex(records = [], options = {}) {
|
|
1209
|
+
const source = normalizeString(options.source) || 'file.timeline.index';
|
|
1210
|
+
const nowIso = this.now();
|
|
1211
|
+
const normalizedRecords = Array.isArray(records)
|
|
1212
|
+
? records.map((item) => ({
|
|
1213
|
+
snapshot_id: normalizeString(item && item.snapshot_id),
|
|
1214
|
+
created_at: normalizeIsoTimestamp(item && item.created_at, nowIso) || nowIso,
|
|
1215
|
+
trigger: normalizeString(item && item.trigger) || null,
|
|
1216
|
+
event: normalizeString(item && item.event) || null,
|
|
1217
|
+
summary: normalizeString(item && item.summary) || null,
|
|
1218
|
+
scene_id: normalizeString(item && item.scene_id) || null,
|
|
1219
|
+
session_id: normalizeString(item && item.session_id) || null,
|
|
1220
|
+
command: normalizeString(item && item.command) || null,
|
|
1221
|
+
file_count: normalizeNonNegativeInteger(item && item.file_count, 0),
|
|
1222
|
+
total_bytes: normalizeNonNegativeInteger(item && item.total_bytes, 0),
|
|
1223
|
+
snapshot_path: normalizeString(item && (item.snapshot_path || item.path)) || null,
|
|
1224
|
+
git: item && item.git && typeof item.git === 'object' ? item.git : {},
|
|
1225
|
+
source,
|
|
1226
|
+
updated_at: nowIso
|
|
1227
|
+
}))
|
|
1228
|
+
.filter((item) => item.snapshot_id)
|
|
1229
|
+
: [];
|
|
1230
|
+
|
|
1231
|
+
if (this._useMemoryBackend()) {
|
|
1232
|
+
for (const item of normalizedRecords) {
|
|
1233
|
+
this._memory.timeline_snapshots[item.snapshot_id] = { ...item };
|
|
1234
|
+
}
|
|
1235
|
+
return {
|
|
1236
|
+
success: true,
|
|
1237
|
+
written: normalizedRecords.length,
|
|
1238
|
+
total: Object.keys(this._memory.timeline_snapshots || {}).length
|
|
1239
|
+
};
|
|
1240
|
+
}
|
|
1241
|
+
|
|
1242
|
+
if (!await this.ensureReady()) {
|
|
1243
|
+
return null;
|
|
1244
|
+
}
|
|
1245
|
+
|
|
1246
|
+
const statement = this._db.prepare(`
|
|
1247
|
+
INSERT OR REPLACE INTO timeline_snapshot_registry(
|
|
1248
|
+
snapshot_id, created_at, trigger, event, summary, scene_id, session_id, command,
|
|
1249
|
+
file_count, total_bytes, snapshot_path, git_json, source, updated_at
|
|
1250
|
+
)
|
|
1251
|
+
VALUES(?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)
|
|
1252
|
+
`);
|
|
1253
|
+
|
|
1254
|
+
this._withTransaction(() => {
|
|
1255
|
+
for (const item of normalizedRecords) {
|
|
1256
|
+
statement.run(
|
|
1257
|
+
item.snapshot_id,
|
|
1258
|
+
item.created_at,
|
|
1259
|
+
item.trigger,
|
|
1260
|
+
item.event,
|
|
1261
|
+
item.summary,
|
|
1262
|
+
item.scene_id,
|
|
1263
|
+
item.session_id,
|
|
1264
|
+
item.command,
|
|
1265
|
+
item.file_count,
|
|
1266
|
+
item.total_bytes,
|
|
1267
|
+
item.snapshot_path,
|
|
1268
|
+
JSON.stringify(item.git || {}),
|
|
1269
|
+
item.source,
|
|
1270
|
+
item.updated_at
|
|
1271
|
+
);
|
|
1272
|
+
}
|
|
1273
|
+
});
|
|
1274
|
+
|
|
1275
|
+
const totalRow = this._db
|
|
1276
|
+
.prepare('SELECT COUNT(*) AS total FROM timeline_snapshot_registry')
|
|
1277
|
+
.get();
|
|
1278
|
+
|
|
1279
|
+
return {
|
|
1280
|
+
success: true,
|
|
1281
|
+
written: normalizedRecords.length,
|
|
1282
|
+
total: normalizeNonNegativeInteger(totalRow && totalRow.total, 0)
|
|
1283
|
+
};
|
|
1284
|
+
}
|
|
1285
|
+
|
|
1286
|
+
async listTimelineSnapshotIndex(options = {}) {
|
|
1287
|
+
const limit = normalizeInteger(options.limit, 100);
|
|
1288
|
+
const triggerFilter = normalizeString(options.trigger);
|
|
1289
|
+
const snapshotIdFilter = normalizeString(options.snapshotId);
|
|
1290
|
+
|
|
1291
|
+
if (this._useMemoryBackend()) {
|
|
1292
|
+
let rows = Object.values(this._memory.timeline_snapshots || {}).map((item) => ({ ...item }));
|
|
1293
|
+
if (triggerFilter) {
|
|
1294
|
+
rows = rows.filter((item) => normalizeString(item.trigger) === triggerFilter);
|
|
1295
|
+
}
|
|
1296
|
+
if (snapshotIdFilter) {
|
|
1297
|
+
rows = rows.filter((item) => normalizeString(item.snapshot_id) === snapshotIdFilter);
|
|
1298
|
+
}
|
|
1299
|
+
rows.sort((left, right) => (Date.parse(right.created_at || '') || 0) - (Date.parse(left.created_at || '') || 0));
|
|
1300
|
+
if (limit > 0) {
|
|
1301
|
+
rows = rows.slice(0, limit);
|
|
1302
|
+
}
|
|
1303
|
+
return rows.map((row) => this._mapTimelineSnapshotRow({
|
|
1304
|
+
...row,
|
|
1305
|
+
git_json: JSON.stringify(row.git || {})
|
|
1306
|
+
}));
|
|
1307
|
+
}
|
|
1308
|
+
|
|
1309
|
+
if (!await this.ensureReady()) {
|
|
1310
|
+
return null;
|
|
1311
|
+
}
|
|
1312
|
+
|
|
1313
|
+
let query = `
|
|
1314
|
+
SELECT snapshot_id, created_at, trigger, event, summary, scene_id, session_id, command,
|
|
1315
|
+
file_count, total_bytes, snapshot_path, git_json, source, updated_at
|
|
1316
|
+
FROM timeline_snapshot_registry
|
|
1317
|
+
`;
|
|
1318
|
+
const clauses = [];
|
|
1319
|
+
const params = [];
|
|
1320
|
+
if (triggerFilter) {
|
|
1321
|
+
clauses.push('trigger = ?');
|
|
1322
|
+
params.push(triggerFilter);
|
|
1323
|
+
}
|
|
1324
|
+
if (snapshotIdFilter) {
|
|
1325
|
+
clauses.push('snapshot_id = ?');
|
|
1326
|
+
params.push(snapshotIdFilter);
|
|
1327
|
+
}
|
|
1328
|
+
if (clauses.length > 0) {
|
|
1329
|
+
query += ` WHERE ${clauses.join(' AND ')}`;
|
|
1330
|
+
}
|
|
1331
|
+
query += ' ORDER BY created_at DESC';
|
|
1332
|
+
if (limit > 0) {
|
|
1333
|
+
query += ' LIMIT ?';
|
|
1334
|
+
params.push(limit);
|
|
1335
|
+
}
|
|
1336
|
+
|
|
1337
|
+
const rows = this._db.prepare(query).all(...params);
|
|
1338
|
+
return rows
|
|
1339
|
+
.map((row) => this._mapTimelineSnapshotRow(row))
|
|
1340
|
+
.filter(Boolean);
|
|
1341
|
+
}
|
|
1342
|
+
|
|
1343
|
+
async upsertSceneSessionCycles(records = [], options = {}) {
|
|
1344
|
+
const source = normalizeString(options.source) || 'file.session.scene-index';
|
|
1345
|
+
const nowIso = this.now();
|
|
1346
|
+
const normalizedRecords = Array.isArray(records)
|
|
1347
|
+
? records.map((item) => ({
|
|
1348
|
+
scene_id: normalizeString(item && item.scene_id),
|
|
1349
|
+
cycle: normalizeNonNegativeInteger(item && item.cycle, 0),
|
|
1350
|
+
session_id: normalizeString(item && item.session_id),
|
|
1351
|
+
status: normalizeString(item && item.status) || null,
|
|
1352
|
+
started_at: normalizeIsoTimestamp(item && item.started_at, nowIso) || nowIso,
|
|
1353
|
+
completed_at: normalizeIsoTimestamp(item && item.completed_at, '') || null,
|
|
1354
|
+
source,
|
|
1355
|
+
updated_at: nowIso
|
|
1356
|
+
}))
|
|
1357
|
+
.filter((item) => item.scene_id && item.cycle > 0 && item.session_id)
|
|
1358
|
+
: [];
|
|
1359
|
+
|
|
1360
|
+
if (this._useMemoryBackend()) {
|
|
1361
|
+
for (const item of normalizedRecords) {
|
|
1362
|
+
const key = `${item.scene_id}::${item.cycle}`;
|
|
1363
|
+
this._memory.scene_session_cycles[key] = { ...item };
|
|
1364
|
+
}
|
|
1365
|
+
return {
|
|
1366
|
+
success: true,
|
|
1367
|
+
written: normalizedRecords.length,
|
|
1368
|
+
total: Object.keys(this._memory.scene_session_cycles || {}).length
|
|
1369
|
+
};
|
|
1370
|
+
}
|
|
1371
|
+
|
|
1372
|
+
if (!await this.ensureReady()) {
|
|
1373
|
+
return null;
|
|
1374
|
+
}
|
|
1375
|
+
|
|
1376
|
+
const statement = this._db.prepare(`
|
|
1377
|
+
INSERT OR REPLACE INTO scene_session_cycle_registry(
|
|
1378
|
+
scene_id, cycle, session_id, status, started_at, completed_at, source, updated_at
|
|
1379
|
+
)
|
|
1380
|
+
VALUES(?, ?, ?, ?, ?, ?, ?, ?)
|
|
1381
|
+
`);
|
|
1382
|
+
|
|
1383
|
+
this._withTransaction(() => {
|
|
1384
|
+
for (const item of normalizedRecords) {
|
|
1385
|
+
statement.run(
|
|
1386
|
+
item.scene_id,
|
|
1387
|
+
item.cycle,
|
|
1388
|
+
item.session_id,
|
|
1389
|
+
item.status,
|
|
1390
|
+
item.started_at,
|
|
1391
|
+
item.completed_at,
|
|
1392
|
+
item.source,
|
|
1393
|
+
item.updated_at
|
|
1394
|
+
);
|
|
1395
|
+
}
|
|
1396
|
+
});
|
|
1397
|
+
|
|
1398
|
+
const totalRow = this._db
|
|
1399
|
+
.prepare('SELECT COUNT(*) AS total FROM scene_session_cycle_registry')
|
|
1400
|
+
.get();
|
|
1401
|
+
|
|
1402
|
+
return {
|
|
1403
|
+
success: true,
|
|
1404
|
+
written: normalizedRecords.length,
|
|
1405
|
+
total: normalizeNonNegativeInteger(totalRow && totalRow.total, 0)
|
|
1406
|
+
};
|
|
1407
|
+
}
|
|
1408
|
+
|
|
1409
|
+
async listSceneSessionCycles(options = {}) {
|
|
1410
|
+
const limit = normalizeInteger(options.limit, 100);
|
|
1411
|
+
const sceneId = normalizeString(options.sceneId);
|
|
1412
|
+
|
|
1413
|
+
if (this._useMemoryBackend()) {
|
|
1414
|
+
let rows = Object.values(this._memory.scene_session_cycles || {}).map((item) => ({ ...item }));
|
|
1415
|
+
if (sceneId) {
|
|
1416
|
+
rows = rows.filter((item) => normalizeString(item.scene_id) === sceneId);
|
|
1417
|
+
}
|
|
1418
|
+
rows.sort((left, right) => {
|
|
1419
|
+
const sceneCompare = `${left.scene_id}`.localeCompare(`${right.scene_id}`);
|
|
1420
|
+
if (sceneCompare !== 0) {
|
|
1421
|
+
return sceneCompare;
|
|
1422
|
+
}
|
|
1423
|
+
return right.cycle - left.cycle;
|
|
1424
|
+
});
|
|
1425
|
+
if (limit > 0) {
|
|
1426
|
+
rows = rows.slice(0, limit);
|
|
1427
|
+
}
|
|
1428
|
+
return rows.map((row) => this._mapSceneSessionCycleRow(row));
|
|
1429
|
+
}
|
|
1430
|
+
|
|
1431
|
+
if (!await this.ensureReady()) {
|
|
1432
|
+
return null;
|
|
1433
|
+
}
|
|
1434
|
+
|
|
1435
|
+
let query = `
|
|
1436
|
+
SELECT scene_id, cycle, session_id, status, started_at, completed_at, source, updated_at
|
|
1437
|
+
FROM scene_session_cycle_registry
|
|
1438
|
+
`;
|
|
1439
|
+
const params = [];
|
|
1440
|
+
if (sceneId) {
|
|
1441
|
+
query += ' WHERE scene_id = ?';
|
|
1442
|
+
params.push(sceneId);
|
|
1443
|
+
}
|
|
1444
|
+
query += ' ORDER BY scene_id ASC, cycle DESC';
|
|
1445
|
+
if (limit > 0) {
|
|
1446
|
+
query += ' LIMIT ?';
|
|
1447
|
+
params.push(limit);
|
|
1448
|
+
}
|
|
1449
|
+
|
|
1450
|
+
const rows = this._db.prepare(query).all(...params);
|
|
1451
|
+
return rows
|
|
1452
|
+
.map((row) => this._mapSceneSessionCycleRow(row))
|
|
1453
|
+
.filter(Boolean);
|
|
1454
|
+
}
|
|
1455
|
+
|
|
1456
|
+
async upsertAgentRuntimeRecords(records = [], options = {}) {
|
|
1457
|
+
const source = normalizeString(options.source) || 'file.agent-registry';
|
|
1458
|
+
const nowIso = this.now();
|
|
1459
|
+
const normalizedRecords = Array.isArray(records)
|
|
1460
|
+
? records.map((item) => ({
|
|
1461
|
+
agent_id: normalizeString(item && item.agent_id),
|
|
1462
|
+
machine_id: normalizeString(item && item.machine_id) || null,
|
|
1463
|
+
instance_index: normalizeNonNegativeInteger(item && item.instance_index, 0),
|
|
1464
|
+
hostname: normalizeString(item && item.hostname) || null,
|
|
1465
|
+
registered_at: normalizeIsoTimestamp(item && item.registered_at, nowIso) || nowIso,
|
|
1466
|
+
last_heartbeat: normalizeIsoTimestamp(item && item.last_heartbeat, nowIso) || nowIso,
|
|
1467
|
+
status: normalizeString(item && item.status) || null,
|
|
1468
|
+
current_task: item && item.current_task && typeof item.current_task === 'object'
|
|
1469
|
+
? item.current_task
|
|
1470
|
+
: null,
|
|
1471
|
+
source,
|
|
1472
|
+
updated_at: nowIso
|
|
1473
|
+
}))
|
|
1474
|
+
.filter((item) => item.agent_id)
|
|
1475
|
+
: [];
|
|
1476
|
+
|
|
1477
|
+
if (this._useMemoryBackend()) {
|
|
1478
|
+
for (const item of normalizedRecords) {
|
|
1479
|
+
this._memory.agent_runtime[item.agent_id] = { ...item };
|
|
1480
|
+
}
|
|
1481
|
+
return {
|
|
1482
|
+
success: true,
|
|
1483
|
+
written: normalizedRecords.length,
|
|
1484
|
+
total: Object.keys(this._memory.agent_runtime || {}).length
|
|
1485
|
+
};
|
|
1486
|
+
}
|
|
1487
|
+
|
|
1488
|
+
if (!await this.ensureReady()) {
|
|
1489
|
+
return null;
|
|
1490
|
+
}
|
|
1491
|
+
|
|
1492
|
+
const statement = this._db.prepare(`
|
|
1493
|
+
INSERT OR REPLACE INTO agent_runtime_registry(
|
|
1494
|
+
agent_id, machine_id, instance_index, hostname, registered_at, last_heartbeat, status, current_task_json, source, updated_at
|
|
1495
|
+
)
|
|
1496
|
+
VALUES(?, ?, ?, ?, ?, ?, ?, ?, ?, ?)
|
|
1497
|
+
`);
|
|
1498
|
+
|
|
1499
|
+
this._withTransaction(() => {
|
|
1500
|
+
for (const item of normalizedRecords) {
|
|
1501
|
+
statement.run(
|
|
1502
|
+
item.agent_id,
|
|
1503
|
+
item.machine_id,
|
|
1504
|
+
item.instance_index,
|
|
1505
|
+
item.hostname,
|
|
1506
|
+
item.registered_at,
|
|
1507
|
+
item.last_heartbeat,
|
|
1508
|
+
item.status,
|
|
1509
|
+
JSON.stringify(item.current_task),
|
|
1510
|
+
item.source,
|
|
1511
|
+
item.updated_at
|
|
1512
|
+
);
|
|
1513
|
+
}
|
|
1514
|
+
});
|
|
1515
|
+
|
|
1516
|
+
const totalRow = this._db
|
|
1517
|
+
.prepare('SELECT COUNT(*) AS total FROM agent_runtime_registry')
|
|
1518
|
+
.get();
|
|
1519
|
+
|
|
1520
|
+
return {
|
|
1521
|
+
success: true,
|
|
1522
|
+
written: normalizedRecords.length,
|
|
1523
|
+
total: normalizeNonNegativeInteger(totalRow && totalRow.total, 0)
|
|
1524
|
+
};
|
|
1525
|
+
}
|
|
1526
|
+
|
|
1527
|
+
async listAgentRuntimeRecords(options = {}) {
|
|
1528
|
+
const limit = normalizeInteger(options.limit, 100);
|
|
1529
|
+
const status = normalizeString(options.status);
|
|
1530
|
+
|
|
1531
|
+
if (this._useMemoryBackend()) {
|
|
1532
|
+
let rows = Object.values(this._memory.agent_runtime || {}).map((item) => ({ ...item }));
|
|
1533
|
+
if (status) {
|
|
1534
|
+
rows = rows.filter((item) => normalizeString(item.status) === status);
|
|
1535
|
+
}
|
|
1536
|
+
rows.sort((left, right) => (Date.parse(right.last_heartbeat || '') || 0) - (Date.parse(left.last_heartbeat || '') || 0));
|
|
1537
|
+
if (limit > 0) {
|
|
1538
|
+
rows = rows.slice(0, limit);
|
|
1539
|
+
}
|
|
1540
|
+
return rows.map((row) => this._mapAgentRuntimeRow({
|
|
1541
|
+
...row,
|
|
1542
|
+
current_task_json: JSON.stringify(row.current_task || null)
|
|
1543
|
+
}));
|
|
1544
|
+
}
|
|
1545
|
+
|
|
1546
|
+
if (!await this.ensureReady()) {
|
|
1547
|
+
return null;
|
|
1548
|
+
}
|
|
1549
|
+
|
|
1550
|
+
let query = `
|
|
1551
|
+
SELECT agent_id, machine_id, instance_index, hostname, registered_at, last_heartbeat, status, current_task_json, source, updated_at
|
|
1552
|
+
FROM agent_runtime_registry
|
|
1553
|
+
`;
|
|
1554
|
+
const params = [];
|
|
1555
|
+
if (status) {
|
|
1556
|
+
query += ' WHERE status = ?';
|
|
1557
|
+
params.push(status);
|
|
1558
|
+
}
|
|
1559
|
+
query += ' ORDER BY last_heartbeat DESC';
|
|
1560
|
+
if (limit > 0) {
|
|
1561
|
+
query += ' LIMIT ?';
|
|
1562
|
+
params.push(limit);
|
|
1563
|
+
}
|
|
1564
|
+
|
|
1565
|
+
const rows = this._db.prepare(query).all(...params);
|
|
1566
|
+
return rows
|
|
1567
|
+
.map((row) => this._mapAgentRuntimeRow(row))
|
|
1568
|
+
.filter(Boolean);
|
|
1569
|
+
}
|
|
1570
|
+
|
|
1571
|
+
async appendStateMigrationRecord(record = {}) {
|
|
1572
|
+
const componentId = normalizeString(record.component_id || record.componentId);
|
|
1573
|
+
if (!componentId) {
|
|
1574
|
+
return null;
|
|
1575
|
+
}
|
|
1576
|
+
const migrationId = normalizeString(record.migration_id || record.migrationId)
|
|
1577
|
+
|| `migration-${Date.now()}-${crypto.randomBytes(3).toString('hex')}`;
|
|
1578
|
+
const startedAt = normalizeIsoTimestamp(record.started_at || record.startedAt, this.now()) || this.now();
|
|
1579
|
+
const completedAt = normalizeIsoTimestamp(record.completed_at || record.completedAt, '') || null;
|
|
1580
|
+
const nowIso = this.now();
|
|
1581
|
+
const normalized = {
|
|
1582
|
+
migration_id: migrationId,
|
|
1583
|
+
component_id: componentId,
|
|
1584
|
+
source_path: normalizeString(record.source_path || record.sourcePath) || null,
|
|
1585
|
+
mode: normalizeString(record.mode) || 'unknown',
|
|
1586
|
+
status: normalizeString(record.status) || 'completed',
|
|
1587
|
+
metrics: record.metrics && typeof record.metrics === 'object' ? record.metrics : {},
|
|
1588
|
+
detail: record.detail && typeof record.detail === 'object' ? record.detail : {},
|
|
1589
|
+
started_at: startedAt,
|
|
1590
|
+
completed_at: completedAt,
|
|
1591
|
+
updated_at: nowIso
|
|
1592
|
+
};
|
|
1593
|
+
|
|
1594
|
+
if (this._useMemoryBackend()) {
|
|
1595
|
+
this._memory.migration_records[normalized.migration_id] = { ...normalized };
|
|
1596
|
+
return { ...normalized };
|
|
1597
|
+
}
|
|
1598
|
+
|
|
1599
|
+
if (!await this.ensureReady()) {
|
|
1600
|
+
return null;
|
|
1601
|
+
}
|
|
1602
|
+
|
|
1603
|
+
this._db
|
|
1604
|
+
.prepare(`
|
|
1605
|
+
INSERT OR REPLACE INTO state_migration_registry(
|
|
1606
|
+
migration_id, component_id, source_path, mode, status, metrics_json, detail_json, started_at, completed_at, updated_at
|
|
1607
|
+
)
|
|
1608
|
+
VALUES(?, ?, ?, ?, ?, ?, ?, ?, ?, ?)
|
|
1609
|
+
`)
|
|
1610
|
+
.run(
|
|
1611
|
+
normalized.migration_id,
|
|
1612
|
+
normalized.component_id,
|
|
1613
|
+
normalized.source_path,
|
|
1614
|
+
normalized.mode,
|
|
1615
|
+
normalized.status,
|
|
1616
|
+
JSON.stringify(normalized.metrics || {}),
|
|
1617
|
+
JSON.stringify(normalized.detail || {}),
|
|
1618
|
+
normalized.started_at,
|
|
1619
|
+
normalized.completed_at,
|
|
1620
|
+
normalized.updated_at
|
|
1621
|
+
);
|
|
1622
|
+
|
|
1623
|
+
return this.listStateMigrations({ migrationId: normalized.migration_id, limit: 1 })
|
|
1624
|
+
.then((rows) => (Array.isArray(rows) && rows.length > 0 ? rows[0] : null));
|
|
1625
|
+
}
|
|
1626
|
+
|
|
1627
|
+
async listStateMigrations(options = {}) {
|
|
1628
|
+
const limit = normalizeInteger(options.limit, 50);
|
|
1629
|
+
const componentId = normalizeString(options.componentId);
|
|
1630
|
+
const migrationId = normalizeString(options.migrationId);
|
|
1631
|
+
|
|
1632
|
+
if (this._useMemoryBackend()) {
|
|
1633
|
+
let rows = Object.values(this._memory.migration_records || {}).map((item) => ({ ...item }));
|
|
1634
|
+
if (componentId) {
|
|
1635
|
+
rows = rows.filter((item) => normalizeString(item.component_id) === componentId);
|
|
1636
|
+
}
|
|
1637
|
+
if (migrationId) {
|
|
1638
|
+
rows = rows.filter((item) => normalizeString(item.migration_id) === migrationId);
|
|
1639
|
+
}
|
|
1640
|
+
rows.sort((left, right) => (Date.parse(right.started_at || '') || 0) - (Date.parse(left.started_at || '') || 0));
|
|
1641
|
+
if (limit > 0) {
|
|
1642
|
+
rows = rows.slice(0, limit);
|
|
1643
|
+
}
|
|
1644
|
+
return rows.map((row) => this._mapStateMigrationRow({
|
|
1645
|
+
...row,
|
|
1646
|
+
metrics_json: JSON.stringify(row.metrics || {}),
|
|
1647
|
+
detail_json: JSON.stringify(row.detail || {})
|
|
1648
|
+
}));
|
|
1649
|
+
}
|
|
1650
|
+
|
|
1651
|
+
if (!await this.ensureReady()) {
|
|
1652
|
+
return null;
|
|
1653
|
+
}
|
|
1654
|
+
|
|
1655
|
+
let query = `
|
|
1656
|
+
SELECT migration_id, component_id, source_path, mode, status, metrics_json, detail_json, started_at, completed_at, updated_at
|
|
1657
|
+
FROM state_migration_registry
|
|
1658
|
+
`;
|
|
1659
|
+
const clauses = [];
|
|
1660
|
+
const params = [];
|
|
1661
|
+
if (componentId) {
|
|
1662
|
+
clauses.push('component_id = ?');
|
|
1663
|
+
params.push(componentId);
|
|
1664
|
+
}
|
|
1665
|
+
if (migrationId) {
|
|
1666
|
+
clauses.push('migration_id = ?');
|
|
1667
|
+
params.push(migrationId);
|
|
1668
|
+
}
|
|
1669
|
+
if (clauses.length > 0) {
|
|
1670
|
+
query += ` WHERE ${clauses.join(' AND ')}`;
|
|
1671
|
+
}
|
|
1672
|
+
query += ' ORDER BY started_at DESC';
|
|
1673
|
+
if (limit > 0) {
|
|
1674
|
+
query += ' LIMIT ?';
|
|
1675
|
+
params.push(limit);
|
|
1676
|
+
}
|
|
1677
|
+
|
|
1678
|
+
const rows = this._db.prepare(query).all(...params);
|
|
1679
|
+
return rows
|
|
1680
|
+
.map((row) => this._mapStateMigrationRow(row))
|
|
1681
|
+
.filter(Boolean);
|
|
1682
|
+
}
|
|
1683
|
+
|
|
1684
|
+
async upsertErrorbookEntryIndexRecords(records = [], options = {}) {
|
|
1685
|
+
const source = normalizeString(options.source) || 'file.errorbook.index';
|
|
1686
|
+
const nowIso = this.now();
|
|
1687
|
+
const normalizedRecords = Array.isArray(records)
|
|
1688
|
+
? records.map((item) => ({
|
|
1689
|
+
entry_id: normalizeString(item && (item.entry_id || item.id)),
|
|
1690
|
+
fingerprint: normalizeString(item && item.fingerprint) || null,
|
|
1691
|
+
title: normalizeString(item && item.title) || null,
|
|
1692
|
+
status: normalizeString(item && item.status) || null,
|
|
1693
|
+
quality_score: normalizeNonNegativeInteger(item && item.quality_score, 0),
|
|
1694
|
+
tags: normalizeStringArray(item && item.tags, []),
|
|
1695
|
+
ontology_tags: normalizeStringArray(item && item.ontology_tags, []),
|
|
1696
|
+
temporary_mitigation_active: normalizeBooleanValue(item && item.temporary_mitigation_active, false),
|
|
1697
|
+
temporary_mitigation_deadline_at: normalizeIsoTimestamp(item && item.temporary_mitigation_deadline_at, '') || null,
|
|
1698
|
+
occurrences: normalizeNonNegativeInteger(item && item.occurrences, 0),
|
|
1699
|
+
created_at: normalizeIsoTimestamp(item && item.created_at, '') || null,
|
|
1700
|
+
updated_at: normalizeIsoTimestamp(item && item.updated_at, nowIso) || nowIso,
|
|
1701
|
+
source,
|
|
1702
|
+
indexed_at: nowIso
|
|
1703
|
+
}))
|
|
1704
|
+
.filter((item) => item.entry_id)
|
|
1705
|
+
: [];
|
|
1706
|
+
|
|
1707
|
+
if (this._useMemoryBackend()) {
|
|
1708
|
+
for (const item of normalizedRecords) {
|
|
1709
|
+
this._memory.errorbook_entry_index[item.entry_id] = { ...item };
|
|
1710
|
+
}
|
|
1711
|
+
return {
|
|
1712
|
+
success: true,
|
|
1713
|
+
written: normalizedRecords.length,
|
|
1714
|
+
total: Object.keys(this._memory.errorbook_entry_index || {}).length
|
|
1715
|
+
};
|
|
1716
|
+
}
|
|
1717
|
+
|
|
1718
|
+
if (!await this.ensureReady()) {
|
|
1719
|
+
return null;
|
|
1720
|
+
}
|
|
1721
|
+
|
|
1722
|
+
const statement = this._db.prepare(`
|
|
1723
|
+
INSERT OR REPLACE INTO errorbook_entry_index_registry(
|
|
1724
|
+
entry_id, fingerprint, title, status, quality_score, tags_json, ontology_tags_json,
|
|
1725
|
+
temporary_mitigation_active, temporary_mitigation_deadline_at, occurrences, created_at, updated_at, source, indexed_at
|
|
1726
|
+
)
|
|
1727
|
+
VALUES(?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)
|
|
1728
|
+
`);
|
|
1729
|
+
|
|
1730
|
+
this._withTransaction(() => {
|
|
1731
|
+
for (const item of normalizedRecords) {
|
|
1732
|
+
statement.run(
|
|
1733
|
+
item.entry_id,
|
|
1734
|
+
item.fingerprint,
|
|
1735
|
+
item.title,
|
|
1736
|
+
item.status,
|
|
1737
|
+
item.quality_score,
|
|
1738
|
+
JSON.stringify(item.tags || []),
|
|
1739
|
+
JSON.stringify(item.ontology_tags || []),
|
|
1740
|
+
item.temporary_mitigation_active ? 1 : 0,
|
|
1741
|
+
item.temporary_mitigation_deadline_at,
|
|
1742
|
+
item.occurrences,
|
|
1743
|
+
item.created_at,
|
|
1744
|
+
item.updated_at,
|
|
1745
|
+
item.source,
|
|
1746
|
+
item.indexed_at
|
|
1747
|
+
);
|
|
1748
|
+
}
|
|
1749
|
+
});
|
|
1750
|
+
|
|
1751
|
+
const totalRow = this._db
|
|
1752
|
+
.prepare('SELECT COUNT(*) AS total FROM errorbook_entry_index_registry')
|
|
1753
|
+
.get();
|
|
1754
|
+
|
|
1755
|
+
return {
|
|
1756
|
+
success: true,
|
|
1757
|
+
written: normalizedRecords.length,
|
|
1758
|
+
total: normalizeNonNegativeInteger(totalRow && totalRow.total, 0)
|
|
1759
|
+
};
|
|
1760
|
+
}
|
|
1761
|
+
|
|
1762
|
+
async listErrorbookEntryIndexRecords(options = {}) {
|
|
1763
|
+
const limit = normalizeInteger(options.limit, 100);
|
|
1764
|
+
const status = normalizeString(options.status);
|
|
1765
|
+
|
|
1766
|
+
if (this._useMemoryBackend()) {
|
|
1767
|
+
let rows = Object.values(this._memory.errorbook_entry_index || {}).map((item) => ({ ...item }));
|
|
1768
|
+
if (status) {
|
|
1769
|
+
rows = rows.filter((item) => normalizeString(item.status) === status);
|
|
1770
|
+
}
|
|
1771
|
+
rows.sort((left, right) => (Date.parse(right.updated_at || '') || 0) - (Date.parse(left.updated_at || '') || 0));
|
|
1772
|
+
if (limit > 0) {
|
|
1773
|
+
rows = rows.slice(0, limit);
|
|
1774
|
+
}
|
|
1775
|
+
return rows.map((row) => this._mapErrorbookEntryIndexRow({
|
|
1776
|
+
...row,
|
|
1777
|
+
tags_json: JSON.stringify(row.tags || []),
|
|
1778
|
+
ontology_tags_json: JSON.stringify(row.ontology_tags || [])
|
|
1779
|
+
}));
|
|
1780
|
+
}
|
|
1781
|
+
|
|
1782
|
+
if (!await this.ensureReady()) {
|
|
1783
|
+
return null;
|
|
1784
|
+
}
|
|
1785
|
+
|
|
1786
|
+
let query = `
|
|
1787
|
+
SELECT entry_id, fingerprint, title, status, quality_score, tags_json, ontology_tags_json,
|
|
1788
|
+
temporary_mitigation_active, temporary_mitigation_deadline_at, occurrences, created_at, updated_at, source, indexed_at
|
|
1789
|
+
FROM errorbook_entry_index_registry
|
|
1790
|
+
`;
|
|
1791
|
+
const params = [];
|
|
1792
|
+
if (status) {
|
|
1793
|
+
query += ' WHERE status = ?';
|
|
1794
|
+
params.push(status);
|
|
1795
|
+
}
|
|
1796
|
+
query += ' ORDER BY updated_at DESC';
|
|
1797
|
+
if (limit > 0) {
|
|
1798
|
+
query += ' LIMIT ?';
|
|
1799
|
+
params.push(limit);
|
|
1800
|
+
}
|
|
1801
|
+
|
|
1802
|
+
const rows = this._db.prepare(query).all(...params);
|
|
1803
|
+
return rows
|
|
1804
|
+
.map((row) => this._mapErrorbookEntryIndexRow(row))
|
|
1805
|
+
.filter(Boolean);
|
|
1806
|
+
}
|
|
1807
|
+
|
|
1808
|
+
async upsertErrorbookIncidentIndexRecords(records = [], options = {}) {
|
|
1809
|
+
const source = normalizeString(options.source) || 'file.errorbook.incident-index';
|
|
1810
|
+
const nowIso = this.now();
|
|
1811
|
+
const normalizedRecords = Array.isArray(records)
|
|
1812
|
+
? records.map((item) => ({
|
|
1813
|
+
incident_id: normalizeString(item && (item.incident_id || item.id)),
|
|
1814
|
+
fingerprint: normalizeString(item && item.fingerprint) || null,
|
|
1815
|
+
title: normalizeString(item && item.title) || null,
|
|
1816
|
+
symptom: normalizeString(item && item.symptom) || null,
|
|
1817
|
+
state: normalizeString(item && item.state) || null,
|
|
1818
|
+
attempt_count: normalizeNonNegativeInteger(item && item.attempt_count, 0),
|
|
1819
|
+
created_at: normalizeIsoTimestamp(item && item.created_at, '') || null,
|
|
1820
|
+
updated_at: normalizeIsoTimestamp(item && item.updated_at, nowIso) || nowIso,
|
|
1821
|
+
last_attempt_at: normalizeIsoTimestamp(item && item.last_attempt_at, '') || null,
|
|
1822
|
+
resolved_at: normalizeIsoTimestamp(item && item.resolved_at, '') || null,
|
|
1823
|
+
linked_entry_id: normalizeString(item && item.linked_entry_id) || null,
|
|
1824
|
+
source,
|
|
1825
|
+
indexed_at: nowIso
|
|
1826
|
+
}))
|
|
1827
|
+
.filter((item) => item.incident_id)
|
|
1828
|
+
: [];
|
|
1829
|
+
|
|
1830
|
+
if (this._useMemoryBackend()) {
|
|
1831
|
+
for (const item of normalizedRecords) {
|
|
1832
|
+
this._memory.errorbook_incident_index[item.incident_id] = { ...item };
|
|
1833
|
+
}
|
|
1834
|
+
return {
|
|
1835
|
+
success: true,
|
|
1836
|
+
written: normalizedRecords.length,
|
|
1837
|
+
total: Object.keys(this._memory.errorbook_incident_index || {}).length
|
|
1838
|
+
};
|
|
1839
|
+
}
|
|
1840
|
+
|
|
1841
|
+
if (!await this.ensureReady()) {
|
|
1842
|
+
return null;
|
|
1843
|
+
}
|
|
1844
|
+
|
|
1845
|
+
const statement = this._db.prepare(`
|
|
1846
|
+
INSERT OR REPLACE INTO errorbook_incident_index_registry(
|
|
1847
|
+
incident_id, fingerprint, title, symptom, state, attempt_count,
|
|
1848
|
+
created_at, updated_at, last_attempt_at, resolved_at, linked_entry_id, source, indexed_at
|
|
1849
|
+
)
|
|
1850
|
+
VALUES(?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)
|
|
1851
|
+
`);
|
|
1852
|
+
|
|
1853
|
+
this._withTransaction(() => {
|
|
1854
|
+
for (const item of normalizedRecords) {
|
|
1855
|
+
statement.run(
|
|
1856
|
+
item.incident_id,
|
|
1857
|
+
item.fingerprint,
|
|
1858
|
+
item.title,
|
|
1859
|
+
item.symptom,
|
|
1860
|
+
item.state,
|
|
1861
|
+
item.attempt_count,
|
|
1862
|
+
item.created_at,
|
|
1863
|
+
item.updated_at,
|
|
1864
|
+
item.last_attempt_at,
|
|
1865
|
+
item.resolved_at,
|
|
1866
|
+
item.linked_entry_id,
|
|
1867
|
+
item.source,
|
|
1868
|
+
item.indexed_at
|
|
1869
|
+
);
|
|
1870
|
+
}
|
|
1871
|
+
});
|
|
1872
|
+
|
|
1873
|
+
const totalRow = this._db
|
|
1874
|
+
.prepare('SELECT COUNT(*) AS total FROM errorbook_incident_index_registry')
|
|
1875
|
+
.get();
|
|
1876
|
+
|
|
1877
|
+
return {
|
|
1878
|
+
success: true,
|
|
1879
|
+
written: normalizedRecords.length,
|
|
1880
|
+
total: normalizeNonNegativeInteger(totalRow && totalRow.total, 0)
|
|
1881
|
+
};
|
|
1882
|
+
}
|
|
1883
|
+
|
|
1884
|
+
async listErrorbookIncidentIndexRecords(options = {}) {
|
|
1885
|
+
const limit = normalizeInteger(options.limit, 100);
|
|
1886
|
+
const state = normalizeString(options.state);
|
|
1887
|
+
|
|
1888
|
+
if (this._useMemoryBackend()) {
|
|
1889
|
+
let rows = Object.values(this._memory.errorbook_incident_index || {}).map((item) => ({ ...item }));
|
|
1890
|
+
if (state) {
|
|
1891
|
+
rows = rows.filter((item) => normalizeString(item.state) === state);
|
|
1892
|
+
}
|
|
1893
|
+
rows.sort((left, right) => (Date.parse(right.updated_at || '') || 0) - (Date.parse(left.updated_at || '') || 0));
|
|
1894
|
+
if (limit > 0) {
|
|
1895
|
+
rows = rows.slice(0, limit);
|
|
1896
|
+
}
|
|
1897
|
+
return rows.map((row) => this._mapErrorbookIncidentIndexRow(row));
|
|
1898
|
+
}
|
|
1899
|
+
|
|
1900
|
+
if (!await this.ensureReady()) {
|
|
1901
|
+
return null;
|
|
1902
|
+
}
|
|
1903
|
+
|
|
1904
|
+
let query = `
|
|
1905
|
+
SELECT incident_id, fingerprint, title, symptom, state, attempt_count,
|
|
1906
|
+
created_at, updated_at, last_attempt_at, resolved_at, linked_entry_id, source, indexed_at
|
|
1907
|
+
FROM errorbook_incident_index_registry
|
|
1908
|
+
`;
|
|
1909
|
+
const params = [];
|
|
1910
|
+
if (state) {
|
|
1911
|
+
query += ' WHERE state = ?';
|
|
1912
|
+
params.push(state);
|
|
1913
|
+
}
|
|
1914
|
+
query += ' ORDER BY updated_at DESC';
|
|
1915
|
+
if (limit > 0) {
|
|
1916
|
+
query += ' LIMIT ?';
|
|
1917
|
+
params.push(limit);
|
|
1918
|
+
}
|
|
1919
|
+
|
|
1920
|
+
const rows = this._db.prepare(query).all(...params);
|
|
1921
|
+
return rows
|
|
1922
|
+
.map((row) => this._mapErrorbookIncidentIndexRow(row))
|
|
1923
|
+
.filter(Boolean);
|
|
1924
|
+
}
|
|
1925
|
+
|
|
1926
|
+
async upsertGovernanceSpecSceneOverrideRecords(records = [], options = {}) {
|
|
1927
|
+
const source = normalizeString(options.source) || 'file.spec-governance.spec-scene-overrides';
|
|
1928
|
+
const nowIso = this.now();
|
|
1929
|
+
const normalizedRecords = Array.isArray(records)
|
|
1930
|
+
? records.map((item) => ({
|
|
1931
|
+
spec_id: normalizeString(item && item.spec_id),
|
|
1932
|
+
scene_id: normalizeString(item && item.scene_id),
|
|
1933
|
+
source: normalizeString(item && item.source) || source,
|
|
1934
|
+
rule_id: normalizeString(item && item.rule_id) || null,
|
|
1935
|
+
updated_at: normalizeIsoTimestamp(item && item.updated_at, nowIso) || nowIso,
|
|
1936
|
+
indexed_at: nowIso
|
|
1937
|
+
}))
|
|
1938
|
+
.filter((item) => item.spec_id && item.scene_id)
|
|
1939
|
+
: [];
|
|
1940
|
+
|
|
1941
|
+
if (this._useMemoryBackend()) {
|
|
1942
|
+
for (const item of normalizedRecords) {
|
|
1943
|
+
this._memory.governance_spec_scene_override[item.spec_id] = { ...item };
|
|
1944
|
+
}
|
|
1945
|
+
return {
|
|
1946
|
+
success: true,
|
|
1947
|
+
written: normalizedRecords.length,
|
|
1948
|
+
total: Object.keys(this._memory.governance_spec_scene_override || {}).length
|
|
1949
|
+
};
|
|
1950
|
+
}
|
|
1951
|
+
|
|
1952
|
+
if (!await this.ensureReady()) {
|
|
1953
|
+
return null;
|
|
1954
|
+
}
|
|
1955
|
+
|
|
1956
|
+
const statement = this._db.prepare(`
|
|
1957
|
+
INSERT OR REPLACE INTO governance_spec_scene_override_registry(
|
|
1958
|
+
spec_id, scene_id, source, rule_id, updated_at, indexed_at
|
|
1959
|
+
)
|
|
1960
|
+
VALUES(?, ?, ?, ?, ?, ?)
|
|
1961
|
+
`);
|
|
1962
|
+
|
|
1963
|
+
this._withTransaction(() => {
|
|
1964
|
+
for (const item of normalizedRecords) {
|
|
1965
|
+
statement.run(
|
|
1966
|
+
item.spec_id,
|
|
1967
|
+
item.scene_id,
|
|
1968
|
+
item.source,
|
|
1969
|
+
item.rule_id,
|
|
1970
|
+
item.updated_at,
|
|
1971
|
+
item.indexed_at
|
|
1972
|
+
);
|
|
1973
|
+
}
|
|
1974
|
+
});
|
|
1975
|
+
|
|
1976
|
+
const totalRow = this._db
|
|
1977
|
+
.prepare('SELECT COUNT(*) AS total FROM governance_spec_scene_override_registry')
|
|
1978
|
+
.get();
|
|
1979
|
+
|
|
1980
|
+
return {
|
|
1981
|
+
success: true,
|
|
1982
|
+
written: normalizedRecords.length,
|
|
1983
|
+
total: normalizeNonNegativeInteger(totalRow && totalRow.total, 0)
|
|
1984
|
+
};
|
|
1985
|
+
}
|
|
1986
|
+
|
|
1987
|
+
async listGovernanceSpecSceneOverrideRecords(options = {}) {
|
|
1988
|
+
const limit = normalizeInteger(options.limit, 100);
|
|
1989
|
+
const sceneId = normalizeString(options.sceneId);
|
|
1990
|
+
const specId = normalizeString(options.specId);
|
|
1991
|
+
|
|
1992
|
+
if (this._useMemoryBackend()) {
|
|
1993
|
+
let rows = Object.values(this._memory.governance_spec_scene_override || {}).map((item) => ({ ...item }));
|
|
1994
|
+
if (sceneId) {
|
|
1995
|
+
rows = rows.filter((item) => normalizeString(item.scene_id) === sceneId);
|
|
1996
|
+
}
|
|
1997
|
+
if (specId) {
|
|
1998
|
+
rows = rows.filter((item) => normalizeString(item.spec_id) === specId);
|
|
1999
|
+
}
|
|
2000
|
+
rows.sort((left, right) => (Date.parse(right.updated_at || '') || 0) - (Date.parse(left.updated_at || '') || 0));
|
|
2001
|
+
if (limit > 0) {
|
|
2002
|
+
rows = rows.slice(0, limit);
|
|
2003
|
+
}
|
|
2004
|
+
return rows.map((row) => this._mapGovernanceSpecSceneOverrideRow(row));
|
|
2005
|
+
}
|
|
2006
|
+
|
|
2007
|
+
if (!await this.ensureReady()) {
|
|
2008
|
+
return null;
|
|
2009
|
+
}
|
|
2010
|
+
|
|
2011
|
+
let query = `
|
|
2012
|
+
SELECT spec_id, scene_id, source, rule_id, updated_at, indexed_at
|
|
2013
|
+
FROM governance_spec_scene_override_registry
|
|
2014
|
+
`;
|
|
2015
|
+
const clauses = [];
|
|
2016
|
+
const params = [];
|
|
2017
|
+
if (sceneId) {
|
|
2018
|
+
clauses.push('scene_id = ?');
|
|
2019
|
+
params.push(sceneId);
|
|
2020
|
+
}
|
|
2021
|
+
if (specId) {
|
|
2022
|
+
clauses.push('spec_id = ?');
|
|
2023
|
+
params.push(specId);
|
|
2024
|
+
}
|
|
2025
|
+
if (clauses.length > 0) {
|
|
2026
|
+
query += ` WHERE ${clauses.join(' AND ')}`;
|
|
2027
|
+
}
|
|
2028
|
+
query += ' ORDER BY updated_at DESC, spec_id ASC';
|
|
2029
|
+
if (limit > 0) {
|
|
2030
|
+
query += ' LIMIT ?';
|
|
2031
|
+
params.push(limit);
|
|
2032
|
+
}
|
|
2033
|
+
|
|
2034
|
+
const rows = this._db.prepare(query).all(...params);
|
|
2035
|
+
return rows
|
|
2036
|
+
.map((row) => this._mapGovernanceSpecSceneOverrideRow(row))
|
|
2037
|
+
.filter(Boolean);
|
|
2038
|
+
}
|
|
2039
|
+
|
|
2040
|
+
async upsertGovernanceSceneIndexRecords(records = [], options = {}) {
|
|
2041
|
+
const source = normalizeString(options.source) || 'file.spec-governance.scene-index';
|
|
2042
|
+
const nowIso = this.now();
|
|
2043
|
+
const normalizedRecords = Array.isArray(records)
|
|
2044
|
+
? records.map((item) => ({
|
|
2045
|
+
scene_id: normalizeString(item && item.scene_id),
|
|
2046
|
+
total_specs: normalizeNonNegativeInteger(item && item.total_specs, 0),
|
|
2047
|
+
active_specs: normalizeNonNegativeInteger(item && item.active_specs, 0),
|
|
2048
|
+
completed_specs: normalizeNonNegativeInteger(item && item.completed_specs, 0),
|
|
2049
|
+
stale_specs: normalizeNonNegativeInteger(item && item.stale_specs, 0),
|
|
2050
|
+
spec_ids: normalizeStringArray(item && item.spec_ids, []),
|
|
2051
|
+
active_spec_ids: normalizeStringArray(item && item.active_spec_ids, []),
|
|
2052
|
+
stale_spec_ids: normalizeStringArray(item && item.stale_spec_ids, []),
|
|
2053
|
+
generated_at: normalizeIsoTimestamp(item && item.generated_at, nowIso) || nowIso,
|
|
2054
|
+
scene_filter: normalizeString(item && item.scene_filter) || null,
|
|
2055
|
+
source: normalizeString(item && item.source) || source,
|
|
2056
|
+
indexed_at: nowIso
|
|
2057
|
+
}))
|
|
2058
|
+
.filter((item) => item.scene_id)
|
|
2059
|
+
: [];
|
|
2060
|
+
|
|
2061
|
+
if (this._useMemoryBackend()) {
|
|
2062
|
+
for (const item of normalizedRecords) {
|
|
2063
|
+
this._memory.governance_scene_index[item.scene_id] = { ...item };
|
|
2064
|
+
}
|
|
2065
|
+
return {
|
|
2066
|
+
success: true,
|
|
2067
|
+
written: normalizedRecords.length,
|
|
2068
|
+
total: Object.keys(this._memory.governance_scene_index || {}).length
|
|
2069
|
+
};
|
|
2070
|
+
}
|
|
2071
|
+
|
|
2072
|
+
if (!await this.ensureReady()) {
|
|
2073
|
+
return null;
|
|
2074
|
+
}
|
|
2075
|
+
|
|
2076
|
+
const statement = this._db.prepare(`
|
|
2077
|
+
INSERT OR REPLACE INTO governance_scene_index_registry(
|
|
2078
|
+
scene_id, total_specs, active_specs, completed_specs, stale_specs,
|
|
2079
|
+
spec_ids_json, active_spec_ids_json, stale_spec_ids_json, generated_at,
|
|
2080
|
+
scene_filter, source, indexed_at
|
|
2081
|
+
)
|
|
2082
|
+
VALUES(?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)
|
|
2083
|
+
`);
|
|
2084
|
+
|
|
2085
|
+
this._withTransaction(() => {
|
|
2086
|
+
for (const item of normalizedRecords) {
|
|
2087
|
+
statement.run(
|
|
2088
|
+
item.scene_id,
|
|
2089
|
+
item.total_specs,
|
|
2090
|
+
item.active_specs,
|
|
2091
|
+
item.completed_specs,
|
|
2092
|
+
item.stale_specs,
|
|
2093
|
+
JSON.stringify(item.spec_ids || []),
|
|
2094
|
+
JSON.stringify(item.active_spec_ids || []),
|
|
2095
|
+
JSON.stringify(item.stale_spec_ids || []),
|
|
2096
|
+
item.generated_at,
|
|
2097
|
+
item.scene_filter,
|
|
2098
|
+
item.source,
|
|
2099
|
+
item.indexed_at
|
|
2100
|
+
);
|
|
2101
|
+
}
|
|
2102
|
+
});
|
|
2103
|
+
|
|
2104
|
+
const totalRow = this._db
|
|
2105
|
+
.prepare('SELECT COUNT(*) AS total FROM governance_scene_index_registry')
|
|
2106
|
+
.get();
|
|
2107
|
+
|
|
2108
|
+
return {
|
|
2109
|
+
success: true,
|
|
2110
|
+
written: normalizedRecords.length,
|
|
2111
|
+
total: normalizeNonNegativeInteger(totalRow && totalRow.total, 0)
|
|
2112
|
+
};
|
|
2113
|
+
}
|
|
2114
|
+
|
|
2115
|
+
async listGovernanceSceneIndexRecords(options = {}) {
|
|
2116
|
+
const limit = normalizeInteger(options.limit, 100);
|
|
2117
|
+
const sceneId = normalizeString(options.sceneId);
|
|
2118
|
+
|
|
2119
|
+
if (this._useMemoryBackend()) {
|
|
2120
|
+
let rows = Object.values(this._memory.governance_scene_index || {}).map((item) => ({ ...item }));
|
|
2121
|
+
if (sceneId) {
|
|
2122
|
+
rows = rows.filter((item) => normalizeString(item.scene_id) === sceneId);
|
|
2123
|
+
}
|
|
2124
|
+
rows.sort((left, right) => {
|
|
2125
|
+
if (right.total_specs !== left.total_specs) {
|
|
2126
|
+
return right.total_specs - left.total_specs;
|
|
2127
|
+
}
|
|
2128
|
+
return `${left.scene_id}`.localeCompare(`${right.scene_id}`);
|
|
2129
|
+
});
|
|
2130
|
+
if (limit > 0) {
|
|
2131
|
+
rows = rows.slice(0, limit);
|
|
2132
|
+
}
|
|
2133
|
+
return rows.map((row) => this._mapGovernanceSceneIndexRow({
|
|
2134
|
+
...row,
|
|
2135
|
+
spec_ids_json: JSON.stringify(row.spec_ids || []),
|
|
2136
|
+
active_spec_ids_json: JSON.stringify(row.active_spec_ids || []),
|
|
2137
|
+
stale_spec_ids_json: JSON.stringify(row.stale_spec_ids || [])
|
|
2138
|
+
}));
|
|
2139
|
+
}
|
|
2140
|
+
|
|
2141
|
+
if (!await this.ensureReady()) {
|
|
2142
|
+
return null;
|
|
2143
|
+
}
|
|
2144
|
+
|
|
2145
|
+
let query = `
|
|
2146
|
+
SELECT scene_id, total_specs, active_specs, completed_specs, stale_specs,
|
|
2147
|
+
spec_ids_json, active_spec_ids_json, stale_spec_ids_json, generated_at,
|
|
2148
|
+
scene_filter, source, indexed_at
|
|
2149
|
+
FROM governance_scene_index_registry
|
|
2150
|
+
`;
|
|
2151
|
+
const params = [];
|
|
2152
|
+
if (sceneId) {
|
|
2153
|
+
query += ' WHERE scene_id = ?';
|
|
2154
|
+
params.push(sceneId);
|
|
2155
|
+
}
|
|
2156
|
+
query += ' ORDER BY total_specs DESC, active_specs DESC, scene_id ASC';
|
|
2157
|
+
if (limit > 0) {
|
|
2158
|
+
query += ' LIMIT ?';
|
|
2159
|
+
params.push(limit);
|
|
2160
|
+
}
|
|
2161
|
+
|
|
2162
|
+
const rows = this._db.prepare(query).all(...params);
|
|
2163
|
+
return rows
|
|
2164
|
+
.map((row) => this._mapGovernanceSceneIndexRow(row))
|
|
2165
|
+
.filter(Boolean);
|
|
2166
|
+
}
|
|
2167
|
+
|
|
888
2168
|
_resolveOrCreateTaskRefInMemory(options = {}) {
|
|
889
2169
|
const sceneId = normalizeString(options.sceneId);
|
|
890
2170
|
const specId = normalizeString(options.specId);
|