sneakoscope 4.6.2 → 4.6.4
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 +2 -2
- package/crates/sks-core/Cargo.lock +1 -1
- package/crates/sks-core/Cargo.toml +1 -1
- package/crates/sks-core/src/main.rs +1 -1
- package/dist/bin/sks.js +1 -1
- package/dist/cli/install-helpers.js +35 -0
- package/dist/commands/doctor.js +3 -1
- package/dist/core/doctor/global-sks-install-cleanup.js +73 -9
- package/dist/core/fsx.js +1 -1
- package/dist/core/mad-db/mad-db-coordinator.js +17 -8
- package/dist/core/mad-db/mad-db-executor.js +59 -7
- package/dist/core/mad-db/mad-db-runtime-profile.js +11 -2
- package/dist/core/mad-db/mad-db-target.js +31 -0
- package/dist/core/retention.js +77 -5
- package/dist/core/update/update-migration-state.js +68 -0
- package/dist/core/update-check.js +11 -1
- package/dist/core/version.js +1 -1
- package/dist/scripts/mad-db-supabase-transport-diagnostics-check.js +47 -0
- package/dist/scripts/postinstall-safe-side-effects-check.js +1 -0
- package/dist/scripts/retention-cleanup-safety-check.js +24 -5
- package/dist/scripts/update-first-command-migration-check.js +1 -0
- package/package.json +3 -2
package/README.md
CHANGED
|
@@ -49,7 +49,7 @@ sks seo-geo-optimizer apply latest --mode seo --apply --json
|
|
|
49
49
|
sks seo-geo-optimizer audit --mode geo --target package --offline --json
|
|
50
50
|
```
|
|
51
51
|
|
|
52
|
-
> 📋 **Current release: `v4.6.
|
|
52
|
+
> 📋 **Current release: `v4.6.4`** — full release history lives in [CHANGELOG.md](CHANGELOG.md). This README documents how Sneakoscope works today, not its version-by-version changes. Release readiness is tracked in [docs/release-readiness.md](docs/release-readiness.md).
|
|
53
53
|
|
|
54
54
|
## 🍥 Parallelism, UX, And Integrations
|
|
55
55
|
|
|
@@ -659,7 +659,7 @@ npm install -g .
|
|
|
659
659
|
sks doctor --fix
|
|
660
660
|
```
|
|
661
661
|
|
|
662
|
-
If PATH or npm has duplicate global installs, `sks doctor --fix` keeps one global npm install
|
|
662
|
+
If PATH or npm has duplicate global installs, `sks doctor --fix` keeps one global npm install, removes duplicate global `sneakoscope` installs, and clears the npm cache so old downloaded package versions do not accumulate. The Sneakoscope source checkout is exempt so local development files are not removed.
|
|
663
663
|
|
|
664
664
|
### SKS keeps asking to update after a global update
|
|
665
665
|
|
|
@@ -4,7 +4,7 @@ use std::io::{self, Read, Seek, SeekFrom};
|
|
|
4
4
|
fn main() {
|
|
5
5
|
let mut args = std::env::args().skip(1);
|
|
6
6
|
match args.next().as_deref() {
|
|
7
|
-
Some("--version") => println!("sks-rs 4.6.
|
|
7
|
+
Some("--version") => println!("sks-rs 4.6.4"),
|
|
8
8
|
Some("compact-info") => {
|
|
9
9
|
let mut input = String::new();
|
|
10
10
|
let _ = io::stdin().read_to_string(&mut input);
|
package/dist/bin/sks.js
CHANGED
|
@@ -92,6 +92,11 @@ export async function postinstall({ bootstrap, args = [] }) {
|
|
|
92
92
|
console.log('SKS update migration: global Doctor ran; project receipt will be finalized on first normal command.');
|
|
93
93
|
else
|
|
94
94
|
console.log(`SKS update migration: global Doctor did not complete; first normal command will retry. ${(postinstallDoctor.blockers || []).join(', ')}`.trim());
|
|
95
|
+
const postinstallRetention = await runPostinstallProjectRetentionCleanup(installRoot);
|
|
96
|
+
if (postinstallRetention.status === 'completed' && postinstallRetention.action_count > 0)
|
|
97
|
+
console.log(`SKS mission cleanup: removed ${postinstallRetention.action_count} disposable runtime artifact(s) from closed missions.`);
|
|
98
|
+
else if (postinstallRetention.status === 'failed')
|
|
99
|
+
console.log(`SKS mission cleanup: skipped (${postinstallRetention.error || 'cleanup failed'}).`);
|
|
95
100
|
// Terminating a third-party app's processes during `npm i` is unsafe by default; opt-in only.
|
|
96
101
|
const appProcessRepair = process.env.SKS_POSTINSTALL_RECONCILE_APP_PROCESSES === '1'
|
|
97
102
|
? await reconcileCodexAppUpgradeProcesses()
|
|
@@ -150,6 +155,36 @@ export async function postinstall({ bootstrap, args = [] }) {
|
|
|
150
155
|
await reportPostinstallCodexLbAuth().catch(() => { });
|
|
151
156
|
}
|
|
152
157
|
}
|
|
158
|
+
async function runPostinstallProjectRetentionCleanup(root) {
|
|
159
|
+
const projectRoot = path.resolve(root || process.cwd());
|
|
160
|
+
if (process.env.SKS_POSTINSTALL_RETENTION_CLEANUP === '0') {
|
|
161
|
+
return { status: 'skipped', reason: 'disabled_by_env', action_count: 0 };
|
|
162
|
+
}
|
|
163
|
+
if (!(await exists(path.join(projectRoot, '.sneakoscope', 'missions')))) {
|
|
164
|
+
return { status: 'skipped', reason: 'missions_missing', action_count: 0 };
|
|
165
|
+
}
|
|
166
|
+
try {
|
|
167
|
+
const { enforceRetention } = await import('../core/retention.js');
|
|
168
|
+
const result = await enforceRetention(projectRoot, {
|
|
169
|
+
mode: 'postinstall_update',
|
|
170
|
+
pruneReportLogs: true,
|
|
171
|
+
policy: { max_tmp_age_hours: 0 }
|
|
172
|
+
});
|
|
173
|
+
return {
|
|
174
|
+
status: 'completed',
|
|
175
|
+
root: projectRoot,
|
|
176
|
+
action_count: Array.isArray(result.actions) ? result.actions.length : 0
|
|
177
|
+
};
|
|
178
|
+
}
|
|
179
|
+
catch (err) {
|
|
180
|
+
return {
|
|
181
|
+
status: 'failed',
|
|
182
|
+
root: projectRoot,
|
|
183
|
+
action_count: 0,
|
|
184
|
+
error: err?.message || String(err)
|
|
185
|
+
};
|
|
186
|
+
}
|
|
187
|
+
}
|
|
153
188
|
async function reportPostinstallCodexLbAuth() {
|
|
154
189
|
const codexLbAuth = await ensureCodexLbAuthDuringInstall();
|
|
155
190
|
if (codexLbAuth.legacy_auth_migrated)
|
package/dist/commands/doctor.js
CHANGED
|
@@ -501,7 +501,7 @@ async function runDoctor(args = [], root, doctorFix) {
|
|
|
501
501
|
warnings_suppressed: false,
|
|
502
502
|
blockers: [err?.message || String(err)]
|
|
503
503
|
}));
|
|
504
|
-
const globalSksInstallCleanup = flag(args, '--fix') && !flag(args, '--local-only')
|
|
504
|
+
const globalSksInstallCleanup = flag(args, '--fix') && !flag(args, '--local-only')
|
|
505
505
|
? await cleanDuplicateGlobalSksInstalls({ root, fix: true }).catch((err) => ({ schema: 'sks.global-sks-install-cleanup.v1', ok: false, fix: true, error: err?.message || String(err), blockers: ['global_sks_install_cleanup_exception'] }))
|
|
506
506
|
: null;
|
|
507
507
|
const { detectImagegenCapability } = await import('../core/imagegen/imagegen-capability.js');
|
|
@@ -879,6 +879,8 @@ async function runDoctor(args = [], root, doctorFix) {
|
|
|
879
879
|
}
|
|
880
880
|
if (globalSksInstallCleanup) {
|
|
881
881
|
console.log(`Global SKS installs: kept ${globalSksInstallCleanup.kept?.length ?? 0}, removed ${globalSksInstallCleanup.removed?.filter((entry) => entry.ok).length ?? 0}, source repo exempt ${globalSksInstallCleanup.candidates?.filter((entry) => entry.source_repo_exempt).length ?? 0}`);
|
|
882
|
+
if (globalSksInstallCleanup.npm_cache)
|
|
883
|
+
console.log(`NPM cache cleanup: ${globalSksInstallCleanup.npm_cache.status}`);
|
|
882
884
|
}
|
|
883
885
|
if (!ready.ready && ready.next_actions?.length) {
|
|
884
886
|
console.log('What still needs you:');
|
|
@@ -12,18 +12,22 @@ export async function cleanDuplicateGlobalSksInstalls(opts = {}) {
|
|
|
12
12
|
const planned = planGlobalSksInstallCleanup(candidates, { sourceRoot });
|
|
13
13
|
const removed = [];
|
|
14
14
|
const blockers = [...planned.blockers];
|
|
15
|
+
const cleanupContract = opts.fix === true && npmBin
|
|
16
|
+
? createRequestedScopeContract({
|
|
17
|
+
route: 'doctor',
|
|
18
|
+
userRequest: 'sks doctor --fix global SKS install and npm cache cleanup',
|
|
19
|
+
projectRoot: sourceRoot,
|
|
20
|
+
overrides: { package_install: true }
|
|
21
|
+
})
|
|
22
|
+
: null;
|
|
23
|
+
const guardContext = cleanupContract && npmBin
|
|
24
|
+
? guardContextForRoute(sourceRoot, cleanupContract, 'sks doctor --fix global SKS install and npm cache cleanup')
|
|
25
|
+
: null;
|
|
15
26
|
if (opts.fix === true && planned.removable.length > 0) {
|
|
16
|
-
if (!npmBin) {
|
|
27
|
+
if (!npmBin || !guardContext) {
|
|
17
28
|
blockers.push('npm_not_found_for_duplicate_global_sks_cleanup');
|
|
18
29
|
}
|
|
19
30
|
else {
|
|
20
|
-
const cleanupContract = createRequestedScopeContract({
|
|
21
|
-
route: 'doctor',
|
|
22
|
-
userRequest: 'sks doctor --fix duplicate global SKS cleanup',
|
|
23
|
-
projectRoot: sourceRoot,
|
|
24
|
-
overrides: { package_install: true }
|
|
25
|
-
});
|
|
26
|
-
const guardContext = guardContextForRoute(sourceRoot, cleanupContract, 'sks doctor --fix duplicate global SKS cleanup');
|
|
27
31
|
for (const candidate of planned.removable) {
|
|
28
32
|
if (!candidate.prefix) {
|
|
29
33
|
blockers.push(`duplicate_global_sks_missing_prefix:${candidate.package_root || candidate.bin || 'unknown'}`);
|
|
@@ -44,9 +48,12 @@ export async function cleanDuplicateGlobalSksInstalls(opts = {}) {
|
|
|
44
48
|
}
|
|
45
49
|
}
|
|
46
50
|
}
|
|
51
|
+
const npmCache = await cleanNpmCache({ fix: opts.fix === true, npmBin, env, guardContext });
|
|
52
|
+
if (opts.fix === true && !npmCache.ok)
|
|
53
|
+
blockers.push(...npmCache.blockers);
|
|
47
54
|
return {
|
|
48
55
|
schema: 'sks.global-sks-install-cleanup.v1',
|
|
49
|
-
ok: blockers.length === 0,
|
|
56
|
+
ok: blockers.length === 0 && npmCache.ok,
|
|
50
57
|
fix: opts.fix === true,
|
|
51
58
|
source_root: sourceRoot,
|
|
52
59
|
package: 'sneakoscope',
|
|
@@ -54,6 +61,7 @@ export async function cleanDuplicateGlobalSksInstalls(opts = {}) {
|
|
|
54
61
|
kept: planned.kept,
|
|
55
62
|
removable: planned.removable,
|
|
56
63
|
removed,
|
|
64
|
+
npm_cache: npmCache,
|
|
57
65
|
blockers
|
|
58
66
|
};
|
|
59
67
|
}
|
|
@@ -217,6 +225,62 @@ function dedupeCandidates(candidates) {
|
|
|
217
225
|
function scoreCandidate(candidate) {
|
|
218
226
|
return (candidate.bin ? 2 : 0) + (candidate.prefix ? 1 : 0);
|
|
219
227
|
}
|
|
228
|
+
async function cleanNpmCache(opts) {
|
|
229
|
+
const args = ['cache', 'clean', '--force', '--silent'];
|
|
230
|
+
const command = opts.npmBin ? [opts.npmBin, ...args].join(' ') : null;
|
|
231
|
+
if (!opts.fix) {
|
|
232
|
+
return {
|
|
233
|
+
schema: 'sks.npm-cache-cleanup.v1',
|
|
234
|
+
ok: true,
|
|
235
|
+
fix: false,
|
|
236
|
+
status: 'skipped',
|
|
237
|
+
command,
|
|
238
|
+
code: null,
|
|
239
|
+
scope: 'npm_cache_all_packages',
|
|
240
|
+
stdout_tail: '',
|
|
241
|
+
stderr_tail: '',
|
|
242
|
+
blockers: []
|
|
243
|
+
};
|
|
244
|
+
}
|
|
245
|
+
if (!opts.npmBin || !opts.guardContext) {
|
|
246
|
+
return {
|
|
247
|
+
schema: 'sks.npm-cache-cleanup.v1',
|
|
248
|
+
ok: false,
|
|
249
|
+
fix: true,
|
|
250
|
+
status: 'npm_missing',
|
|
251
|
+
command: null,
|
|
252
|
+
code: null,
|
|
253
|
+
scope: 'npm_cache_all_packages',
|
|
254
|
+
stdout_tail: '',
|
|
255
|
+
stderr_tail: '',
|
|
256
|
+
blockers: ['npm_not_found_for_npm_cache_cleanup']
|
|
257
|
+
};
|
|
258
|
+
}
|
|
259
|
+
const result = await guardedPackageInstall(opts.guardContext, 'npm cache clean --force', {
|
|
260
|
+
confirmed: true,
|
|
261
|
+
command: opts.npmBin,
|
|
262
|
+
args,
|
|
263
|
+
env: opts.env,
|
|
264
|
+
timeoutMs: 60_000,
|
|
265
|
+
maxOutputBytes: 16 * 1024
|
|
266
|
+
}).catch((err) => ({ code: 1, stdout: '', stderr: err?.message || String(err) }));
|
|
267
|
+
const ok = result.code === 0;
|
|
268
|
+
return {
|
|
269
|
+
schema: 'sks.npm-cache-cleanup.v1',
|
|
270
|
+
ok,
|
|
271
|
+
fix: true,
|
|
272
|
+
status: ok ? 'cleaned' : 'failed',
|
|
273
|
+
command,
|
|
274
|
+
code: result.code,
|
|
275
|
+
scope: 'npm_cache_all_packages',
|
|
276
|
+
stdout_tail: tail(result.stdout),
|
|
277
|
+
stderr_tail: tail(result.stderr),
|
|
278
|
+
blockers: ok ? [] : ['npm_cache_clean_failed']
|
|
279
|
+
};
|
|
280
|
+
}
|
|
281
|
+
function tail(text) {
|
|
282
|
+
return String(text || '').slice(-2000);
|
|
283
|
+
}
|
|
220
284
|
async function realpathOrSelf(value) {
|
|
221
285
|
try {
|
|
222
286
|
return await fsp.realpath(value);
|
package/dist/core/fsx.js
CHANGED
|
@@ -5,7 +5,7 @@ import os from 'node:os';
|
|
|
5
5
|
import crypto from 'node:crypto';
|
|
6
6
|
import { spawn } from 'node:child_process';
|
|
7
7
|
import { fileURLToPath } from 'node:url';
|
|
8
|
-
export const PACKAGE_VERSION = '4.6.
|
|
8
|
+
export const PACKAGE_VERSION = '4.6.4';
|
|
9
9
|
export const DEFAULT_PROCESS_TAIL_BYTES = 256 * 1024;
|
|
10
10
|
export const DEFAULT_PROCESS_TIMEOUT_MS = 30 * 60 * 1000;
|
|
11
11
|
export function nowIso() {
|
|
@@ -17,10 +17,10 @@ export async function prepareMadDbMission(input) {
|
|
|
17
17
|
const blockers = [...target.blockers];
|
|
18
18
|
let profile;
|
|
19
19
|
if (!target.project_ref) {
|
|
20
|
-
profile = await createMadDbRuntimeProfile({ root: input.root, missionId: id, cycleId, projectRef: 'missing-project-ref', runtimeSessionId });
|
|
20
|
+
profile = await createMadDbRuntimeProfile({ root: input.root, missionId: id, cycleId, projectRef: 'missing-project-ref', runtimeSessionId, mcpUrl: target.mcp_url });
|
|
21
21
|
}
|
|
22
22
|
else {
|
|
23
|
-
profile = await createMadDbRuntimeProfile({ root: input.root, missionId: id, cycleId, projectRef: target.project_ref, runtimeSessionId });
|
|
23
|
+
profile = await createMadDbRuntimeProfile({ root: input.root, missionId: id, cycleId, projectRef: target.project_ref, runtimeSessionId, mcpUrl: target.mcp_url });
|
|
24
24
|
}
|
|
25
25
|
const capability = await createMadDbCapability(input.root, {
|
|
26
26
|
missionId: id,
|
|
@@ -54,7 +54,7 @@ export async function prepareMadDbMission(input) {
|
|
|
54
54
|
command: '$MAD-DB',
|
|
55
55
|
mode: 'MADDB',
|
|
56
56
|
task: input.task,
|
|
57
|
-
target:
|
|
57
|
+
target: redactTarget(target),
|
|
58
58
|
capability_file: 'mad-db-capability.json',
|
|
59
59
|
runtime_profile_manifest: 'mad-db/runtime/runtime-profile-manifest.json',
|
|
60
60
|
tool_inventory: toolInventory ? 'mad-db/runtime/tool-inventory.json' : null
|
|
@@ -118,6 +118,8 @@ export async function runMadDbCycle(input) {
|
|
|
118
118
|
await writeJsonAtomic(path.join(missionDir(input.root, prepared.mission_id), 'mad-db', 'runtime', 'tool-inventory.json'), inventory);
|
|
119
119
|
if (!inventory.ok) {
|
|
120
120
|
blockers.push('mad_db_execute_sql_or_apply_migration_unavailable');
|
|
121
|
+
if (inventory.error_kind)
|
|
122
|
+
blockers.push(inventory.error_kind);
|
|
121
123
|
throw new Error('mad_db_tool_inventory_failed');
|
|
122
124
|
}
|
|
123
125
|
await activateMadDbCapability(input.root, prepared.mission_id);
|
|
@@ -154,6 +156,8 @@ export async function runMadDbCycle(input) {
|
|
|
154
156
|
});
|
|
155
157
|
if (!execution.ok)
|
|
156
158
|
blockers.push('mad_db_tool_execution_failed');
|
|
159
|
+
if (!execution.ok && execution.error_kind)
|
|
160
|
+
blockers.push(execution.error_kind);
|
|
157
161
|
if (execution.ok && input.verifySql) {
|
|
158
162
|
const verifyStart = Date.now();
|
|
159
163
|
readBack = await runReadBackChecks({
|
|
@@ -227,10 +231,7 @@ async function clearMadDbCurrentState(root, missionId, ok, restoration) {
|
|
|
227
231
|
function redactCycleResult(result) {
|
|
228
232
|
return {
|
|
229
233
|
...result,
|
|
230
|
-
target:
|
|
231
|
-
...result.target,
|
|
232
|
-
project_ref: result.target.project_ref ? '<redacted>' : null
|
|
233
|
-
}
|
|
234
|
+
target: redactTarget(result.target)
|
|
234
235
|
};
|
|
235
236
|
}
|
|
236
237
|
async function resolveSqlInput(input) {
|
|
@@ -252,13 +253,21 @@ async function recreateProfileFromPrepared(root, prepared) {
|
|
|
252
253
|
profile_path: prepared.runtime_profile.profile_path,
|
|
253
254
|
profile_sha256: prepared.runtime_profile.profile_sha256,
|
|
254
255
|
server_url_redacted: prepared.runtime_profile.server_url_redacted,
|
|
255
|
-
server_url: `https://mcp.supabase.com/mcp?project_ref=${encodeURIComponent(projectRef)}&features=database`,
|
|
256
|
+
server_url: prepared.target.mcp_url || `https://mcp.supabase.com/mcp?project_ref=${encodeURIComponent(projectRef)}&features=database`,
|
|
257
|
+
server_url_source: prepared.target.mcp_url ? 'explicit_mcp_url' : 'generated_project_ref',
|
|
256
258
|
features: ['database'],
|
|
257
259
|
write_capable: true,
|
|
258
260
|
normal_config_hash_before: prepared.runtime_profile.normal_config_hash_before,
|
|
259
261
|
created_at: prepared.runtime_profile.created_at
|
|
260
262
|
};
|
|
261
263
|
}
|
|
264
|
+
function redactTarget(target) {
|
|
265
|
+
return {
|
|
266
|
+
...target,
|
|
267
|
+
project_ref: target.project_ref ? `<hash:${target.project_ref_hash}>` : null,
|
|
268
|
+
mcp_url: target.mcp_url ? '<redacted>' : null
|
|
269
|
+
};
|
|
270
|
+
}
|
|
262
271
|
export async function madDbRouteIdentityProof(root, missionId) {
|
|
263
272
|
const capability = await readMadDbCapability(root, missionId);
|
|
264
273
|
const state = await readJson(path.join(root, '.sneakoscope', 'state', 'current.json'), {});
|
|
@@ -33,10 +33,15 @@ export class MadDbMcpExecutor {
|
|
|
33
33
|
execute_sql_available: hasTool(names, 'execute_sql'),
|
|
34
34
|
apply_migration_available: hasTool(names, 'apply_migration'),
|
|
35
35
|
duration_ms: Math.round(performance.now() - started),
|
|
36
|
-
error_digest: null
|
|
36
|
+
error_digest: null,
|
|
37
|
+
error_summary: null,
|
|
38
|
+
error_kind: null,
|
|
39
|
+
retry_guidance: null
|
|
37
40
|
};
|
|
38
41
|
}
|
|
39
42
|
catch (err) {
|
|
43
|
+
const summary = summarizeMadDbError(err);
|
|
44
|
+
const kind = classifyMadDbError(summary);
|
|
40
45
|
return {
|
|
41
46
|
schema: 'sks.mad-db-tool-inventory.v1',
|
|
42
47
|
checked_at: nowIso(),
|
|
@@ -45,7 +50,10 @@ export class MadDbMcpExecutor {
|
|
|
45
50
|
execute_sql_available: false,
|
|
46
51
|
apply_migration_available: false,
|
|
47
52
|
duration_ms: Math.round(performance.now() - started),
|
|
48
|
-
error_digest: sha256(
|
|
53
|
+
error_digest: sha256(summary).slice(0, 32),
|
|
54
|
+
error_summary: summary,
|
|
55
|
+
error_kind: kind,
|
|
56
|
+
retry_guidance: madDbRetryGuidance(kind)
|
|
49
57
|
};
|
|
50
58
|
}
|
|
51
59
|
}
|
|
@@ -77,21 +85,29 @@ export class MadDbMcpExecutor {
|
|
|
77
85
|
result_digest: sha256(JSON.stringify(redactToolResult(result))).slice(0, 32),
|
|
78
86
|
row_count: extractRowCount(result),
|
|
79
87
|
is_error: result.isError === true,
|
|
80
|
-
duration_ms: Math.round(performance.now() - started)
|
|
88
|
+
duration_ms: Math.round(performance.now() - started),
|
|
89
|
+
error_summary: result.isError === true ? summarizeMadDbToolError(result) : null,
|
|
90
|
+
error_kind: result.isError === true ? classifyMadDbError(summarizeMadDbToolError(result)) : null,
|
|
91
|
+
retry_guidance: result.isError === true ? madDbRetryGuidance(classifyMadDbError(summarizeMadDbToolError(result))) : null
|
|
81
92
|
};
|
|
82
93
|
}
|
|
83
94
|
catch (err) {
|
|
84
95
|
lastError = err;
|
|
85
96
|
}
|
|
86
97
|
}
|
|
98
|
+
const summary = summarizeMadDbError(lastError);
|
|
99
|
+
const kind = classifyMadDbError(summary);
|
|
87
100
|
return {
|
|
88
101
|
schema: 'sks.mad-db-tool-result.v1',
|
|
89
102
|
ok: false,
|
|
90
103
|
tool_name: tool,
|
|
91
|
-
result_digest: sha256(
|
|
104
|
+
result_digest: sha256(summary).slice(0, 32),
|
|
92
105
|
row_count: null,
|
|
93
106
|
is_error: true,
|
|
94
|
-
duration_ms: 0
|
|
107
|
+
duration_ms: 0,
|
|
108
|
+
error_summary: summary,
|
|
109
|
+
error_kind: kind,
|
|
110
|
+
retry_guidance: madDbRetryGuidance(kind)
|
|
95
111
|
};
|
|
96
112
|
}
|
|
97
113
|
}
|
|
@@ -112,9 +128,45 @@ function authHeaders() {
|
|
|
112
128
|
return undefined;
|
|
113
129
|
return { Authorization: `Bearer ${token}` };
|
|
114
130
|
}
|
|
115
|
-
function
|
|
131
|
+
export function summarizeMadDbError(err) {
|
|
116
132
|
const text = err instanceof Error ? `${err.name}:${err.message}` : String(err);
|
|
117
|
-
return text
|
|
133
|
+
return redactErrorText(text).slice(0, 1200);
|
|
134
|
+
}
|
|
135
|
+
function summarizeMadDbToolError(result) {
|
|
136
|
+
return redactErrorText(JSON.stringify(redactToolResult(result))).slice(0, 1200);
|
|
137
|
+
}
|
|
138
|
+
function redactErrorText(text) {
|
|
139
|
+
return String(text || '')
|
|
140
|
+
.replace(/(Bearer\s+)[A-Za-z0-9._~+/=-]+/gi, '$1<redacted>')
|
|
141
|
+
.replace(/(token|password|secret|apikey)=([^&\s]+)/gi, '$1=<redacted>')
|
|
142
|
+
.replace(/(postgres(?:ql)?:\/\/[^:\s/]+:)([^@\s]+)(@)/gi, '$1<redacted>$3');
|
|
143
|
+
}
|
|
144
|
+
export function classifyMadDbError(summary) {
|
|
145
|
+
const text = String(summary || '').toLowerCase();
|
|
146
|
+
if (/read[_ -]?only|read only|permission denied.+read/i.test(text))
|
|
147
|
+
return 'supabase_mcp_read_only_transport';
|
|
148
|
+
if (/timeout|timed out|etimedout|i\/o timeout|aborterror|deadline/i.test(text))
|
|
149
|
+
return 'supabase_sql_plane_timeout';
|
|
150
|
+
if (/connection terminated|econnreset|unexpected eof|socket hang up|server closed/i.test(text))
|
|
151
|
+
return 'supabase_sql_plane_connection_interrupted';
|
|
152
|
+
if (/sasl|password authentication failed|invalid login|authentication failed|sqlstate 28p01/i.test(text))
|
|
153
|
+
return 'supabase_sql_plane_auth_failed';
|
|
154
|
+
if (/fetch failed|enotfound|econnrefused|network|dns/i.test(text))
|
|
155
|
+
return 'supabase_mcp_transport_unreachable';
|
|
156
|
+
return 'supabase_mcp_tool_error';
|
|
157
|
+
}
|
|
158
|
+
export function madDbRetryGuidance(kind) {
|
|
159
|
+
if (kind === 'supabase_mcp_read_only_transport')
|
|
160
|
+
return 'The active MadDB cycle must use the mission-local write-capable MCP URL. Pass --mcp-url or SKS_MAD_DB_MCP_URL if a project-local read-only Supabase MCP config is shadowing the intended transport.';
|
|
161
|
+
if (kind === 'supabase_sql_plane_timeout')
|
|
162
|
+
return 'Retry with an explicit Supabase pooler/session/direct MCP transport if available; Supabase CLI docs also support --db-url for CLI migration paths when pooler/direct connectivity differs.';
|
|
163
|
+
if (kind === 'supabase_sql_plane_connection_interrupted')
|
|
164
|
+
return 'Treat this as a SQL-plane connectivity interruption, not a DB safety denial. Retry after checking Supabase project health, pooler/direct connection mode, and network reachability.';
|
|
165
|
+
if (kind === 'supabase_sql_plane_auth_failed')
|
|
166
|
+
return 'Check SUPABASE_ACCESS_TOKEN and the project/database credentials used by the Supabase MCP server; do not retry destructive SQL until auth is corrected.';
|
|
167
|
+
if (kind === 'supabase_mcp_transport_unreachable')
|
|
168
|
+
return 'Check access to https://mcp.supabase.com/mcp or pass a trusted local MCP URL with --mcp-url for the active MadDB cycle.';
|
|
169
|
+
return 'Inspect error_summary in mad-db-result.json; the failure occurred after MadDB capability gating, so do not report it as a safety-gate denial.';
|
|
118
170
|
}
|
|
119
171
|
function redactToolResult(value) {
|
|
120
172
|
if (value == null)
|
|
@@ -5,7 +5,7 @@ import { missionDir } from '../mission.js';
|
|
|
5
5
|
export async function createMadDbRuntimeProfile(input) {
|
|
6
6
|
const dir = path.join(missionDir(input.root, input.missionId), 'mad-db', 'runtime');
|
|
7
7
|
await fs.mkdir(dir, { recursive: true });
|
|
8
|
-
const url = madDbMcpUrl(input.projectRef);
|
|
8
|
+
const url = madDbMcpUrl(input.projectRef, input.mcpUrl);
|
|
9
9
|
const text = [
|
|
10
10
|
'[mcp_servers.supabase_mad_db]',
|
|
11
11
|
`url = "${url}"`,
|
|
@@ -26,6 +26,7 @@ export async function createMadDbRuntimeProfile(input) {
|
|
|
26
26
|
profile_sha256: profileHash,
|
|
27
27
|
server_url_redacted: redactSupabaseUrl(url),
|
|
28
28
|
server_url: url,
|
|
29
|
+
server_url_source: input.mcpUrl ? 'explicit_mcp_url' : 'generated_project_ref',
|
|
29
30
|
features: ['database'],
|
|
30
31
|
write_capable: true,
|
|
31
32
|
normal_config_hash_before: normalHash,
|
|
@@ -74,7 +75,15 @@ export function redactedRuntimeProfile(profile) {
|
|
|
74
75
|
const { server_url: _serverUrl, ...rest } = profile;
|
|
75
76
|
return rest;
|
|
76
77
|
}
|
|
77
|
-
export function madDbMcpUrl(projectRef) {
|
|
78
|
+
export function madDbMcpUrl(projectRef, explicitUrl) {
|
|
79
|
+
if (explicitUrl) {
|
|
80
|
+
const parsed = new URL(explicitUrl);
|
|
81
|
+
if (projectRef && !parsed.searchParams.get('project_ref'))
|
|
82
|
+
parsed.searchParams.set('project_ref', projectRef);
|
|
83
|
+
parsed.searchParams.set('features', 'database');
|
|
84
|
+
parsed.searchParams.delete('read_only');
|
|
85
|
+
return parsed.toString();
|
|
86
|
+
}
|
|
78
87
|
const params = new URLSearchParams();
|
|
79
88
|
params.set('project_ref', projectRef);
|
|
80
89
|
params.set('features', 'database');
|
|
@@ -1,10 +1,13 @@
|
|
|
1
1
|
import path from 'node:path';
|
|
2
2
|
import { exists, readText, sha256 } from '../fsx.js';
|
|
3
|
+
import { redactSupabaseUrl } from './mad-db-runtime-profile.js';
|
|
3
4
|
export async function resolveMadDbTarget(root, input = {}) {
|
|
4
5
|
const args = input.args || [];
|
|
5
6
|
const explicit = input.projectRef || readOption(args, '--project-ref', '') || process.env.SKS_MAD_DB_PROJECT_REF || process.env.SKS_MAD_DB_E2E_PROJECT_REF || '';
|
|
6
7
|
const candidates = explicit ? [explicit] : await projectRefCandidates(root);
|
|
7
8
|
const projectRef = explicit || (candidates.length === 1 ? candidates[0] || '' : '');
|
|
9
|
+
const explicitMcpUrl = readOption(args, '--mcp-url', '') || process.env.SKS_MAD_DB_MCP_URL || process.env.SKS_MAD_DB_SUPABASE_MCP_URL || '';
|
|
10
|
+
const normalizedMcp = normalizeExplicitMcpUrl(explicitMcpUrl, projectRef || null);
|
|
8
11
|
const target = normalizeTarget(input.target || readOption(args, '--target', '') || process.env.SKS_MAD_DB_TARGET || process.env.SKS_MAD_DB_E2E_TARGET || 'production');
|
|
9
12
|
const allowedSchemas = input.allowedSchemas?.length
|
|
10
13
|
? input.allowedSchemas
|
|
@@ -12,10 +15,16 @@ export async function resolveMadDbTarget(root, input = {}) {
|
|
|
12
15
|
const blockers = [];
|
|
13
16
|
if (!projectRef)
|
|
14
17
|
blockers.push(candidates.length > 1 ? 'mad_db_project_ref_ambiguous' : 'mad_db_project_ref_missing');
|
|
18
|
+
if (normalizedMcp.blocker)
|
|
19
|
+
blockers.push(normalizedMcp.blocker);
|
|
15
20
|
return {
|
|
16
21
|
schema: 'sks.mad-db-target.v1',
|
|
17
22
|
project_ref: projectRef || null,
|
|
18
23
|
project_ref_hash: projectRef ? sha256(projectRef).slice(0, 16) : null,
|
|
24
|
+
mcp_url: normalizedMcp.url,
|
|
25
|
+
mcp_url_hash: normalizedMcp.url ? sha256(normalizedMcp.url).slice(0, 16) : null,
|
|
26
|
+
mcp_url_redacted: normalizedMcp.url ? redactSupabaseUrl(normalizedMcp.url) : null,
|
|
27
|
+
mcp_url_source: normalizedMcp.url ? 'explicit_or_environment' : 'default_generated',
|
|
19
28
|
target_environment: target,
|
|
20
29
|
allowed_schemas: allowedSchemas.length ? allowedSchemas : ['public'],
|
|
21
30
|
source: explicit ? 'explicit_or_environment' : candidates.length === 1 ? 'managed_config_single_candidate' : 'unresolved',
|
|
@@ -23,6 +32,28 @@ export async function resolveMadDbTarget(root, input = {}) {
|
|
|
23
32
|
candidates: candidates.map((candidate) => `${sha256(candidate).slice(0, 8)}:${candidate.slice(0, 2)}...`)
|
|
24
33
|
};
|
|
25
34
|
}
|
|
35
|
+
function normalizeExplicitMcpUrl(value, projectRef) {
|
|
36
|
+
if (!value)
|
|
37
|
+
return { url: null, blocker: null };
|
|
38
|
+
let parsed;
|
|
39
|
+
try {
|
|
40
|
+
parsed = new URL(value);
|
|
41
|
+
}
|
|
42
|
+
catch {
|
|
43
|
+
return { url: null, blocker: 'mad_db_mcp_url_invalid' };
|
|
44
|
+
}
|
|
45
|
+
const isSupabaseRemote = parsed.protocol === 'https:' && parsed.hostname === 'mcp.supabase.com' && parsed.pathname === '/mcp';
|
|
46
|
+
const isLocalMcp = parsed.protocol === 'http:' && /^localhost$|^127\.0\.0\.1$/.test(parsed.hostname) && parsed.pathname.endsWith('/mcp');
|
|
47
|
+
if (!isSupabaseRemote && !isLocalMcp)
|
|
48
|
+
return { url: null, blocker: 'mad_db_mcp_url_untrusted_host' };
|
|
49
|
+
if (parsed.searchParams.get('read_only') === 'true')
|
|
50
|
+
return { url: null, blocker: 'mad_db_mcp_url_read_only_conflict' };
|
|
51
|
+
if (projectRef && !parsed.searchParams.get('project_ref'))
|
|
52
|
+
parsed.searchParams.set('project_ref', projectRef);
|
|
53
|
+
parsed.searchParams.set('features', 'database');
|
|
54
|
+
parsed.searchParams.delete('read_only');
|
|
55
|
+
return { url: parsed.toString(), blocker: null };
|
|
56
|
+
}
|
|
26
57
|
export async function projectRootHash(root) {
|
|
27
58
|
return sha256(path.resolve(root)).slice(0, 24);
|
|
28
59
|
}
|
package/dist/core/retention.js
CHANGED
|
@@ -14,6 +14,7 @@ export const DEFAULT_RETENTION_POLICY = Object.freeze({
|
|
|
14
14
|
run_gc_after_each_cycle: true,
|
|
15
15
|
compact_oversize_missions: false,
|
|
16
16
|
compact_closed_mission_workdirs: true,
|
|
17
|
+
compact_terminal_session_runtime_homes: true,
|
|
17
18
|
prune_disposable_report_logs: false,
|
|
18
19
|
max_wiki_artifacts: 40,
|
|
19
20
|
max_wiki_artifact_age_days: 30,
|
|
@@ -46,7 +47,11 @@ const DISPOSABLE_MISSION_DIRS = Object.freeze([
|
|
|
46
47
|
'tmp',
|
|
47
48
|
'cycles',
|
|
48
49
|
'arenas',
|
|
50
|
+
'sessions',
|
|
51
|
+
'codex-sdk-workers',
|
|
49
52
|
'agents/lanes',
|
|
53
|
+
'agents/sessions',
|
|
54
|
+
'agents/codex-sdk-workers',
|
|
50
55
|
'agents/tmp',
|
|
51
56
|
'agents/worktrees',
|
|
52
57
|
'research/cycles',
|
|
@@ -56,6 +61,10 @@ const DISPOSABLE_MISSION_FILES = Object.freeze([
|
|
|
56
61
|
'agents/agent-intelligent-work-graph.json',
|
|
57
62
|
'agents/agent-intelligent-work-graph-v2.json'
|
|
58
63
|
]);
|
|
64
|
+
const DISPOSABLE_RUNTIME_HOME_DIR_NAMES = Object.freeze([
|
|
65
|
+
'codex-sdk-home',
|
|
66
|
+
'codex-sdk-workers'
|
|
67
|
+
]);
|
|
59
68
|
const MISSION_CLOSE_GATES = Object.freeze([
|
|
60
69
|
'team-gate.json',
|
|
61
70
|
'reflection-gate.json',
|
|
@@ -175,6 +184,15 @@ function proofClosed(proof) {
|
|
|
175
184
|
return false;
|
|
176
185
|
return ['verified', 'verified_partial', 'pass', 'passed'].includes(status);
|
|
177
186
|
}
|
|
187
|
+
function sessionsTerminal(cleanup) {
|
|
188
|
+
if (!cleanup || typeof cleanup !== 'object')
|
|
189
|
+
return false;
|
|
190
|
+
if (cleanup.all_sessions_terminal === true || cleanup.all_sessions_closed === true)
|
|
191
|
+
return true;
|
|
192
|
+
const terminal = Number(cleanup.terminal_session_count);
|
|
193
|
+
const total = Number(cleanup.total_sessions);
|
|
194
|
+
return Number.isFinite(terminal) && Number.isFinite(total) && total > 0 && terminal >= total;
|
|
195
|
+
}
|
|
178
196
|
async function missionClosed(mission, opts = {}) {
|
|
179
197
|
const proof = await readJson(path.join(mission.path, 'completion-proof.json'), null).catch(() => null);
|
|
180
198
|
if (opts.completedMissionId && mission.id === opts.completedMissionId)
|
|
@@ -195,6 +213,19 @@ async function missionClosed(mission, opts = {}) {
|
|
|
195
213
|
}
|
|
196
214
|
return false;
|
|
197
215
|
}
|
|
216
|
+
async function missionSessionsTerminal(mission) {
|
|
217
|
+
const cleanupFiles = [
|
|
218
|
+
'agents/agent-session-cleanup.json',
|
|
219
|
+
'team-session-cleanup.json',
|
|
220
|
+
'agent-session-cleanup.json'
|
|
221
|
+
];
|
|
222
|
+
for (const rel of cleanupFiles) {
|
|
223
|
+
const cleanup = await readJson(path.join(mission.path, rel), null).catch(() => null);
|
|
224
|
+
if (sessionsTerminal(cleanup))
|
|
225
|
+
return true;
|
|
226
|
+
}
|
|
227
|
+
return false;
|
|
228
|
+
}
|
|
198
229
|
async function removePath(action, target, dryRun, actions, extra = {}) {
|
|
199
230
|
const st = await fs.stat(target).catch(() => null);
|
|
200
231
|
if (!st)
|
|
@@ -208,20 +239,58 @@ async function removePath(action, target, dryRun, actions, extra = {}) {
|
|
|
208
239
|
function missionRelative(mission, file) {
|
|
209
240
|
return path.relative(mission.path, file).split(path.sep).join('/');
|
|
210
241
|
}
|
|
211
|
-
function isPreservedSessionPath(rel) {
|
|
212
|
-
return rel.startsWith('sessions/') || rel.startsWith('agents/sessions/');
|
|
213
|
-
}
|
|
214
242
|
async function pruneMissionDisposableLogs(mission, dryRun, actions) {
|
|
215
243
|
const files = await listFilesRecursive(mission.path, { ignore: [], maxFiles: 10000, maxDepth: 8 }).catch(() => []);
|
|
216
244
|
for (const file of files) {
|
|
217
245
|
const rel = missionRelative(mission, file);
|
|
218
|
-
if (isPreservedSessionPath(rel))
|
|
219
|
-
continue;
|
|
220
246
|
if (!DISPOSABLE_LOG_RE.test(rel))
|
|
221
247
|
continue;
|
|
222
248
|
await removePath('remove_closed_mission_raw_log', file, dryRun, actions, { mission: mission.id, reason: 'closed_mission_disposable_log' });
|
|
223
249
|
}
|
|
224
250
|
}
|
|
251
|
+
async function collectRuntimeHomeDirs(dir, depth = 10) {
|
|
252
|
+
if (depth < 0 || !(await exists(dir)))
|
|
253
|
+
return [];
|
|
254
|
+
const out = [];
|
|
255
|
+
const entries = await fs.readdir(dir, { withFileTypes: true }).catch(() => []);
|
|
256
|
+
for (const entry of entries) {
|
|
257
|
+
if (!entry.isDirectory())
|
|
258
|
+
continue;
|
|
259
|
+
const child = path.join(dir, entry.name);
|
|
260
|
+
if (DISPOSABLE_RUNTIME_HOME_DIR_NAMES.includes(entry.name)) {
|
|
261
|
+
out.push(child);
|
|
262
|
+
continue;
|
|
263
|
+
}
|
|
264
|
+
out.push(...await collectRuntimeHomeDirs(child, depth - 1));
|
|
265
|
+
}
|
|
266
|
+
return out;
|
|
267
|
+
}
|
|
268
|
+
async function pruneMissionRuntimeHomeDirs(mission, action, dryRun, actions, reason) {
|
|
269
|
+
const dirs = await collectRuntimeHomeDirs(mission.path);
|
|
270
|
+
for (const dir of dirs) {
|
|
271
|
+
await removePath(action, dir, dryRun, actions, { mission: mission.id, rel: missionRelative(mission, dir), reason });
|
|
272
|
+
}
|
|
273
|
+
}
|
|
274
|
+
async function pruneTerminalSessionRuntimeHomes(root, policy, dryRun, actions, opts = {}) {
|
|
275
|
+
if (policy.compact_terminal_session_runtime_homes === false && opts.compactTerminalSessionRuntimeHomes !== true)
|
|
276
|
+
return;
|
|
277
|
+
const activeId = await activeMissionId(root);
|
|
278
|
+
const targetOnly = Boolean(opts.afterRoute && opts.completedMissionId && opts.sweepClosedMissions !== true);
|
|
279
|
+
const missions = targetOnly
|
|
280
|
+
? [await missionDirById(root, opts.completedMissionId)]
|
|
281
|
+
: await listMissionDirs(root);
|
|
282
|
+
for (const mission of missions.filter(Boolean)) {
|
|
283
|
+
if (await missionClosed(mission, opts))
|
|
284
|
+
continue;
|
|
285
|
+
const active = activeId && mission.id === activeId;
|
|
286
|
+
const activeRouteTarget = Boolean(opts.afterRoute && opts.completedMissionId === mission.id);
|
|
287
|
+
if (active && !activeRouteTarget && opts.allowActiveMissionCleanup !== true)
|
|
288
|
+
continue;
|
|
289
|
+
if (!(await missionSessionsTerminal(mission)))
|
|
290
|
+
continue;
|
|
291
|
+
await pruneMissionRuntimeHomeDirs(mission, 'remove_terminal_session_runtime_home', dryRun, actions, 'terminal_agent_session_runtime_home');
|
|
292
|
+
}
|
|
293
|
+
}
|
|
225
294
|
async function compactClosedMissionWorkdirs(root, policy, dryRun, actions, opts = {}) {
|
|
226
295
|
if (policy.compact_closed_mission_workdirs === false || opts.compactClosedMissionWorkdirs === false)
|
|
227
296
|
return;
|
|
@@ -539,6 +608,8 @@ export async function enforceRetention(root, opts = {}) {
|
|
|
539
608
|
}
|
|
540
609
|
if (shouldCompactClosedMissions)
|
|
541
610
|
await compactClosedMissionWorkdirs(root, policy, dryRun, actions, opts);
|
|
611
|
+
if (fullMissionSweep || opts.compactTerminalSessionRuntimeHomes === true || Boolean(opts.afterRoute && opts.completedMissionId))
|
|
612
|
+
await pruneTerminalSessionRuntimeHomes(root, policy, dryRun, actions, opts);
|
|
542
613
|
if (fullMissionSweep || opts.rotateLargeJsonl === true)
|
|
543
614
|
await rotateLargeJsonl(root, policy, dryRun, actions);
|
|
544
615
|
await pruneDisposableReportLogs(root, policy, dryRun, actions, opts);
|
|
@@ -556,6 +627,7 @@ export async function enforceRetention(root, opts = {}) {
|
|
|
556
627
|
protected_durable_context: DURABLE_RETENTION_CLASSES,
|
|
557
628
|
disposable_mission_dirs: DISPOSABLE_MISSION_DIRS,
|
|
558
629
|
disposable_mission_files: DISPOSABLE_MISSION_FILES,
|
|
630
|
+
disposable_runtime_home_dir_names: DISPOSABLE_RUNTIME_HOME_DIR_NAMES,
|
|
559
631
|
prune_report_logs: Boolean(opts.pruneReportLogs || policy.prune_disposable_report_logs),
|
|
560
632
|
completed_mission_id: opts.completedMissionId || null,
|
|
561
633
|
actions
|
|
@@ -2,6 +2,7 @@ import fsp from 'node:fs/promises';
|
|
|
2
2
|
import path from 'node:path';
|
|
3
3
|
import { ensureDir, exists, globalSksRoot, nowIso, packageRoot, PACKAGE_VERSION, projectRoot, readJson, runProcess, sha256, which, writeJsonAtomic } from '../fsx.js';
|
|
4
4
|
import { MANAGED_ASSET_VERSION } from '../managed-assets/managed-assets-manifest.js';
|
|
5
|
+
import { enforceRetention } from '../retention.js';
|
|
5
6
|
export const UPDATE_MIGRATION_SCHEMA = 'sks.project-migration-receipt.v2';
|
|
6
7
|
export const INSTALLATION_EPOCH_SCHEMA = 'sks.installation-epoch.v1';
|
|
7
8
|
const ALLOWLIST_COMMANDS = new Set([
|
|
@@ -98,6 +99,7 @@ export async function clearPendingUpdateMigration() {
|
|
|
98
99
|
export async function writeProjectUpdateMigrationReceipt(input) {
|
|
99
100
|
const receiptPath = projectUpdateMigrationReceiptPath(input.root);
|
|
100
101
|
const epoch = await ensureInstallationEpoch(input.source);
|
|
102
|
+
const retentionCleanup = await runUpdateRetentionCleanup(input.root, input.source);
|
|
101
103
|
const requiredBlockers = input.blockers || [];
|
|
102
104
|
const optionalWarnings = input.warnings || [];
|
|
103
105
|
const receipt = {
|
|
@@ -113,6 +115,7 @@ export async function writeProjectUpdateMigrationReceipt(input) {
|
|
|
113
115
|
pending_marker_path: installationEpochPath(),
|
|
114
116
|
installation_epoch_path: installationEpochPath(),
|
|
115
117
|
doctor: input.doctor || null,
|
|
118
|
+
retention_cleanup: retentionCleanup,
|
|
116
119
|
update_stages: input.updateStages || [],
|
|
117
120
|
required_blockers: requiredBlockers,
|
|
118
121
|
optional_warnings: optionalWarnings,
|
|
@@ -122,6 +125,71 @@ export async function writeProjectUpdateMigrationReceipt(input) {
|
|
|
122
125
|
await writeJsonAtomic(receiptPath, receipt);
|
|
123
126
|
return receipt;
|
|
124
127
|
}
|
|
128
|
+
export async function runUpdateRetentionCleanup(root, source = 'update-migration') {
|
|
129
|
+
const missionsPath = path.join(root, '.sneakoscope', 'missions');
|
|
130
|
+
const cleanupPath = path.join(root, '.sneakoscope', 'reports', 'retention-cleanup.json');
|
|
131
|
+
const storagePath = path.join(root, '.sneakoscope', 'reports', 'storage.json');
|
|
132
|
+
if (process.env.SKS_UPDATE_RETENTION_CLEANUP === '0') {
|
|
133
|
+
return {
|
|
134
|
+
schema: 'sks.update-retention-cleanup.v1',
|
|
135
|
+
ok: true,
|
|
136
|
+
status: 'skipped',
|
|
137
|
+
root,
|
|
138
|
+
source,
|
|
139
|
+
generated_at: nowIso(),
|
|
140
|
+
action_count: 0,
|
|
141
|
+
cleanup_report_path: null,
|
|
142
|
+
storage_report_path: null,
|
|
143
|
+
reason: 'disabled_by_env'
|
|
144
|
+
};
|
|
145
|
+
}
|
|
146
|
+
if (!(await exists(missionsPath))) {
|
|
147
|
+
return {
|
|
148
|
+
schema: 'sks.update-retention-cleanup.v1',
|
|
149
|
+
ok: true,
|
|
150
|
+
status: 'skipped',
|
|
151
|
+
root,
|
|
152
|
+
source,
|
|
153
|
+
generated_at: nowIso(),
|
|
154
|
+
action_count: 0,
|
|
155
|
+
cleanup_report_path: null,
|
|
156
|
+
storage_report_path: null,
|
|
157
|
+
reason: 'missions_missing'
|
|
158
|
+
};
|
|
159
|
+
}
|
|
160
|
+
try {
|
|
161
|
+
const result = await enforceRetention(root, {
|
|
162
|
+
mode: 'update_migration',
|
|
163
|
+
pruneReportLogs: true,
|
|
164
|
+
policy: { max_tmp_age_hours: 0 }
|
|
165
|
+
});
|
|
166
|
+
return {
|
|
167
|
+
schema: 'sks.update-retention-cleanup.v1',
|
|
168
|
+
ok: true,
|
|
169
|
+
status: 'completed',
|
|
170
|
+
root,
|
|
171
|
+
source,
|
|
172
|
+
generated_at: nowIso(),
|
|
173
|
+
action_count: Array.isArray(result.actions) ? result.actions.length : 0,
|
|
174
|
+
cleanup_report_path: cleanupPath,
|
|
175
|
+
storage_report_path: storagePath
|
|
176
|
+
};
|
|
177
|
+
}
|
|
178
|
+
catch (err) {
|
|
179
|
+
return {
|
|
180
|
+
schema: 'sks.update-retention-cleanup.v1',
|
|
181
|
+
ok: false,
|
|
182
|
+
status: 'failed',
|
|
183
|
+
root,
|
|
184
|
+
source,
|
|
185
|
+
generated_at: nowIso(),
|
|
186
|
+
action_count: 0,
|
|
187
|
+
cleanup_report_path: null,
|
|
188
|
+
storage_report_path: null,
|
|
189
|
+
error: err?.message || String(err)
|
|
190
|
+
};
|
|
191
|
+
}
|
|
192
|
+
}
|
|
125
193
|
export async function ensureCurrentMigrationBeforeCommand(input) {
|
|
126
194
|
const env = input.env || process.env;
|
|
127
195
|
const command = input.command;
|
|
@@ -389,7 +389,7 @@ export async function detectEffectiveSksVersion(options = {}) {
|
|
|
389
389
|
const pathCandidate = candidates.find((candidate) => candidate.source.startsWith('PATH:'))?.version || null;
|
|
390
390
|
const npmGlobalCandidate = candidates.find((candidate) => candidate.source.startsWith('npm-global:'))?.version || null;
|
|
391
391
|
const packageRootCandidate = candidates.find((candidate) => candidate.source === 'packageRoot:package.json')?.version || null;
|
|
392
|
-
const current =
|
|
392
|
+
const current = effectiveInstalledVersion(candidates);
|
|
393
393
|
return {
|
|
394
394
|
current,
|
|
395
395
|
runtime_current: PACKAGE_VERSION,
|
|
@@ -533,6 +533,16 @@ function updateNowError(install, newBinary, newVersionDoctor, migrationCurrent)
|
|
|
533
533
|
return 'project update migration receipt was not current';
|
|
534
534
|
return 'update failed';
|
|
535
535
|
}
|
|
536
|
+
function effectiveInstalledVersion(candidates) {
|
|
537
|
+
const firstBySource = (source) => candidates.find((candidate) => candidate.source === source)?.version || null;
|
|
538
|
+
const firstByPrefix = (prefix) => candidates.find((candidate) => candidate.source.startsWith(prefix))?.version || null;
|
|
539
|
+
return firstBySource('env:SKS_INSTALLED_SKS_VERSION')
|
|
540
|
+
|| firstByPrefix('npm-global:')
|
|
541
|
+
|| firstByPrefix('PATH:')
|
|
542
|
+
|| firstBySource('runtime')
|
|
543
|
+
|| firstBySource('packageRoot:package.json')
|
|
544
|
+
|| highestPackageVersion(candidates.map((candidate) => candidate.version));
|
|
545
|
+
}
|
|
536
546
|
function highestPackageVersion(versions) {
|
|
537
547
|
return versions
|
|
538
548
|
.filter((version) => typeof version === 'string' && version.length > 0)
|
package/dist/core/version.js
CHANGED
|
@@ -1,2 +1,2 @@
|
|
|
1
|
-
export const PACKAGE_VERSION = '4.6.
|
|
1
|
+
export const PACKAGE_VERSION = '4.6.4';
|
|
2
2
|
//# sourceMappingURL=version.js.map
|
|
@@ -0,0 +1,47 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
import fs from 'node:fs/promises';
|
|
3
|
+
import os from 'node:os';
|
|
4
|
+
import path from 'node:path';
|
|
5
|
+
import { createMission } from '../core/mission.js';
|
|
6
|
+
import { classifyMadDbError, madDbRetryGuidance, summarizeMadDbError } from '../core/mad-db/mad-db-executor.js';
|
|
7
|
+
import { createMadDbRuntimeProfile, madDbMcpUrl } from '../core/mad-db/mad-db-runtime-profile.js';
|
|
8
|
+
import { resolveMadDbTarget } from '../core/mad-db/mad-db-target.js';
|
|
9
|
+
import { assertGate, emitGate } from './sks-1-18-gate-lib.js';
|
|
10
|
+
const timeoutSummary = summarizeMadDbError(new Error('failed to connect to postgres: dial tcp 1.2.3.4:6543: i/o timeout token=secret-token'));
|
|
11
|
+
assertGate(classifyMadDbError(timeoutSummary) === 'supabase_sql_plane_timeout', 'timeout errors must be classified as SQL-plane timeout', { timeoutSummary });
|
|
12
|
+
assertGate(!timeoutSummary.includes('secret-token'), 'error summary must redact token query values', { timeoutSummary });
|
|
13
|
+
assertGate(madDbRetryGuidance('supabase_sql_plane_timeout').includes('--db-url'), 'timeout guidance should mention explicit db-url fallback evidence from Supabase CLI docs');
|
|
14
|
+
const readOnlyKind = classifyMadDbError('MCP server denied apply_migration because this connection is read only');
|
|
15
|
+
assertGate(readOnlyKind === 'supabase_mcp_read_only_transport', 'read-only transport errors must be separated from SQL-plane connectivity', { readOnlyKind });
|
|
16
|
+
const root = await fs.mkdtemp(path.join(os.tmpdir(), 'sks-mad-db-transport-'));
|
|
17
|
+
await fs.mkdir(path.join(root, '.codex'), { recursive: true });
|
|
18
|
+
await fs.writeFile(path.join(root, '.codex', 'config.toml'), '[mcp_servers.supabase]\nurl = "https://mcp.supabase.com/mcp?project_ref=localref&read_only=true&features=database,docs"\n', 'utf8');
|
|
19
|
+
const target = await resolveMadDbTarget(root, {
|
|
20
|
+
args: ['--project-ref', 'explicitref', '--mcp-url', 'https://mcp.supabase.com/mcp?features=docs']
|
|
21
|
+
});
|
|
22
|
+
assertGate(target.blockers.length === 0, 'explicit trusted MCP URL without read_only should be accepted', target);
|
|
23
|
+
assertGate(target.mcp_url?.includes('project_ref=explicitref'), 'explicit MCP URL should inherit project_ref when omitted', target);
|
|
24
|
+
assertGate(target.mcp_url?.includes('features=database'), 'explicit MCP URL should force database feature for MadDB SQL-plane execution', target);
|
|
25
|
+
assertGate(!target.mcp_url?.includes('read_only=true'), 'active MadDB explicit MCP URL must not remain read-only', target);
|
|
26
|
+
const conflict = await resolveMadDbTarget(root, {
|
|
27
|
+
args: ['--project-ref', 'explicitref', '--mcp-url', 'https://mcp.supabase.com/mcp?read_only=true']
|
|
28
|
+
});
|
|
29
|
+
assertGate(conflict.blockers.includes('mad_db_mcp_url_read_only_conflict'), 'read-only explicit MCP URL must be rejected in active MadDB', conflict);
|
|
30
|
+
const mission = await createMission(root, { mode: 'mad-db', prompt: 'transport diagnostics fixture' });
|
|
31
|
+
const profile = await createMadDbRuntimeProfile({
|
|
32
|
+
root,
|
|
33
|
+
missionId: mission.id,
|
|
34
|
+
cycleId: 'cycle-fixture',
|
|
35
|
+
projectRef: 'explicitref',
|
|
36
|
+
runtimeSessionId: 'session-fixture',
|
|
37
|
+
mcpUrl: target.mcp_url
|
|
38
|
+
});
|
|
39
|
+
assertGate(profile.server_url_source === 'explicit_mcp_url', 'runtime profile must record explicit MCP URL source', profile);
|
|
40
|
+
assertGate(profile.server_url === target.mcp_url, 'runtime profile must use normalized explicit MCP URL', { profile, target });
|
|
41
|
+
assertGate(madDbMcpUrl('abc123') === 'https://mcp.supabase.com/mcp?project_ref=abc123&features=database', 'generated MCP URL should stay project scoped and database-feature scoped');
|
|
42
|
+
emitGate('mad-db:supabase-transport-diagnostics', {
|
|
43
|
+
timeout_kind: classifyMadDbError(timeoutSummary),
|
|
44
|
+
read_only_kind: readOnlyKind,
|
|
45
|
+
mcp_url_source: profile.server_url_source
|
|
46
|
+
});
|
|
47
|
+
//# sourceMappingURL=mad-db-supabase-transport-diagnostics-check.js.map
|
|
@@ -11,6 +11,7 @@ const helpers = readText('src/cli/install-helpers.ts');
|
|
|
11
11
|
assertGate(helpers.includes('SKS_POSTINSTALL_AUTO_INSTALL_CLI_TOOLS'), 'postinstall cli tool install must be gated behind SKS_POSTINSTALL_AUTO_INSTALL_CLI_TOOLS');
|
|
12
12
|
// Killing a third-party app's processes is opt-in only.
|
|
13
13
|
assertGate(helpers.includes("SKS_POSTINSTALL_RECONCILE_APP_PROCESSES === '1'"), 'postinstall process reconciliation must be gated behind SKS_POSTINSTALL_RECONCILE_APP_PROCESSES');
|
|
14
|
+
assertGate(helpers.includes('runPostinstallProjectRetentionCleanup') && helpers.includes('SKS_POSTINSTALL_RETENTION_CLEANUP'), 'postinstall mission retention cleanup must be project-scoped and disableable');
|
|
14
15
|
// Config writes are gated (unparseable preserved / unsafe rewrite skipped) and backed up.
|
|
15
16
|
assertGate(helpers.includes('unparseable_config_preserved'), 'postinstall must preserve unparseable config');
|
|
16
17
|
assertGate(helpers.includes('skipped_unsafe_rewrite'), 'postinstall must skip unsafe config rewrites');
|
|
@@ -38,11 +38,12 @@ try {
|
|
|
38
38
|
'.sneakoscope/missions/M-old/completion-proof.json',
|
|
39
39
|
'.sneakoscope/missions/M-old/trust-report.json',
|
|
40
40
|
'.sneakoscope/missions/M-old/reflection.md',
|
|
41
|
-
'.sneakoscope/missions/M-done/sessions/terminal-transcript.log',
|
|
42
|
-
'.sneakoscope/missions/M-done/agents/sessions/session-1/terminal-transcript.log',
|
|
43
41
|
'.sneakoscope/missions/M-active/team-inbox/active.md',
|
|
42
|
+
'.sneakoscope/missions/M-active/agents/sessions/session-1/gen-1/worker/codex-sdk-home/codex/cache.bin',
|
|
44
43
|
'.sneakoscope/missions/M-blocked/team-inbox/blocked.md',
|
|
45
|
-
'.sneakoscope/missions/M-blocked/scout.stderr.log'
|
|
44
|
+
'.sneakoscope/missions/M-blocked/scout.stderr.log',
|
|
45
|
+
'.sneakoscope/missions/M-blocked-terminal/team-inbox/blocked.md',
|
|
46
|
+
'.sneakoscope/missions/M-blocked-terminal/agents/sessions/session-1/gen-1/worker/worker-result.json'
|
|
46
47
|
]) {
|
|
47
48
|
assertGate(exists(path.join(applyRoot, rel)), `durable or active artifact was removed: ${rel}`, { rel, actions: applied.actions });
|
|
48
49
|
}
|
|
@@ -51,10 +52,15 @@ try {
|
|
|
51
52
|
'.sneakoscope/missions/M-done/team-inbox/worker.md',
|
|
52
53
|
'.sneakoscope/missions/M-done/bus/event.jsonl',
|
|
53
54
|
'.sneakoscope/missions/M-done/agents/lanes/lane-1.json',
|
|
55
|
+
'.sneakoscope/missions/M-done/sessions/terminal-transcript.log',
|
|
56
|
+
'.sneakoscope/missions/M-done/agents/sessions/session-1/terminal-transcript.log',
|
|
57
|
+
'.sneakoscope/missions/M-done/agents/sessions/session-1/gen-1/worker/codex-sdk-home/codex/cache.bin',
|
|
54
58
|
'.sneakoscope/missions/M-done/scout.stdout.log',
|
|
55
59
|
'.sneakoscope/missions/M-done/scout.stderr.log',
|
|
56
60
|
'.sneakoscope/missions/M-old/team-inbox/worker.md',
|
|
61
|
+
'.sneakoscope/missions/M-old/agents/sessions/session-1/gen-1/worker/codex-sdk-home/codex/cache.bin',
|
|
57
62
|
'.sneakoscope/missions/M-old/scout.stdout.log',
|
|
63
|
+
'.sneakoscope/missions/M-blocked-terminal/agents/sessions/session-1/gen-1/worker/codex-sdk-home/codex/cache.bin',
|
|
58
64
|
'.sneakoscope/reports/release-parallel-logs/build.stdout.log'
|
|
59
65
|
]) {
|
|
60
66
|
assertGate(!exists(path.join(applyRoot, rel)), `disposable artifact survived cleanup: ${rel}`, { rel, actions: applied.actions });
|
|
@@ -64,6 +70,7 @@ try {
|
|
|
64
70
|
for (const kind of ['remove_tmp', 'remove_closed_mission_raw_log', 'remove_disposable_report_log_dir']) {
|
|
65
71
|
assertGate(actionKinds.has(kind), `retention cleanup did not report action kind: ${kind}`, { actions: applied.actions });
|
|
66
72
|
}
|
|
73
|
+
assertGate(actionKinds.has('remove_terminal_session_runtime_home'), 'retention cleanup did not remove terminal session runtime homes', { actions: applied.actions });
|
|
67
74
|
assertGate(actionKinds.has('remove_closed_mission_workdir') || actionKinds.has('remove_old_mission_workdir'), 'retention cleanup did not report mission workdir cleanup', { actions: applied.actions });
|
|
68
75
|
assertGate(actionKinds.has('retain_mission_durable_context'), 'retention cleanup did not preserve old durable mission context', { actions: applied.actions });
|
|
69
76
|
assertGate(dry.actions.length >= applied.actions.length, 'dry-run should plan cleanup actions without deleting files', { applied: applied.actions.length, dry: dry.actions.length });
|
|
@@ -95,6 +102,7 @@ async function writeFixture(projectRoot) {
|
|
|
95
102
|
await writeMission(projectRoot, 'M-active', false);
|
|
96
103
|
await writeOldDurableMission(projectRoot);
|
|
97
104
|
await writeBlockedMission(projectRoot);
|
|
105
|
+
await writeTerminalBlockedMission(projectRoot);
|
|
98
106
|
await writeText(path.join(projectRoot, '.sneakoscope', 'reports', 'release-parallel-logs', 'build.stdout.log'), 'summarized release log\n');
|
|
99
107
|
}
|
|
100
108
|
async function writeMission(projectRoot, missionId, closed) {
|
|
@@ -110,14 +118,16 @@ async function writeMission(projectRoot, missionId, closed) {
|
|
|
110
118
|
await write(path.join(dir, 'team-gate.json'), { passed: true });
|
|
111
119
|
await write(path.join(dir, 'team-session-cleanup.json'), { passed: true, all_sessions_closed: true });
|
|
112
120
|
await write(path.join(dir, 'agents', 'agent-proof-evidence.json'), { ok: true, all_sessions_closed: true });
|
|
113
|
-
await writeText(path.join(dir, 'sessions', 'terminal-transcript.log'), 'transcript
|
|
114
|
-
await writeText(path.join(dir, 'agents', 'sessions', 'session-1', 'terminal-transcript.log'), 'agent transcript
|
|
121
|
+
await writeText(path.join(dir, 'sessions', 'terminal-transcript.log'), 'transcript is disposable after close\n');
|
|
122
|
+
await writeText(path.join(dir, 'agents', 'sessions', 'session-1', 'terminal-transcript.log'), 'agent transcript is disposable after close\n');
|
|
123
|
+
await writeText(path.join(dir, 'agents', 'sessions', 'session-1', 'gen-1', 'worker', 'codex-sdk-home', 'codex', 'cache.bin'), 'large sdk cache\n');
|
|
115
124
|
await writeText(path.join(dir, 'scout.stdout.log'), 'raw stdout\n');
|
|
116
125
|
await writeText(path.join(dir, 'scout.stderr.log'), 'raw stderr\n');
|
|
117
126
|
await writeText(path.join(dir, 'team-inbox', 'worker.md'), 'temporary inbox\n');
|
|
118
127
|
}
|
|
119
128
|
else {
|
|
120
129
|
await writeText(path.join(dir, 'team-inbox', 'active.md'), 'active mission scratch stays\n');
|
|
130
|
+
await writeText(path.join(dir, 'agents', 'sessions', 'session-1', 'gen-1', 'worker', 'codex-sdk-home', 'codex', 'cache.bin'), 'active sdk cache stays\n');
|
|
121
131
|
}
|
|
122
132
|
await writeText(path.join(dir, 'bus', 'event.jsonl'), '{"event":"temporary"}\n');
|
|
123
133
|
await writeText(path.join(dir, 'agents', 'lanes', 'lane-1.json'), '{"lane":"temporary"}\n');
|
|
@@ -129,6 +139,7 @@ async function writeOldDurableMission(projectRoot) {
|
|
|
129
139
|
await write(path.join(dir, 'evidence-index.json'), { evidence: [] });
|
|
130
140
|
await writeText(path.join(dir, 'reflection.md'), '# old retained reflection\n');
|
|
131
141
|
await writeText(path.join(dir, 'team-inbox', 'worker.md'), 'old scratch\n');
|
|
142
|
+
await writeText(path.join(dir, 'agents', 'sessions', 'session-1', 'gen-1', 'worker', 'codex-sdk-home', 'codex', 'cache.bin'), 'old sdk cache\n');
|
|
132
143
|
await writeText(path.join(dir, 'scout.stdout.log'), 'old raw log\n');
|
|
133
144
|
await old(dir);
|
|
134
145
|
}
|
|
@@ -138,6 +149,14 @@ async function writeBlockedMission(projectRoot) {
|
|
|
138
149
|
await writeText(path.join(dir, 'team-inbox', 'blocked.md'), 'diagnostic scratch\n');
|
|
139
150
|
await writeText(path.join(dir, 'scout.stderr.log'), 'diagnostic raw log\n');
|
|
140
151
|
}
|
|
152
|
+
async function writeTerminalBlockedMission(projectRoot) {
|
|
153
|
+
const dir = path.join(projectRoot, '.sneakoscope', 'missions', 'M-blocked-terminal');
|
|
154
|
+
await write(path.join(dir, 'completion-proof.json'), { schema: 'sks.completion-proof.v1', status: 'blocked', blockers: ['fixture_blocker'] });
|
|
155
|
+
await write(path.join(dir, 'agents', 'agent-session-cleanup.json'), { all_sessions_terminal: true, terminal_session_count: 1, total_sessions: 1 });
|
|
156
|
+
await writeText(path.join(dir, 'team-inbox', 'blocked.md'), 'diagnostic scratch\n');
|
|
157
|
+
await writeText(path.join(dir, 'agents', 'sessions', 'session-1', 'gen-1', 'worker', 'worker-result.json'), '{"status":"blocked"}\n');
|
|
158
|
+
await writeText(path.join(dir, 'agents', 'sessions', 'session-1', 'gen-1', 'worker', 'codex-sdk-home', 'codex', 'cache.bin'), 'terminal sdk cache\n');
|
|
159
|
+
}
|
|
141
160
|
async function write(file, data) {
|
|
142
161
|
await writeText(file, `${JSON.stringify(data, null, 2)}\n`);
|
|
143
162
|
}
|
|
@@ -8,6 +8,7 @@ assertGate(helper.includes('INSTALLATION_EPOCH_SCHEMA') && helper.includes('inst
|
|
|
8
8
|
assertGate(helper.includes('projectUpdateMigrationReceiptPath'), 'migration helper must keep a project receipt');
|
|
9
9
|
assertGate(helper.includes("'--profile', 'migration'") && helper.includes("'--machine-only'") && helper.includes("'--report-file'"), 'first normal command must repair through package-local migration Doctor machine report before continuing');
|
|
10
10
|
assertGate(helper.includes('isProjectReceiptCurrentForEpoch'), 'project receipts must be compared against the current installation epoch');
|
|
11
|
+
assertGate(helper.includes('runUpdateRetentionCleanup') && helper.includes('retention_cleanup'), 'project update migration receipt must run mission retention cleanup and record its receipt');
|
|
11
12
|
assertGate(helper.includes('clearPendingUpdateMigration') && helper.includes('one project must not consume global migration state'), 'legacy clear helper must preserve the persistent epoch contract');
|
|
12
13
|
assertGate(scriptContains('update:first-command-migration', 'update-first-command-migration-check.js'), 'package script must expose first command migration gate');
|
|
13
14
|
emitGate('update:first-command-migration');
|
package/package.json
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "sneakoscope",
|
|
3
3
|
"displayName": "ㅅㅋㅅ",
|
|
4
|
-
"version": "4.6.
|
|
4
|
+
"version": "4.6.4",
|
|
5
5
|
"description": "Sneakoscope Codex: fast proof-first Codex trust layer with image-based Voxel TriWiki.",
|
|
6
6
|
"type": "module",
|
|
7
7
|
"homepage": "https://github.com/mandarange/Sneakoscope-Codex#readme",
|
|
@@ -745,6 +745,7 @@
|
|
|
745
745
|
"mad-db:runtime-profile": "node ./dist/scripts/mad-db-runtime-profile-lifecycle-check.js",
|
|
746
746
|
"mad-db:skill-policy": "node ./dist/scripts/mad-db-skill-policy-snapshot-check.js",
|
|
747
747
|
"mad-db:policy-v2": "node ./dist/scripts/mad-db-policy-v2-check.js",
|
|
748
|
+
"mad-db:supabase-transport-diagnostics": "node ./dist/scripts/mad-db-supabase-transport-diagnostics-check.js",
|
|
748
749
|
"release:speed-summary": "node ./dist/scripts/release-speed-summary-check.js",
|
|
749
750
|
"release:speed-summary:check": "node ./dist/scripts/release-speed-summary-check.js",
|
|
750
751
|
"naruto:ssot-routing": "node ./dist/scripts/naruto-ssot-routing-check.js",
|
|
@@ -778,7 +779,7 @@
|
|
|
778
779
|
"mad-db:lifecycle-hook-decision": "node ./dist/scripts/mad-db-lifecycle-hook-decision-check.js",
|
|
779
780
|
"mad-db:mcp-result-lifecycle": "node ./dist/scripts/mad-db-mcp-result-lifecycle-check.js",
|
|
780
781
|
"mad-db:operation-lifecycle-blackbox": "node ./dist/scripts/mad-db-operation-lifecycle-blackbox.js",
|
|
781
|
-
"mad-db:unit": "npm run mad-db:capability && npm run mad-db:command && npm run mad-db:mad-command && npm run mad-db:priority-resolver && npm run mad-db:ledger && npm run mad-db:one-cycle-consumption && npm run mad-db:safety-conflict-matrix && npm run mad-db:one-cycle-bounded && npm run mad-db:operation-lifecycle-ledger && npm run mad-db:route-identity && npm run mad-db:hook-idempotency && npm run mad-db:direct-apply-migration-hook && npm run mad-db:parallel-lifecycle && npm run mad-db:runtime-profile && npm run mad-db:skill-policy && npm run mad-db:policy-v2 && npm run mad-db:lifecycle-hook-decision && npm run mad-db:mcp-result-lifecycle && npm run mad-db:operation-lifecycle-blackbox",
|
|
782
|
+
"mad-db:unit": "npm run mad-db:capability && npm run mad-db:command && npm run mad-db:mad-command && npm run mad-db:priority-resolver && npm run mad-db:ledger && npm run mad-db:one-cycle-consumption && npm run mad-db:safety-conflict-matrix && npm run mad-db:one-cycle-bounded && npm run mad-db:operation-lifecycle-ledger && npm run mad-db:route-identity && npm run mad-db:hook-idempotency && npm run mad-db:direct-apply-migration-hook && npm run mad-db:parallel-lifecycle && npm run mad-db:runtime-profile && npm run mad-db:skill-policy && npm run mad-db:policy-v2 && npm run mad-db:supabase-transport-diagnostics && npm run mad-db:lifecycle-hook-decision && npm run mad-db:mcp-result-lifecycle && npm run mad-db:operation-lifecycle-blackbox",
|
|
782
783
|
"mad-db:real-e2e": "node ./dist/scripts/mad-db-real-supabase-e2e.js --require-real",
|
|
783
784
|
"mad-db:all": "npm run build && npm run mad-db:unit",
|
|
784
785
|
"mad-db:release": "npm run mad-db:all && npm run mad-db:real-e2e",
|