@wipcomputer/wip-ldm-os 0.4.85-alpha.24 → 0.4.85-alpha.26
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 +1 -0
- package/SKILL.md +9 -2
- package/bin/ldm.js +103 -33
- package/package.json +3 -2
- package/scripts/test-install-prompt-policy.mjs +9 -3
- package/scripts/test-ldm-status-concurrency.mjs +88 -0
- package/scripts/test-ldm-status-timeout.mjs +1 -1
- package/shared/templates/install-prompt.md +1 -0
package/README.md
CHANGED
|
@@ -24,6 +24,7 @@ Read https://wip.computer/install/wip-ldm-os.txt
|
|
|
24
24
|
|
|
25
25
|
Use the install document and live local checks as the source of truth.
|
|
26
26
|
Do not search memory or prior notes for this install. Do not pre-load context from `MEMORY.md`, `crystal_search`, local skill dev guides, or other local memory before fetching the install document.
|
|
27
|
+
Do not run GitHub commands during this install flow. Do not run or request approval for `gh release`, `gh api`, or `gh search`. If release notes are not available from local or npm metadata, say that and do not fetch them from GitHub.
|
|
27
28
|
|
|
28
29
|
Check if LDM OS is installed (`which ldm && ldm --version`). Branch.
|
|
29
30
|
|
package/SKILL.md
CHANGED
|
@@ -33,7 +33,7 @@ Memory policy for install flows: do not consult `MEMORY.md`, do not run `crystal
|
|
|
33
33
|
|
|
34
34
|
Use this install document and live local checks as the source of truth. Do not search memory or prior notes for this install.
|
|
35
35
|
|
|
36
|
-
Do not run GitHub
|
|
36
|
+
Do not run GitHub commands during the install-state flow. Do not run or request approval for `gh release list`, `gh release view`, `gh api repos/*`, `gh search`, or any other GitHub query unless the user explicitly asks for release notes. Install-state answers should come from local commands, `ldm status`, and npm package metadata.
|
|
37
37
|
|
|
38
38
|
## Step 1: Check if installed
|
|
39
39
|
|
|
@@ -70,7 +70,14 @@ Always a table. Every component with an update gets its own row.
|
|
|
70
70
|
|
|
71
71
|
**Do NOT skip this step.**
|
|
72
72
|
|
|
73
|
-
Use the output of `ldm status`, installed package metadata, and npm metadata. Do not use GitHub
|
|
73
|
+
Use the output of `ldm status`, installed package metadata, and npm metadata. Do not use GitHub commands here.
|
|
74
|
+
|
|
75
|
+
If npm metadata for a package does not include release notes:
|
|
76
|
+
- Show the version difference, for example `wip-repos v1.9.69 -> v1.9.70`.
|
|
77
|
+
- Say "release notes not available from local metadata."
|
|
78
|
+
- Do not fetch from GitHub. Do not run `gh release`, `gh api`, `gh search`, or any other GitHub query.
|
|
79
|
+
- Do not infer release-note content from package descriptions, commit messages, or repo READMEs.
|
|
80
|
+
- If the user wants release-note details, wait for an explicit request in plain language. An approval dialog is not a user request.
|
|
74
81
|
|
|
75
82
|
Translate available update information to user language. Every bullet answers "what changed for ME?" If the status output does not include enough detail for a component, say that clearly and do not invent release notes.
|
|
76
83
|
|
package/bin/ldm.js
CHANGED
|
@@ -22,7 +22,7 @@
|
|
|
22
22
|
|
|
23
23
|
import { existsSync, readFileSync, writeFileSync, mkdirSync, readdirSync, cpSync, chmodSync, unlinkSync, readlinkSync, renameSync, statSync, lstatSync, symlinkSync } from 'node:fs';
|
|
24
24
|
import { join, basename, resolve, dirname } from 'node:path';
|
|
25
|
-
import {
|
|
25
|
+
import { execFile, execSync, spawnSync } from 'node:child_process';
|
|
26
26
|
import { fileURLToPath } from 'node:url';
|
|
27
27
|
|
|
28
28
|
const __filename = fileURLToPath(import.meta.url);
|
|
@@ -3284,19 +3284,34 @@ async function cmdDoctor() {
|
|
|
3284
3284
|
|
|
3285
3285
|
const STATUS_NPM_TIMEOUT_MS = parsePositiveInt(process.env.LDM_STATUS_NPM_TIMEOUT_MS, 5000);
|
|
3286
3286
|
const STATUS_TOTAL_BUDGET_MS = parsePositiveInt(process.env.LDM_STATUS_TOTAL_BUDGET_MS, 60000);
|
|
3287
|
+
const STATUS_NPM_CONCURRENCY = parsePositiveInt(process.env.LDM_STATUS_NPM_CONCURRENCY, 8);
|
|
3287
3288
|
|
|
3288
3289
|
function npmViewVersionForStatus(pkg, timeoutMs) {
|
|
3289
|
-
return
|
|
3290
|
-
|
|
3291
|
-
|
|
3292
|
-
|
|
3293
|
-
|
|
3290
|
+
return new Promise((resolvePromise, rejectPromise) => {
|
|
3291
|
+
execFile('npm', ['view', pkg, 'version'], {
|
|
3292
|
+
encoding: 'utf8',
|
|
3293
|
+
timeout: timeoutMs,
|
|
3294
|
+
maxBuffer: 1024 * 1024,
|
|
3295
|
+
}, (error, stdout) => {
|
|
3296
|
+
if (error) {
|
|
3297
|
+
rejectPromise(error);
|
|
3298
|
+
return;
|
|
3299
|
+
}
|
|
3300
|
+
resolvePromise(String(stdout || '').trim());
|
|
3301
|
+
});
|
|
3302
|
+
});
|
|
3294
3303
|
}
|
|
3295
3304
|
|
|
3296
3305
|
function remainingStatusBudgetMs(startedAt) {
|
|
3297
3306
|
return Math.max(0, STATUS_TOTAL_BUDGET_MS - (Date.now() - startedAt));
|
|
3298
3307
|
}
|
|
3299
3308
|
|
|
3309
|
+
function formatStatusElapsed(ms) {
|
|
3310
|
+
if (!Number.isFinite(ms) || ms <= 0) return '0ms';
|
|
3311
|
+
if (ms < 1000) return `${Math.round(ms)}ms`;
|
|
3312
|
+
return `${(ms / 1000).toFixed(1)}s`;
|
|
3313
|
+
}
|
|
3314
|
+
|
|
3300
3315
|
function classifyStatusCheckError(error) {
|
|
3301
3316
|
if (error?.signal === 'SIGTERM' || error?.code === 'ETIMEDOUT' || String(error?.message || '').includes('ETIMEDOUT')) {
|
|
3302
3317
|
return 'timeout';
|
|
@@ -3304,7 +3319,48 @@ function classifyStatusCheckError(error) {
|
|
|
3304
3319
|
return 'unavailable';
|
|
3305
3320
|
}
|
|
3306
3321
|
|
|
3307
|
-
function
|
|
3322
|
+
async function runStatusProbesWithConcurrency(items, concurrency, statusStartedAt) {
|
|
3323
|
+
if (items.length === 0) return [];
|
|
3324
|
+
|
|
3325
|
+
const results = new Array(items.length);
|
|
3326
|
+
const workerCount = Math.max(1, Math.min(concurrency, items.length));
|
|
3327
|
+
let nextIndex = 0;
|
|
3328
|
+
|
|
3329
|
+
async function worker() {
|
|
3330
|
+
while (nextIndex < items.length) {
|
|
3331
|
+
const index = nextIndex;
|
|
3332
|
+
nextIndex += 1;
|
|
3333
|
+
const item = items[index];
|
|
3334
|
+
const remaining = remainingStatusBudgetMs(statusStartedAt);
|
|
3335
|
+
|
|
3336
|
+
if (remaining <= 0) {
|
|
3337
|
+
results[index] = { ...item, status: 'skipped', reason: 'budget', elapsedMs: 0 };
|
|
3338
|
+
continue;
|
|
3339
|
+
}
|
|
3340
|
+
|
|
3341
|
+
const timeout = Math.min(STATUS_NPM_TIMEOUT_MS, remaining);
|
|
3342
|
+
const probeStartedAt = Date.now();
|
|
3343
|
+
console.log(` ${item.name}: checking npm`);
|
|
3344
|
+
|
|
3345
|
+
try {
|
|
3346
|
+
const latest = await npmViewVersionForStatus(item.npm, timeout);
|
|
3347
|
+
results[index] = { ...item, status: 'ok', latest, elapsedMs: Date.now() - probeStartedAt };
|
|
3348
|
+
} catch (error) {
|
|
3349
|
+
results[index] = {
|
|
3350
|
+
...item,
|
|
3351
|
+
status: 'skipped',
|
|
3352
|
+
reason: classifyStatusCheckError(error),
|
|
3353
|
+
elapsedMs: Date.now() - probeStartedAt,
|
|
3354
|
+
};
|
|
3355
|
+
}
|
|
3356
|
+
}
|
|
3357
|
+
}
|
|
3358
|
+
|
|
3359
|
+
await Promise.all(Array.from({ length: workerCount }, () => worker()));
|
|
3360
|
+
return results;
|
|
3361
|
+
}
|
|
3362
|
+
|
|
3363
|
+
async function cmdStatus() {
|
|
3308
3364
|
const version = readJSON(VERSION_PATH);
|
|
3309
3365
|
const registry = readJSON(REGISTRY_PATH);
|
|
3310
3366
|
const extCount = Object.keys(registry?.extensions || {}).length;
|
|
@@ -3338,18 +3394,14 @@ function cmdStatus() {
|
|
|
3338
3394
|
console.log('');
|
|
3339
3395
|
console.log(' Checking updates:');
|
|
3340
3396
|
|
|
3341
|
-
let cliUpdate = null;
|
|
3342
|
-
try {
|
|
3343
|
-
const timeout = Math.min(STATUS_NPM_TIMEOUT_MS, remainingStatusBudgetMs(statusStartedAt));
|
|
3344
|
-
if (timeout <= 0) throw new Error('status update-check budget exhausted');
|
|
3345
|
-
console.log(' ldm cli: checking npm');
|
|
3346
|
-
const latest = npmViewVersionForStatus('@wipcomputer/wip-ldm-os', timeout);
|
|
3347
|
-
if (latest && semverNewer(latest, PKG_VERSION)) cliUpdate = latest;
|
|
3348
|
-
} catch (error) {
|
|
3349
|
-
skipped.push({ name: 'ldm cli', npm: '@wipcomputer/wip-ldm-os', reason: classifyStatusCheckError(error) });
|
|
3350
|
-
}
|
|
3351
|
-
|
|
3352
3397
|
// Check extensions against npm using registry source info (#262)
|
|
3398
|
+
const probeItems = [{
|
|
3399
|
+
kind: 'cli',
|
|
3400
|
+
name: 'ldm cli',
|
|
3401
|
+
npm: '@wipcomputer/wip-ldm-os',
|
|
3402
|
+
current: PKG_VERSION,
|
|
3403
|
+
}];
|
|
3404
|
+
|
|
3353
3405
|
const updates = [];
|
|
3354
3406
|
for (const [name, info] of Object.entries(registry?.extensions || {})) {
|
|
3355
3407
|
// Use registry source.npm (v2) or fall back to extension's package.json
|
|
@@ -3362,19 +3414,37 @@ function cmdStatus() {
|
|
|
3362
3414
|
if (!npmPkg) continue;
|
|
3363
3415
|
const currentVersion = info?.installed?.version || info.version;
|
|
3364
3416
|
if (!currentVersion) continue;
|
|
3365
|
-
|
|
3366
|
-
|
|
3367
|
-
|
|
3368
|
-
|
|
3369
|
-
|
|
3370
|
-
|
|
3371
|
-
|
|
3372
|
-
|
|
3373
|
-
|
|
3374
|
-
|
|
3375
|
-
|
|
3376
|
-
|
|
3377
|
-
|
|
3417
|
+
probeItems.push({
|
|
3418
|
+
kind: 'extension',
|
|
3419
|
+
name,
|
|
3420
|
+
npm: npmPkg,
|
|
3421
|
+
current: currentVersion,
|
|
3422
|
+
});
|
|
3423
|
+
}
|
|
3424
|
+
|
|
3425
|
+
let cliUpdate = null;
|
|
3426
|
+
const probeResults = await runStatusProbesWithConcurrency(probeItems, STATUS_NPM_CONCURRENCY, statusStartedAt);
|
|
3427
|
+
for (const result of probeResults) {
|
|
3428
|
+
if (!result) continue;
|
|
3429
|
+
if (result.status === 'skipped') {
|
|
3430
|
+
skipped.push({
|
|
3431
|
+
name: result.name,
|
|
3432
|
+
npm: result.npm,
|
|
3433
|
+
reason: result.reason,
|
|
3434
|
+
elapsedMs: result.elapsedMs,
|
|
3435
|
+
});
|
|
3436
|
+
continue;
|
|
3437
|
+
}
|
|
3438
|
+
|
|
3439
|
+
if (result.kind === 'cli') {
|
|
3440
|
+
if (result.latest && semverNewer(result.latest, PKG_VERSION)) cliUpdate = result.latest;
|
|
3441
|
+
} else if (result.latest && semverNewer(result.latest, result.current)) {
|
|
3442
|
+
updates.push({
|
|
3443
|
+
name: result.name,
|
|
3444
|
+
current: result.current,
|
|
3445
|
+
latest: result.latest,
|
|
3446
|
+
npm: result.npm,
|
|
3447
|
+
});
|
|
3378
3448
|
}
|
|
3379
3449
|
}
|
|
3380
3450
|
|
|
@@ -3407,7 +3477,7 @@ function cmdStatus() {
|
|
|
3407
3477
|
console.log('');
|
|
3408
3478
|
console.log(' Update checks skipped:');
|
|
3409
3479
|
for (const item of skipped) {
|
|
3410
|
-
console.log(` ${item.name}: [${item.reason}] ${item.npm}`);
|
|
3480
|
+
console.log(` ${item.name}: [${item.reason} ${formatStatusElapsed(item.elapsedMs)}] ${item.npm}`);
|
|
3411
3481
|
}
|
|
3412
3482
|
}
|
|
3413
3483
|
|
|
@@ -4866,7 +4936,7 @@ async function main() {
|
|
|
4866
4936
|
await cmdDoctor();
|
|
4867
4937
|
break;
|
|
4868
4938
|
case 'status':
|
|
4869
|
-
cmdStatus();
|
|
4939
|
+
await cmdStatus();
|
|
4870
4940
|
break;
|
|
4871
4941
|
case 'sessions':
|
|
4872
4942
|
await cmdSessions();
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@wipcomputer/wip-ldm-os",
|
|
3
|
-
"version": "0.4.85-alpha.
|
|
3
|
+
"version": "0.4.85-alpha.26",
|
|
4
4
|
"type": "module",
|
|
5
5
|
"description": "LDM OS: identity, memory, and sovereignty infrastructure for AI agents",
|
|
6
6
|
"engines": {
|
|
@@ -18,11 +18,12 @@
|
|
|
18
18
|
"scripts": {
|
|
19
19
|
"build:bridge": "cd src/bridge && npm install && npx tsup core.ts mcp-server.ts cli.ts openclaw.ts --format esm --dts --clean --outDir ../../dist/bridge",
|
|
20
20
|
"build": "npm run build:bridge",
|
|
21
|
-
"prepublishOnly": "npm run build:bridge && npm run validate:bin-manifest && npm run test:install-prompt-policy && npm run test:ldm-status-timeout",
|
|
21
|
+
"prepublishOnly": "npm run build:bridge && npm run validate:bin-manifest && npm run test:install-prompt-policy && npm run test:ldm-status-timeout && npm run test:ldm-status-concurrency",
|
|
22
22
|
"validate:bin-manifest": "node scripts/validate-bin-manifest.mjs",
|
|
23
23
|
"test:skill-frontmatter": "node scripts/test-skill-frontmatter.mjs",
|
|
24
24
|
"test:install-prompt-policy": "node scripts/test-install-prompt-policy.mjs",
|
|
25
25
|
"test:ldm-status-timeout": "node scripts/test-ldm-status-timeout.mjs",
|
|
26
|
+
"test:ldm-status-concurrency": "node scripts/test-ldm-status-concurrency.mjs",
|
|
26
27
|
"test:installer-update-tracks": "node scripts/test-installer-update-tracks.mjs",
|
|
27
28
|
"test:installer-hook-toolname": "node scripts/test-installer-hook-toolname.mjs",
|
|
28
29
|
"test:installer-target-self-update": "node scripts/test-installer-target-self-update.mjs",
|
|
@@ -21,6 +21,8 @@ for (const file of ["README.md", "shared/templates/install-prompt.md"]) {
|
|
|
21
21
|
for (const phrase of [
|
|
22
22
|
"Use the install document and live local checks as the source of truth.",
|
|
23
23
|
"Do not search memory or prior notes for this install. Do not pre-load context from `MEMORY.md`, `crystal_search`, local skill dev guides, or other local memory before fetching the install document.",
|
|
24
|
+
"Do not run GitHub commands during this install flow. Do not run or request approval for `gh release`, `gh api`, or `gh search`.",
|
|
25
|
+
"If release notes are not available from local or npm metadata, say that and do not fetch them from GitHub.",
|
|
24
26
|
"If installed: run `ldm status`",
|
|
25
27
|
"If yes to dry run, run `ldm install --dry-run`.",
|
|
26
28
|
"`npm install -g @wipcomputer/wip-ldm-os@latest && ldm install && ldm doctor`",
|
|
@@ -41,10 +43,14 @@ for (const phrase of [
|
|
|
41
43
|
"Memory policy for install flows: do not consult `MEMORY.md`, do not run `crystal_search`, and do not search prior notes when this skill is invoked, including in any parallel or batched exploration step.",
|
|
42
44
|
"The only context sources for this install flow are `https://wip.computer/install/wip-ldm-os.txt` and the live local commands that document prescribes.",
|
|
43
45
|
"Read that document and run those commands. Do not pre-load other context.",
|
|
44
|
-
"Do not run GitHub
|
|
45
|
-
"Do not run `gh release list
|
|
46
|
+
"Do not run GitHub commands during the install-state flow.",
|
|
47
|
+
"Do not run or request approval for `gh release list`, `gh release view`, `gh api repos/*`, `gh search`, or any other GitHub query unless the user explicitly asks for release notes.",
|
|
46
48
|
"Use the output of `ldm status`, installed package metadata, and npm metadata.",
|
|
47
|
-
"Do not use GitHub
|
|
49
|
+
"Do not use GitHub commands here.",
|
|
50
|
+
"If npm metadata for a package does not include release notes:",
|
|
51
|
+
"Say \"release notes not available from local metadata.\"",
|
|
52
|
+
"Do not infer release-note content from package descriptions, commit messages, or repo READMEs.",
|
|
53
|
+
"An approval dialog is not a user request.",
|
|
48
54
|
]) {
|
|
49
55
|
if (!skill.includes(phrase)) {
|
|
50
56
|
failures.push(`SKILL.md missing install policy phrase: ${phrase}`);
|
|
@@ -0,0 +1,88 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
import { chmodSync, mkdirSync, mkdtempSync, readFileSync, rmSync, writeFileSync } from 'node:fs';
|
|
3
|
+
import { tmpdir } from 'node:os';
|
|
4
|
+
import { dirname, join } from 'node:path';
|
|
5
|
+
import { spawnSync } from 'node:child_process';
|
|
6
|
+
import { fileURLToPath } from 'node:url';
|
|
7
|
+
|
|
8
|
+
const root = dirname(dirname(fileURLToPath(import.meta.url)));
|
|
9
|
+
const tempRoot = mkdtempSync(join(tmpdir(), 'ldm-status-concurrency-'));
|
|
10
|
+
const sourceVersion = JSON.parse(readFileSync(join(root, 'package.json'), 'utf8')).version;
|
|
11
|
+
|
|
12
|
+
function assert(condition, message) {
|
|
13
|
+
if (!condition) throw new Error(message);
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
function writeFixture(home, fakeBin) {
|
|
17
|
+
const extensions = join(home, '.ldm', 'extensions');
|
|
18
|
+
mkdirSync(extensions, { recursive: true });
|
|
19
|
+
writeFileSync(join(home, '.ldm', 'version.json'), JSON.stringify({
|
|
20
|
+
version: '0.0.0-test',
|
|
21
|
+
installed: '2026-05-12T00:00:00.000Z',
|
|
22
|
+
updated: '2026-05-12T00:00:00.000Z',
|
|
23
|
+
}, null, 2) + '\n');
|
|
24
|
+
|
|
25
|
+
const registry = { extensions: {} };
|
|
26
|
+
for (let i = 1; i <= 8; i += 1) {
|
|
27
|
+
registry.extensions[`ext-${i}`] = {
|
|
28
|
+
source: { npm: `ext-${i}` },
|
|
29
|
+
installed: { version: '1.0.0' },
|
|
30
|
+
};
|
|
31
|
+
}
|
|
32
|
+
writeFileSync(join(extensions, 'registry.json'), JSON.stringify(registry, null, 2) + '\n');
|
|
33
|
+
|
|
34
|
+
mkdirSync(fakeBin, { recursive: true });
|
|
35
|
+
const fakeNpm = join(fakeBin, 'npm');
|
|
36
|
+
writeFileSync(fakeNpm, `#!/usr/bin/env bash
|
|
37
|
+
if [ "$1" = "view" ]; then
|
|
38
|
+
sleep "\${FAKE_NPM_SLEEP:-1}"
|
|
39
|
+
echo "1.0.0"
|
|
40
|
+
exit 0
|
|
41
|
+
fi
|
|
42
|
+
echo "unexpected npm command: $*" >&2
|
|
43
|
+
exit 64
|
|
44
|
+
`);
|
|
45
|
+
chmodSync(fakeNpm, 0o755);
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
function runStatus({ concurrency, sleepSeconds, timeoutMs }) {
|
|
49
|
+
const home = join(tempRoot, `home-${concurrency}-${sleepSeconds}`);
|
|
50
|
+
const fakeBin = join(tempRoot, `bin-${concurrency}-${sleepSeconds}`);
|
|
51
|
+
writeFixture(home, fakeBin);
|
|
52
|
+
|
|
53
|
+
const startedAt = Date.now();
|
|
54
|
+
const result = spawnSync(process.execPath, [join(root, 'bin', 'ldm.js'), 'status'], {
|
|
55
|
+
cwd: root,
|
|
56
|
+
encoding: 'utf8',
|
|
57
|
+
timeout: timeoutMs,
|
|
58
|
+
env: {
|
|
59
|
+
...process.env,
|
|
60
|
+
HOME: home,
|
|
61
|
+
PATH: `${fakeBin}:${process.env.PATH || ''}`,
|
|
62
|
+
FAKE_NPM_SLEEP: String(sleepSeconds),
|
|
63
|
+
LDM_STATUS_NPM_CONCURRENCY: String(concurrency),
|
|
64
|
+
LDM_STATUS_NPM_TIMEOUT_MS: '2000',
|
|
65
|
+
LDM_STATUS_TOTAL_BUDGET_MS: '10000',
|
|
66
|
+
},
|
|
67
|
+
});
|
|
68
|
+
return { result, elapsedMs: Date.now() - startedAt };
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
try {
|
|
72
|
+
const concurrent = runStatus({ concurrency: 4, sleepSeconds: 1, timeoutMs: 7000 });
|
|
73
|
+
assert(concurrent.result.status === 0, `concurrent ldm status exited ${concurrent.result.status}\nstdout:\n${concurrent.result.stdout}\nstderr:\n${concurrent.result.stderr}`);
|
|
74
|
+
assert(concurrent.elapsedMs < 6500, `concurrent ldm status should finish well before serial runtime; elapsed ${concurrent.elapsedMs}ms`);
|
|
75
|
+
assert(concurrent.result.stdout.includes(`LDM OS v${sourceVersion}`), `status should print installed LDM OS version\n${concurrent.result.stdout}`);
|
|
76
|
+
assert(concurrent.result.stdout.includes('Extensions: 8'), `status should print extension count\n${concurrent.result.stdout}`);
|
|
77
|
+
assert(concurrent.result.stdout.includes('ext-8: checking npm'), `status should check every staged extension\n${concurrent.result.stdout}`);
|
|
78
|
+
assert(!concurrent.result.stdout.includes('Update checks skipped:'), `concurrent status should not skip checks in this fixture\n${concurrent.result.stdout}`);
|
|
79
|
+
|
|
80
|
+
const serialFallback = runStatus({ concurrency: 1, sleepSeconds: 0.05, timeoutMs: 5000 });
|
|
81
|
+
assert(serialFallback.result.status === 0, `serial fallback ldm status exited ${serialFallback.result.status}\nstdout:\n${serialFallback.result.stdout}\nstderr:\n${serialFallback.result.stderr}`);
|
|
82
|
+
assert(serialFallback.result.stdout.includes('ext-8: checking npm'), `serial fallback should still check every staged extension\n${serialFallback.result.stdout}`);
|
|
83
|
+
assert(!serialFallback.result.stdout.includes('Update checks skipped:'), `serial fallback should not skip checks in this fixture\n${serialFallback.result.stdout}`);
|
|
84
|
+
} finally {
|
|
85
|
+
rmSync(tempRoot, { recursive: true, force: true });
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
console.log('ldm status concurrency regression passed');
|
|
@@ -72,7 +72,7 @@ exit 64
|
|
|
72
72
|
assert(result.stdout.includes('Checking updates:'), `status should show progress before update checks\n${result.stdout}`);
|
|
73
73
|
assert(result.stdout.includes('hung-extension: checking npm'), `status should print the extension name before probing it\n${result.stdout}`);
|
|
74
74
|
assert(result.stdout.includes('Update checks skipped:'), `status should report skipped checks instead of hanging\n${result.stdout}`);
|
|
75
|
-
assert(
|
|
75
|
+
assert(/hung-extension: \[timeout \d+(ms|\.\d+s)\] hung-extension/.test(result.stdout), `hung extension should be reported as a timeout with elapsed time\n${result.stdout}`);
|
|
76
76
|
} finally {
|
|
77
77
|
rmSync(tempRoot, { recursive: true, force: true });
|
|
78
78
|
}
|
|
@@ -8,6 +8,7 @@ Read https://wip.computer/install/wip-ldm-os.txt
|
|
|
8
8
|
|
|
9
9
|
Use the install document and live local checks as the source of truth.
|
|
10
10
|
Do not search memory or prior notes for this install. Do not pre-load context from `MEMORY.md`, `crystal_search`, local skill dev guides, or other local memory before fetching the install document.
|
|
11
|
+
Do not run GitHub commands during this install flow. Do not run or request approval for `gh release`, `gh api`, or `gh search`. If release notes are not available from local or npm metadata, say that and do not fetch them from GitHub.
|
|
11
12
|
|
|
12
13
|
Check if LDM OS is installed (`which ldm && ldm --version`). Branch.
|
|
13
14
|
|