refactor: hard-fork GSD -> MSD (Make Software Done)
Mechanical rename produced by scripts/msd-rename.cjs: gsd/Gsd/GSD -> msd/Msd/MSD across contents and paths, upstream package/repo coordinates -> @golem15/msd-core and golem15com/msd-core. Deep links into upstream history, sibling upstream packages, the GSD-2 import feature, CHANGELOG.md and .changeset/ are kept as-is. Hand edits on top: MSD block-letter banner and logos, LICENSE copyright line, package/plugin identity, regenerated lockfile, install-tree fixtures, derived registries and benchmark baseline; migration checksum baseline re-locked (MSD keeps its own install state, so no install had applied the old sums); sort-order and regex-escaped expectations in tests adjusted.
This commit is contained in:
@@ -1,15 +1,15 @@
|
||||
// docs-guard-exempt: docs/reference/gsd-capability-command.md is cited only in a header comment as rationale; no docs/ path is ever read.
|
||||
// docs-guard-exempt: docs/reference/msd-capability-command.md is cited only in a header comment as rationale; no docs/ path is ever read.
|
||||
'use strict';
|
||||
|
||||
/**
|
||||
* capability-cli.test.cjs — behavioral tests for the `gsd capability` MANAGEMENT CLI
|
||||
* capability-cli.test.cjs — behavioral tests for the `msd capability` MANAGEMENT CLI
|
||||
* (ADR-1244 D5/D6): install / update / remove / list / disable / enable wired in
|
||||
* gsd-tools.cjs `case 'capability'` to the Phase-4 lifecycle + Phase-3 ledger.
|
||||
* msd-tools.cjs `case 'capability'` to the Phase-4 lifecycle + Phase-3 ledger.
|
||||
*
|
||||
* These exercise the REAL CLI end-to-end via runGsdTools (subprocess), the REAL
|
||||
* source resolver (local-path kind — no network), and a GSD_HOME-sandboxed global
|
||||
* These exercise the REAL CLI end-to-end via runMsdTools (subprocess), the REAL
|
||||
* source resolver (local-path kind — no network), and a MSD_HOME-sandboxed global
|
||||
* scope so no developer state is touched. They are the contract the reference doc
|
||||
* (docs/reference/gsd-capability-command.md) is verified against.
|
||||
* (docs/reference/msd-capability-command.md) is verified against.
|
||||
*/
|
||||
|
||||
const { describe, test } = require('node:test');
|
||||
@@ -18,7 +18,7 @@ const fs = require('node:fs');
|
||||
const os = require('node:os');
|
||||
const path = require('node:path');
|
||||
|
||||
const { runGsdTools, cleanup } = require('./helpers.cjs');
|
||||
const { runMsdTools, cleanup } = require('./helpers.cjs');
|
||||
const { PROBE_TIMEOUT_MS } = require('./helpers/timeouts.cjs');
|
||||
|
||||
// ─── Fixtures ───────────────────────────────────────────────────────────────
|
||||
@@ -31,9 +31,9 @@ function tmpDir(prefix) {
|
||||
}
|
||||
test.after(() => { for (const d of tmps) cleanup(d); });
|
||||
|
||||
/** A GSD_HOME-sandboxed env that also neutralizes ambient GSD_ vars (test hermeticity). */
|
||||
/** A MSD_HOME-sandboxed env that also neutralizes ambient MSD_ vars (test hermeticity). */
|
||||
function scopeEnv(home) {
|
||||
return { GSD_HOME: home, GSD_WORKSTREAM: '', GSD_PROJECT: '' };
|
||||
return { MSD_HOME: home, MSD_WORKSTREAM: '', MSD_PROJECT: '' };
|
||||
}
|
||||
|
||||
/** A cwd with a .planning/ root so findProjectRoot resolves cleanly. */
|
||||
@@ -92,8 +92,8 @@ function writeCapSource(id, { version = '1.0.0', hooks = [], engines, mcp, requi
|
||||
return src;
|
||||
}
|
||||
|
||||
function ledgerPath(home) { return path.join(home, '.gsd-capabilities.json'); }
|
||||
function capDir(home, id) { return path.join(home, '.gsd', 'capabilities', id); }
|
||||
function ledgerPath(home) { return path.join(home, '.msd-capabilities.json'); }
|
||||
function capDir(home, id) { return path.join(home, '.msd', 'capabilities', id); }
|
||||
function readLedgerEntry(home, id) {
|
||||
try {
|
||||
const l = JSON.parse(fs.readFileSync(ledgerPath(home), 'utf8'));
|
||||
@@ -108,7 +108,7 @@ describe('capability install', () => {
|
||||
test('declarative local capability installs to the global scope and records the ledger', () => {
|
||||
const home = tmpDir('cap-cli-home-');
|
||||
const src = writeCapSource('declcap');
|
||||
const r = runGsdTools(['capability', 'install', src, '--scope', 'global', '--raw'], makeCwd(), scopeEnv(home));
|
||||
const r = runMsdTools(['capability', 'install', src, '--scope', 'global', '--raw'], makeCwd(), scopeEnv(home));
|
||||
assert.equal(r.success, true, `install failed: ${r.error || r.output}`);
|
||||
const o = parse(r.output);
|
||||
assert.equal(o.status, 'installed');
|
||||
@@ -121,7 +121,7 @@ describe('capability install', () => {
|
||||
test('executable capability WITHOUT --yes aborts for consent and writes nothing', () => {
|
||||
const home = tmpDir('cap-cli-home-');
|
||||
const src = writeCapSource('execcap', { hooks: [{ event: 'PostToolUse', script: 'hooks/run.js' }] });
|
||||
const r = runGsdTools(['capability', 'install', src, '--scope', 'global'], makeCwd(), scopeEnv(home));
|
||||
const r = runMsdTools(['capability', 'install', src, '--scope', 'global'], makeCwd(), scopeEnv(home));
|
||||
assert.equal(r.success, false, 'unconsented executable install must fail');
|
||||
assert.match(`${r.error}\n${r.output}`, /consent/i);
|
||||
assert.equal(readLedgerEntry(home, 'execcap'), null, 'no ledger entry');
|
||||
@@ -131,7 +131,7 @@ describe('capability install', () => {
|
||||
test('executable capability WITH --yes installs', () => {
|
||||
const home = tmpDir('cap-cli-home-');
|
||||
const src = writeCapSource('execyes', { hooks: [{ event: 'PostToolUse', script: 'hooks/run.js' }] });
|
||||
const r = runGsdTools(['capability', 'install', src, '--scope', 'global', '--yes', '--raw'], makeCwd(), scopeEnv(home));
|
||||
const r = runMsdTools(['capability', 'install', src, '--scope', 'global', '--yes', '--raw'], makeCwd(), scopeEnv(home));
|
||||
assert.equal(r.success, true, `install failed: ${r.error || r.output}`);
|
||||
assert.equal(parse(r.output).status, 'installed');
|
||||
assert.ok(readLedgerEntry(home, 'execyes'), 'ledger entry recorded');
|
||||
@@ -139,31 +139,31 @@ describe('capability install', () => {
|
||||
|
||||
test('a reserved-namespace id is blocked', () => {
|
||||
const home = tmpDir('cap-cli-home-');
|
||||
const src = writeCapSource('gsd-evil');
|
||||
const r = runGsdTools(['capability', 'install', src, '--scope', 'global'], makeCwd(), scopeEnv(home));
|
||||
const src = writeCapSource('msd-evil');
|
||||
const r = runMsdTools(['capability', 'install', src, '--scope', 'global'], makeCwd(), scopeEnv(home));
|
||||
assert.equal(r.success, false);
|
||||
assert.match(`${r.error}\n${r.output}`, /blocked|reserved/i);
|
||||
assert.ok(!fs.existsSync(capDir(home, 'gsd-evil')));
|
||||
assert.ok(!fs.existsSync(capDir(home, 'msd-evil')));
|
||||
});
|
||||
|
||||
test('an engines-incompatible capability is blocked', () => {
|
||||
const home = tmpDir('cap-cli-home-');
|
||||
const src = writeCapSource('engcap', { engines: { gsd: '>=99.0.0' } });
|
||||
const r = runGsdTools(['capability', 'install', src, '--scope', 'global'], makeCwd(), scopeEnv(home));
|
||||
const src = writeCapSource('engcap', { engines: { msd: '>=99.0.0' } });
|
||||
const r = runMsdTools(['capability', 'install', src, '--scope', 'global'], makeCwd(), scopeEnv(home));
|
||||
assert.equal(r.success, false);
|
||||
assert.match(`${r.error}\n${r.output}`, /blocked/i);
|
||||
assert.equal(readLedgerEntry(home, 'engcap'), null);
|
||||
});
|
||||
|
||||
test('missing <spec> is a usage error', () => {
|
||||
const r = runGsdTools(['capability', 'install'], makeCwd(), scopeEnv(tmpDir('cap-cli-home-')));
|
||||
const r = runMsdTools(['capability', 'install'], makeCwd(), scopeEnv(tmpDir('cap-cli-home-')));
|
||||
assert.equal(r.success, false);
|
||||
assert.match(`${r.error}\n${r.output}`, /Missing <spec>/i);
|
||||
});
|
||||
|
||||
test('an invalid --scope is rejected', () => {
|
||||
const src = writeCapSource('scopecap');
|
||||
const r = runGsdTools(['capability', 'install', src, '--scope', 'bogus'], makeCwd(), scopeEnv(tmpDir('cap-cli-home-')));
|
||||
const r = runMsdTools(['capability', 'install', src, '--scope', 'bogus'], makeCwd(), scopeEnv(tmpDir('cap-cli-home-')));
|
||||
assert.equal(r.success, false);
|
||||
assert.match(`${r.error}\n${r.output}`, /Invalid --scope/i);
|
||||
});
|
||||
@@ -173,7 +173,7 @@ describe('capability install', () => {
|
||||
|
||||
describe('capability list', () => {
|
||||
test('--json emits an array including first-party capabilities', () => {
|
||||
const r = runGsdTools(['capability', 'list', '--json'], makeCwd(), scopeEnv(tmpDir('cap-cli-home-')));
|
||||
const r = runMsdTools(['capability', 'list', '--json'], makeCwd(), scopeEnv(tmpDir('cap-cli-home-')));
|
||||
assert.equal(r.success, true, `list failed: ${r.error || r.output}`);
|
||||
const rows = parse(r.output);
|
||||
assert.ok(Array.isArray(rows), 'list is an array');
|
||||
@@ -185,8 +185,8 @@ describe('capability list', () => {
|
||||
test('an installed overlay capability appears with its scope', () => {
|
||||
const home = tmpDir('cap-cli-home-');
|
||||
const src = writeCapSource('listcap');
|
||||
assert.equal(runGsdTools(['capability', 'install', src, '--scope', 'global', '--raw'], makeCwd(), scopeEnv(home)).success, true);
|
||||
const r = runGsdTools(['capability', 'list', '--json'], makeCwd(), scopeEnv(home));
|
||||
assert.equal(runMsdTools(['capability', 'install', src, '--scope', 'global', '--raw'], makeCwd(), scopeEnv(home)).success, true);
|
||||
const r = runMsdTools(['capability', 'list', '--json'], makeCwd(), scopeEnv(home));
|
||||
assert.equal(r.success, true, `list failed: ${r.error || r.output}`);
|
||||
const row = parse(r.output).find((x) => x.id === 'listcap');
|
||||
assert.ok(row, 'installed capability listed');
|
||||
@@ -200,19 +200,19 @@ describe('capability list', () => {
|
||||
|
||||
describe('capability update', () => {
|
||||
test('a not-installed id errors', () => {
|
||||
const r = runGsdTools(['capability', 'update', 'nope', '--scope', 'global'], makeCwd(), scopeEnv(tmpDir('cap-cli-home-')));
|
||||
const r = runMsdTools(['capability', 'update', 'nope', '--scope', 'global'], makeCwd(), scopeEnv(tmpDir('cap-cli-home-')));
|
||||
assert.equal(r.success, false);
|
||||
assert.match(`${r.error}\n${r.output}`, /not installed/i);
|
||||
});
|
||||
|
||||
test('requires <id> or --all', () => {
|
||||
const r = runGsdTools(['capability', 'update', '--scope', 'global'], makeCwd(), scopeEnv(tmpDir('cap-cli-home-')));
|
||||
const r = runMsdTools(['capability', 'update', '--scope', 'global'], makeCwd(), scopeEnv(tmpDir('cap-cli-home-')));
|
||||
assert.equal(r.success, false);
|
||||
assert.match(`${r.error}\n${r.output}`, /requires <id> or --all/i);
|
||||
});
|
||||
|
||||
test('<id> and --all are mutually exclusive', () => {
|
||||
const r = runGsdTools(['capability', 'update', 'foo', '--all', '--scope', 'global'], makeCwd(), scopeEnv(tmpDir('cap-cli-home-')));
|
||||
const r = runMsdTools(['capability', 'update', 'foo', '--all', '--scope', 'global'], makeCwd(), scopeEnv(tmpDir('cap-cli-home-')));
|
||||
assert.equal(r.success, false);
|
||||
assert.match(`${r.error}\n${r.output}`, /not both/i);
|
||||
});
|
||||
@@ -220,12 +220,12 @@ describe('capability update', () => {
|
||||
test('an installed capability upgrades to a newer version from its recorded source', () => {
|
||||
const home = tmpDir('cap-cli-home-');
|
||||
const src = writeCapSource('upcap', { version: '1.0.0' });
|
||||
assert.equal(runGsdTools(['capability', 'install', src, '--scope', 'global', '--raw'], makeCwd(), scopeEnv(home)).success, true);
|
||||
assert.equal(runMsdTools(['capability', 'install', src, '--scope', 'global', '--raw'], makeCwd(), scopeEnv(home)).success, true);
|
||||
// Bump the recorded source to a newer version, then update by id.
|
||||
const cap = JSON.parse(fs.readFileSync(path.join(src, 'capability.json'), 'utf8'));
|
||||
cap.version = '2.0.0';
|
||||
fs.writeFileSync(path.join(src, 'capability.json'), JSON.stringify(cap, null, 2));
|
||||
const r = runGsdTools(['capability', 'update', 'upcap', '--scope', 'global', '--raw'], makeCwd(), scopeEnv(home));
|
||||
const r = runMsdTools(['capability', 'update', 'upcap', '--scope', 'global', '--raw'], makeCwd(), scopeEnv(home));
|
||||
assert.equal(r.success, true, `update failed: ${r.error || r.output}`);
|
||||
const o = parse(r.output);
|
||||
assert.equal(o.status, 'upgraded');
|
||||
@@ -252,7 +252,7 @@ describe('capability update', () => {
|
||||
|
||||
const sharedArgs = [];
|
||||
for (let i = 0; i < 300; i++) { sharedArgs.push('--shared-file', `f${i}.json`); } // over the 256 cap
|
||||
const r = runGsdTools(
|
||||
const r = runMsdTools(
|
||||
['capability', 'update', '--all', '--scope', 'global', ...sharedArgs, '--raw'],
|
||||
makeCwd(), scopeEnv(home),
|
||||
);
|
||||
@@ -271,8 +271,8 @@ describe('capability remove', () => {
|
||||
test('an installed overlay capability is removed (ledger + bundle gone)', () => {
|
||||
const home = tmpDir('cap-cli-home-');
|
||||
const src = writeCapSource('rmcap');
|
||||
assert.equal(runGsdTools(['capability', 'install', src, '--scope', 'global', '--raw'], makeCwd(), scopeEnv(home)).success, true);
|
||||
const r = runGsdTools(['capability', 'remove', 'rmcap', '--scope', 'global', '--raw'], makeCwd(), scopeEnv(home));
|
||||
assert.equal(runMsdTools(['capability', 'install', src, '--scope', 'global', '--raw'], makeCwd(), scopeEnv(home)).success, true);
|
||||
const r = runMsdTools(['capability', 'remove', 'rmcap', '--scope', 'global', '--raw'], makeCwd(), scopeEnv(home));
|
||||
assert.equal(r.success, true, `remove failed: ${r.error || r.output}`);
|
||||
assert.equal(parse(r.output).status, 'removed');
|
||||
assert.equal(readLedgerEntry(home, 'rmcap'), null, 'ledger entry gone');
|
||||
@@ -280,22 +280,22 @@ describe('capability remove', () => {
|
||||
});
|
||||
|
||||
test('a not-installed id errors', () => {
|
||||
const r = runGsdTools(['capability', 'remove', 'nope', '--scope', 'global'], makeCwd(), scopeEnv(tmpDir('cap-cli-home-')));
|
||||
const r = runMsdTools(['capability', 'remove', 'nope', '--scope', 'global'], makeCwd(), scopeEnv(tmpDir('cap-cli-home-')));
|
||||
assert.equal(r.success, false);
|
||||
assert.match(`${r.error}\n${r.output}`, /not installed/i);
|
||||
});
|
||||
|
||||
test('a first-party capability cannot be removed here', () => {
|
||||
// Pick a real first-party id from the registry.
|
||||
const reg = require('../gsd-core/bin/lib/capability-registry.cjs');
|
||||
const reg = require('../msd-core/bin/lib/capability-registry.cjs');
|
||||
const firstParty = Object.keys(reg.capabilities)[0];
|
||||
const r = runGsdTools(['capability', 'remove', firstParty, '--scope', 'global'], makeCwd(), scopeEnv(tmpDir('cap-cli-home-')));
|
||||
const r = runMsdTools(['capability', 'remove', firstParty, '--scope', 'global'], makeCwd(), scopeEnv(tmpDir('cap-cli-home-')));
|
||||
assert.equal(r.success, false);
|
||||
assert.match(`${r.error}\n${r.output}`, /first-party/i);
|
||||
});
|
||||
|
||||
test('missing <id> is a usage error', () => {
|
||||
const r = runGsdTools(['capability', 'remove'], makeCwd(), scopeEnv(tmpDir('cap-cli-home-')));
|
||||
const r = runMsdTools(['capability', 'remove'], makeCwd(), scopeEnv(tmpDir('cap-cli-home-')));
|
||||
assert.equal(r.success, false);
|
||||
assert.match(`${r.error}\n${r.output}`, /Missing <id>/i);
|
||||
});
|
||||
@@ -307,17 +307,17 @@ describe('capability disable / enable', () => {
|
||||
test('disable then enable a first-party capability toggles its activation state', () => {
|
||||
const cwd = makeCwd();
|
||||
const rcd = tmpDir('cap-cli-rcd-');
|
||||
const off = runGsdTools(['capability', 'disable', 'ui', '--config-dir', rcd, '--raw'], cwd);
|
||||
const off = runMsdTools(['capability', 'disable', 'ui', '--config-dir', rcd, '--raw'], cwd);
|
||||
assert.equal(off.success, true, `disable failed: ${off.error || off.output}`);
|
||||
const ui = parse(off.output).capabilities.find((c) => c.id === 'ui');
|
||||
assert.equal(ui.enabled, false, 'ui disabled');
|
||||
const on = runGsdTools(['capability', 'enable', 'ui', '--config-dir', rcd, '--raw'], cwd);
|
||||
const on = runMsdTools(['capability', 'enable', 'ui', '--config-dir', rcd, '--raw'], cwd);
|
||||
assert.equal(on.success, true, `enable failed: ${on.error || on.output}`);
|
||||
assert.equal(parse(on.output).capabilities.find((c) => c.id === 'ui').enabled, true, 'ui re-enabled');
|
||||
});
|
||||
|
||||
test('disable without <id> is a usage error', () => {
|
||||
const r = runGsdTools(['capability', 'disable'], makeCwd());
|
||||
const r = runMsdTools(['capability', 'disable'], makeCwd());
|
||||
assert.equal(r.success, false);
|
||||
assert.match(`${r.error}\n${r.output}`, /Missing <id>/i);
|
||||
});
|
||||
@@ -327,7 +327,7 @@ describe('capability disable / enable', () => {
|
||||
|
||||
describe('capability (unknown)', () => {
|
||||
test('an unknown subcommand lists the full available set (incl. outdated)', () => {
|
||||
const r = runGsdTools(['capability', 'bogus'], makeCwd());
|
||||
const r = runMsdTools(['capability', 'bogus'], makeCwd());
|
||||
assert.equal(r.success, false);
|
||||
assert.match(`${r.error}\n${r.output}`, /install, update, remove, list, outdated, trust, disable, enable, state, set/);
|
||||
});
|
||||
@@ -338,10 +338,10 @@ describe('capability (unknown)', () => {
|
||||
describe('capability outdated', () => {
|
||||
test('empty ledger → --json empty array; default table shows the empty marker', () => {
|
||||
const home = tmpDir('cap-cli-home-');
|
||||
const json = runGsdTools(['capability', 'outdated', '--scope', 'global', '--json'], makeCwd(), scopeEnv(home));
|
||||
const json = runMsdTools(['capability', 'outdated', '--scope', 'global', '--json'], makeCwd(), scopeEnv(home));
|
||||
assert.equal(json.success, true, `outdated --json failed: ${json.error || json.output}`);
|
||||
assert.deepEqual(parse(json.output), []);
|
||||
const table = runGsdTools(['capability', 'outdated', '--scope', 'global'], makeCwd(), scopeEnv(home));
|
||||
const table = runMsdTools(['capability', 'outdated', '--scope', 'global'], makeCwd(), scopeEnv(home));
|
||||
assert.equal(table.success, true, `outdated table failed: ${table.error || table.output}`);
|
||||
assert.match(table.output, /no installed overlay capabilities/i);
|
||||
});
|
||||
@@ -349,13 +349,13 @@ describe('capability outdated', () => {
|
||||
test('local source whose path now declares a newer version → status outdated (records shape)', () => {
|
||||
const home = tmpDir('cap-cli-home-');
|
||||
const src = writeCapSource('outcap', { version: '1.0.0' });
|
||||
assert.equal(runGsdTools(['capability', 'install', src, '--scope', 'global', '--raw'], makeCwd(), scopeEnv(home)).success, true);
|
||||
assert.equal(runMsdTools(['capability', 'install', src, '--scope', 'global', '--raw'], makeCwd(), scopeEnv(home)).success, true);
|
||||
// Bump the recorded LOCAL source to a newer version — the peek re-reads it.
|
||||
const cap = JSON.parse(fs.readFileSync(path.join(src, 'capability.json'), 'utf8'));
|
||||
cap.version = '2.0.0';
|
||||
fs.writeFileSync(path.join(src, 'capability.json'), JSON.stringify(cap, null, 2));
|
||||
|
||||
const r = runGsdTools(['capability', 'outdated', '--scope', 'global', '--json'], makeCwd(), scopeEnv(home));
|
||||
const r = runMsdTools(['capability', 'outdated', '--scope', 'global', '--json'], makeCwd(), scopeEnv(home));
|
||||
assert.equal(r.success, true, `outdated failed: ${r.error || r.output}`);
|
||||
const rows = parse(r.output);
|
||||
const row = rows.find((x) => x.id === 'outcap');
|
||||
@@ -371,8 +371,8 @@ describe('capability outdated', () => {
|
||||
test('local source at the same version → status current; default emits a table with the row', () => {
|
||||
const home = tmpDir('cap-cli-home-');
|
||||
const src = writeCapSource('samecap', { version: '1.0.0' });
|
||||
assert.equal(runGsdTools(['capability', 'install', src, '--scope', 'global', '--raw'], makeCwd(), scopeEnv(home)).success, true);
|
||||
const r = runGsdTools(['capability', 'outdated', '--scope', 'global'], makeCwd(), scopeEnv(home));
|
||||
assert.equal(runMsdTools(['capability', 'install', src, '--scope', 'global', '--raw'], makeCwd(), scopeEnv(home)).success, true);
|
||||
const r = runMsdTools(['capability', 'outdated', '--scope', 'global'], makeCwd(), scopeEnv(home));
|
||||
assert.equal(r.success, true, `outdated failed: ${r.error || r.output}`);
|
||||
// Table form: header columns + the capability row with status current.
|
||||
assert.match(r.output, /ID\s+Source\s+Current\s+Latest\s+Status/);
|
||||
@@ -382,12 +382,12 @@ describe('capability outdated', () => {
|
||||
test('tarball source → status manual (not auto-detectable)', () => {
|
||||
// Plant a project-scope ledger entry with a tarball source directly (install would need network).
|
||||
const cwd = makeCwd();
|
||||
const ledgerMod = require('../gsd-core/bin/lib/capability-ledger.cjs');
|
||||
const ledgerMod = require('../msd-core/bin/lib/capability-ledger.cjs');
|
||||
ledgerMod.recordInstall(cwd, {
|
||||
id: 'tarcap', version: '1.0.0', source: 'https://host/path/cap-1.0.0.tgz',
|
||||
integrity: '', files: ['.gsd/capabilities/tarcap'], sharedEdits: [],
|
||||
integrity: '', files: ['.msd/capabilities/tarcap'], sharedEdits: [],
|
||||
});
|
||||
const r = runGsdTools(['capability', 'outdated', '--scope', 'project', '--json'], cwd, { GSD_WORKSTREAM: '', GSD_PROJECT: '' });
|
||||
const r = runMsdTools(['capability', 'outdated', '--scope', 'project', '--json'], cwd, { MSD_WORKSTREAM: '', MSD_PROJECT: '' });
|
||||
assert.equal(r.success, true, `outdated failed: ${r.error || r.output}`);
|
||||
const row = parse(r.output).find((x) => x.id === 'tarcap');
|
||||
assert.ok(row, 'tarball capability reported');
|
||||
@@ -396,7 +396,7 @@ describe('capability outdated', () => {
|
||||
});
|
||||
|
||||
test('invalid --scope is rejected', () => {
|
||||
const r = runGsdTools(['capability', 'outdated', '--scope', 'bogus'], makeCwd(), scopeEnv(tmpDir('cap-cli-home-')));
|
||||
const r = runMsdTools(['capability', 'outdated', '--scope', 'bogus'], makeCwd(), scopeEnv(tmpDir('cap-cli-home-')));
|
||||
assert.equal(r.success, false);
|
||||
assert.match(`${r.error}\n${r.output}`, /Invalid --scope/i);
|
||||
});
|
||||
@@ -407,11 +407,11 @@ describe('capability outdated', () => {
|
||||
describe('capability install (trust hardening)', () => {
|
||||
test('an overlay reusing a first-party capability id is blocked', () => {
|
||||
// Pick a real first-party id and try to install an overlay that shadows it.
|
||||
const reg = require('../gsd-core/bin/lib/capability-registry.cjs');
|
||||
const reg = require('../msd-core/bin/lib/capability-registry.cjs');
|
||||
const firstParty = Object.keys(reg.capabilities)[0];
|
||||
const home = tmpDir('cap-cli-home-');
|
||||
const src = writeCapSource(firstParty);
|
||||
const r = runGsdTools(['capability', 'install', src, '--scope', 'global'], makeCwd(), scopeEnv(home));
|
||||
const r = runMsdTools(['capability', 'install', src, '--scope', 'global'], makeCwd(), scopeEnv(home));
|
||||
assert.equal(r.success, false);
|
||||
assert.match(`${r.error}\n${r.output}`, /first-party capability id/i);
|
||||
assert.equal(readLedgerEntry(home, firstParty), null);
|
||||
@@ -420,14 +420,14 @@ describe('capability install (trust hardening)', () => {
|
||||
test('a malformed strict_known_registries value fails closed (does not downgrade to permissive)', () => {
|
||||
// A hand-edited string instead of an array must BLOCK an external source, not be ignored.
|
||||
const cwd = makeCwdWithStrict('github.com');
|
||||
const r = runGsdTools(['capability', 'install', 'https://github.com/x/y.git', '--scope', 'project'], cwd, { GSD_WORKSTREAM: '', GSD_PROJECT: '' });
|
||||
const r = runMsdTools(['capability', 'install', 'https://github.com/x/y.git', '--scope', 'project'], cwd, { MSD_WORKSTREAM: '', MSD_PROJECT: '' });
|
||||
assert.equal(r.success, false);
|
||||
assert.match(`${r.error}\n${r.output}`, /must be an array|blocked/i);
|
||||
});
|
||||
|
||||
test('strict_known_registries: [] (lockdown) blocks an external source', () => {
|
||||
const cwd = makeCwdWithStrict([]);
|
||||
const r = runGsdTools(['capability', 'install', 'https://github.com/x/y.git', '--scope', 'project'], cwd, { GSD_WORKSTREAM: '', GSD_PROJECT: '' });
|
||||
const r = runMsdTools(['capability', 'install', 'https://github.com/x/y.git', '--scope', 'project'], cwd, { MSD_WORKSTREAM: '', MSD_PROJECT: '' });
|
||||
assert.equal(r.success, false);
|
||||
assert.match(`${r.error}\n${r.output}`, /external capability installs are disabled|blocked/i);
|
||||
});
|
||||
@@ -437,13 +437,13 @@ describe('capability update (id-pinning + reporting)', () => {
|
||||
test('update refuses when the recorded source now resolves to a different id', () => {
|
||||
const home = tmpDir('cap-cli-home-');
|
||||
const src = writeCapSource('orig');
|
||||
assert.equal(runGsdTools(['capability', 'install', src, '--scope', 'global', '--raw'], makeCwd(), scopeEnv(home)).success, true);
|
||||
assert.equal(runMsdTools(['capability', 'install', src, '--scope', 'global', '--raw'], makeCwd(), scopeEnv(home)).success, true);
|
||||
// Retarget the source to a different manifest id.
|
||||
const cap = JSON.parse(fs.readFileSync(path.join(src, 'capability.json'), 'utf8'));
|
||||
cap.id = 'switched';
|
||||
cap.version = '2.0.0';
|
||||
fs.writeFileSync(path.join(src, 'capability.json'), JSON.stringify(cap, null, 2));
|
||||
const r = runGsdTools(['capability', 'update', 'orig', '--scope', 'global'], makeCwd(), scopeEnv(home));
|
||||
const r = runMsdTools(['capability', 'update', 'orig', '--scope', 'global'], makeCwd(), scopeEnv(home));
|
||||
assert.equal(r.success, false);
|
||||
assert.match(`${r.error}\n${r.output}`, /different capability id|refusing/i);
|
||||
// The original is untouched; nothing named 'switched' got installed.
|
||||
@@ -454,14 +454,14 @@ describe('capability update (id-pinning + reporting)', () => {
|
||||
test('update --all exits non-zero when any entry fails to upgrade', () => {
|
||||
const home = tmpDir('cap-cli-home-');
|
||||
const src = writeCapSource('exupd', { hooks: [{ event: 'PostToolUse', script: 'hooks/a.js' }] });
|
||||
assert.equal(runGsdTools(['capability', 'install', src, '--scope', 'global', '--yes', '--raw'], makeCwd(), scopeEnv(home)).success, true);
|
||||
assert.equal(runMsdTools(['capability', 'install', src, '--scope', 'global', '--yes', '--raw'], makeCwd(), scopeEnv(home)).success, true);
|
||||
// Change the executable surface (new hook script) so the update needs re-consent.
|
||||
const cap = JSON.parse(fs.readFileSync(path.join(src, 'capability.json'), 'utf8'));
|
||||
cap.version = '2.0.0';
|
||||
cap.hooks = [{ event: 'PostToolUse', script: 'hooks/b.js' }];
|
||||
fs.writeFileSync(path.join(src, 'capability.json'), JSON.stringify(cap, null, 2));
|
||||
fs.writeFileSync(path.join(src, 'hooks', 'b.js'), '// artifact');
|
||||
const r = runGsdTools(['capability', 'update', '--all', '--scope', 'global'], makeCwd(), scopeEnv(home));
|
||||
const r = runMsdTools(['capability', 'update', '--all', '--scope', 'global'], makeCwd(), scopeEnv(home));
|
||||
assert.equal(r.success, false, 'partial --all failure must be non-zero');
|
||||
assert.match(`${r.error}\n${r.output}`, /did not upgrade/i);
|
||||
// The aborted update left the old version intact.
|
||||
@@ -471,11 +471,11 @@ describe('capability update (id-pinning + reporting)', () => {
|
||||
test('a successful executable update reports the consented disclosure', () => {
|
||||
const home = tmpDir('cap-cli-home-');
|
||||
const src = writeCapSource('discl', { hooks: [{ event: 'PostToolUse', script: 'hooks/run.js' }] });
|
||||
assert.equal(runGsdTools(['capability', 'install', src, '--scope', 'global', '--yes', '--raw'], makeCwd(), scopeEnv(home)).success, true);
|
||||
assert.equal(runMsdTools(['capability', 'install', src, '--scope', 'global', '--yes', '--raw'], makeCwd(), scopeEnv(home)).success, true);
|
||||
const cap = JSON.parse(fs.readFileSync(path.join(src, 'capability.json'), 'utf8'));
|
||||
cap.version = '2.0.0'; // same hook script => same exec set, no re-consent needed
|
||||
fs.writeFileSync(path.join(src, 'capability.json'), JSON.stringify(cap, null, 2));
|
||||
const r = runGsdTools(['capability', 'update', 'discl', '--scope', 'global', '--yes', '--raw'], makeCwd(), scopeEnv(home));
|
||||
const r = runMsdTools(['capability', 'update', 'discl', '--scope', 'global', '--yes', '--raw'], makeCwd(), scopeEnv(home));
|
||||
assert.equal(r.success, true, `update failed: ${r.error || r.output}`);
|
||||
const o = parse(r.output);
|
||||
assert.equal(o.status, 'upgraded');
|
||||
@@ -486,7 +486,7 @@ describe('capability update (id-pinning + reporting)', () => {
|
||||
describe('capability disable (overlay boundary)', () => {
|
||||
test('disabling an unknown id (non-raw) reports the error on stderr and exits non-zero', () => {
|
||||
const rcd = tmpDir('cap-cli-rcd-');
|
||||
const r = runGsdTools(['capability', 'disable', 'totally-unknown-xyz', '--config-dir', rcd], makeCwd());
|
||||
const r = runMsdTools(['capability', 'disable', 'totally-unknown-xyz', '--config-dir', rcd], makeCwd());
|
||||
assert.equal(r.success, false);
|
||||
assert.match(`${r.error}\n${r.output}`, /unknown capability/i);
|
||||
});
|
||||
@@ -498,7 +498,7 @@ describe('capability disable (overlay boundary)', () => {
|
||||
// error envelope is flushed to stdout AND the exit code stays non-zero.
|
||||
test('disabling an unknown id in --raw mode emits the JSON error envelope on stdout (not silent)', () => {
|
||||
const rcd = tmpDir('cap-cli-rcd-');
|
||||
const r = runGsdTools(['capability', 'disable', 'totally-unknown-xyz', '--config-dir', rcd, '--raw'], makeCwd());
|
||||
const r = runMsdTools(['capability', 'disable', 'totally-unknown-xyz', '--config-dir', rcd, '--raw'], makeCwd());
|
||||
assert.equal(r.success, false, 'must exit non-zero');
|
||||
assert.ok(r.output && r.output.length > 0, 'stdout must NOT be empty in raw error mode');
|
||||
const out = JSON.parse(r.output);
|
||||
@@ -517,7 +517,7 @@ describe('capability install (--shared-file confinement)', () => {
|
||||
// Plant a symlink inside the scope root pointing outside it.
|
||||
fs.symlinkSync(outside, path.join(home, 'evil'));
|
||||
const src = writeCapSource('symcap', { hooks: [{ event: 'PostToolUse', script: 'hooks/run.js' }] });
|
||||
const r = runGsdTools(
|
||||
const r = runMsdTools(
|
||||
['capability', 'install', src, '--scope', 'global', '--yes', '--shared-file', 'evil/settings.json', '--raw'],
|
||||
makeCwd(), scopeEnv(home),
|
||||
);
|
||||
@@ -529,8 +529,8 @@ describe('capability install (--shared-file confinement)', () => {
|
||||
// Finding 5(b) (MEDIUM): the --shared-file COUNT must be bounded EARLY — at the CLI/lifecycle
|
||||
// entry, BEFORE source resolution / staging / shared-config writes — so an over-cap install fails
|
||||
// fast with a clear count error instead of writing files + leaving a _pending to reconcile.
|
||||
// Revert-fails: remove the early count check in installCapability/gsd-tools → the install proceeds
|
||||
// to staging (a .gsd/capabilities/.staging dir is created) before any cap is enforced, so the
|
||||
// Revert-fails: remove the early count check in installCapability/msd-tools → the install proceeds
|
||||
// to staging (a .msd/capabilities/.staging dir is created) before any cap is enforced, so the
|
||||
// "no staging created" assertion fails (and there is no clear count error).
|
||||
test('finding-5b: an install with OVER-CAP --shared-file count is rejected BEFORE any staging dir is created', () => {
|
||||
const home = tmpDir('cap-cli-home-');
|
||||
@@ -539,7 +539,7 @@ describe('capability install (--shared-file confinement)', () => {
|
||||
// Build 300 --shared-file args (over the 256 generous cap).
|
||||
const sharedArgs = [];
|
||||
for (let i = 0; i < 300; i++) { sharedArgs.push('--shared-file', `f${i}.json`); }
|
||||
const r = runGsdTools(
|
||||
const r = runMsdTools(
|
||||
['capability', 'install', src, '--scope', 'global', '--yes', ...sharedArgs, '--raw'],
|
||||
makeCwd(), scopeEnv(home),
|
||||
);
|
||||
@@ -547,7 +547,7 @@ describe('capability install (--shared-file confinement)', () => {
|
||||
assert.match(`${r.error}\n${r.output}`, /shared.?file|count|too many|256/i,
|
||||
'the failure must clearly name the shared-file count problem');
|
||||
// NO staging dir may have been created — the bound is enforced before resolution/staging.
|
||||
const staging = path.join(home, '.gsd', 'capabilities', '.staging');
|
||||
const staging = path.join(home, '.msd', 'capabilities', '.staging');
|
||||
assert.equal(fs.existsSync(staging) && fs.readdirSync(staging).length > 0, false,
|
||||
'no staging dir may be created when the over-cap install is rejected early');
|
||||
// NO ledger entry / _pending must be left behind.
|
||||
@@ -562,7 +562,7 @@ describe('capability install (--shared-file confinement)', () => {
|
||||
// Pre-existing user settings with an mcpServers entry the capability will also declare.
|
||||
fs.writeFileSync(path.join(home, 'settings.json'), JSON.stringify({ mcpServers: { shared: { command: 'user-server' } } }));
|
||||
const src = writeCapSource('mcpcap', { mcp: { shared: { command: 'cap-server' } } });
|
||||
const r = runGsdTools(
|
||||
const r = runMsdTools(
|
||||
['capability', 'install', src, '--scope', 'global', '--yes', '--shared-file', 'settings.json', '--raw'],
|
||||
makeCwd(), scopeEnv(home),
|
||||
);
|
||||
@@ -577,9 +577,9 @@ describe('capability install (config policy fail-closed)', () => {
|
||||
const cwd = tmpDir('cap-cli-cwd-');
|
||||
fs.mkdirSync(path.join(cwd, '.planning'), { recursive: true });
|
||||
fs.writeFileSync(path.join(cwd, '.planning', 'config.json'), '{ this is not valid json');
|
||||
const r = runGsdTools(
|
||||
const r = runMsdTools(
|
||||
['capability', 'install', 'https://github.com/x/y.git', '--scope', 'project'],
|
||||
cwd, { GSD_WORKSTREAM: '', GSD_PROJECT: '' },
|
||||
cwd, { MSD_WORKSTREAM: '', MSD_PROJECT: '' },
|
||||
);
|
||||
assert.equal(r.success, false, 'broken config must not silently permit an external install');
|
||||
assert.match(`${r.error}\n${r.output}`, /external capability installs are disabled|blocked|array/i);
|
||||
@@ -590,7 +590,7 @@ describe('capability install (config policy fail-closed)', () => {
|
||||
|
||||
describe('capability (argument + empty-state handling)', () => {
|
||||
test('update --all over an empty ledger succeeds with an empty result set', () => {
|
||||
const r = runGsdTools(['capability', 'update', '--all', '--scope', 'global', '--raw'], makeCwd(), scopeEnv(tmpDir('cap-cli-home-')));
|
||||
const r = runMsdTools(['capability', 'update', '--all', '--scope', 'global', '--raw'], makeCwd(), scopeEnv(tmpDir('cap-cli-home-')));
|
||||
assert.equal(r.success, true, `update --all failed: ${r.error || r.output}`);
|
||||
const o = parse(r.output);
|
||||
assert.deepEqual(o.updated, [], 'no installed capabilities → empty updated list');
|
||||
@@ -599,7 +599,7 @@ describe('capability (argument + empty-state handling)', () => {
|
||||
test('a flag value that looks like another flag is rejected (no value swallowing)', () => {
|
||||
const src = writeCapSource('flagcap');
|
||||
// `--integrity --scope` — the value after --integrity is itself a flag, which must error, not be consumed.
|
||||
const r = runGsdTools(['capability', 'install', src, '--integrity', '--scope', 'global'], makeCwd(), scopeEnv(tmpDir('cap-cli-home-')));
|
||||
const r = runMsdTools(['capability', 'install', src, '--integrity', '--scope', 'global'], makeCwd(), scopeEnv(tmpDir('cap-cli-home-')));
|
||||
assert.equal(r.success, false);
|
||||
assert.match(`${r.error}\n${r.output}`, /Missing value for --integrity/i);
|
||||
});
|
||||
@@ -613,7 +613,7 @@ describe('capability list (corrupt ledger fail-closed — site A)', () => {
|
||||
fs.mkdirSync(home, { recursive: true });
|
||||
// Write a corrupt (unparseable) ledger file in the global scope location.
|
||||
fs.writeFileSync(ledgerPath(home), '{ broken json ---');
|
||||
const r = runGsdTools(['capability', 'list', '--json', '--scope', 'global'], makeCwd(), scopeEnv(home));
|
||||
const r = runMsdTools(['capability', 'list', '--json', '--scope', 'global'], makeCwd(), scopeEnv(home));
|
||||
// Must exit non-zero (fail-closed — finding 19). A silent exit-0 is not acceptable.
|
||||
assert.equal(r.success, false, 'capability list must exit non-zero when the ledger is corrupt (fail-closed)');
|
||||
const combined = `${r.error}\n${r.output}`;
|
||||
@@ -624,13 +624,13 @@ describe('capability list (corrupt ledger fail-closed — site A)', () => {
|
||||
test('capability list --scope global: healthy global + corrupt project → exits zero (finding-8)', () => {
|
||||
// When --scope global is given, only the global ledger is read.
|
||||
// A corrupt project ledger must not block a global-only list.
|
||||
// Project scope runtimeDir = cwd (where .gsd-capabilities.json would live).
|
||||
// Project scope runtimeDir = cwd (where .msd-capabilities.json would live).
|
||||
const home = tmpDir('cap-cli-home-list-scoped-');
|
||||
fs.mkdirSync(home, { recursive: true });
|
||||
const cwd = makeCwd();
|
||||
// Write a corrupt ledger at the project scope location (cwd/.gsd-capabilities.json).
|
||||
fs.writeFileSync(path.join(cwd, '.gsd-capabilities.json'), '{ broken project ledger ---');
|
||||
const r = runGsdTools(['capability', 'list', '--json', '--scope', 'global'], cwd, scopeEnv(home));
|
||||
// Write a corrupt ledger at the project scope location (cwd/.msd-capabilities.json).
|
||||
fs.writeFileSync(path.join(cwd, '.msd-capabilities.json'), '{ broken project ledger ---');
|
||||
const r = runMsdTools(['capability', 'list', '--json', '--scope', 'global'], cwd, scopeEnv(home));
|
||||
// Global scope is healthy (no ledger = null = fine). Only the global scope is read.
|
||||
assert.equal(r.success, true, `list --scope global must succeed when only the project ledger is corrupt; got: ${r.error || r.output}`);
|
||||
const rows = parse(r.output);
|
||||
@@ -643,9 +643,9 @@ describe('capability list (corrupt ledger fail-closed — site A)', () => {
|
||||
const home = tmpDir('cap-cli-home-list-proj-corrupt-');
|
||||
fs.mkdirSync(home, { recursive: true });
|
||||
const cwd = makeCwd();
|
||||
// Project scope runtimeDir = cwd, so corrupt ledger goes at cwd/.gsd-capabilities.json.
|
||||
fs.writeFileSync(path.join(cwd, '.gsd-capabilities.json'), '{ broken project ledger ---');
|
||||
const r = runGsdTools(['capability', 'list', '--json', '--scope', 'project'], cwd, scopeEnv(home));
|
||||
// Project scope runtimeDir = cwd, so corrupt ledger goes at cwd/.msd-capabilities.json.
|
||||
fs.writeFileSync(path.join(cwd, '.msd-capabilities.json'), '{ broken project ledger ---');
|
||||
const r = runMsdTools(['capability', 'list', '--json', '--scope', 'project'], cwd, scopeEnv(home));
|
||||
assert.equal(r.success, false, 'list --scope project must fail when the project ledger is corrupt');
|
||||
assert.match(`${r.error}\n${r.output}`, /corrupt|blocked/i, 'must mention corruption');
|
||||
});
|
||||
@@ -657,7 +657,7 @@ describe('capability remove (corrupt ledger fail-closed — site C)', () => {
|
||||
fs.mkdirSync(home, { recursive: true });
|
||||
// Write a corrupt (unparseable) ledger file so the scope has one.
|
||||
fs.writeFileSync(ledgerPath(home), '{ broken json ---');
|
||||
const r = runGsdTools(['capability', 'remove', 'some-cap', '--scope', 'global'], makeCwd(), scopeEnv(home));
|
||||
const r = runMsdTools(['capability', 'remove', 'some-cap', '--scope', 'global'], makeCwd(), scopeEnv(home));
|
||||
assert.equal(r.success, false, 'must exit non-zero on corrupt ledger');
|
||||
// Must NOT silently report "not installed" — that would hide the corruption.
|
||||
assert.doesNotMatch(`${r.error}\n${r.output}`, /not installed/i,
|
||||
@@ -669,13 +669,13 @@ describe('capability remove (corrupt ledger fail-closed — site C)', () => {
|
||||
test('capability remove first-party id on corrupt ledger surfaces corruption, not first-party error (finding-7)', () => {
|
||||
// Finding 7: with readLedger (old), a corrupt ledger + first-party id reports "first-party cannot be removed"
|
||||
// (hiding the corruption). With readLedgerStrict, corruption is surfaced first.
|
||||
const reg = require('../gsd-core/bin/lib/capability-registry.cjs');
|
||||
const reg = require('../msd-core/bin/lib/capability-registry.cjs');
|
||||
const firstParty = Object.keys(reg.capabilities)[0];
|
||||
const home = tmpDir('cap-cli-home-f7-');
|
||||
fs.mkdirSync(home, { recursive: true });
|
||||
// Corrupt the ledger.
|
||||
fs.writeFileSync(ledgerPath(home), '{ broken json ---');
|
||||
const r = runGsdTools(['capability', 'remove', firstParty, '--scope', 'global'], makeCwd(), scopeEnv(home));
|
||||
const r = runMsdTools(['capability', 'remove', firstParty, '--scope', 'global'], makeCwd(), scopeEnv(home));
|
||||
assert.equal(r.success, false, 'must exit non-zero on corrupt ledger');
|
||||
// Must NOT report "first-party" (which would hide the corruption).
|
||||
assert.doesNotMatch(`${r.error}\n${r.output}`, /first-party/i,
|
||||
@@ -688,9 +688,9 @@ describe('capability remove (corrupt ledger fail-closed — site C)', () => {
|
||||
const home = tmpDir('cap-cli-home-remove-proj-corrupt-');
|
||||
fs.mkdirSync(home, { recursive: true });
|
||||
const cwd = makeCwd();
|
||||
// Project scope runtimeDir = cwd, so corrupt ledger goes at cwd/.gsd-capabilities.json.
|
||||
fs.writeFileSync(path.join(cwd, '.gsd-capabilities.json'), '{ broken project json ---');
|
||||
const r = runGsdTools(['capability', 'remove', 'some-cap', '--scope', 'project'], cwd, scopeEnv(home));
|
||||
// Project scope runtimeDir = cwd, so corrupt ledger goes at cwd/.msd-capabilities.json.
|
||||
fs.writeFileSync(path.join(cwd, '.msd-capabilities.json'), '{ broken project json ---');
|
||||
const r = runMsdTools(['capability', 'remove', 'some-cap', '--scope', 'project'], cwd, scopeEnv(home));
|
||||
assert.equal(r.success, false, 'must exit non-zero on corrupt project ledger');
|
||||
assert.match(`${r.error}\n${r.output}`, /corrupt|blocked/i, 'must mention corruption or blocked');
|
||||
});
|
||||
@@ -704,7 +704,7 @@ describe('capability update (corrupt ledger fail-closed)', () => {
|
||||
fs.mkdirSync(home, { recursive: true });
|
||||
// Write a corrupt (unparseable) ledger file.
|
||||
fs.writeFileSync(ledgerPath(home), '{ broken json ---');
|
||||
const r = runGsdTools(['capability', 'update', 'some-cap', '--scope', 'global'], makeCwd(), scopeEnv(home));
|
||||
const r = runMsdTools(['capability', 'update', 'some-cap', '--scope', 'global'], makeCwd(), scopeEnv(home));
|
||||
assert.equal(r.success, false, 'must exit non-zero on corrupt ledger');
|
||||
// Must NOT report "not installed" — that would hide the corruption silently.
|
||||
assert.doesNotMatch(`${r.error}\n${r.output}`, /not installed/i,
|
||||
@@ -718,7 +718,7 @@ describe('capability update (corrupt ledger fail-closed)', () => {
|
||||
fs.mkdirSync(home, { recursive: true });
|
||||
// Write a corrupt (unparseable) ledger file.
|
||||
fs.writeFileSync(ledgerPath(home), '{ broken json ---');
|
||||
const r = runGsdTools(['capability', 'update', '--all', '--scope', 'global'], makeCwd(), scopeEnv(home));
|
||||
const r = runMsdTools(['capability', 'update', '--all', '--scope', 'global'], makeCwd(), scopeEnv(home));
|
||||
assert.equal(r.success, false, 'must exit non-zero on corrupt ledger for --all');
|
||||
assert.match(`${r.error}\n${r.output}`, /corrupt|blocked/i,
|
||||
'must mention corruption or blocked; not silently succeed');
|
||||
@@ -733,14 +733,14 @@ describe('capability update --all (UX-1: structured stdout on partial failure)',
|
||||
// Install an executable capability, then change its exec surface so the update needs re-consent
|
||||
// and (without --yes) ABORTS — a partial-failure --all run.
|
||||
const src = writeCapSource('ux1cap', { hooks: [{ event: 'PostToolUse', script: 'hooks/a.js' }] });
|
||||
assert.equal(runGsdTools(['capability', 'install', src, '--scope', 'global', '--yes', '--raw'], makeCwd(), scopeEnv(home)).success, true);
|
||||
assert.equal(runMsdTools(['capability', 'install', src, '--scope', 'global', '--yes', '--raw'], makeCwd(), scopeEnv(home)).success, true);
|
||||
const cap = JSON.parse(fs.readFileSync(path.join(src, 'capability.json'), 'utf8'));
|
||||
cap.version = '2.0.0';
|
||||
cap.hooks = [{ event: 'PostToolUse', script: 'hooks/b.js' }];
|
||||
fs.writeFileSync(path.join(src, 'capability.json'), JSON.stringify(cap, null, 2));
|
||||
fs.writeFileSync(path.join(src, 'hooks', 'b.js'), '// artifact');
|
||||
|
||||
const r = runGsdTools(['capability', 'update', '--all', '--scope', 'global', '--raw'], makeCwd(), scopeEnv(home));
|
||||
const r = runMsdTools(['capability', 'update', '--all', '--scope', 'global', '--raw'], makeCwd(), scopeEnv(home));
|
||||
// Non-zero exit (partial failure).
|
||||
assert.equal(r.success, false, 'a partial --all failure must exit non-zero');
|
||||
// STRUCTURED data on STDOUT (not embedded in the error string) — UX-1.
|
||||
@@ -758,8 +758,8 @@ describe('capability list (UX-3: corrupt-scope error names the scope)', () => {
|
||||
const home = tmpDir('cap-cli-home-ux3-');
|
||||
fs.mkdirSync(home, { recursive: true });
|
||||
const cwd = makeCwd();
|
||||
fs.writeFileSync(path.join(cwd, '.gsd-capabilities.json'), '{ broken project ledger ---');
|
||||
const r = runGsdTools(['capability', 'list', '--json', '--scope', 'project'], cwd, scopeEnv(home));
|
||||
fs.writeFileSync(path.join(cwd, '.msd-capabilities.json'), '{ broken project ledger ---');
|
||||
const r = runMsdTools(['capability', 'list', '--json', '--scope', 'project'], cwd, scopeEnv(home));
|
||||
assert.equal(r.success, false, 'list --scope project must fail when the project ledger is corrupt');
|
||||
assert.match(`${r.error}\n${r.output}`, /\bproject\b/,
|
||||
`the corrupt-scope error must name the scope ("project"); got: ${r.error}\n${r.output}`);
|
||||
@@ -770,7 +770,7 @@ describe('capability install (UX-5: structured aborted/requiresConsent on stdout
|
||||
test('UX-5: an executable install WITHOUT --yes in --raw mode emits a structured aborted envelope on stdout', () => {
|
||||
const home = tmpDir('cap-cli-home-ux5-');
|
||||
const src = writeCapSource('ux5cap', { hooks: [{ event: 'PostToolUse', script: 'hooks/run.js' }] });
|
||||
const r = runGsdTools(['capability', 'install', src, '--scope', 'global', '--raw'], makeCwd(), scopeEnv(home));
|
||||
const r = runMsdTools(['capability', 'install', src, '--scope', 'global', '--raw'], makeCwd(), scopeEnv(home));
|
||||
assert.equal(r.success, false, 'an executable install without --yes must exit non-zero');
|
||||
assert.ok(r.output && r.output.length > 0, 'stdout must NOT be empty in raw aborted mode (UX-5)');
|
||||
const out = JSON.parse(r.output);
|
||||
@@ -794,7 +794,7 @@ describe('capability update (UX-6: normalized per-entry fields)', () => {
|
||||
},
|
||||
};
|
||||
fs.writeFileSync(ledgerPath(home), JSON.stringify(ledger, null, 2));
|
||||
const r = runGsdTools(['capability', 'update', '--all', '--scope', 'global', '--raw'], makeCwd(), scopeEnv(home));
|
||||
const r = runMsdTools(['capability', 'update', '--all', '--scope', 'global', '--raw'], makeCwd(), scopeEnv(home));
|
||||
// Partial failure (the blocked entry) → non-zero, structured stdout.
|
||||
assert.equal(r.success, false, 'a blocked --all entry must exit non-zero');
|
||||
const parsed = JSON.parse(r.output);
|
||||
@@ -819,7 +819,7 @@ describe('capability install (UX-2: reconcile warnings surfaced on stderr)', ()
|
||||
fs.mkdirSync(home, { recursive: true });
|
||||
fs.writeFileSync(ledgerPath(home), '{ broken json ---');
|
||||
const src = writeCapSource('ux2cap');
|
||||
const r = runGsdTools(['capability', 'install', src, '--scope', 'global'], makeCwd(), scopeEnv(home));
|
||||
const r = runMsdTools(['capability', 'install', src, '--scope', 'global'], makeCwd(), scopeEnv(home));
|
||||
assert.equal(r.success, false, 'install on a corrupt ledger must exit non-zero');
|
||||
// The reconcile report's warning must be surfaced with its distinctive prefix on stderr — proving
|
||||
// the report was captured and emitted, not discarded in a bare try/catch.
|
||||
@@ -831,7 +831,7 @@ describe('capability install (UX-2: reconcile warnings surfaced on stderr)', ()
|
||||
// ─── #1459: user-owned consent store (trust list/revoke; inactive marking) ────
|
||||
|
||||
describe('capability consent store (#1459)', () => {
|
||||
const consentMod = require('../gsd-core/bin/lib/capability-consent.cjs');
|
||||
const consentMod = require('../msd-core/bin/lib/capability-consent.cjs');
|
||||
|
||||
/** A project cwd that is its OWN project root (.planning) — project scope runtimeDir === cwd. */
|
||||
function projectCwd() {
|
||||
@@ -841,15 +841,15 @@ describe('capability consent store (#1459)', () => {
|
||||
return cwd;
|
||||
}
|
||||
|
||||
test('project install lands the consent record under GSD_HOME, NOT under the project cwd', () => {
|
||||
test('project install lands the consent record under MSD_HOME, NOT under the project cwd', () => {
|
||||
const home = tmpDir('cap-cli-home-');
|
||||
const cwd = projectCwd();
|
||||
const src = writeCapSource('proj-consent-cap');
|
||||
const r = runGsdTools(['capability', 'install', src, '--scope', 'project', '--raw'], cwd, scopeEnv(home));
|
||||
const r = runMsdTools(['capability', 'install', src, '--scope', 'project', '--raw'], cwd, scopeEnv(home));
|
||||
assert.equal(r.success, true, `${r.error}\n${r.output}`);
|
||||
// Consent store is under the GSD_HOME-sandboxed home, not in the project repo.
|
||||
assert.ok(fs.existsSync(consentMod.consentStorePath(home)), 'consent store under GSD_HOME');
|
||||
assert.ok(!fs.existsSync(path.join(cwd, '.gsd', 'consent.json')), 'NOT written under the project cwd');
|
||||
// Consent store is under the MSD_HOME-sandboxed home, not in the project repo.
|
||||
assert.ok(fs.existsSync(consentMod.consentStorePath(home)), 'consent store under MSD_HOME');
|
||||
assert.ok(!fs.existsSync(path.join(cwd, '.msd', 'consent.json')), 'NOT written under the project cwd');
|
||||
const store = consentMod.readConsentStore(home);
|
||||
assert.equal(Object.keys(store.records).length, 1, 'one consent record written');
|
||||
});
|
||||
@@ -858,8 +858,8 @@ describe('capability consent store (#1459)', () => {
|
||||
const home = tmpDir('cap-cli-home-');
|
||||
const cwd = projectCwd();
|
||||
const src = writeCapSource('proj-active-cap');
|
||||
assert.equal(runGsdTools(['capability', 'install', src, '--scope', 'project', '--raw'], cwd, scopeEnv(home)).success, true);
|
||||
const r = runGsdTools(['capability', 'list', '--json', '--scope', 'project'], cwd, scopeEnv(home));
|
||||
assert.equal(runMsdTools(['capability', 'install', src, '--scope', 'project', '--raw'], cwd, scopeEnv(home)).success, true);
|
||||
const r = runMsdTools(['capability', 'list', '--json', '--scope', 'project'], cwd, scopeEnv(home));
|
||||
assert.equal(r.success, true, `${r.error}\n${r.output}`);
|
||||
const row = parse(r.output).find((x) => x.id === 'proj-active-cap');
|
||||
assert.ok(row, 'consented project cap is listed');
|
||||
@@ -871,18 +871,18 @@ describe('capability consent store (#1459)', () => {
|
||||
const cwd = projectCwd();
|
||||
// Plant a committed-looking project ledger + bundle WITHOUT going through install (no consent).
|
||||
const capId = 'planted-cap';
|
||||
const dir = path.join(cwd, '.gsd', 'capabilities', capId);
|
||||
const dir = path.join(cwd, '.msd', 'capabilities', capId);
|
||||
fs.mkdirSync(dir, { recursive: true });
|
||||
fs.writeFileSync(path.join(dir, 'capability.json'), JSON.stringify({
|
||||
id: capId, role: 'feature', version: '1.0.0', title: capId, description: 'd', tier: 'standard',
|
||||
requires: [], runtimeCompat: { supported: ['*'], unsupported: [] }, skills: [], agents: [],
|
||||
hooks: [], config: {}, steps: [], contributions: [], gates: [],
|
||||
}));
|
||||
fs.writeFileSync(path.join(cwd, '.gsd-capabilities.json'), JSON.stringify({
|
||||
fs.writeFileSync(path.join(cwd, '.msd-capabilities.json'), JSON.stringify({
|
||||
version: '1', updatedAt: '2026-01-01T00:00:00Z',
|
||||
entries: { [capId]: { id: capId, version: '1.0.0', source: 's', integrity: '', files: [], sharedEdits: [] } },
|
||||
}));
|
||||
const r = runGsdTools(['capability', 'list', '--json', '--scope', 'project'], cwd, scopeEnv(home));
|
||||
const r = runMsdTools(['capability', 'list', '--json', '--scope', 'project'], cwd, scopeEnv(home));
|
||||
assert.equal(r.success, true, `${r.error}\n${r.output}`);
|
||||
const row = parse(r.output).find((x) => x.id === capId);
|
||||
assert.ok(row, 'planted cap is still LISTED (discovered)');
|
||||
@@ -891,7 +891,7 @@ describe('capability consent store (#1459)', () => {
|
||||
});
|
||||
|
||||
test('IC-02: `capability list` marks inactive via the STRUCTURAL kind discriminant (not the reason prose)', () => {
|
||||
// revert-fails: if gsd-tools `list` filtered on /consent/i.test(reason) (the old prose match) AND
|
||||
// revert-fails: if msd-tools `list` filtered on /consent/i.test(reason) (the old prose match) AND
|
||||
// the loader's inactive warning omitted `kind`, this still passes by accident. To make it
|
||||
// anti-vacuous we (a) assert the loader emits the STRUCTURAL kind:'unconsented' (the discriminant
|
||||
// the filter must key on) and (b) assert the list marks the row inactive. Reverting the filter to
|
||||
@@ -901,32 +901,32 @@ describe('capability consent store (#1459)', () => {
|
||||
const home = tmpDir('cap-cli-home-');
|
||||
const cwd = projectCwd();
|
||||
const capId = 'kind-inactive-cap';
|
||||
const dir = path.join(cwd, '.gsd', 'capabilities', capId);
|
||||
const dir = path.join(cwd, '.msd', 'capabilities', capId);
|
||||
fs.mkdirSync(dir, { recursive: true });
|
||||
fs.writeFileSync(path.join(dir, 'capability.json'), JSON.stringify({
|
||||
id: capId, role: 'feature', version: '1.0.0', title: capId, description: 'd', tier: 'standard',
|
||||
requires: [], runtimeCompat: { supported: ['*'], unsupported: [] }, skills: [], agents: [],
|
||||
hooks: [], config: {}, steps: [], contributions: [], gates: [],
|
||||
}));
|
||||
fs.writeFileSync(path.join(cwd, '.gsd-capabilities.json'), JSON.stringify({
|
||||
fs.writeFileSync(path.join(cwd, '.msd-capabilities.json'), JSON.stringify({
|
||||
version: '1', updatedAt: '2026-01-01T00:00:00Z',
|
||||
entries: { [capId]: { id: capId, version: '1.0.0', source: 's', integrity: '', files: [], sharedEdits: [] } },
|
||||
}));
|
||||
// (a) The loader's overlay warning carries the structural discriminant kind:'unconsented'.
|
||||
const loader = require('../gsd-core/bin/lib/capability-loader.cjs');
|
||||
const savedHome = process.env.GSD_HOME;
|
||||
const loader = require('../msd-core/bin/lib/capability-loader.cjs');
|
||||
const savedHome = process.env.MSD_HOME;
|
||||
let reg;
|
||||
try {
|
||||
process.env.GSD_HOME = home;
|
||||
reg = loader.loadRegistry({ includeInstalled: true, cwd, gsdHome: home });
|
||||
process.env.MSD_HOME = home;
|
||||
reg = loader.loadRegistry({ includeInstalled: true, cwd, msdHome: home });
|
||||
} finally {
|
||||
if (savedHome === undefined) delete process.env.GSD_HOME; else process.env.GSD_HOME = savedHome;
|
||||
if (savedHome === undefined) delete process.env.MSD_HOME; else process.env.MSD_HOME = savedHome;
|
||||
}
|
||||
const warn = (reg._overlay && reg._overlay.warnings || []).find((w) => w.id === capId);
|
||||
assert.ok(warn, 'loader records a discovered-but-inactive warning for the unconsented cap');
|
||||
assert.equal(warn.kind, 'unconsented', 'the warning carries the structural kind discriminant');
|
||||
// (b) The CLI list marks the row inactive (via the kind-keyed filter).
|
||||
const r = runGsdTools(['capability', 'list', '--json', '--scope', 'project'], cwd, scopeEnv(home));
|
||||
const r = runMsdTools(['capability', 'list', '--json', '--scope', 'project'], cwd, scopeEnv(home));
|
||||
assert.equal(r.success, true, `${r.error}\n${r.output}`);
|
||||
const row = parse(r.output).find((x) => x.id === capId);
|
||||
assert.ok(row && row.status === 'inactive', 'list marks the unconsented cap inactive via kind');
|
||||
@@ -938,8 +938,8 @@ describe('capability consent store (#1459)', () => {
|
||||
const home = tmpDir('cap-cli-home-');
|
||||
const cwd = projectCwd();
|
||||
const src = writeCapSource('trust-fields-cap');
|
||||
assert.equal(runGsdTools(['capability', 'install', src, '--scope', 'project', '--raw'], cwd, scopeEnv(home)).success, true);
|
||||
const r = runGsdTools(['capability', 'trust', 'list', '--json'], cwd, scopeEnv(home));
|
||||
assert.equal(runMsdTools(['capability', 'install', src, '--scope', 'project', '--raw'], cwd, scopeEnv(home)).success, true);
|
||||
const r = runMsdTools(['capability', 'trust', 'list', '--json'], cwd, scopeEnv(home));
|
||||
assert.equal(r.success, true, `${r.error}\n${r.output}`);
|
||||
const row = parse(r.output).find((x) => x.id === 'trust-fields-cap');
|
||||
assert.ok(row, 'consent record listed');
|
||||
@@ -951,8 +951,8 @@ describe('capability consent store (#1459)', () => {
|
||||
const home = tmpDir('cap-cli-home-');
|
||||
const cwd = projectCwd();
|
||||
const src = writeCapSource('trust-list-cap');
|
||||
assert.equal(runGsdTools(['capability', 'install', src, '--scope', 'project', '--raw'], cwd, scopeEnv(home)).success, true);
|
||||
const r = runGsdTools(['capability', 'trust', 'list', '--json'], cwd, scopeEnv(home));
|
||||
assert.equal(runMsdTools(['capability', 'install', src, '--scope', 'project', '--raw'], cwd, scopeEnv(home)).success, true);
|
||||
const r = runMsdTools(['capability', 'trust', 'list', '--json'], cwd, scopeEnv(home));
|
||||
assert.equal(r.success, true, `${r.error}\n${r.output}`);
|
||||
const rows = parse(r.output);
|
||||
assert.ok(Array.isArray(rows) && rows.some((x) => x.id === 'trust-list-cap' && x.scope === 'project'), 'consent record listed');
|
||||
@@ -962,13 +962,13 @@ describe('capability consent store (#1459)', () => {
|
||||
const home = tmpDir('cap-cli-home-');
|
||||
const cwd = projectCwd();
|
||||
const src = writeCapSource('trust-revoke-cap');
|
||||
assert.equal(runGsdTools(['capability', 'install', src, '--scope', 'project', '--raw'], cwd, scopeEnv(home)).success, true);
|
||||
assert.equal(runMsdTools(['capability', 'install', src, '--scope', 'project', '--raw'], cwd, scopeEnv(home)).success, true);
|
||||
// Revoke.
|
||||
const rev = runGsdTools(['capability', 'trust', 'revoke', 'trust-revoke-cap', '--raw'], cwd, scopeEnv(home));
|
||||
const rev = runMsdTools(['capability', 'trust', 'revoke', 'trust-revoke-cap', '--raw'], cwd, scopeEnv(home));
|
||||
assert.equal(rev.success, true, `${rev.error}\n${rev.output}`);
|
||||
assert.equal(consentMod.readConsentStore(home).records && Object.keys(consentMod.readConsentStore(home).records).length, 0, 'record removed');
|
||||
// The cap (bundle + ledger still present) now lists inactive.
|
||||
const list = runGsdTools(['capability', 'list', '--json', '--scope', 'project'], cwd, scopeEnv(home));
|
||||
const list = runMsdTools(['capability', 'list', '--json', '--scope', 'project'], cwd, scopeEnv(home));
|
||||
const row = parse(list.output).find((x) => x.id === 'trust-revoke-cap');
|
||||
assert.ok(row && row.status === 'inactive', 'after revoke the cap is inactive');
|
||||
});
|
||||
@@ -981,7 +981,7 @@ describe('capability consent store (#1459)', () => {
|
||||
const home = tmpDir('cap-cli-home-');
|
||||
const cwd = projectCwd();
|
||||
const src = writeCapSource('trust-locked-cap');
|
||||
assert.equal(runGsdTools(['capability', 'install', src, '--scope', 'project', '--raw'], cwd, scopeEnv(home)).success, true, 'install (records consent)');
|
||||
assert.equal(runMsdTools(['capability', 'install', src, '--scope', 'project', '--raw'], cwd, scopeEnv(home)).success, true, 'install (records consent)');
|
||||
// Plant a FRESH, well-formed consent-store lock owned by THIS test process. A fresh lock (ts ≈ now,
|
||||
// age <= LOCK_STALE_MS) is NEVER stolen by the shared lock primitive, so the subprocess's bounded
|
||||
// waitForFresh budget exhausts and acquireConsentLock returns null → revokeProjectConsent throws.
|
||||
@@ -990,7 +990,7 @@ describe('capability consent store (#1459)', () => {
|
||||
const os = require('node:os');
|
||||
fs.writeFileSync(lockPath, JSON.stringify({ token: 'test-holder', pid: process.pid, hostname: os.hostname(), startTime: null, ts: Date.now() }), { flag: 'wx' });
|
||||
try {
|
||||
const rev = runGsdTools(['capability', 'trust', 'revoke', 'trust-locked-cap', '--raw'], cwd, scopeEnv(home));
|
||||
const rev = runMsdTools(['capability', 'trust', 'revoke', 'trust-locked-cap', '--raw'], cwd, scopeEnv(home));
|
||||
assert.equal(rev.success, false, 'a lock-held revoke exits non-zero');
|
||||
const combined = `${rev.error}\n${rev.output}`;
|
||||
assert.match(combined, /consent-store lock|consent store lock|another capability operation/i, 'clean, actionable lock message');
|
||||
@@ -1001,14 +1001,14 @@ describe('capability consent store (#1459)', () => {
|
||||
});
|
||||
|
||||
test('convergence-2: `capability list` with a FIFO project capability.json does not hang; the entry is omitted, exit clean', { skip: process.platform === 'win32' }, () => {
|
||||
// revert-fails: with the raw fs.readFileSync(path,'utf8') in the gsd-tools `list` metadata read,
|
||||
// revert-fails: with the raw fs.readFileSync(path,'utf8') in the msd-tools `list` metadata read,
|
||||
// reading the FIFO capability.json BLOCKS forever (no writer) → `capability list` hangs and the test
|
||||
// times out (runGsdTools never returns). The bounded reader (readSmallRegularFile) fstat-rejects the
|
||||
// times out (runMsdTools never returns). The bounded reader (readSmallRegularFile) fstat-rejects the
|
||||
// FIFO BEFORE reading, so the list omits that entry and exits cleanly.
|
||||
const home = tmpDir('cap-cli-home-');
|
||||
const cwd = projectCwd();
|
||||
const capId = 'fifo-list-cap';
|
||||
const dir = path.join(cwd, '.gsd', 'capabilities', capId);
|
||||
const dir = path.join(cwd, '.msd', 'capabilities', capId);
|
||||
fs.mkdirSync(dir, { recursive: true });
|
||||
const { runHook } = require('./helpers/process-seam.cjs');
|
||||
const { throwIfFailed } = require('./helpers/git-fixture.cjs');
|
||||
@@ -1018,11 +1018,11 @@ describe('capability consent store (#1459)', () => {
|
||||
`mkfifo ${fifoPath}`,
|
||||
);
|
||||
// A committed project ledger so the list iterates this entry (the FIFO is on the metadata-read path).
|
||||
fs.writeFileSync(path.join(cwd, '.gsd-capabilities.json'), JSON.stringify({
|
||||
fs.writeFileSync(path.join(cwd, '.msd-capabilities.json'), JSON.stringify({
|
||||
version: '1', updatedAt: '2026-01-01T00:00:00Z',
|
||||
entries: { [capId]: { id: capId, version: '1.0.0', source: 's', integrity: '', files: [], sharedEdits: [] } },
|
||||
}));
|
||||
const r = runGsdTools(['capability', 'list', '--json', '--scope', 'project'], cwd, scopeEnv(home));
|
||||
const r = runMsdTools(['capability', 'list', '--json', '--scope', 'project'], cwd, scopeEnv(home));
|
||||
assert.equal(r.success, true, `list must exit cleanly (not hang) on a FIFO manifest: ${r.error}\n${r.output}`);
|
||||
const rows = parse(r.output);
|
||||
// The entry is LISTED (the ledger knows it) but with no metadata (null role/tier/title) since the
|
||||
@@ -1051,21 +1051,21 @@ describe('capability consent store (#1459)', () => {
|
||||
entries: { [id]: { id, version: '1.0.0', source: 's', integrity: '', files: [], sharedEdits: [] } },
|
||||
});
|
||||
// CONTROL: a small valid manifest lists WITH metadata.
|
||||
const ctrlDir = path.join(ctrlCwd, '.gsd', 'capabilities', 'small-list-cap');
|
||||
const ctrlDir = path.join(ctrlCwd, '.msd', 'capabilities', 'small-list-cap');
|
||||
fs.mkdirSync(ctrlDir, { recursive: true });
|
||||
fs.writeFileSync(path.join(ctrlDir, 'capability.json'), mkManifest('small-list-cap'));
|
||||
fs.writeFileSync(path.join(ctrlCwd, '.gsd-capabilities.json'), ledgerFor('small-list-cap'));
|
||||
const ctrl = runGsdTools(['capability', 'list', '--json', '--scope', 'project'], ctrlCwd, scopeEnv(tmpDir('cap-cli-home-')));
|
||||
fs.writeFileSync(path.join(ctrlCwd, '.msd-capabilities.json'), ledgerFor('small-list-cap'));
|
||||
const ctrl = runMsdTools(['capability', 'list', '--json', '--scope', 'project'], ctrlCwd, scopeEnv(tmpDir('cap-cli-home-')));
|
||||
assert.equal(ctrl.success, true, `${ctrl.error}\n${ctrl.output}`);
|
||||
const ctrlRow = parse(ctrl.output).find((x) => x.id === 'small-list-cap');
|
||||
assert.ok(ctrlRow && ctrlRow.role === 'feature' && ctrlRow.title === 'small-list-cap', 'CONTROL: a small manifest lists with metadata');
|
||||
// SUBJECT: an oversized manifest (>8 MiB) — bounded reader refuses; metadata dropped, no OOM.
|
||||
const capId = 'oversized-list-cap';
|
||||
const dir = path.join(cwd, '.gsd', 'capabilities', capId);
|
||||
const dir = path.join(cwd, '.msd', 'capabilities', capId);
|
||||
fs.mkdirSync(dir, { recursive: true });
|
||||
fs.writeFileSync(path.join(dir, 'capability.json'), mkManifest(capId, { description: 'x'.repeat(9 * 1024 * 1024) }));
|
||||
fs.writeFileSync(path.join(cwd, '.gsd-capabilities.json'), ledgerFor(capId));
|
||||
const r = runGsdTools(['capability', 'list', '--json', '--scope', 'project'], cwd, scopeEnv(home));
|
||||
fs.writeFileSync(path.join(cwd, '.msd-capabilities.json'), ledgerFor(capId));
|
||||
const r = runMsdTools(['capability', 'list', '--json', '--scope', 'project'], cwd, scopeEnv(home));
|
||||
assert.equal(r.success, true, `list must exit cleanly (not OOM) on an oversized manifest: ${r.error}\n${r.output}`);
|
||||
const row = parse(r.output).find((x) => x.id === capId);
|
||||
if (row) {
|
||||
@@ -1078,15 +1078,15 @@ describe('capability consent store (#1459)', () => {
|
||||
const home = tmpDir('cap-cli-home-');
|
||||
const cwd = projectCwd();
|
||||
const src = writeCapSource('proj-remove-cap');
|
||||
assert.equal(runGsdTools(['capability', 'install', src, '--scope', 'project', '--raw'], cwd, scopeEnv(home)).success, true);
|
||||
assert.equal(runMsdTools(['capability', 'install', src, '--scope', 'project', '--raw'], cwd, scopeEnv(home)).success, true);
|
||||
assert.equal(Object.keys(consentMod.readConsentStore(home).records).length, 1, 'consent present after install');
|
||||
const r = runGsdTools(['capability', 'remove', 'proj-remove-cap', '--scope', 'project', '--raw'], cwd, scopeEnv(home));
|
||||
const r = runMsdTools(['capability', 'remove', 'proj-remove-cap', '--scope', 'project', '--raw'], cwd, scopeEnv(home));
|
||||
assert.equal(r.success, true, `${r.error}\n${r.output}`);
|
||||
assert.equal(Object.keys(consentMod.readConsentStore(home).records).length, 0, 'remove revokes the consent record');
|
||||
});
|
||||
|
||||
test('an unknown trust subcommand errors with guidance', () => {
|
||||
const r = runGsdTools(['capability', 'trust', 'bogus'], makeCwd(), scopeEnv(tmpDir('cap-cli-home-')));
|
||||
const r = runMsdTools(['capability', 'trust', 'bogus'], makeCwd(), scopeEnv(tmpDir('cap-cli-home-')));
|
||||
assert.equal(r.success, false);
|
||||
assert.match(`${r.error}\n${r.output}`, /trust/i);
|
||||
});
|
||||
@@ -1099,7 +1099,7 @@ describe('issue-2322: capability set --runtime materializes an installed third-p
|
||||
* A conformant local capability source that declares one owned skill
|
||||
* (`skills: [stem]`) and ships that skill's already-authored SKILL.md
|
||||
* verbatim under `skills/<stem>/SKILL.md` — mirroring the real on-disk
|
||||
* shape `capability install` copies into `~/.gsd/capabilities/<id>/`.
|
||||
* shape `capability install` copies into `~/.msd/capabilities/<id>/`.
|
||||
*/
|
||||
function writeCapSourceWithSkill(id, stem, skillContent, { tier = 'standard' } = {}) {
|
||||
const src = tmpDir(`cap-cli-src-${id}-`);
|
||||
@@ -1139,7 +1139,7 @@ describe('issue-2322: capability set --runtime materializes an installed third-p
|
||||
const src = writeCapSourceWithSkill('my-thing', 'my-thing', AUTHORED);
|
||||
|
||||
// Step 2 of the issue repro: capability install ./capabilities/my-thing --yes
|
||||
const install = runGsdTools(['capability', 'install', src, '--scope', 'global', '--raw'], cwd, scopeEnv(home));
|
||||
const install = runMsdTools(['capability', 'install', src, '--scope', 'global', '--raw'], cwd, scopeEnv(home));
|
||||
assert.equal(install.success, true, `install failed: ${install.error || install.output}`);
|
||||
assert.equal(parse(install.output).status, 'installed');
|
||||
assert.ok(fs.existsSync(path.join(capDir(home, 'my-thing'), 'skills', 'my-thing', 'SKILL.md')), 'sanity: install copies the skill verbatim into the capabilities root');
|
||||
@@ -1149,8 +1149,8 @@ describe('issue-2322: capability set --runtime materializes an installed third-p
|
||||
// A global materialization needs one complete Runtime Surface provider.
|
||||
// This fixture exercises the supported legacy compatibility-marker path;
|
||||
// source-less global trees now fail closed and must be upgraded first.
|
||||
fs.writeFileSync(path.join(rcd, '.gsd-source'), path.join(__dirname, '..', 'commands', 'gsd') + '\n');
|
||||
const setResult = runGsdTools(
|
||||
fs.writeFileSync(path.join(rcd, '.msd-source'), path.join(__dirname, '..', 'commands', 'msd') + '\n');
|
||||
const setResult = runMsdTools(
|
||||
['capability', 'set', 'my-thing', '--runtime', 'claude', '--scope', 'global', '--config-dir', rcd, '--raw'],
|
||||
cwd,
|
||||
scopeEnv(home),
|
||||
@@ -1165,11 +1165,11 @@ describe('issue-2322: capability set --runtime materializes an installed third-p
|
||||
assert.equal(myThing.installed, true, 'installed:true (matches issue step 4)');
|
||||
assert.equal(myThing.surfaced, true, 'surfaced:true (matches issue step 4 — #2045 registry union already works)');
|
||||
|
||||
// Step 5 of the issue repro: ls ~/.claude/skills/gsd-my-thing/ -> No such file or directory (THE BUG)
|
||||
const stagedPath = path.join(rcd, 'skills', 'gsd-my-thing', 'SKILL.md');
|
||||
// Step 5 of the issue repro: ls ~/.claude/skills/msd-my-thing/ -> No such file or directory (THE BUG)
|
||||
const stagedPath = path.join(rcd, 'skills', 'msd-my-thing', 'SKILL.md');
|
||||
assert.ok(
|
||||
fs.existsSync(stagedPath),
|
||||
`gsd-my-thing/SKILL.md must exist under ${rcd}/skills/ — capability state reports surfaced:true but nothing was materialized to disk (#2322)`,
|
||||
`msd-my-thing/SKILL.md must exist under ${rcd}/skills/ — capability state reports surfaced:true but nothing was materialized to disk (#2322)`,
|
||||
);
|
||||
const staged = fs.readFileSync(stagedPath, 'utf8');
|
||||
assert.notEqual(
|
||||
@@ -1191,7 +1191,7 @@ describe('#3929: install-time validation is seeded with first-party + installed
|
||||
// capability and the (now-live) tier-monotone check forbids standard
|
||||
// requiring full, so the candidate must be full to require it at all.
|
||||
const src = writeCapSource('needs-fp', { requires: ['tdd'], tier: 'full' });
|
||||
const r = runGsdTools(['capability', 'install', src, '--scope', 'global', '--raw'], makeCwd(), scopeEnv(home));
|
||||
const r = runMsdTools(['capability', 'install', src, '--scope', 'global', '--raw'], makeCwd(), scopeEnv(home));
|
||||
assert.equal(r.success, true, `install must succeed: tdd is first-party — got: ${r.error || r.output}`);
|
||||
const o = parse(r.output);
|
||||
assert.equal(o.status, 'installed');
|
||||
@@ -1201,7 +1201,7 @@ describe('#3929: install-time validation is seeded with first-party + installed
|
||||
test('requires on a genuinely missing capability still refuses', () => {
|
||||
const home = tmpDir('cap-cli-3929-missing-');
|
||||
const src = writeCapSource('needs-missing', { requires: ['no-such-capability-xyz'] });
|
||||
const r = runGsdTools(['capability', 'install', src, '--scope', 'global'], makeCwd(), scopeEnv(home));
|
||||
const r = runMsdTools(['capability', 'install', src, '--scope', 'global'], makeCwd(), scopeEnv(home));
|
||||
assert.equal(r.success, false, 'a genuinely missing requires id must still be refused');
|
||||
assert.match(`${r.error}\n${r.output}`, /no-such-capability-xyz/);
|
||||
assert.equal(readLedgerEntry(home, 'needs-missing'), null, 'no ledger entry');
|
||||
@@ -1210,11 +1210,11 @@ describe('#3929: install-time validation is seeded with first-party + installed
|
||||
test('requires on an installed overlay capability resolves at install time', () => {
|
||||
const home = tmpDir('cap-cli-3929-overlay-');
|
||||
const srcA = writeCapSource('overlay-a');
|
||||
const first = runGsdTools(['capability', 'install', srcA, '--scope', 'global', '--raw'], makeCwd(), scopeEnv(home));
|
||||
const first = runMsdTools(['capability', 'install', srcA, '--scope', 'global', '--raw'], makeCwd(), scopeEnv(home));
|
||||
assert.equal(first.success, true, `seed install failed: ${first.error || first.output}`);
|
||||
|
||||
const srcB = writeCapSource('overlay-b', { requires: ['overlay-a'] });
|
||||
const r = runGsdTools(['capability', 'install', srcB, '--scope', 'global', '--raw'], makeCwd(), scopeEnv(home));
|
||||
const r = runMsdTools(['capability', 'install', srcB, '--scope', 'global', '--raw'], makeCwd(), scopeEnv(home));
|
||||
assert.equal(r.success, true, `install must succeed: overlay-a is a committed installed overlay — got: ${r.error || r.output}`);
|
||||
assert.ok(readLedgerEntry(home, 'overlay-b'), 'ledger entry recorded');
|
||||
});
|
||||
@@ -1222,7 +1222,7 @@ describe('#3929: install-time validation is seeded with first-party + installed
|
||||
test('a pending (uncommitted) overlay does not satisfy requires', () => {
|
||||
const home = tmpDir('cap-cli-3929-pending-');
|
||||
const srcA = writeCapSource('pending-a');
|
||||
const first = runGsdTools(['capability', 'install', srcA, '--scope', 'global', '--raw'], makeCwd(), scopeEnv(home));
|
||||
const first = runMsdTools(['capability', 'install', srcA, '--scope', 'global', '--raw'], makeCwd(), scopeEnv(home));
|
||||
assert.equal(first.success, true, `seed install failed: ${first.error || first.output}`);
|
||||
|
||||
// Flip cap-a's committed ledger entry to an in-flight _pending intent —
|
||||
@@ -1237,7 +1237,7 @@ describe('#3929: install-time validation is seeded with first-party + installed
|
||||
fs.writeFileSync(lp, JSON.stringify(ledger, null, 2));
|
||||
|
||||
const srcB = writeCapSource('pending-b', { requires: ['pending-a'] });
|
||||
const r = runGsdTools(['capability', 'install', srcB, '--scope', 'global'], makeCwd(), scopeEnv(home));
|
||||
const r = runMsdTools(['capability', 'install', srcB, '--scope', 'global'], makeCwd(), scopeEnv(home));
|
||||
assert.equal(r.success, false, 'a pending overlay must not satisfy requires (committed-only rule)');
|
||||
assert.match(`${r.error}\n${r.output}`, /pending-a/);
|
||||
});
|
||||
@@ -1249,7 +1249,7 @@ describe('#3929: install-time validation is seeded with first-party + installed
|
||||
const src = writeCapSource('claims-central', {
|
||||
config: { mode: { type: 'string', default: 'standard', description: 'collides with the central schema' } },
|
||||
});
|
||||
const r = runGsdTools(['capability', 'install', src, '--scope', 'global'], makeCwd(), scopeEnv(home));
|
||||
const r = runMsdTools(['capability', 'install', src, '--scope', 'global'], makeCwd(), scopeEnv(home));
|
||||
assert.equal(r.success, false, 'declaring a central config key must refuse at install');
|
||||
assert.match(`${r.error}\n${r.output}`, /central config-schema/);
|
||||
});
|
||||
@@ -1290,7 +1290,7 @@ describe('#3929: a throwing (duplicate-producer) overlay is skipped, never attri
|
||||
// poison-a must be seeded (and accepted) BEFORE poison-b trips the
|
||||
// duplicate-producer throw during the seed's own suite run.
|
||||
const srcA = writeCapSource('poison-a');
|
||||
const first = runGsdTools(['capability', 'install', srcA, '--scope', 'global', '--raw'], cwd, scopeEnv(home));
|
||||
const first = runMsdTools(['capability', 'install', srcA, '--scope', 'global', '--raw'], cwd, scopeEnv(home));
|
||||
assert.equal(first.success, true, `seed install failed: ${first.error || first.output}`);
|
||||
|
||||
const poisonedStep = {
|
||||
@@ -1321,7 +1321,7 @@ describe('#3929: a throwing (duplicate-producer) overlay is skipped, never attri
|
||||
// the seed — if it were retained, the whole-map suite would throw during
|
||||
// the candidate's own validation and this install would fail.
|
||||
const srcC = writeCapSource('poison-c', { requires: ['poison-a'] });
|
||||
const r = runGsdTools(['capability', 'install', srcC, '--scope', 'global', '--raw'], cwd, scopeEnv(home));
|
||||
const r = runMsdTools(['capability', 'install', srcC, '--scope', 'global', '--raw'], cwd, scopeEnv(home));
|
||||
assert.equal(r.success, true, `install must succeed despite the poisoned overlay: ${r.error || r.output}`);
|
||||
assert.ok(readLedgerEntry(home, 'poison-c'), 'candidate ledger entry recorded');
|
||||
});
|
||||
|
||||
Reference in New Issue
Block a user