test(#3339): fold the state/phase/dispatch & model-profile issue-* cluster — Wave 7

Folds 9 legacy issue-*.test.cjs regression files (140 test() blocks) into
their module's main suite, per H3 (#3315) of the test-hygiene epic (#3053).
LAST of 4 issue-* waves — closes out the 74-file fix-*/issue-* backlog
(pending BUG_FILE_RE extension, held for a follow-up commit until Wave 6
is confirmed merged, per the epic's own zero-backlog precondition).

- issue-2828-flat-roadmap-total-phases.test.cjs (1) + issue-3204-state-
  writer-phase-count.test.cjs (21): both target state-document.cjs
  buildStateFrontmatter via different CLI entrypoints — merged jointly
  into state-document.test.cjs, 0 dropped.
- issue-2945-phase-complete-checkbox-rollback.test.cjs (4) + issue-2949-
  phase-complete-stage3-sentinel.test.cjs (4): both target phase.cts
  cmdPhaseComplete; issue explicitly warned of overlap — verified
  disjoint fixtures/assertions, 0 dropped, merged into phase.test.cjs.
- issue-2927-reviewer-lane-overlay-invocation.test.cjs (10) merged into
  review-lane-descriptor.test.cjs.
- issue-2939-dispatch-flatten-maxdepth.test.cjs (9) merged into
  host-integration.test.cjs, 2 dropped as verified exact duplicates.
- issue-2977-frontmatter-bom.test.cjs (5) merged into frontmatter.test.cjs.
- issue-2045-third-party-skills-surface.test.cjs (6) merged into
  capability-loader.test.cjs.
- issue-2517-runtime-aware-profiles.test.cjs (80, the largest single
  fold in the epic) merged into model-resolver.test.cjs, 1 dropped as a
  verified true duplicate (checked against src/model-resolver.cts logic,
  not just title similarity).

Fixed a genuine eslint irregular-whitespace finding: a literal BOM
character embedded in a doc comment (pre-existing content from the
original #2977 source, illustrating what a BOM looks like) — replaced
with a readable U+FEFF notation.

3 stale doc references found and fixed (docs/adr/2313, 3180, 443).

Zero net test-coverage loss. No production code changed.
This commit is contained in:
sim
2026-08-12 00:51:07 -04:00
parent 4a0b5e26b4
commit 1bc7f7e6b0
19 changed files with 2941 additions and 2912 deletions

View File

@@ -1353,3 +1353,277 @@ describe('loadRegistry — #1461 finding 1: a throwing per-candidate validator s
'a null gate records no blockedGates entry');
});
});
// ────────────────────────────────────────────────────────────────────────
// Folded from tests/issue-2045-third-party-skills-surface.test.cjs — test-hygiene sweep #3339 (H3 Wave 7)
// ────────────────────────────────────────────────────────────────────────
{
const { describe: __foldDescribe } = require('node:test');
__foldDescribe('folded:issue-2045-third-party-skills-surface', () => {
'use strict';
/**
* issue-2045-third-party-skills-surface.test.cjs — regression for bug #2045.
*
* A skills-only `role: feature` third-party capability installs "active" but its
* skills never surface, `capability enable`/`set` reject it as "unknown", and
* `capability list` disagrees with `capability state`. Three defects, fix shape
* "1b" (teach resolveSurface, no on-disk linking):
*
* D1 (materialization): resolveSurface built the `skills` Set only from the
* on-disk skill manifest; third-party cap skills live at
* ~/.gsd/capabilities/<id>/skills/ and never entered the Set → surfaced:false.
* FIX: union registry.capabilityClusters values into the Set.
* D2 (enable/set unknown): setCapabilityState validated the capId against the
* STATIC first-party registry instead of the composed overlay-aware registry.
* FIX: validate against loadRegistry({ includeInstalled }).
* D3 (list vs state): `capability list` derived `status` purely from ledger
* existence, never surface composition. FIX: add a `surfaced` field so list
* reflects the same surface state `capability state` reports.
*
* Acceptance criteria (each is a release blocker, per the issue's "I'd expect"
* table):
* AC1 [D1]: resolveSurface includes a third-party cap's skill stems.
* AC2: capability state reports the cap present with surfaced:true.
* AC3 [D2]: capability enable/set on an installed third-party cap does NOT
* error "unknown capability".
* AC4 [D3]: capability list reflects surface state (list/state agree).
* AC5: first-party caps unaffected; writer still rejects truly-unknown ids.
*/
const { describe, test, after } = require('node:test');
const assert = require('node:assert/strict');
const fs = require('node:fs');
const os = require('node:os');
const path = require('node:path');
const { runGsdTools, cleanup } = require('./helpers.cjs');
const { resolveCapabilityRuntimeState } = require('../gsd-core/bin/lib/capability-state.cjs');
const { setCapabilityState } = require('../gsd-core/bin/lib/capability-writer.cjs');
const { resolveSurface } = require('../gsd-core/bin/lib/surface.cjs');
const { loadRegistry } = require('../gsd-core/bin/lib/capability-loader.cjs');
// ─── Fixture helpers ─────────────────────────────────────────────────────────
const CAP_ID = 'demo-2045-cap';
const CAP_SKILLS = ['demo-2045-alpha', 'demo-2045-beta'];
const HOST_RANGE = '>=1.0.0';
const tmps = [];
function tmpDir(prefix) {
const d = fs.mkdtempSync(path.join(os.tmpdir(), prefix));
tmps.push(d);
return d;
}
after(() => { for (const d of tmps) cleanup(d); });
/** A conformant skills-only feature capability manifest (the reporter's shape). */
function skillsOnlyCap(id, skills) {
return {
id,
role: 'feature',
version: '1.0.0',
title: id,
description: 'skills-only third-party capability (issue #2045 fixture)',
tier: 'full',
requires: [],
engines: { gsd: HOST_RANGE },
runtimeCompat: { supported: ['*'], unsupported: [] },
skills,
agents: [],
hooks: [],
config: {},
steps: [],
contributions: [],
gates: [],
};
}
/** Write a global-scope overlay bundle at <home>/.gsd/capabilities/<id>/. */
function writeGlobalBundle(home, id, skills) {
const dir = path.join(home, '.gsd', 'capabilities', id);
fs.mkdirSync(dir, { recursive: true });
fs.writeFileSync(path.join(dir, 'capability.json'), JSON.stringify(skillsOnlyCap(id, skills)), 'utf8');
// Materialize each declared skill so the bundle mirrors a real install.
for (const stem of skills) {
const skillDir = path.join(dir, 'skills', stem);
fs.mkdirSync(skillDir, { recursive: true });
fs.writeFileSync(path.join(skillDir, 'SKILL.md'), `---\ndescription: ${stem}\n---\n# ${stem}\n`, 'utf8');
}
return dir;
}
/** A temp runtime config dir with NO .gsd-profile marker → default 'full' profile. */
function makeRcd() {
return tmpDir('issue2045-rcd-');
}
/** A temp cwd with .planning/config.json so project-root resolution is hermetic. */
function makeCwd() {
const cwd = tmpDir('issue2045-cwd-');
fs.mkdirSync(path.join(cwd, '.planning'), { recursive: true });
fs.writeFileSync(path.join(cwd, '.planning', 'config.json'), '{}');
return cwd;
}
/** GSD_HOME-sandboxed env that also neutralizes ambient GSD_ vars (hermeticity). */
function scopeEnv(home) {
return { GSD_HOME: home, GSD_WORKSTREAM: '', GSD_PROJECT: '' };
}
const savedGsdHome = process.env.GSD_HOME;
// ─── AC1 [D1]: resolveSurface unions registry.capabilityClusters ─────────────
describe('issue #2045 AC1 — resolveSurface includes third-party cap skills [D1]', () => {
test('a composed registry surfaces third-party cap skills without on-disk linking', () => {
const home = tmpDir('issue2045-home-');
writeGlobalBundle(home, CAP_ID, CAP_SKILLS);
const rcd = makeRcd();
const cwd = makeCwd();
process.env.GSD_HOME = home;
try {
// Composed overlay-aware registry (the loader composes ACTIVE overlay caps).
const registry = loadRegistry({ includeInstalled: true, cwd, gsdHome: home });
const clusters = (registry && registry.capabilityClusters) || {};
assert.ok(Array.isArray(clusters[CAP_ID]), `fixture cap "${CAP_ID}" must own skills in capabilityClusters (loader did not accept the overlay — check engines/validation)`);
// Empty manifest + composed registry: in the 'full' profile the skills Set
// is materialized from the manifest (empty here) THEN, after the fix, unioned
// with capabilityClusters values. Third-party skills are NOT on disk in rcd,
// so they can only appear via the registry union.
const surface = resolveSurface(rcd, new Map(), undefined, registry);
assert.ok(surface.skills instanceof Set, 'resolveSurface returns a skills Set');
for (const stem of CAP_SKILLS) {
assert.ok(
surface.skills.has(stem),
`AC1: third-party skill "${stem}" must be in the surfaced Set (no on-disk linking) — got [${[...surface.skills].join(', ')}]`,
);
}
} finally {
process.env.GSD_HOME = savedGsdHome;
}
});
});
// ─── AC2: capability state reports present + surfaced:true ───────────────────
describe('issue #2045 AC2 — capability state reports surfaced:true', () => {
test('resolveCapabilityRuntimeState surfaces an installed third-party skills cap', () => {
const home = tmpDir('issue2045-home-');
writeGlobalBundle(home, CAP_ID, CAP_SKILLS);
const rcd = makeRcd();
const cwd = makeCwd();
process.env.GSD_HOME = home;
try {
const state = resolveCapabilityRuntimeState(cwd, rcd);
const cap = state.capabilities.find((c) => c.id === CAP_ID);
assert.ok(cap, `AC2: capability state must list "${CAP_ID}" as present`);
assert.equal(cap.surfaced, true, 'AC2: surfaced must be true');
assert.equal(cap.installed, true, 'AC2: installed must be true (default full profile)');
// No activationKey on the fixture → active === enabled.
assert.equal(cap.active, true, 'AC2: active must be true (no config gate)');
} finally {
process.env.GSD_HOME = savedGsdHome;
}
});
});
// ─── AC3 [D2]: enable/set does NOT error "unknown capability" ────────────────
describe('issue #2045 AC3 — enable/set accepts an installed third-party cap [D2]', () => {
test('setCapabilityState({enabled:true}) on an installed third-party cap is not "unknown"', () => {
const home = tmpDir('issue2045-home-');
writeGlobalBundle(home, CAP_ID, CAP_SKILLS);
const rcd = makeRcd();
const cwd = makeCwd();
process.env.GSD_HOME = home;
try {
const result = setCapabilityState(cwd, rcd, [{ id: CAP_ID, enabled: true }]);
const unknownErr = (result.errors || []).find((e) => /unknown capability/i.test(String(e)));
assert.ok(!unknownErr, `AC3: must not error "unknown capability" for installed third-party cap — got errors: ${JSON.stringify(result.errors)}`);
const cap = (result.capabilities || []).find((c) => c.id === CAP_ID);
assert.ok(cap, 'AC3: result must include the third-party cap');
} finally {
process.env.GSD_HOME = savedGsdHome;
}
});
test('AC5 (regression): a truly-unknown id is STILL rejected as "unknown capability"', () => {
const rcd = makeRcd();
const cwd = makeCwd();
const home = tmpDir('issue2045-home-empty-');
process.env.GSD_HOME = home;
try {
const result = setCapabilityState(cwd, rcd, [{ id: 'nonexistent-cap-2045', enabled: true }]);
const unknownErr = (result.errors || []).find((e) => /unknown capability/i.test(String(e)));
assert.ok(unknownErr, 'AC5: truly-unknown id must still be rejected (writer validates against composed registry, not "accept all")');
} finally {
process.env.GSD_HOME = savedGsdHome;
}
});
test('AC5 (regression): a first-party cap still resolves and surfaces', () => {
const rcd = makeRcd();
const cwd = makeCwd();
const home = tmpDir('issue2045-home-empty-');
process.env.GSD_HOME = home;
try {
const state = resolveCapabilityRuntimeState(cwd, rcd);
// 'ui' is a first-party skill-owning capability; it must still be present.
const ui = state.capabilities.find((c) => c.id === 'ui');
assert.ok(ui, 'AC5: first-party "ui" capability still present');
} finally {
process.env.GSD_HOME = savedGsdHome;
}
});
});
// ─── AC4 [D3]: capability list reflects surface state (list/state agree) ─────
describe('issue #2045 AC4 — capability list reflects surface state [D3]', () => {
test('an installed skills cap: list `surfaced` agrees with `capability state`', () => {
const home = tmpDir('issue2045-home-');
const cwd = makeCwd();
// Build a local source dir that declares skills AND materializes them, then
// install it globally — the real end-to-end path the reporter used.
const src = tmpDir('issue2045-src-');
const cap = skillsOnlyCap(CAP_ID, CAP_SKILLS);
fs.writeFileSync(path.join(src, 'capability.json'), JSON.stringify(cap), 'utf8');
for (const stem of CAP_SKILLS) {
const skillDir = path.join(src, 'skills', stem);
fs.mkdirSync(skillDir, { recursive: true });
fs.writeFileSync(path.join(skillDir, 'SKILL.md'), `---\ndescription: ${stem}\n---\n# ${stem}\n`, 'utf8');
}
const installRes = runGsdTools(['capability', 'install', src, '--scope', 'global', '--raw'], cwd, scopeEnv(home));
assert.equal(installRes.success, true, `install failed: ${installRes.error || installRes.output}`);
// capability list --json must include a `surfaced` field for the overlay row.
const listRes = runGsdTools(['capability', 'list', '--json'], cwd, scopeEnv(home));
assert.equal(listRes.success, true, `list failed: ${listRes.error || listRes.output}`);
const listRows = JSON.parse(listRes.output);
const listRow = listRows.find((r) => r.id === CAP_ID);
assert.ok(listRow, 'AC4: installed cap present in list');
assert.ok(
Object.prototype.hasOwnProperty.call(listRow, 'surfaced'),
`AC4: list row must carry a 'surfaced' field reflecting surface composition — got keys: ${Object.keys(listRow).join(', ')}`,
);
assert.equal(listRow.surfaced, true, 'AC4: list surfaced === true (skills resolved via registry union)');
// capability state <id> --json must agree.
const stateRes = runGsdTools(['capability', 'state', CAP_ID, '--json'], cwd, scopeEnv(home));
assert.equal(stateRes.success, true, `state failed: ${stateRes.error || stateRes.output}`);
const stateObj = JSON.parse(stateRes.output);
const stateCap = (stateObj.capabilities || []).find((c) => c.id === CAP_ID);
assert.ok(stateCap, 'AC4: capability state must list the cap');
assert.equal(stateCap.surfaced, true, 'AC4: state surfaced === true');
// The agreement the reporter asked for: list.surfaced === state.surfaced.
assert.equal(listRow.surfaced, stateCap.surfaced, 'AC4: list and state must AGREE on surfaced');
});
});
});
}