@skillsmith/core 0.11.4 → 0.11.6
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/CHANGELOG.md +60 -0
- package/README.md +10 -7
- package/dist/.tsbuildinfo +1 -1
- package/dist/src/api/client.d.ts.map +1 -1
- package/dist/src/api/client.js +20 -5
- package/dist/src/api/client.js.map +1 -1
- package/dist/src/api/client.private-registry.d.ts +80 -0
- package/dist/src/api/client.private-registry.d.ts.map +1 -0
- package/dist/src/api/client.private-registry.js +97 -0
- package/dist/src/api/client.private-registry.js.map +1 -0
- package/dist/src/api/client.private-registry.test.d.ts +9 -0
- package/dist/src/api/client.private-registry.test.d.ts.map +1 -0
- package/dist/src/api/client.private-registry.test.js +127 -0
- package/dist/src/api/client.private-registry.test.js.map +1 -0
- package/dist/src/api/client.test.js +39 -0
- package/dist/src/api/client.test.js.map +1 -1
- package/dist/src/api/client.toSkill.test.d.ts +25 -0
- package/dist/src/api/client.toSkill.test.d.ts.map +1 -0
- package/dist/src/api/client.toSkill.test.js +97 -0
- package/dist/src/api/client.toSkill.test.js.map +1 -0
- package/dist/src/api/client.token-refresh.d.ts +8 -0
- package/dist/src/api/client.token-refresh.d.ts.map +1 -1
- package/dist/src/api/client.token-refresh.js +26 -0
- package/dist/src/api/client.token-refresh.js.map +1 -1
- package/dist/src/api/client.token-refresh.test.js +82 -0
- package/dist/src/api/client.token-refresh.test.js.map +1 -1
- package/dist/src/api/index.d.ts +2 -0
- package/dist/src/api/index.d.ts.map +1 -1
- package/dist/src/api/index.js +9 -0
- package/dist/src/api/index.js.map +1 -1
- package/dist/src/api/security-summary.d.ts +73 -0
- package/dist/src/api/security-summary.d.ts.map +1 -0
- package/dist/src/api/security-summary.js +88 -0
- package/dist/src/api/security-summary.js.map +1 -0
- package/dist/src/api/security-summary.test.d.ts +9 -0
- package/dist/src/api/security-summary.test.d.ts.map +1 -0
- package/dist/src/api/security-summary.test.js +94 -0
- package/dist/src/api/security-summary.test.js.map +1 -0
- package/dist/src/compatibility/slugs.d.ts +7 -3
- package/dist/src/compatibility/slugs.d.ts.map +1 -1
- package/dist/src/compatibility/slugs.js +11 -4
- package/dist/src/compatibility/slugs.js.map +1 -1
- package/dist/src/config/config-atomic-write.d.ts +30 -10
- package/dist/src/config/config-atomic-write.d.ts.map +1 -1
- package/dist/src/config/config-atomic-write.js +33 -80
- package/dist/src/config/config-atomic-write.js.map +1 -1
- package/dist/src/config/config-atomic-write.test.d.ts +8 -0
- package/dist/src/config/config-atomic-write.test.d.ts.map +1 -1
- package/dist/src/config/config-atomic-write.test.js +64 -12
- package/dist/src/config/config-atomic-write.test.js.map +1 -1
- package/dist/src/config/owned-lock.acquire.d.ts +53 -0
- package/dist/src/config/owned-lock.acquire.d.ts.map +1 -0
- package/dist/src/config/owned-lock.acquire.js +140 -0
- package/dist/src/config/owned-lock.acquire.js.map +1 -0
- package/dist/src/config/owned-lock.claim.d.ts +84 -0
- package/dist/src/config/owned-lock.claim.d.ts.map +1 -0
- package/dist/src/config/owned-lock.claim.js +310 -0
- package/dist/src/config/owned-lock.claim.js.map +1 -0
- package/dist/src/config/owned-lock.d.ts +113 -0
- package/dist/src/config/owned-lock.d.ts.map +1 -0
- package/dist/src/config/owned-lock.js +121 -0
- package/dist/src/config/owned-lock.js.map +1 -0
- package/dist/src/config/owned-lock.test.d.ts +12 -0
- package/dist/src/config/owned-lock.test.d.ts.map +1 -0
- package/dist/src/config/owned-lock.test.js +412 -0
- package/dist/src/config/owned-lock.test.js.map +1 -0
- package/dist/src/config/owned-lock.types.d.ts +78 -0
- package/dist/src/config/owned-lock.types.d.ts.map +1 -0
- package/dist/src/config/owned-lock.types.js +30 -0
- package/dist/src/config/owned-lock.types.js.map +1 -0
- package/dist/src/db/createDatabase.d.ts.map +1 -1
- package/dist/src/db/createDatabase.js +19 -7
- package/dist/src/db/createDatabase.js.map +1 -1
- package/dist/src/db/drivers/sqljsDriver.d.ts +8 -0
- package/dist/src/db/drivers/sqljsDriver.d.ts.map +1 -1
- package/dist/src/db/drivers/sqljsDriver.js +24 -2
- package/dist/src/db/drivers/sqljsDriver.js.map +1 -1
- package/dist/src/db/migration-runner.d.ts.map +1 -1
- package/dist/src/db/migration-runner.js +23 -2
- package/dist/src/db/migration-runner.js.map +1 -1
- package/dist/src/db/schema.d.ts.map +1 -1
- package/dist/src/db/schema.js +29 -3
- package/dist/src/db/schema.js.map +1 -1
- package/dist/src/embeddings/hnsw-store.exports.d.ts +11 -0
- package/dist/src/embeddings/hnsw-store.exports.d.ts.map +1 -0
- package/dist/src/embeddings/hnsw-store.exports.js +10 -0
- package/dist/src/embeddings/hnsw-store.exports.js.map +1 -0
- package/dist/src/embeddings/index.d.ts +1 -2
- package/dist/src/embeddings/index.d.ts.map +1 -1
- package/dist/src/embeddings/index.js +17 -3
- package/dist/src/embeddings/index.js.map +1 -1
- package/dist/src/embeddings/probe.d.ts.map +1 -1
- package/dist/src/embeddings/probe.js +4 -8
- package/dist/src/embeddings/probe.js.map +1 -1
- package/dist/src/exports/services.d.ts +5 -1
- package/dist/src/exports/services.d.ts.map +1 -1
- package/dist/src/exports/services.js +27 -0
- package/dist/src/exports/services.js.map +1 -1
- package/dist/src/index.d.ts +3 -3
- package/dist/src/index.d.ts.map +1 -1
- package/dist/src/index.js +9 -3
- package/dist/src/index.js.map +1 -1
- package/dist/src/install/index.d.ts +2 -2
- package/dist/src/install/index.d.ts.map +1 -1
- package/dist/src/install/index.js +1 -1
- package/dist/src/install/index.js.map +1 -1
- package/dist/src/install/paths.d.ts +165 -1
- package/dist/src/install/paths.d.ts.map +1 -1
- package/dist/src/install/paths.js +254 -1
- package/dist/src/install/paths.js.map +1 -1
- package/dist/src/install/paths.test.js +174 -2
- package/dist/src/install/paths.test.js.map +1 -1
- package/dist/src/security/scanner/multiline-category-closure.test.d.ts +80 -0
- package/dist/src/security/scanner/multiline-category-closure.test.d.ts.map +1 -0
- package/dist/src/security/scanner/multiline-category-closure.test.js +287 -0
- package/dist/src/security/scanner/multiline-category-closure.test.js.map +1 -0
- package/dist/src/services/context-words.d.ts +40 -0
- package/dist/src/services/context-words.d.ts.map +1 -0
- package/dist/src/services/context-words.js +178 -0
- package/dist/src/services/context-words.js.map +1 -0
- package/dist/src/services/context-words.test.d.ts +2 -0
- package/dist/src/services/context-words.test.d.ts.map +1 -0
- package/dist/src/services/context-words.test.js +94 -0
- package/dist/src/services/context-words.test.js.map +1 -0
- package/dist/src/services/recommend-guard.d.ts +28 -0
- package/dist/src/services/recommend-guard.d.ts.map +1 -0
- package/dist/src/services/recommend-guard.js +34 -0
- package/dist/src/services/recommend-guard.js.map +1 -0
- package/dist/src/services/recommend-guard.test.d.ts +2 -0
- package/dist/src/services/recommend-guard.test.d.ts.map +1 -0
- package/dist/src/services/recommend-guard.test.js +28 -0
- package/dist/src/services/recommend-guard.test.js.map +1 -0
- package/dist/src/services/skill-installation.content.d.ts +96 -0
- package/dist/src/services/skill-installation.content.d.ts.map +1 -0
- package/dist/src/services/skill-installation.content.js +319 -0
- package/dist/src/services/skill-installation.content.js.map +1 -0
- package/dist/src/services/skill-installation.helpers.d.ts +32 -1
- package/dist/src/services/skill-installation.helpers.d.ts.map +1 -1
- package/dist/src/services/skill-installation.helpers.js +62 -7
- package/dist/src/services/skill-installation.helpers.js.map +1 -1
- package/dist/src/services/skill-installation.helpers.test.js +108 -2
- package/dist/src/services/skill-installation.helpers.test.js.map +1 -1
- package/dist/src/services/skill-installation.io.d.ts +22 -1
- package/dist/src/services/skill-installation.io.d.ts.map +1 -1
- package/dist/src/services/skill-installation.io.js +110 -6
- package/dist/src/services/skill-installation.io.js.map +1 -1
- package/dist/src/services/skill-installation.service.d.ts +22 -1
- package/dist/src/services/skill-installation.service.d.ts.map +1 -1
- package/dist/src/services/skill-installation.service.js +30 -5
- package/dist/src/services/skill-installation.service.js.map +1 -1
- package/dist/src/services/skill-installation.types.d.ts +42 -1
- package/dist/src/services/skill-installation.types.d.ts.map +1 -1
- package/dist/src/services/skill-installation.types.js.map +1 -1
- package/dist/src/services/skill-manifest.d.ts +21 -0
- package/dist/src/services/skill-manifest.d.ts.map +1 -1
- package/dist/src/services/skill-manifest.js +51 -6
- package/dist/src/services/skill-manifest.js.map +1 -1
- package/dist/src/services/skill-manifest.test.d.ts +2 -0
- package/dist/src/services/skill-manifest.test.d.ts.map +1 -0
- package/dist/src/services/skill-manifest.test.js +202 -0
- package/dist/src/services/skill-manifest.test.js.map +1 -0
- package/dist/src/services/skill-resolution.d.ts +67 -0
- package/dist/src/services/skill-resolution.d.ts.map +1 -0
- package/dist/src/services/skill-resolution.js +67 -0
- package/dist/src/services/skill-resolution.js.map +1 -0
- package/dist/src/services/skill-resolution.test.d.ts +2 -0
- package/dist/src/services/skill-resolution.test.d.ts.map +1 -0
- package/dist/src/services/skill-resolution.test.js +143 -0
- package/dist/src/services/skill-resolution.test.js.map +1 -0
- package/dist/src/types/skill.d.ts +10 -0
- package/dist/src/types/skill.d.ts.map +1 -1
- package/dist/src/types.d.ts +9 -5
- package/dist/src/types.d.ts.map +1 -1
- package/dist/src/utils/index.d.ts +1 -0
- package/dist/src/utils/index.d.ts.map +1 -1
- package/dist/src/utils/index.js +4 -0
- package/dist/src/utils/index.js.map +1 -1
- package/dist/src/utils/quiet-mode.d.ts +21 -0
- package/dist/src/utils/quiet-mode.d.ts.map +1 -0
- package/dist/src/utils/quiet-mode.js +26 -0
- package/dist/src/utils/quiet-mode.js.map +1 -0
- package/dist/tests/EmbeddingService.test.js +58 -1
- package/dist/tests/EmbeddingService.test.js.map +1 -1
- package/dist/tests/SecurityScanner.performance.test.js +5 -2
- package/dist/tests/SecurityScanner.performance.test.js.map +1 -1
- package/dist/tests/db/database-abstraction.test.js +45 -1
- package/dist/tests/db/database-abstraction.test.js.map +1 -1
- package/dist/tests/db/migration-runner-race.test.d.ts +2 -0
- package/dist/tests/db/migration-runner-race.test.d.ts.map +1 -0
- package/dist/tests/db/migration-runner-race.test.js +75 -0
- package/dist/tests/db/migration-runner-race.test.js.map +1 -0
- package/dist/tests/db/schema-async.test.js +37 -1
- package/dist/tests/db/schema-async.test.js.map +1 -1
- package/dist/tests/db/sqljsDriver.test.js +63 -1
- package/dist/tests/db/sqljsDriver.test.js.map +1 -1
- package/dist/tests/helpers/deterministic-dead-pid.d.ts +13 -0
- package/dist/tests/helpers/deterministic-dead-pid.d.ts.map +1 -0
- package/dist/tests/helpers/deterministic-dead-pid.js +31 -0
- package/dist/tests/helpers/deterministic-dead-pid.js.map +1 -0
- package/dist/tests/integration/owned-lock-lost-update.test.d.ts +12 -0
- package/dist/tests/integration/owned-lock-lost-update.test.d.ts.map +1 -0
- package/dist/tests/integration/owned-lock-lost-update.test.js +52 -0
- package/dist/tests/integration/owned-lock-lost-update.test.js.map +1 -0
- package/dist/tests/integration/owned-lock-reclaim-race.test.d.ts +19 -0
- package/dist/tests/integration/owned-lock-reclaim-race.test.d.ts.map +1 -0
- package/dist/tests/integration/owned-lock-reclaim-race.test.js +177 -0
- package/dist/tests/integration/owned-lock-reclaim-race.test.js.map +1 -0
- package/dist/tests/schema.test.js +43 -1
- package/dist/tests/schema.test.js.map +1 -1
- package/dist/tests/unit/services/skill-installation.content.test.d.ts +10 -0
- package/dist/tests/unit/services/skill-installation.content.test.d.ts.map +1 -0
- package/dist/tests/unit/services/skill-installation.content.test.js +290 -0
- package/dist/tests/unit/services/skill-installation.content.test.js.map +1 -0
- package/dist/tests/unit/services/skill-installation.io.client-agent-path.test.d.ts +34 -0
- package/dist/tests/unit/services/skill-installation.io.client-agent-path.test.d.ts.map +1 -0
- package/dist/tests/unit/services/skill-installation.io.client-agent-path.test.js +246 -0
- package/dist/tests/unit/services/skill-installation.io.client-agent-path.test.js.map +1 -0
- package/dist/tests/unit/services/skill-installation.io.symlink.test.d.ts +8 -0
- package/dist/tests/unit/services/skill-installation.io.symlink.test.d.ts.map +1 -0
- package/dist/tests/unit/services/skill-installation.io.symlink.test.js +72 -0
- package/dist/tests/unit/services/skill-installation.io.symlink.test.js.map +1 -0
- package/dist/tests/unit/services/skill-installation.service.companion-basedir.test.d.ts +17 -0
- package/dist/tests/unit/services/skill-installation.service.companion-basedir.test.d.ts.map +1 -0
- package/dist/tests/unit/services/skill-installation.service.companion-basedir.test.js +112 -0
- package/dist/tests/unit/services/skill-installation.service.companion-basedir.test.js.map +1 -0
- package/dist/tests/unit/services/skill-installation.service.error-codes.test.d.ts +15 -0
- package/dist/tests/unit/services/skill-installation.service.error-codes.test.d.ts.map +1 -0
- package/dist/tests/unit/services/skill-installation.service.error-codes.test.js +190 -0
- package/dist/tests/unit/services/skill-installation.service.error-codes.test.js.map +1 -0
- package/dist/tests/unit/services/skill-installation.service.multi-client.test.d.ts +17 -0
- package/dist/tests/unit/services/skill-installation.service.multi-client.test.d.ts.map +1 -0
- package/dist/tests/unit/services/skill-installation.service.multi-client.test.js +126 -0
- package/dist/tests/unit/services/skill-installation.service.multi-client.test.js.map +1 -0
- package/dist/tests/unit/services/skill-installation.service.test.js +7 -112
- package/dist/tests/unit/services/skill-installation.service.test.js.map +1 -1
- package/dist/tests/utils/quiet-mode.test.d.ts +12 -0
- package/dist/tests/utils/quiet-mode.test.d.ts.map +1 -0
- package/dist/tests/utils/quiet-mode.test.js +42 -0
- package/dist/tests/utils/quiet-mode.test.js.map +1 -0
- package/package.json +17 -5
|
@@ -0,0 +1,246 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* SMI-5980 (Wave 3): writeInstallFiles must route the companion-subagent
|
|
3
|
+
* write through COMPANION_AGENT_TARGETS (install/paths.ts) for the caller's
|
|
4
|
+
* `client`, not a hardcoded `path.join(os.homedir(), '.claude', 'agents')`
|
|
5
|
+
* literal.
|
|
6
|
+
*
|
|
7
|
+
* `homedir()` is mocked to a controlled tmp directory BEFORE `paths.ts` is
|
|
8
|
+
* first imported — COMPANION_AGENT_TARGETS is a module-level constant
|
|
9
|
+
* computed once at import time from `homedir()`, so every assertion below
|
|
10
|
+
* compares against that SAME already-resolved map rather than a second,
|
|
11
|
+
* independently-hardcoded expectation. This also keeps the test from ever
|
|
12
|
+
* writing to a real developer/CI machine's actual home directory.
|
|
13
|
+
*
|
|
14
|
+
* SMI-5982 (Wave 6): antigravity's `COMPANION_AGENT_TARGETS` entry uses a
|
|
15
|
+
* RELATIVE `dir` (resolved against `process.cwd()`, not `homedir()`) — its
|
|
16
|
+
* own dedicated describe block below chdir's into an isolated tmp directory
|
|
17
|
+
* per test (never TEST_HOME, never the real repo checkout) rather than
|
|
18
|
+
* relying on the homedir() mock every other client's assertions use.
|
|
19
|
+
*
|
|
20
|
+
* SMI-5982 code-review fix #1 (BLOCKING, cwd-dependent resolution):
|
|
21
|
+
* `writeInstallFiles()` now resolves antigravity's relative `dir` via
|
|
22
|
+
* `resolveCompanionAgentPath()`'s explicit `baseDir` param (defaulting to
|
|
23
|
+
* `process.cwd()` at the call site, not deferred to whatever `fs` call
|
|
24
|
+
* eventually consumes the path) — so `result.subagentPath` is now an
|
|
25
|
+
* ABSOLUTE path even for antigravity, not the raw relative string. The
|
|
26
|
+
* `it.each` below covers both the omitted-`companionBaseDir` (falls back to
|
|
27
|
+
* `process.cwd()`, which this describe block's chdir controls) and an
|
|
28
|
+
* explicit `companionBaseDir` override.
|
|
29
|
+
*
|
|
30
|
+
* Split out of skill-installation.io.test.ts to stay under the 500-line
|
|
31
|
+
* CI gate (same rationale as skill-installation.io.symlink.test.ts).
|
|
32
|
+
*/
|
|
33
|
+
import { afterAll, afterEach, beforeEach, describe, expect, it, vi } from 'vitest';
|
|
34
|
+
import * as fs from 'fs/promises';
|
|
35
|
+
import * as path from 'path';
|
|
36
|
+
import * as os from 'node:os';
|
|
37
|
+
const { TEST_HOME, homedirMock } = vi.hoisted(() => {
|
|
38
|
+
const home = '/tmp/skillsmith-wif-client-agent-path-test-' + process.pid;
|
|
39
|
+
return { TEST_HOME: home, homedirMock: vi.fn(() => home) };
|
|
40
|
+
});
|
|
41
|
+
vi.mock('node:os', async (importOriginal) => {
|
|
42
|
+
const actual = await importOriginal();
|
|
43
|
+
return { ...actual, homedir: homedirMock };
|
|
44
|
+
});
|
|
45
|
+
import { writeInstallFiles } from '../../../src/services/skill-installation.io.js';
|
|
46
|
+
import { CLIENT_IDS, COMPANION_AGENT_TARGETS } from '../../../src/install/paths.js';
|
|
47
|
+
afterAll(async () => {
|
|
48
|
+
await fs.rm(TEST_HOME, { recursive: true, force: true }).catch(() => { });
|
|
49
|
+
});
|
|
50
|
+
/**
|
|
51
|
+
* Fresh skillsDir/installPath per test so parallel `it.each` iterations
|
|
52
|
+
* (all sharing TEST_HOME) never collide with each other.
|
|
53
|
+
*/
|
|
54
|
+
async function freshInstallPath(label) {
|
|
55
|
+
const skillsDir = path.join(TEST_HOME, 'src', label, 'skills');
|
|
56
|
+
await fs.mkdir(skillsDir, { recursive: true });
|
|
57
|
+
return { skillsDir, installPath: path.join(skillsDir, 'my-skill') };
|
|
58
|
+
}
|
|
59
|
+
describe('writeInstallFiles companion-subagent path per client (SMI-5980 regression)', () => {
|
|
60
|
+
beforeEach(() => {
|
|
61
|
+
homedirMock.mockReturnValue(TEST_HOME);
|
|
62
|
+
});
|
|
63
|
+
// SMI-5982 (Wave 6): antigravity is excluded from this generic 'flat'
|
|
64
|
+
// it.each — its dir is a RELATIVE path (resolved against process.cwd(),
|
|
65
|
+
// NOT the mocked homedir()), so writing it here would land outside
|
|
66
|
+
// TEST_HOME, pollute the real working tree, and never get cleaned up by
|
|
67
|
+
// this file's afterAll. It gets its own cwd-isolated describe block below.
|
|
68
|
+
it.each(CLIENT_IDS.filter((id) => id !== 'antigravity'))('writes the companion subagent for client=%s at exactly COMPANION_AGENT_TARGETS[client]', async (client) => {
|
|
69
|
+
const { skillsDir, installPath } = await freshInstallPath(client);
|
|
70
|
+
const result = await writeInstallFiles(installPath, skillsDir, 'my-skill', '# hello', [], '---\nname: my-skill-specialist\n---\nbody', client);
|
|
71
|
+
// Every remaining client uses 'flat' mode (copilot's own
|
|
72
|
+
// '{name}.agent.md' pattern, SMI-5980 PR-review finding, included).
|
|
73
|
+
const target = COMPANION_AGENT_TARGETS[client];
|
|
74
|
+
const expectedPath = path.join(target.dir, target.filenamePattern.replace('{name}', 'my-skill'));
|
|
75
|
+
expect(result.subagentPath).toBe(expectedPath);
|
|
76
|
+
expect(await fs.readFile(expectedPath, 'utf8')).toBe('---\nname: my-skill-specialist\n---\nbody');
|
|
77
|
+
});
|
|
78
|
+
it('defaults to the canonical (claude-code) target when client is omitted (regression: unchanged)', async () => {
|
|
79
|
+
const { skillsDir, installPath } = await freshInstallPath('default-client');
|
|
80
|
+
const result = await writeInstallFiles(installPath, skillsDir, 'my-skill', '# hello', [], 'subagent body'
|
|
81
|
+
// client omitted — must resolve exactly like the old hardcoded default.
|
|
82
|
+
);
|
|
83
|
+
const expectedPath = path.join(COMPANION_AGENT_TARGETS['claude-code'].dir, 'my-skill-specialist.md');
|
|
84
|
+
expect(result.subagentPath).toBe(expectedPath);
|
|
85
|
+
expect(await fs.readFile(expectedPath, 'utf8')).toBe('subagent body');
|
|
86
|
+
});
|
|
87
|
+
it('claude-code and cursor land in the SAME directory (both default, no independent evidence for cursor)', async () => {
|
|
88
|
+
const claudeCode = await freshInstallPath('cc-vs-cursor-claude');
|
|
89
|
+
const cursor = await freshInstallPath('cc-vs-cursor-cursor');
|
|
90
|
+
const claudeResult = await writeInstallFiles(claudeCode.installPath, claudeCode.skillsDir, 'my-skill', '# hello', [], 'body', 'claude-code');
|
|
91
|
+
const cursorResult = await writeInstallFiles(cursor.installPath, cursor.skillsDir, 'my-skill', '# hello', [], 'body', 'cursor');
|
|
92
|
+
expect(path.dirname(cursorResult.subagentPath ?? '')).toBe(path.dirname(claudeResult.subagentPath ?? ''));
|
|
93
|
+
});
|
|
94
|
+
it('copilot and opencode land at their own real, literal directories (code-review finding: not just "different from claude-code")', async () => {
|
|
95
|
+
const copilot = await freshInstallPath('copilot-diff');
|
|
96
|
+
const opencode = await freshInstallPath('opencode-diff');
|
|
97
|
+
const copilotResult = await writeInstallFiles(copilot.installPath, copilot.skillsDir, 'my-skill', '# hello', [], 'body', 'copilot');
|
|
98
|
+
const opencodeResult = await writeInstallFiles(opencode.installPath, opencode.skillsDir, 'my-skill', '# hello', [], 'body', 'opencode');
|
|
99
|
+
// Independently-constructed literal expected paths (via the SAME mocked
|
|
100
|
+
// TEST_HOME this file's own homedir() mock uses, but NOT derived from
|
|
101
|
+
// COMPANION_AGENT_TARGETS itself) — a code-review pass on an earlier
|
|
102
|
+
// version of this test caught that comparing only against
|
|
103
|
+
// COMPANION_AGENT_TARGETS['claude-code'].dir proves "different from
|
|
104
|
+
// claude-code," not "equals the actual intended copilot/opencode value";
|
|
105
|
+
// a typo in the map (e.g. singular '.copilot/agent') would have still
|
|
106
|
+
// passed the old assertion.
|
|
107
|
+
expect(path.dirname(copilotResult.subagentPath ?? '')).toBe(path.join(TEST_HOME, '.copilot', 'agents'));
|
|
108
|
+
expect(path.dirname(opencodeResult.subagentPath ?? '')).toBe(path.join(TEST_HOME, '.config', 'opencode', 'agents'));
|
|
109
|
+
// Belt-and-suspenders: still confirm both differ from claude-code's dir.
|
|
110
|
+
expect(path.dirname(copilotResult.subagentPath ?? '')).not.toBe(COMPANION_AGENT_TARGETS['claude-code'].dir);
|
|
111
|
+
expect(path.dirname(opencodeResult.subagentPath ?? '')).not.toBe(COMPANION_AGENT_TARGETS['claude-code'].dir);
|
|
112
|
+
});
|
|
113
|
+
it('does not write any companion-subagent file when subagentContent is undefined, regardless of client', async () => {
|
|
114
|
+
const { skillsDir, installPath } = await freshInstallPath('no-subagent');
|
|
115
|
+
const result = await writeInstallFiles(installPath, skillsDir, 'my-skill', '# hello', [], undefined, 'copilot');
|
|
116
|
+
expect(result.subagentPath).toBeUndefined();
|
|
117
|
+
});
|
|
118
|
+
});
|
|
119
|
+
/**
|
|
120
|
+
* SMI-5982 (Wave 6): antigravity's companion-agent dir is a RELATIVE path
|
|
121
|
+
* (`.agents/agents`, project-scoped per the plan's Step 1 — no existing
|
|
122
|
+
* global-vs-project distinction to hook into) resolved against
|
|
123
|
+
* `process.cwd()`, unlike every other client's `homedir()`-anchored
|
|
124
|
+
* absolute dir. This describe block is isolated from the rest of the file:
|
|
125
|
+
* each test chdir's into its own fresh tmp directory (never TEST_HOME, and
|
|
126
|
+
* never the real repo checkout) and restores the original cwd in
|
|
127
|
+
* `afterEach` even on assertion failure, so a broken assertion can never
|
|
128
|
+
* leave the whole test PROCESS's cwd corrupted for later tests/files.
|
|
129
|
+
*/
|
|
130
|
+
describe('writeInstallFiles companion-subagent path for antigravity (directory-package, SMI-5982 Wave 6)', () => {
|
|
131
|
+
let originalCwd;
|
|
132
|
+
let projectDir;
|
|
133
|
+
beforeEach(async () => {
|
|
134
|
+
originalCwd = process.cwd();
|
|
135
|
+
projectDir = await fs.mkdtemp(path.join(os.tmpdir(), 'skillsmith-antigravity-cwd-'));
|
|
136
|
+
process.chdir(projectDir);
|
|
137
|
+
});
|
|
138
|
+
afterEach(async () => {
|
|
139
|
+
process.chdir(originalCwd);
|
|
140
|
+
await fs.rm(projectDir, { recursive: true, force: true }).catch(() => { });
|
|
141
|
+
});
|
|
142
|
+
it('writes agent.md inside a skill-named subdirectory when companionBaseDir is passed explicitly (SMI-5982 code-review fix #1)', async () => {
|
|
143
|
+
const { skillsDir, installPath } = await freshInstallPath('antigravity-write');
|
|
144
|
+
const result = await writeInstallFiles(installPath, skillsDir, 'my-skill', '# hello', [], '---\nname: my-skill-specialist\n---\nbody', 'antigravity',
|
|
145
|
+
// PR-review follow-up: writeInstallFiles() no longer falls back to
|
|
146
|
+
// process.cwd() itself when companionBaseDir is omitted — an explicit
|
|
147
|
+
// value (here, this describe block's chdir target) is required for
|
|
148
|
+
// directory-package clients. See the dedicated "omitted
|
|
149
|
+
// companionBaseDir" test below for the fail-closed case.
|
|
150
|
+
projectDir);
|
|
151
|
+
// SMI-5982 code-review fix #1: resolveCompanionAgentPath() resolves
|
|
152
|
+
// antigravity's relative dir against the explicit baseDir BEFORE
|
|
153
|
+
// returning — so subagentPath is ABSOLUTE, not the raw relative string.
|
|
154
|
+
// Independently verify the REAL file via an absolute path built from the
|
|
155
|
+
// known projectDir (not derived from result.subagentPath, so a bug that
|
|
156
|
+
// silently mis-resolved the value would still be caught).
|
|
157
|
+
const expectedRelativePath = path.join('.agents', 'agents', 'my-skill', 'agent.md');
|
|
158
|
+
const expectedAbsolutePath = path.join(projectDir, expectedRelativePath);
|
|
159
|
+
expect(result.subagentPath).toBe(expectedAbsolutePath);
|
|
160
|
+
expect(await fs.readFile(expectedAbsolutePath, 'utf8')).toBe('---\nname: my-skill-specialist\n---\nbody');
|
|
161
|
+
// Directory-package structure: agent.md lives INSIDE a subdirectory
|
|
162
|
+
// named after the skill, not directly inside the agents dir.
|
|
163
|
+
expect(path.basename(path.dirname(expectedAbsolutePath))).toBe('my-skill');
|
|
164
|
+
});
|
|
165
|
+
it('omitting companionBaseDir for antigravity now rejects the install instead of silently defaulting to process.cwd() (PR-review follow-up)', async () => {
|
|
166
|
+
const { skillsDir, installPath } = await freshInstallPath('antigravity-omitted-basedir');
|
|
167
|
+
await expect(writeInstallFiles(installPath, skillsDir, 'my-skill', '# hello', [], '---\nname: my-skill-specialist\n---\nbody', 'antigravity'
|
|
168
|
+
// companionBaseDir omitted — must now throw (fail closed), not
|
|
169
|
+
// resolve against process.cwd(). SKILL.md IS written before this
|
|
170
|
+
// point (the companion-agent step runs last), but writeInstallFiles'
|
|
171
|
+
// existing rollback-on-failure logic unwinds installPath entirely on
|
|
172
|
+
// any thrown error — so nothing survives on disk after the throw.
|
|
173
|
+
)).rejects.toThrow(/directory-package mode.*explicit baseDir is required/s);
|
|
174
|
+
await expect(fs.access(installPath)).rejects.toThrow();
|
|
175
|
+
});
|
|
176
|
+
it('writes agent.md resolved against an explicit companionBaseDir, not process.cwd() (SMI-5982 code-review fix #1)', async () => {
|
|
177
|
+
// Deliberately DIFFERENT from projectDir (this block's chdir target) to
|
|
178
|
+
// prove companionBaseDir — not the ambient cwd — drives resolution. This
|
|
179
|
+
// is exactly the MCP-server scenario the fix targets: a long-running
|
|
180
|
+
// process whose own cwd does not track the caller's real project.
|
|
181
|
+
const explicitBaseDir = await fs.mkdtemp(path.join(os.tmpdir(), 'skillsmith-antigravity-explicit-basedir-'));
|
|
182
|
+
try {
|
|
183
|
+
const { skillsDir, installPath } = await freshInstallPath('antigravity-explicit-basedir');
|
|
184
|
+
const result = await writeInstallFiles(installPath, skillsDir, 'my-skill', '# hello', [], '---\nname: my-skill-specialist\n---\nbody', 'antigravity', explicitBaseDir);
|
|
185
|
+
const expectedAbsolutePath = path.join(explicitBaseDir, '.agents', 'agents', 'my-skill', 'agent.md');
|
|
186
|
+
expect(result.subagentPath).toBe(expectedAbsolutePath);
|
|
187
|
+
expect(result.subagentPath?.startsWith(projectDir)).toBe(false);
|
|
188
|
+
expect(await fs.readFile(expectedAbsolutePath, 'utf8')).toBe('---\nname: my-skill-specialist\n---\nbody');
|
|
189
|
+
}
|
|
190
|
+
finally {
|
|
191
|
+
await fs.rm(explicitBaseDir, { recursive: true, force: true }).catch(() => { });
|
|
192
|
+
}
|
|
193
|
+
});
|
|
194
|
+
it('a safeWriteFile failure on the companion-agent path does not crash, and never force-removes a non-empty per-skill directory', async () => {
|
|
195
|
+
// Reachable, non-mocked failure mode for the LAST step in
|
|
196
|
+
// writeInstallFiles's try block (companion-agent write): a symlink
|
|
197
|
+
// already sitting at the exact target path. safeWriteFile's O_NOFOLLOW
|
|
198
|
+
// open() refuses it (SymlinkError), so the rollback branch added in
|
|
199
|
+
// SMI-5982 runs with subagentPath already assigned. The per-skill
|
|
200
|
+
// directory is NOT empty (the symlink itself is still inside it), so
|
|
201
|
+
// the rollback's rmdir() must be a safe no-op here, never a force-delete
|
|
202
|
+
// — this proves the fix degrades safely rather than that it always
|
|
203
|
+
// reclaims disk space (an EMPTY orphaned directory can only arise from a
|
|
204
|
+
// transient I/O failure between mkdir and the write, not reproducible
|
|
205
|
+
// deterministically without mocking fs internals).
|
|
206
|
+
const { skillsDir, installPath } = await freshInstallPath('antigravity-symlink');
|
|
207
|
+
const agentSkillDir = path.join(projectDir, '.agents', 'agents', 'my-skill');
|
|
208
|
+
await fs.mkdir(agentSkillDir, { recursive: true });
|
|
209
|
+
await fs.symlink('/nonexistent-target', path.join(agentSkillDir, 'agent.md'));
|
|
210
|
+
await expect(writeInstallFiles(installPath, skillsDir, 'my-skill', '# hello', [], '---\nname: my-skill-specialist\n---\nbody', 'antigravity',
|
|
211
|
+
// PR-review follow-up: explicit now that writeInstallFiles() no
|
|
212
|
+
// longer defaults a missing companionBaseDir to process.cwd() —
|
|
213
|
+
// required so this test still reaches the symlink failure it is
|
|
214
|
+
// actually exercising, rather than the (also-now-thrown)
|
|
215
|
+
// required-baseDir guard.
|
|
216
|
+
projectDir)).rejects.toThrow(/symlink/i);
|
|
217
|
+
// The directory (and its symlink) must still exist — rollback did not
|
|
218
|
+
// blindly force-delete non-empty content.
|
|
219
|
+
const stats = await fs.lstat(agentSkillDir);
|
|
220
|
+
expect(stats.isDirectory()).toBe(true);
|
|
221
|
+
});
|
|
222
|
+
it('rejects a path-traversal skillName ("..") for the companion-agent path and rolls back the already-written SKILL.md (SMI-5982 code-review fix #2)', async () => {
|
|
223
|
+
// resolveCompanionAgentPath()'s own directory-package validation (fix #2)
|
|
224
|
+
// is the last line of defense here: this test passes a malicious
|
|
225
|
+
// skillName distinct from what installPath was built from, exactly the
|
|
226
|
+
// "a future caller bypasses upstream sanitization" scenario the fix's
|
|
227
|
+
// doc comment calls out.
|
|
228
|
+
const { skillsDir, installPath } = await freshInstallPath('antigravity-traversal');
|
|
229
|
+
await expect(writeInstallFiles(installPath, skillsDir, '..', '# hello', [], '---\nname: specialist\n---\nbody', 'antigravity',
|
|
230
|
+
// PR-review follow-up: explicit now that writeInstallFiles() no
|
|
231
|
+
// longer defaults a missing companionBaseDir to process.cwd() —
|
|
232
|
+
// required so this test still reaches the skillName traversal
|
|
233
|
+
// guard it is actually exercising, rather than the
|
|
234
|
+
// (also-now-thrown) required-baseDir guard.
|
|
235
|
+
projectDir)).rejects.toThrow(/Unsafe skill name for directory-package companion path/);
|
|
236
|
+
// Rollback: the existing catch block in writeInstallFiles() already
|
|
237
|
+
// covers this — SKILL.md was written before the throw (subagentPath is
|
|
238
|
+
// resolved last), so it (and installPath itself) must be gone. Nothing
|
|
239
|
+
// was ever created under the companion-agent dir, since the throw
|
|
240
|
+
// happens BEFORE any fs.mkdir/write for that path.
|
|
241
|
+
await expect(fs.access(installPath)).rejects.toThrow();
|
|
242
|
+
const agentsParentDir = path.join(projectDir, '.agents');
|
|
243
|
+
await expect(fs.access(agentsParentDir)).rejects.toThrow();
|
|
244
|
+
});
|
|
245
|
+
});
|
|
246
|
+
//# sourceMappingURL=skill-installation.io.client-agent-path.test.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"skill-installation.io.client-agent-path.test.js","sourceRoot":"","sources":["../../../../tests/unit/services/skill-installation.io.client-agent-path.test.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @fileoverview writeInstallFiles nested sub-skill path + symlink-safety tests
|
|
3
|
+
* @see SMI-5905 final code review (Sol, GPT-5.6-Sol via NEEDLE) findings #2/#4
|
|
4
|
+
*
|
|
5
|
+
* Split out of skill-installation.io.test.ts (515/500 lines once these tests landed).
|
|
6
|
+
*/
|
|
7
|
+
export {};
|
|
8
|
+
//# sourceMappingURL=skill-installation.io.symlink.test.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"skill-installation.io.symlink.test.d.ts","sourceRoot":"","sources":["../../../../tests/unit/services/skill-installation.io.symlink.test.ts"],"names":[],"mappings":"AAAA;;;;;GAKG"}
|
|
@@ -0,0 +1,72 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @fileoverview writeInstallFiles nested sub-skill path + symlink-safety tests
|
|
3
|
+
* @see SMI-5905 final code review (Sol, GPT-5.6-Sol via NEEDLE) findings #2/#4
|
|
4
|
+
*
|
|
5
|
+
* Split out of skill-installation.io.test.ts (515/500 lines once these tests landed).
|
|
6
|
+
*/
|
|
7
|
+
import { describe, it, expect } from 'vitest';
|
|
8
|
+
import * as fs from 'fs/promises';
|
|
9
|
+
import * as os from 'os';
|
|
10
|
+
import * as path from 'path';
|
|
11
|
+
import { writeInstallFiles } from '../../../src/services/skill-installation.io.js';
|
|
12
|
+
/**
|
|
13
|
+
* SMI-5905 Sol final-code-review findings #2/#4: nested sub-skill filenames (e.g.
|
|
14
|
+
* "scripts/run.sh", used by private-registry content installs) previously had no parent
|
|
15
|
+
* directory created (ENOENT on any clean install) and, once fixed, must not silently write
|
|
16
|
+
* through a pre-existing symlink at an intermediate path component.
|
|
17
|
+
*/
|
|
18
|
+
describe('writeInstallFiles nested sub-skill paths (SMI-5905 final review #2/#4)', () => {
|
|
19
|
+
it('creates the parent directory for a nested sub-skill filename', async () => {
|
|
20
|
+
const root = await fs.mkdtemp(path.join(os.tmpdir(), 'wif-nested-'));
|
|
21
|
+
try {
|
|
22
|
+
const skillsDir = path.join(root, 'skills');
|
|
23
|
+
await fs.mkdir(skillsDir, { recursive: true });
|
|
24
|
+
const installPath = path.join(skillsDir, 'my-skill');
|
|
25
|
+
const result = await writeInstallFiles(installPath, skillsDir, 'my-skill', '# hello', [{ filename: 'scripts/run.sh', content: 'echo hi' }], undefined);
|
|
26
|
+
expect(await fs.readFile(path.join(installPath, 'scripts', 'run.sh'), 'utf8')).toBe('echo hi');
|
|
27
|
+
expect(result.writtenFiles).toContain(path.join(installPath, 'scripts', 'run.sh'));
|
|
28
|
+
}
|
|
29
|
+
finally {
|
|
30
|
+
await fs.rm(root, { recursive: true, force: true }).catch(() => { });
|
|
31
|
+
}
|
|
32
|
+
});
|
|
33
|
+
it('creates parents for two nested files sharing the same new subdirectory (concurrent-write race)', async () => {
|
|
34
|
+
const root = await fs.mkdtemp(path.join(os.tmpdir(), 'wif-nested-race-'));
|
|
35
|
+
try {
|
|
36
|
+
const skillsDir = path.join(root, 'skills');
|
|
37
|
+
await fs.mkdir(skillsDir, { recursive: true });
|
|
38
|
+
const installPath = path.join(skillsDir, 'my-skill');
|
|
39
|
+
await writeInstallFiles(installPath, skillsDir, 'my-skill', '# hello', [
|
|
40
|
+
{ filename: 'scripts/a.sh', content: 'a' },
|
|
41
|
+
{ filename: 'scripts/b.sh', content: 'b' },
|
|
42
|
+
], undefined);
|
|
43
|
+
expect(await fs.readFile(path.join(installPath, 'scripts', 'a.sh'), 'utf8')).toBe('a');
|
|
44
|
+
expect(await fs.readFile(path.join(installPath, 'scripts', 'b.sh'), 'utf8')).toBe('b');
|
|
45
|
+
}
|
|
46
|
+
finally {
|
|
47
|
+
await fs.rm(root, { recursive: true, force: true }).catch(() => { });
|
|
48
|
+
}
|
|
49
|
+
});
|
|
50
|
+
it('refuses to write through a pre-existing symlinked intermediate directory', async () => {
|
|
51
|
+
const root = await fs.mkdtemp(path.join(os.tmpdir(), 'wif-symlink-'));
|
|
52
|
+
try {
|
|
53
|
+
const skillsDir = path.join(root, 'skills');
|
|
54
|
+
await fs.mkdir(skillsDir, { recursive: true });
|
|
55
|
+
const installPath = path.join(skillsDir, 'my-skill');
|
|
56
|
+
await fs.mkdir(installPath, { recursive: true });
|
|
57
|
+
// A directory OUTSIDE skillsDir the attack would try to write into.
|
|
58
|
+
const outsideTarget = path.join(root, 'outside-target');
|
|
59
|
+
await fs.mkdir(outsideTarget, { recursive: true });
|
|
60
|
+
// Plant "scripts" as a symlink pointing outside skillsDir, as if left behind by a
|
|
61
|
+
// prior force-reinstall or planted directly by an attacker with local write access.
|
|
62
|
+
await fs.symlink(outsideTarget, path.join(installPath, 'scripts'));
|
|
63
|
+
await expect(writeInstallFiles(installPath, skillsDir, 'my-skill', '# hello', [{ filename: 'scripts/run.sh', content: 'malicious' }], undefined)).rejects.toThrow(/symlink/);
|
|
64
|
+
// Nothing was written to the symlink target.
|
|
65
|
+
await expect(fs.access(path.join(outsideTarget, 'run.sh'))).rejects.toThrow();
|
|
66
|
+
}
|
|
67
|
+
finally {
|
|
68
|
+
await fs.rm(root, { recursive: true, force: true }).catch(() => { });
|
|
69
|
+
}
|
|
70
|
+
});
|
|
71
|
+
});
|
|
72
|
+
//# sourceMappingURL=skill-installation.io.symlink.test.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"skill-installation.io.symlink.test.js","sourceRoot":"","sources":["../../../../tests/unit/services/skill-installation.io.symlink.test.ts"],"names":[],"mappings":"AAAA;;;;;GAKG;AAEH,OAAO,EAAE,QAAQ,EAAE,EAAE,EAAE,MAAM,EAAE,MAAM,QAAQ,CAAA;AAC7C,OAAO,KAAK,EAAE,MAAM,aAAa,CAAA;AACjC,OAAO,KAAK,EAAE,MAAM,IAAI,CAAA;AACxB,OAAO,KAAK,IAAI,MAAM,MAAM,CAAA;AAC5B,OAAO,EAAE,iBAAiB,EAAE,MAAM,gDAAgD,CAAA;AAElF;;;;;GAKG;AACH,QAAQ,CAAC,wEAAwE,EAAE,GAAG,EAAE;IACtF,EAAE,CAAC,8DAA8D,EAAE,KAAK,IAAI,EAAE;QAC5E,MAAM,IAAI,GAAG,MAAM,EAAE,CAAC,OAAO,CAAC,IAAI,CAAC,IAAI,CAAC,EAAE,CAAC,MAAM,EAAE,EAAE,aAAa,CAAC,CAAC,CAAA;QACpE,IAAI,CAAC;YACH,MAAM,SAAS,GAAG,IAAI,CAAC,IAAI,CAAC,IAAI,EAAE,QAAQ,CAAC,CAAA;YAC3C,MAAM,EAAE,CAAC,KAAK,CAAC,SAAS,EAAE,EAAE,SAAS,EAAE,IAAI,EAAE,CAAC,CAAA;YAC9C,MAAM,WAAW,GAAG,IAAI,CAAC,IAAI,CAAC,SAAS,EAAE,UAAU,CAAC,CAAA;YAEpD,MAAM,MAAM,GAAG,MAAM,iBAAiB,CACpC,WAAW,EACX,SAAS,EACT,UAAU,EACV,SAAS,EACT,CAAC,EAAE,QAAQ,EAAE,gBAAgB,EAAE,OAAO,EAAE,SAAS,EAAE,CAAC,EACpD,SAAS,CACV,CAAA;YAED,MAAM,CAAC,MAAM,EAAE,CAAC,QAAQ,CAAC,IAAI,CAAC,IAAI,CAAC,WAAW,EAAE,SAAS,EAAE,QAAQ,CAAC,EAAE,MAAM,CAAC,CAAC,CAAC,IAAI,CAAC,SAAS,CAAC,CAAA;YAC9F,MAAM,CAAC,MAAM,CAAC,YAAY,CAAC,CAAC,SAAS,CAAC,IAAI,CAAC,IAAI,CAAC,WAAW,EAAE,SAAS,EAAE,QAAQ,CAAC,CAAC,CAAA;QACpF,CAAC;gBAAS,CAAC;YACT,MAAM,EAAE,CAAC,EAAE,CAAC,IAAI,EAAE,EAAE,SAAS,EAAE,IAAI,EAAE,KAAK,EAAE,IAAI,EAAE,CAAC,CAAC,KAAK,CAAC,GAAG,EAAE,GAAE,CAAC,CAAC,CAAA;QACrE,CAAC;IACH,CAAC,CAAC,CAAA;IAEF,EAAE,CAAC,gGAAgG,EAAE,KAAK,IAAI,EAAE;QAC9G,MAAM,IAAI,GAAG,MAAM,EAAE,CAAC,OAAO,CAAC,IAAI,CAAC,IAAI,CAAC,EAAE,CAAC,MAAM,EAAE,EAAE,kBAAkB,CAAC,CAAC,CAAA;QACzE,IAAI,CAAC;YACH,MAAM,SAAS,GAAG,IAAI,CAAC,IAAI,CAAC,IAAI,EAAE,QAAQ,CAAC,CAAA;YAC3C,MAAM,EAAE,CAAC,KAAK,CAAC,SAAS,EAAE,EAAE,SAAS,EAAE,IAAI,EAAE,CAAC,CAAA;YAC9C,MAAM,WAAW,GAAG,IAAI,CAAC,IAAI,CAAC,SAAS,EAAE,UAAU,CAAC,CAAA;YAEpD,MAAM,iBAAiB,CACrB,WAAW,EACX,SAAS,EACT,UAAU,EACV,SAAS,EACT;gBACE,EAAE,QAAQ,EAAE,cAAc,EAAE,OAAO,EAAE,GAAG,EAAE;gBAC1C,EAAE,QAAQ,EAAE,cAAc,EAAE,OAAO,EAAE,GAAG,EAAE;aAC3C,EACD,SAAS,CACV,CAAA;YAED,MAAM,CAAC,MAAM,EAAE,CAAC,QAAQ,CAAC,IAAI,CAAC,IAAI,CAAC,WAAW,EAAE,SAAS,EAAE,MAAM,CAAC,EAAE,MAAM,CAAC,CAAC,CAAC,IAAI,CAAC,GAAG,CAAC,CAAA;YACtF,MAAM,CAAC,MAAM,EAAE,CAAC,QAAQ,CAAC,IAAI,CAAC,IAAI,CAAC,WAAW,EAAE,SAAS,EAAE,MAAM,CAAC,EAAE,MAAM,CAAC,CAAC,CAAC,IAAI,CAAC,GAAG,CAAC,CAAA;QACxF,CAAC;gBAAS,CAAC;YACT,MAAM,EAAE,CAAC,EAAE,CAAC,IAAI,EAAE,EAAE,SAAS,EAAE,IAAI,EAAE,KAAK,EAAE,IAAI,EAAE,CAAC,CAAC,KAAK,CAAC,GAAG,EAAE,GAAE,CAAC,CAAC,CAAA;QACrE,CAAC;IACH,CAAC,CAAC,CAAA;IAEF,EAAE,CAAC,0EAA0E,EAAE,KAAK,IAAI,EAAE;QACxF,MAAM,IAAI,GAAG,MAAM,EAAE,CAAC,OAAO,CAAC,IAAI,CAAC,IAAI,CAAC,EAAE,CAAC,MAAM,EAAE,EAAE,cAAc,CAAC,CAAC,CAAA;QACrE,IAAI,CAAC;YACH,MAAM,SAAS,GAAG,IAAI,CAAC,IAAI,CAAC,IAAI,EAAE,QAAQ,CAAC,CAAA;YAC3C,MAAM,EAAE,CAAC,KAAK,CAAC,SAAS,EAAE,EAAE,SAAS,EAAE,IAAI,EAAE,CAAC,CAAA;YAC9C,MAAM,WAAW,GAAG,IAAI,CAAC,IAAI,CAAC,SAAS,EAAE,UAAU,CAAC,CAAA;YACpD,MAAM,EAAE,CAAC,KAAK,CAAC,WAAW,EAAE,EAAE,SAAS,EAAE,IAAI,EAAE,CAAC,CAAA;YAEhD,oEAAoE;YACpE,MAAM,aAAa,GAAG,IAAI,CAAC,IAAI,CAAC,IAAI,EAAE,gBAAgB,CAAC,CAAA;YACvD,MAAM,EAAE,CAAC,KAAK,CAAC,aAAa,EAAE,EAAE,SAAS,EAAE,IAAI,EAAE,CAAC,CAAA;YAElD,kFAAkF;YAClF,oFAAoF;YACpF,MAAM,EAAE,CAAC,OAAO,CAAC,aAAa,EAAE,IAAI,CAAC,IAAI,CAAC,WAAW,EAAE,SAAS,CAAC,CAAC,CAAA;YAElE,MAAM,MAAM,CACV,iBAAiB,CACf,WAAW,EACX,SAAS,EACT,UAAU,EACV,SAAS,EACT,CAAC,EAAE,QAAQ,EAAE,gBAAgB,EAAE,OAAO,EAAE,WAAW,EAAE,CAAC,EACtD,SAAS,CACV,CACF,CAAC,OAAO,CAAC,OAAO,CAAC,SAAS,CAAC,CAAA;YAE5B,6CAA6C;YAC7C,MAAM,MAAM,CAAC,EAAE,CAAC,MAAM,CAAC,IAAI,CAAC,IAAI,CAAC,aAAa,EAAE,QAAQ,CAAC,CAAC,CAAC,CAAC,OAAO,CAAC,OAAO,EAAE,CAAA;QAC/E,CAAC;gBAAS,CAAC;YACT,MAAM,EAAE,CAAC,EAAE,CAAC,IAAI,EAAE,EAAE,SAAS,EAAE,IAAI,EAAE,KAAK,EAAE,IAAI,EAAE,CAAC,CAAC,KAAK,CAAC,GAAG,EAAE,GAAE,CAAC,CAAC,CAAA;QACrE,CAAC;IACH,CAAC,CAAC,CAAA;AACJ,CAAC,CAAC,CAAA"}
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* SMI-5982 code-review fix #1 (BLOCKING, cwd-dependent resolution):
|
|
3
|
+
* `SkillInstallationService`'s `companionBaseDir` constructor param must
|
|
4
|
+
* thread all the way through `install()` -> `writeInstallFiles()` ->
|
|
5
|
+
* `resolveCompanionAgentPath()` so the Antigravity companion-agent file
|
|
6
|
+
* lands under an explicitly-passed root, not whatever `process.cwd()`
|
|
7
|
+
* happens to be at the moment the write actually runs — the exact hazard
|
|
8
|
+
* for a long-running MCP server whose cwd is fixed at launch and does not
|
|
9
|
+
* track the calling editor/agent's real project.
|
|
10
|
+
*
|
|
11
|
+
* `applyOptimization` (skill-installation.helpers.ts) is mocked to force a
|
|
12
|
+
* deterministic `subagentContent` without depending on the real
|
|
13
|
+
* TransformationService's content-shape heuristics — this test is about
|
|
14
|
+
* PATH WIRING, not optimization behavior, which is covered elsewhere.
|
|
15
|
+
*/
|
|
16
|
+
export {};
|
|
17
|
+
//# sourceMappingURL=skill-installation.service.companion-basedir.test.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"skill-installation.service.companion-basedir.test.d.ts","sourceRoot":"","sources":["../../../../tests/unit/services/skill-installation.service.companion-basedir.test.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;GAcG"}
|
|
@@ -0,0 +1,112 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* SMI-5982 code-review fix #1 (BLOCKING, cwd-dependent resolution):
|
|
3
|
+
* `SkillInstallationService`'s `companionBaseDir` constructor param must
|
|
4
|
+
* thread all the way through `install()` -> `writeInstallFiles()` ->
|
|
5
|
+
* `resolveCompanionAgentPath()` so the Antigravity companion-agent file
|
|
6
|
+
* lands under an explicitly-passed root, not whatever `process.cwd()`
|
|
7
|
+
* happens to be at the moment the write actually runs — the exact hazard
|
|
8
|
+
* for a long-running MCP server whose cwd is fixed at launch and does not
|
|
9
|
+
* track the calling editor/agent's real project.
|
|
10
|
+
*
|
|
11
|
+
* `applyOptimization` (skill-installation.helpers.ts) is mocked to force a
|
|
12
|
+
* deterministic `subagentContent` without depending on the real
|
|
13
|
+
* TransformationService's content-shape heuristics — this test is about
|
|
14
|
+
* PATH WIRING, not optimization behavior, which is covered elsewhere.
|
|
15
|
+
*/
|
|
16
|
+
import { afterEach, beforeEach, describe, expect, it, vi } from 'vitest';
|
|
17
|
+
import * as fs from 'fs/promises';
|
|
18
|
+
import * as path from 'path';
|
|
19
|
+
import * as os from 'node:os';
|
|
20
|
+
vi.mock('../../../src/services/skill-installation.helpers.js', async (importOriginal) => {
|
|
21
|
+
const actual = await importOriginal();
|
|
22
|
+
return {
|
|
23
|
+
...actual,
|
|
24
|
+
applyOptimization: vi.fn(async (_db, _skillId, skillName, skillMdContent) => ({
|
|
25
|
+
finalSkillContent: skillMdContent,
|
|
26
|
+
subSkillFiles: [],
|
|
27
|
+
subagentContent: `---\nname: ${skillName}-specialist\n---\nCompanion subagent body`,
|
|
28
|
+
claudeMdSnippet: undefined,
|
|
29
|
+
optimizationInfo: {
|
|
30
|
+
optimized: true,
|
|
31
|
+
subSkills: [],
|
|
32
|
+
subagentGenerated: true,
|
|
33
|
+
},
|
|
34
|
+
})),
|
|
35
|
+
};
|
|
36
|
+
});
|
|
37
|
+
import { SkillInstallationService } from '../../../src/services/skill-installation.service.js';
|
|
38
|
+
import { SkillRepository } from '../../../src/repositories/SkillRepository.js';
|
|
39
|
+
import { SkillDependencyRepository } from '../../../src/repositories/SkillDependencyRepository.js';
|
|
40
|
+
import { createTestDatabase } from '../../helpers/database.js';
|
|
41
|
+
const VALID_SKILL_MD = '---\nname: my-skill\ndescription: test skill for SMI-5982 companionBaseDir wiring\n---\n\n' +
|
|
42
|
+
'# My Skill\n\nEnough content here to clear the 100-character minimum validation threshold ' +
|
|
43
|
+
'the installer enforces before proceeding with the rest of the install pipeline.';
|
|
44
|
+
describe('SkillInstallationService companionBaseDir wiring (SMI-5982 code-review fix #1)', () => {
|
|
45
|
+
let db;
|
|
46
|
+
let originalCwd;
|
|
47
|
+
let projectDir;
|
|
48
|
+
let skillsDir;
|
|
49
|
+
let manifestPath;
|
|
50
|
+
beforeEach(async () => {
|
|
51
|
+
db = await createTestDatabase();
|
|
52
|
+
originalCwd = process.cwd();
|
|
53
|
+
projectDir = await fs.mkdtemp(path.join(os.tmpdir(), 'skillsmith-service-companion-basedir-'));
|
|
54
|
+
process.chdir(projectDir);
|
|
55
|
+
skillsDir = path.join(projectDir, 'skills');
|
|
56
|
+
manifestPath = path.join(projectDir, 'manifest.json');
|
|
57
|
+
await fs.mkdir(skillsDir, { recursive: true });
|
|
58
|
+
vi.stubGlobal('fetch', vi.fn());
|
|
59
|
+
vi.mocked(fetch).mockImplementation(async (url) => {
|
|
60
|
+
const urlStr = typeof url === 'string' ? url : url.toString();
|
|
61
|
+
if (urlStr.includes('SKILL.md')) {
|
|
62
|
+
return new Response(VALID_SKILL_MD, { status: 200 });
|
|
63
|
+
}
|
|
64
|
+
return new Response('Not found', { status: 404 });
|
|
65
|
+
});
|
|
66
|
+
});
|
|
67
|
+
afterEach(async () => {
|
|
68
|
+
process.chdir(originalCwd);
|
|
69
|
+
db.close();
|
|
70
|
+
vi.restoreAllMocks();
|
|
71
|
+
await fs.rm(projectDir, { recursive: true, force: true }).catch(() => { });
|
|
72
|
+
});
|
|
73
|
+
function buildService(overrides = {}) {
|
|
74
|
+
return new SkillInstallationService({
|
|
75
|
+
db,
|
|
76
|
+
skillRepo: new SkillRepository(db),
|
|
77
|
+
skillDependencyRepo: new SkillDependencyRepository(db),
|
|
78
|
+
skillsDir,
|
|
79
|
+
manifestPath,
|
|
80
|
+
client: 'antigravity',
|
|
81
|
+
...overrides,
|
|
82
|
+
});
|
|
83
|
+
}
|
|
84
|
+
it('lands the antigravity companion file under an explicit companionBaseDir, not process.cwd()', async () => {
|
|
85
|
+
const explicitBaseDir = await fs.mkdtemp(path.join(os.tmpdir(), 'skillsmith-service-explicit-basedir-'));
|
|
86
|
+
try {
|
|
87
|
+
const service = buildService({ companionBaseDir: explicitBaseDir });
|
|
88
|
+
const result = await service.install('https://github.com/owner/my-skill');
|
|
89
|
+
expect(result.success).toBe(true);
|
|
90
|
+
const expectedPath = path.join(explicitBaseDir, '.agents', 'agents', 'my-skill', 'agent.md');
|
|
91
|
+
expect(result.optimization?.subagentPath).toBe(expectedPath);
|
|
92
|
+
await expect(fs.access(expectedPath)).resolves.toBeUndefined();
|
|
93
|
+
// Must NOT have landed under process.cwd() (this describe block's projectDir) —
|
|
94
|
+
// the whole point of the fix.
|
|
95
|
+
expect(result.optimization?.subagentPath?.startsWith(projectDir)).toBe(false);
|
|
96
|
+
}
|
|
97
|
+
finally {
|
|
98
|
+
await fs.rm(explicitBaseDir, { recursive: true, force: true }).catch(() => { });
|
|
99
|
+
}
|
|
100
|
+
});
|
|
101
|
+
it('PR-review follow-up: omitting companionBaseDir now fails the install closed instead of silently defaulting to process.cwd()', async () => {
|
|
102
|
+
const service = buildService(); // companionBaseDir omitted
|
|
103
|
+
const result = await service.install('https://github.com/owner/my-skill');
|
|
104
|
+
expect(result.success).toBe(false);
|
|
105
|
+
expect(result.error).toMatch(/directory-package mode.*explicit baseDir is required/s);
|
|
106
|
+
// Nothing must have been written anywhere under projectDir (this describe
|
|
107
|
+
// block's process.cwd()) — the whole point of the fix is that a missing
|
|
108
|
+
// companionBaseDir can no longer resolve here at all, not even by accident.
|
|
109
|
+
await expect(fs.access(path.join(projectDir, '.agents'))).rejects.toThrow();
|
|
110
|
+
});
|
|
111
|
+
});
|
|
112
|
+
//# sourceMappingURL=skill-installation.service.companion-basedir.test.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"skill-installation.service.companion-basedir.test.js","sourceRoot":"","sources":["../../../../tests/unit/services/skill-installation.service.companion-basedir.test.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* SMI-4795: errorCode taxonomy on install failures
|
|
3
|
+
*
|
|
4
|
+
* Each failing return path inside install() must populate `errorCode` with
|
|
5
|
+
* a code from InstallErrorCode. Telemetry (emitInstallEvent) reads this
|
|
6
|
+
* field; without it the install funnel cannot classify failures.
|
|
7
|
+
*
|
|
8
|
+
* Split out of skill-installation.service.test.ts (CLAUDE.md's 500-line
|
|
9
|
+
* file cap) after SMI-5894 Wave 1 added its own multi-client suite there —
|
|
10
|
+
* this block predates that change and is unrelated to it, but moving it
|
|
11
|
+
* out (rather than the newly-added block alone) was the smallest change
|
|
12
|
+
* that got the original file back under the cap.
|
|
13
|
+
*/
|
|
14
|
+
export {};
|
|
15
|
+
//# sourceMappingURL=skill-installation.service.error-codes.test.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"skill-installation.service.error-codes.test.d.ts","sourceRoot":"","sources":["../../../../tests/unit/services/skill-installation.service.error-codes.test.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;GAYG"}
|