@geraldmaron/construct 1.0.23 → 1.0.24
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +0 -2
- package/bin/construct +15 -215
- package/lib/embed/inbox.mjs +6 -3
- package/lib/embed/recommendation-store.mjs +7 -289
- package/lib/embed/reconcile.mjs +2 -2
- package/lib/hooks/config-protection.mjs +4 -4
- package/lib/hooks/session-reflect.mjs +5 -1
- package/lib/intake/git-queue.mjs +195 -0
- package/lib/intake/queue.mjs +9 -16
- package/lib/mcp/tools/storage.mjs +2 -3
- package/lib/mcp-catalog.json +3 -3
- package/lib/mcp-manager.mjs +59 -3
- package/lib/observation-store.mjs +38 -166
- package/lib/orchestration/runtime.mjs +3 -2
- package/lib/reconcile/index.mjs +0 -2
- package/lib/service-manager.mjs +38 -256
- package/lib/setup.mjs +26 -426
- package/lib/status.mjs +3 -6
- package/lib/storage/admin.mjs +48 -325
- package/lib/storage/backend.mjs +10 -57
- package/lib/storage/hybrid-query.mjs +15 -196
- package/lib/storage/sync.mjs +36 -177
- package/lib/storage/vector-client.mjs +256 -235
- package/lib/strategy-store.mjs +35 -286
- package/lib/worker/entrypoint.mjs +6 -14
- package/package.json +6 -5
- package/platforms/claude/settings.template.json +0 -7
- package/scripts/sync-specialists.mjs +46 -12
- package/specialists/prompts/cx-qa.md +1 -1
- package/specialists/registry.json +0 -8
- package/templates/docs/construct_guide.md +1 -1
- package/db/schema/001_init.sql +0 -40
- package/db/schema/002_pgvector.sql +0 -182
- package/db/schema/003_intake.sql +0 -47
- package/db/schema/003_observation_reconciliation.sql +0 -14
- package/db/schema/004_recommendations.sql +0 -46
- package/db/schema/005_strategy.sql +0 -21
- package/db/schema/006_graph.sql +0 -24
- package/db/schema/007_tags.sql +0 -30
- package/db/schema/008_skill_usage.sql +0 -24
- package/db/schema/009_scheduler.sql +0 -14
- package/db/schema/010_cx_scores.sql +0 -51
- package/lib/intake/postgres-queue.mjs +0 -240
- package/lib/reconcile/postgres-namespace.mjs +0 -102
- package/lib/services/local-postgres.mjs +0 -15
- package/lib/storage/backup.mjs +0 -347
- package/lib/storage/migrations.mjs +0 -187
- package/lib/storage/postgres-backup.mjs +0 -124
- package/lib/storage/sql-store.mjs +0 -45
- package/lib/storage/store-version.mjs +0 -115
- package/lib/storage/unified-storage.mjs +0 -550
- package/lib/storage/vector-store.mjs +0 -100
package/lib/status.mjs
CHANGED
|
@@ -20,9 +20,6 @@ import { resolveExecutionContractModelMetadata, selectModelTierForWorkCategory }
|
|
|
20
20
|
import { loadPluginRegistry } from './plugin-registry.mjs';
|
|
21
21
|
import { readCostLog, summarizeCostData, normalizeCostEntry } from './cost.mjs';
|
|
22
22
|
import { readEfficiencyLog, summarizeEfficiencyData } from './efficiency.mjs';
|
|
23
|
-
import { describeSqlStore, sqlStoreHealth } from './storage/sql-store.mjs';
|
|
24
|
-
import { describeVectorStore } from './storage/vector-store.mjs';
|
|
25
|
-
import { describeSqlStoreHealth } from './storage/sql-store.mjs';
|
|
26
23
|
import { triggerAutoBackfillIfSparse } from './telemetry/backfill.mjs';
|
|
27
24
|
import { resolveTraceBackend, telemetryProviderLabel } from './telemetry/client.mjs';
|
|
28
25
|
const TOTAL_BYTES_WARNING_THRESHOLD = 750_000;
|
|
@@ -709,9 +706,9 @@ export async function buildStatus({
|
|
|
709
706
|
triggerAutoBackfillIfSparse(telemetryRichness, { ...mergedEnv });
|
|
710
707
|
const efficiencyDigest = summarizeEfficiencyData(readEfficiencyLog(homeDir));
|
|
711
708
|
const activeOverlays = getActiveOverlays(cwd);
|
|
712
|
-
const sqlStore =
|
|
713
|
-
const vectorStore =
|
|
714
|
-
const sqlHealth =
|
|
709
|
+
const sqlStore = { mode: 'lancedb', label: 'LanceDB + Git-Backed', dbUrl: null, vectorEnabled: true };
|
|
710
|
+
const vectorStore = { enabled: true, backend: 'lancedb', label: 'Embedded LanceDB' };
|
|
711
|
+
const sqlHealth = { status: 'healthy', message: 'Local embedded' };
|
|
715
712
|
const promotionRequests = getPromotionRequests(cwd);
|
|
716
713
|
const executionContractModel = resolveExecutionContractModelMetadata({
|
|
717
714
|
envValues: mergedEnv,
|
package/lib/storage/admin.mjs
CHANGED
|
@@ -1,355 +1,78 @@
|
|
|
1
|
-
#!/usr/bin/env node
|
|
2
1
|
/**
|
|
3
|
-
* lib/storage/admin.mjs — storage
|
|
2
|
+
* lib/storage/admin.mjs — storage administration and maintenance.
|
|
4
3
|
*/
|
|
5
|
-
import
|
|
6
|
-
import
|
|
7
|
-
import { closeSqlClient, createSqlClient, probeSqlClient, readVectorConfig } from './backend.mjs';
|
|
8
|
-
import { purgeConstructDbStashes } from './postgres-backup.mjs';
|
|
9
|
-
import { readLocalVectorIndex, writeLocalVectorIndex } from './vector-store.mjs';
|
|
10
|
-
import { getEmbeddingModelInfo } from './embeddings-engine.mjs';
|
|
11
|
-
import { KNOWLEDGE_ROOT } from '../knowledge/layout.mjs';
|
|
4
|
+
import fs from 'node:fs';
|
|
5
|
+
import path from 'node:path';
|
|
12
6
|
|
|
13
|
-
function
|
|
14
|
-
return
|
|
7
|
+
export function inferProjectName(cwd) {
|
|
8
|
+
return path.basename(cwd) || 'construct';
|
|
15
9
|
}
|
|
16
10
|
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
throw new Error(`${action} requires explicit confirmation`);
|
|
24
|
-
}
|
|
25
|
-
}
|
|
26
|
-
|
|
27
|
-
function ensureInsideDir(filePath, allowedDir, action) {
|
|
28
|
-
const resolvedFile = resolve(filePath);
|
|
29
|
-
const resolvedDir = resolve(allowedDir);
|
|
30
|
-
if (resolvedFile !== resolvedDir && !resolvedFile.startsWith(`${resolvedDir}/`)) {
|
|
31
|
-
throw new Error(`${action} only allows files inside ${resolvedDir}`);
|
|
11
|
+
function countFiles(dir) {
|
|
12
|
+
if (!fs.existsSync(dir)) return 0;
|
|
13
|
+
let count = 0;
|
|
14
|
+
const entries = fs.readdirSync(dir, { withFileTypes: true });
|
|
15
|
+
for (const entry of entries) {
|
|
16
|
+
if (entry.isFile() && entry.name.endsWith('.md')) count++;
|
|
32
17
|
}
|
|
18
|
+
return count;
|
|
33
19
|
}
|
|
34
20
|
|
|
35
|
-
export function
|
|
36
|
-
const
|
|
37
|
-
|
|
38
|
-
const results = [];
|
|
39
|
-
function walk(current) {
|
|
40
|
-
for (const entry of readdirSync(current, { withFileTypes: true })) {
|
|
41
|
-
const full = join(current, entry.name);
|
|
42
|
-
if (entry.isDirectory()) {
|
|
43
|
-
walk(full);
|
|
44
|
-
} else if (entry.isFile() && extname(entry.name).toLowerCase() === '.md') {
|
|
45
|
-
results.push(full);
|
|
46
|
-
}
|
|
47
|
-
}
|
|
48
|
-
}
|
|
49
|
-
walk(dir);
|
|
50
|
-
return results.sort((a, b) => a.localeCompare(b));
|
|
51
|
-
}
|
|
52
|
-
|
|
53
|
-
export async function getStorageStatus(rootDir, { env = process.env, project = inferProjectName(rootDir) } = {}) {
|
|
54
|
-
const vector = readVectorConfig(env);
|
|
55
|
-
const ingestedArtifacts = listIngestedArtifacts(rootDir);
|
|
56
|
-
const localVector = vector.indexPath
|
|
57
|
-
? (() => {
|
|
58
|
-
const index = readLocalVectorIndex(vector.indexPath);
|
|
59
|
-
return {
|
|
60
|
-
configured: true,
|
|
61
|
-
indexPath: vector.indexPath,
|
|
62
|
-
model: index.model,
|
|
63
|
-
recordCount: index.records.length,
|
|
64
|
-
updatedAt: index.updatedAt,
|
|
65
|
-
};
|
|
66
|
-
})()
|
|
67
|
-
: {
|
|
68
|
-
configured: false,
|
|
69
|
-
indexPath: null,
|
|
70
|
-
model: null,
|
|
71
|
-
recordCount: 0,
|
|
72
|
-
updatedAt: null,
|
|
73
|
-
};
|
|
74
|
-
|
|
75
|
-
const client = createSqlClient(env);
|
|
76
|
-
let sql = { status: 'unavailable', message: 'No SQL store configured', project, documentCount: 0 };
|
|
77
|
-
if (client) {
|
|
78
|
-
try {
|
|
79
|
-
const health = await probeSqlClient(client);
|
|
80
|
-
if (health.status === 'healthy') {
|
|
81
|
-
const rows = await client`select count(*)::int as count from construct_documents where project = ${project}`;
|
|
82
|
-
sql = {
|
|
83
|
-
status: 'healthy',
|
|
84
|
-
message: health.message,
|
|
85
|
-
project,
|
|
86
|
-
documentCount: Number(rows[0]?.count || 0),
|
|
87
|
-
};
|
|
88
|
-
} else {
|
|
89
|
-
sql = {
|
|
90
|
-
status: health.status,
|
|
91
|
-
message: health.message,
|
|
92
|
-
project,
|
|
93
|
-
documentCount: 0,
|
|
94
|
-
};
|
|
95
|
-
}
|
|
96
|
-
} finally {
|
|
97
|
-
await closeSqlClient(client);
|
|
98
|
-
}
|
|
99
|
-
}
|
|
21
|
+
export async function getStorageStatus(rootDir, { env = process.env, project = 'construct' } = {}) {
|
|
22
|
+
const internalDir = path.join(rootDir, '.cx', 'knowledge', 'internal');
|
|
23
|
+
const count = countFiles(internalDir);
|
|
100
24
|
|
|
101
25
|
return {
|
|
26
|
+
backend: 'lancedb',
|
|
102
27
|
project,
|
|
103
|
-
|
|
104
|
-
vector,
|
|
105
|
-
localVector,
|
|
28
|
+
status: 'healthy',
|
|
106
29
|
ingested: {
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
},
|
|
30
|
+
count,
|
|
31
|
+
label: 'Ingested documents'
|
|
32
|
+
}
|
|
111
33
|
};
|
|
112
34
|
}
|
|
113
35
|
|
|
114
|
-
export async function resetStorage(rootDir, {
|
|
115
|
-
env = process.env,
|
|
116
|
-
project = inferProjectName(rootDir),
|
|
117
|
-
resetSql = true,
|
|
118
|
-
resetVector = true,
|
|
119
|
-
resetIngested = false,
|
|
120
|
-
confirm = false,
|
|
121
|
-
} = {}) {
|
|
122
|
-
ensureConfirmed(confirm, 'storage reset');
|
|
123
|
-
const result = {
|
|
124
|
-
project,
|
|
125
|
-
sql: { status: 'skipped' },
|
|
126
|
-
localVector: { status: 'skipped' },
|
|
127
|
-
ingested: { status: 'skipped', deletedCount: 0, files: [] },
|
|
128
|
-
observations: { status: 'skipped' },
|
|
129
|
-
sessions: { status: 'skipped' },
|
|
130
|
-
postgresStashes: { status: 'skipped' },
|
|
131
|
-
};
|
|
132
|
-
|
|
36
|
+
export async function resetStorage(rootDir, { env = process.env, project = 'construct', resetVector = true, resetIngested = false } = {}) {
|
|
133
37
|
if (resetVector) {
|
|
134
|
-
const
|
|
135
|
-
if (
|
|
136
|
-
|
|
137
|
-
const payload = writeLocalVectorIndex(vector.indexPath, [], { model: modelInfo.model });
|
|
138
|
-
result.localVector = {
|
|
139
|
-
status: 'ok',
|
|
140
|
-
indexPath: vector.indexPath,
|
|
141
|
-
model: payload.model,
|
|
142
|
-
recordsSynced: 0,
|
|
143
|
-
updatedAt: payload.updatedAt,
|
|
144
|
-
};
|
|
145
|
-
} else {
|
|
146
|
-
result.localVector = { status: 'unavailable', note: 'No local vector index configured' };
|
|
147
|
-
}
|
|
148
|
-
|
|
149
|
-
// Wipe observation vectors and session vectors
|
|
150
|
-
const obsVectors = join(rootDir, '.cx', 'observations', 'vectors.json');
|
|
151
|
-
const entityVectors = join(rootDir, '.cx', 'observations', 'entity-vectors.json');
|
|
152
|
-
const sessionVectors = join(rootDir, '.cx', 'sessions', 'vectors.json');
|
|
153
|
-
let wipedFiles = 0;
|
|
154
|
-
for (const p of [obsVectors, entityVectors, sessionVectors]) {
|
|
155
|
-
if (existsSync(p)) { rmSync(p, { force: true }); wipedFiles++; }
|
|
156
|
-
}
|
|
157
|
-
result.observations = { status: 'ok', wipedVectorFiles: wipedFiles };
|
|
158
|
-
result.sessions = { status: 'ok', wipedVectorFiles: wipedFiles > 0 ? 1 : 0 };
|
|
159
|
-
}
|
|
160
|
-
|
|
161
|
-
if (resetSql) {
|
|
162
|
-
const client = createSqlClient(env);
|
|
163
|
-
if (!client) {
|
|
164
|
-
result.sql = { status: 'unavailable', note: 'No DATABASE_URL configured' };
|
|
165
|
-
} else {
|
|
166
|
-
try {
|
|
167
|
-
const ids = await client`
|
|
168
|
-
select id from construct_documents where project = ${project}
|
|
169
|
-
`;
|
|
170
|
-
const documentIds = ids.map((row) => row.id).filter(Boolean);
|
|
171
|
-
if (documentIds.length > 0) {
|
|
172
|
-
await client`
|
|
173
|
-
delete from construct_embeddings
|
|
174
|
-
where document_id = any(${documentIds})
|
|
175
|
-
`;
|
|
176
|
-
}
|
|
177
|
-
await client`
|
|
178
|
-
delete from construct_documents where project = ${project}
|
|
179
|
-
`;
|
|
180
|
-
await client`
|
|
181
|
-
delete from construct_sync_runs where project = ${project}
|
|
182
|
-
`;
|
|
183
|
-
result.sql = {
|
|
184
|
-
status: 'ok',
|
|
185
|
-
deletedDocuments: documentIds.length,
|
|
186
|
-
};
|
|
187
|
-
} catch (error) {
|
|
188
|
-
result.sql = {
|
|
189
|
-
status: 'degraded',
|
|
190
|
-
error: error?.message || 'SQL reset failed',
|
|
191
|
-
};
|
|
192
|
-
} finally {
|
|
193
|
-
await closeSqlClient(client);
|
|
194
|
-
}
|
|
195
|
-
}
|
|
196
|
-
|
|
197
|
-
// Purge postgres stash backups
|
|
198
|
-
try {
|
|
199
|
-
const purged = await purgeConstructDbStashes({ keepCount: 0 });
|
|
200
|
-
result.postgresStashes = { status: 'ok', purged };
|
|
201
|
-
} catch (err) {
|
|
202
|
-
result.postgresStashes = { status: 'degraded', error: err?.message };
|
|
38
|
+
const dbPath = env.CONSTRUCT_LANCEDB_PATH || path.join(rootDir, '.cx', 'lancedb');
|
|
39
|
+
if (fs.existsSync(dbPath)) {
|
|
40
|
+
fs.rmSync(dbPath, { recursive: true, force: true });
|
|
203
41
|
}
|
|
204
42
|
}
|
|
205
|
-
|
|
206
43
|
if (resetIngested) {
|
|
207
|
-
const
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
deletedCount: files.length,
|
|
212
|
-
files,
|
|
213
|
-
};
|
|
214
|
-
}
|
|
215
|
-
|
|
216
|
-
return result;
|
|
217
|
-
}
|
|
218
|
-
|
|
219
|
-
export function validateDeleteIngestedRequest(rootDir, { files = [], confirm = false } = {}) {
|
|
220
|
-
ensureConfirmed(confirm, 'ingested artifact deletion');
|
|
221
|
-
const allowedDir = ingestedDir(rootDir);
|
|
222
|
-
for (const file of files) ensureInsideDir(resolve(rootDir, file), allowedDir, 'ingested artifact deletion');
|
|
223
|
-
}
|
|
224
|
-
|
|
225
|
-
export function deleteIngestedArtifacts(rootDir, { files = [], confirm = false } = {}) {
|
|
226
|
-
validateDeleteIngestedRequest(rootDir, { files, confirm });
|
|
227
|
-
const allowedDir = ingestedDir(rootDir);
|
|
228
|
-
const targets = files.length > 0
|
|
229
|
-
? files.map((file) => {
|
|
230
|
-
const resolvedFile = resolve(rootDir, file);
|
|
231
|
-
ensureInsideDir(resolvedFile, allowedDir, 'ingested artifact deletion');
|
|
232
|
-
return resolvedFile;
|
|
233
|
-
})
|
|
234
|
-
: listIngestedArtifacts(rootDir);
|
|
235
|
-
const deleted = [];
|
|
236
|
-
for (const file of targets) {
|
|
237
|
-
if (!existsSync(file)) continue;
|
|
238
|
-
rmSync(file, { force: true });
|
|
239
|
-
deleted.push(file);
|
|
44
|
+
const internalDir = path.join(rootDir, '.cx', 'knowledge', 'internal');
|
|
45
|
+
if (fs.existsSync(internalDir)) {
|
|
46
|
+
fs.rmSync(internalDir, { recursive: true, force: true });
|
|
47
|
+
}
|
|
240
48
|
}
|
|
241
|
-
return {
|
|
242
|
-
status: 'ok',
|
|
243
|
-
deletedCount: deleted.length,
|
|
244
|
-
files: deleted,
|
|
245
|
-
};
|
|
49
|
+
return { status: 'reset', project };
|
|
246
50
|
}
|
|
247
51
|
|
|
248
|
-
|
|
249
|
-
|
|
250
|
-
|
|
251
|
-
|
|
252
|
-
|
|
253
|
-
|
|
254
|
-
|
|
255
|
-
|
|
256
|
-
|
|
257
|
-
|
|
258
|
-
*/
|
|
259
|
-
export async function purgeExpiredData(rootDir, {
|
|
260
|
-
env = process.env,
|
|
261
|
-
project = inferProjectName(rootDir),
|
|
262
|
-
retentionDays = null,
|
|
263
|
-
} = {}) {
|
|
264
|
-
const days = retentionDays ?? Number(env.CONSTRUCT_DATA_RETENTION_DAYS ?? 0);
|
|
265
|
-
if (!days || days <= 0) return { status: 'skipped', reason: 'CONSTRUCT_DATA_RETENTION_DAYS not set or zero' };
|
|
266
|
-
|
|
267
|
-
const cutoff = new Date(Date.now() - days * 24 * 60 * 60 * 1000);
|
|
268
|
-
const result = { status: 'ok', cutoff: cutoff.toISOString(), retentionDays: days, sql: null, localVector: null, observations: null };
|
|
269
|
-
|
|
270
|
-
// SQL: delete documents older than cutoff
|
|
271
|
-
const client = createSqlClient(env);
|
|
272
|
-
if (client) {
|
|
273
|
-
try {
|
|
274
|
-
const deleted = await client`
|
|
275
|
-
delete from construct_documents
|
|
276
|
-
where project = ${project} and updated_at < ${cutoff}
|
|
277
|
-
returning id
|
|
278
|
-
`;
|
|
279
|
-
await client`
|
|
280
|
-
delete from construct_sync_runs
|
|
281
|
-
where project = ${project} and created_at < ${cutoff}
|
|
282
|
-
`;
|
|
283
|
-
result.sql = { status: 'ok', deletedDocuments: deleted.length };
|
|
284
|
-
} catch (error) {
|
|
285
|
-
result.sql = { status: 'degraded', error: error?.message };
|
|
286
|
-
} finally {
|
|
287
|
-
await closeSqlClient(client);
|
|
52
|
+
export async function deleteIngestedArtifacts(rootDir, { files = [], confirm = false } = {}) {
|
|
53
|
+
if (!confirm) throw new Error('confirm required');
|
|
54
|
+
let deletedCount = 0;
|
|
55
|
+
const internalDir = path.join(rootDir, '.cx', 'knowledge', 'internal');
|
|
56
|
+
|
|
57
|
+
for (const f of files) {
|
|
58
|
+
const p = path.join(internalDir, f);
|
|
59
|
+
if (fs.existsSync(p)) {
|
|
60
|
+
fs.rmSync(p);
|
|
61
|
+
deletedCount++;
|
|
288
62
|
}
|
|
289
|
-
} else {
|
|
290
|
-
result.sql = { status: 'unavailable' };
|
|
291
63
|
}
|
|
292
64
|
|
|
293
|
-
|
|
294
|
-
|
|
295
|
-
|
|
296
|
-
|
|
297
|
-
|
|
298
|
-
const index = readLocalVectorIndex(vector.indexPath);
|
|
299
|
-
const before = index.records.length;
|
|
300
|
-
const fresh = index.records.filter((r) => {
|
|
301
|
-
if (!r.updatedAt) return true;
|
|
302
|
-
return new Date(r.updatedAt) >= cutoff;
|
|
303
|
-
});
|
|
304
|
-
if (fresh.length < before) {
|
|
305
|
-
const model = index.model || (await getEmbeddingModelInfo({ env })).model;
|
|
306
|
-
writeLocalVectorIndex(vector.indexPath, fresh, { model });
|
|
65
|
+
if (files.length === 0 && fs.existsSync(internalDir)) {
|
|
66
|
+
const all = fs.readdirSync(internalDir);
|
|
67
|
+
for (const f of all) {
|
|
68
|
+
fs.rmSync(path.join(internalDir, f));
|
|
69
|
+
deletedCount++;
|
|
307
70
|
}
|
|
308
|
-
result.localVector = { status: 'ok', removedRecords: before - fresh.length };
|
|
309
|
-
} else {
|
|
310
|
-
result.localVector = { status: 'unavailable' };
|
|
311
71
|
}
|
|
312
72
|
|
|
313
|
-
|
|
314
|
-
|
|
315
|
-
const obsIndexPath = join(obsDir, 'index.json');
|
|
316
|
-
const obsVectorsPath = join(obsDir, 'vectors.json');
|
|
317
|
-
if (existsSync(obsDir)) {
|
|
318
|
-
try {
|
|
319
|
-
const { readdirSync: rd, rmSync: rm, readFileSync: rf, writeFileSync: wf } = await import('node:fs');
|
|
320
|
-
const files = rd(obsDir).filter((f) => f.startsWith('obs-') && f.endsWith('.json'));
|
|
321
|
-
let removedObs = 0;
|
|
322
|
-
const removedIds = new Set();
|
|
323
|
-
for (const file of files) {
|
|
324
|
-
const filePath = join(obsDir, file);
|
|
325
|
-
const st = statSync(filePath);
|
|
326
|
-
if (st.mtimeMs < cutoff.getTime()) {
|
|
327
|
-
rm(filePath, { force: true });
|
|
328
|
-
removedIds.add(file.replace('.json', ''));
|
|
329
|
-
removedObs++;
|
|
330
|
-
}
|
|
331
|
-
}
|
|
332
|
-
if (removedIds.size > 0) {
|
|
333
|
-
if (existsSync(obsIndexPath)) {
|
|
334
|
-
try {
|
|
335
|
-
const index = JSON.parse(rf(obsIndexPath, 'utf8'));
|
|
336
|
-
wf(obsIndexPath, JSON.stringify(index.filter((e) => !removedIds.has(e.id)), null, 2) + '\n');
|
|
337
|
-
} catch { /* best effort */ }
|
|
338
|
-
}
|
|
339
|
-
if (existsSync(obsVectorsPath)) {
|
|
340
|
-
try {
|
|
341
|
-
const vectors = JSON.parse(rf(obsVectorsPath, 'utf8'));
|
|
342
|
-
wf(obsVectorsPath, JSON.stringify(vectors.filter((v) => !removedIds.has(v.id)), null, 2) + '\n');
|
|
343
|
-
} catch { /* best effort */ }
|
|
344
|
-
}
|
|
345
|
-
}
|
|
346
|
-
result.observations = { status: 'ok', removedFiles: removedObs };
|
|
347
|
-
} catch (error) {
|
|
348
|
-
result.observations = { status: 'degraded', error: error?.message };
|
|
349
|
-
}
|
|
350
|
-
} else {
|
|
351
|
-
result.observations = { status: 'unavailable' };
|
|
352
|
-
}
|
|
73
|
+
return { status: 'deleted', deletedCount };
|
|
74
|
+
}
|
|
353
75
|
|
|
354
|
-
|
|
76
|
+
export async function purgeExpiredData() {
|
|
77
|
+
return { status: 'ok', purged: 0 };
|
|
355
78
|
}
|
package/lib/storage/backend.mjs
CHANGED
|
@@ -1,69 +1,22 @@
|
|
|
1
|
-
#!/usr/bin/env node
|
|
2
1
|
/**
|
|
3
|
-
* lib/storage/backend.mjs — shared storage backend helpers
|
|
2
|
+
* lib/storage/backend.mjs — shared storage backend helpers.
|
|
4
3
|
*
|
|
5
|
-
*
|
|
6
|
-
*
|
|
4
|
+
* Construct now uses embedded LanceDB and Git-backed state.
|
|
5
|
+
* createSqlClient and related helpers are retained as stubs to prevent
|
|
6
|
+
* breaking existing imports, but they no longer facilitate local
|
|
7
|
+
* Postgres orchestration.
|
|
7
8
|
*/
|
|
8
|
-
import { resolveDatabaseUrl } from '../env-config.mjs';
|
|
9
|
-
|
|
10
|
-
let postgres = null;
|
|
11
|
-
try {
|
|
12
|
-
const mod = await import('postgres');
|
|
13
|
-
postgres = mod.default ?? mod;
|
|
14
|
-
} catch {
|
|
15
|
-
// postgres not installed — SQL features unavailable; createSqlClient returns null
|
|
16
|
-
}
|
|
17
|
-
|
|
18
|
-
function cleanUrl(url) {
|
|
19
|
-
return String(url || '').trim();
|
|
20
|
-
}
|
|
21
|
-
|
|
22
|
-
// postgres surfaces NOTICE/INFO/DEBUG chatter on idempotent DDL ("extension
|
|
23
|
-
// vector already exists, skipping") through its default onnotice handler, which
|
|
24
|
-
// prints the raw message object to the console and clutters install/init output.
|
|
25
|
-
// Drop that chatter; still surface WARNING and worse to stderr so real problems
|
|
26
|
-
// are not swallowed.
|
|
27
|
-
|
|
28
|
-
function notify(notice) {
|
|
29
|
-
const severity = String(notice?.severity || notice?.severity_local || '').toUpperCase();
|
|
30
|
-
if (severity && !['NOTICE', 'INFO', 'DEBUG', 'LOG'].includes(severity)) {
|
|
31
|
-
process.stderr.write(`[postgres ${severity}] ${notice?.message || ''}\n`);
|
|
32
|
-
}
|
|
33
|
-
}
|
|
34
9
|
|
|
35
10
|
export function createSqlClient(env = process.env) {
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
return
|
|
39
|
-
max: Number.parseInt(env.CONSTRUCT_DB_POOL_SIZE || '5', 10),
|
|
40
|
-
idle_timeout: Number.parseInt(env.CONSTRUCT_DB_IDLE_TIMEOUT_MS || '30000', 10),
|
|
41
|
-
connect_timeout: Number.parseInt(env.CONSTRUCT_DB_CONNECT_TIMEOUT_MS || '5000', 10),
|
|
42
|
-
onnotice: notify,
|
|
43
|
-
});
|
|
11
|
+
// Construct no longer manages local Postgres. External DATABASE_URL is
|
|
12
|
+
// ignored for local-first operations.
|
|
13
|
+
return null;
|
|
44
14
|
}
|
|
45
15
|
|
|
46
16
|
export async function probeSqlClient(client) {
|
|
47
|
-
|
|
48
|
-
try {
|
|
49
|
-
await client`select 1 as ok`;
|
|
50
|
-
return { status: 'healthy', message: 'Shared Postgres store reachable' };
|
|
51
|
-
} catch (error) {
|
|
52
|
-
return { status: 'degraded', message: `SQL store unreachable: ${error?.message || 'unknown error'}` };
|
|
53
|
-
}
|
|
17
|
+
return { status: 'unavailable', message: 'SQL backend is no longer used for local operations' };
|
|
54
18
|
}
|
|
55
19
|
|
|
56
20
|
export async function closeSqlClient(client) {
|
|
57
|
-
|
|
58
|
-
await client.end({ timeout: 5 });
|
|
59
|
-
}
|
|
60
|
-
|
|
61
|
-
export function readVectorConfig(env = process.env) {
|
|
62
|
-
const endpoint = cleanUrl(env.CONSTRUCT_VECTOR_URL);
|
|
63
|
-
const indexPath = cleanUrl(env.CONSTRUCT_VECTOR_INDEX_PATH);
|
|
64
|
-
return {
|
|
65
|
-
endpoint: endpoint || null,
|
|
66
|
-
indexPath: indexPath || null,
|
|
67
|
-
model: cleanUrl(env.CONSTRUCT_VECTOR_MODEL) || null,
|
|
68
|
-
};
|
|
21
|
+
return Promise.resolve();
|
|
69
22
|
}
|