Files
pbakaus_impeccable/tests/lib/transformers/providers.test.js
T
6ebc24ad66 Add DeepSeek Harness as a supported skills provider (#746)
* Add DeepSeek Harness as a supported skills provider

npx impeccable install now detects ~/.dsh (or $DSH_HOME when it sits
under home) and installs into ~/.dsh/skills, the user-level skill root
DeepSeek Harness scans, with project-level .dsh/skills on the same
layout as other providers. Aliases: dsh, deepseek, deepseek-harness.

Engine: PROVIDER_DIRS / aliases / display / input order / global hint,
$DSH_HOME-aware user skills dir, provider id resolution from the skill
dir, pin harness dirs, bundle path normalization for hashing.

Build: dsh transformer target emitting the frontmatter DeepSeek Harness
reads (user-invocable, license, compatibility, metadata; unknown keys
are ignored there) with no emitHooks (DSH hooks are in-process plugins,
not on-disk manifests) and no agentFormat (no documented on-disk
subagent format); placeholders (AGENTS.md config file, ask_user_question
tool, / command prefix), provider block tags, universal README entry.

Docs: HARNESSES.md row and frontmatter column, CLI-CONTRACT constants,
README/DEVELOP/AGENTS provider lists.

Validation: cargo test --workspace; node scripts/run-tests.mjs core
(138 pass); bun run build (19 providers, dist/dsh artifact verified);
engine smoke against a fake HOME with a local bundle: install
--providers=dsh --scope=global, auto-detected install, and update all
resolve the .dsh provider.

Generated provider output intentionally omitted per repo policy; the
sync workflow regenerates tracked .dsh/skills after merge.

Prepared with AI assistance (DeepSeek Harness coding agent).

* Address review: DSH_HOME-only detection, generated-output pathspecs

- Detect DeepSeek Harness through the resolved $DSH_HOME (fallback
  ~/.dsh) instead of gating on a fixed ~/.dsh path, so a DSH_HOME-only
  setup is offered by a provider-less install; generalize the two
  env-relocated config-dir hints (OpenCode, DSH) into one shared probe.
- Add .dsh to the sync workflow's GENERATED_PATHS and CI's generated
  drift check so the tracked .dsh/skills payload is committed and
  validated.
- Cover both behaviors: new install_detection_tests (DSH_HOME-only,
  default ~/.dsh, refused outside-home override) and a CLI-CONTRACT
  note on the resolved detection path.

Validation: cargo test --workspace; node scripts/run-tests.mjs core
(138 pass); engine smoke: DSH_HOME-only fake HOME installs globally
into the resolved skills dir.

Prepared with AI assistance (DeepSeek Harness coding agent).

* Fix DeepSeek Harness home paths on Windows

Use native relative-path containment, cover case and drive boundaries, and verify relocated global install/update without changing project skills. Add DSH output coverage and correct the install documentation.

AI assistance: Codex, under pbakaus maintainer direction.

* Document the CLI limit on external DSH homes

Clarify that outside-home manual copies are not detected or updated by the CLI.

AI assistance: Codex, under pbakaus maintainer direction.

---------

Co-authored-by: Paul Bakaus <paul.bakaus@gmail.com>
2026-09-06 14:46:05 -07:00

221 lines
9.7 KiB
JavaScript

import { describe, test, expect, beforeEach, afterEach } from 'bun:test';
import fs from 'fs';
import path from 'path';
import { PROVIDERS } from '../../../scripts/lib/transformers/providers.js';
import { createTransformer } from '../../../scripts/lib/transformers/factory.js';
import { parseFrontmatter, PROVIDER_PLACEHOLDERS } from '../../../scripts/lib/utils.js';
const TEST_DIR = path.join(process.cwd(), 'test-tmp-providers');
function providerTestDir(provider, suffix = '') {
return path.join(TEST_DIR, `${provider}${suffix}`);
}
function skillPath(config, skillName, suffix = '') {
return path.join(TEST_DIR, `${config.provider}${suffix}/${config.configDir}/skills/${skillName}/SKILL.md`);
}
// Test every provider config
for (const [key, config] of Object.entries(PROVIDERS)) {
describe(`Provider: ${config.displayName} (${key})`, () => {
const transform = createTransformer(config);
beforeEach(() => {
if (fs.existsSync(TEST_DIR)) {
fs.rmSync(TEST_DIR, { recursive: true, force: true });
}
});
afterEach(() => {
if (fs.existsSync(TEST_DIR)) {
fs.rmSync(TEST_DIR, { recursive: true, force: true });
}
});
test('should create correct directory structure', () => {
transform([], TEST_DIR);
expect(fs.existsSync(path.join(TEST_DIR, `${config.provider}/${config.configDir}/skills`))).toBe(true);
});
test('should replace {{model}} placeholder correctly', () => {
const skills = [{ name: 'test', description: 'Test', body: 'Ask {{model}} for help.' }];
transform(skills, TEST_DIR);
const content = fs.readFileSync(skillPath(config, 'test'), 'utf-8');
const expected = PROVIDER_PLACEHOLDERS[config.placeholderProvider || config.provider].model;
expect(content).toContain(`Ask ${expected} for help.`);
});
test('should replace {{config_file}} placeholder correctly', () => {
const skills = [{ name: 'test', description: 'Test', body: 'See {{config_file}}.' }];
transform(skills, TEST_DIR);
const content = fs.readFileSync(skillPath(config, 'test'), 'utf-8');
const expected = PROVIDER_PLACEHOLDERS[config.placeholderProvider || config.provider].config_file;
expect(content).toContain(`See ${expected}.`);
});
test('should copy reference files', () => {
const skills = [{
name: 'test',
description: 'Test',
body: 'Body',
references: [{ name: 'ref', content: 'Ref content', filePath: '/fake/ref.md' }]
}];
transform(skills, TEST_DIR);
const refPath = path.join(TEST_DIR, `${config.provider}/${config.configDir}/skills/test/reference/ref.md`);
expect(fs.existsSync(refPath)).toBe(true);
});
if (key === 'dsh') {
test('uses DSH tools and resource paths without installing unsupported hooks or agents', () => {
transform([{
name: 'impeccable',
description: 'Test',
userInvocable: true,
allowedTools: 'Bash',
body: '{{ask_instruction}} Run `{{scripts_path}}/impeccable context`.',
references: [{ name: 'polish', content: 'Run `{{scripts_path}}/impeccable detect`.', filePath: '/fake/polish.md' }],
}], TEST_DIR);
const content = fs.readFileSync(skillPath(config, 'impeccable'), 'utf8');
expect(content).toContain('call the ask_user_question tool');
expect(content).toContain('.dsh/skills/impeccable/scripts/impeccable context');
expect(parseFrontmatter(content).frontmatter['allowed-tools']).toBeUndefined();
const root = path.join(TEST_DIR, 'dsh', '.dsh');
expect(fs.readFileSync(path.join(root, 'skills/impeccable/reference/polish.md'), 'utf8'))
.toContain('.dsh/skills/impeccable/scripts/impeccable detect');
expect(fs.readdirSync(root)).toEqual(['skills']);
expect(config.emitHooks).toBeUndefined();
expect(config.agentFormat).toBeUndefined();
});
}
test('should emit skillsVersion in generated skill frontmatter', () => {
const skills = [{ name: 'test', description: 'Test', body: 'Body' }];
transform(skills, TEST_DIR, { skillsVersion: '1.2.3-test' });
const content = fs.readFileSync(skillPath(config, 'test'), 'utf-8');
const parsed = parseFrontmatter(content);
if (key === 'codex' || key === 'agents') {
expect(parsed.frontmatter.version).toBeUndefined();
expect(content).toContain('metadata:\n version: 1.2.3-test');
} else {
expect(parsed.frontmatter.version).toBe('1.2.3-test');
}
});
// Field-specific tests based on provider config
if (config.frontmatterFields.includes('user-invocable')) {
test('should emit user-invocable for user-invocable skills', () => {
const skills = [{ name: 'test', description: 'Test', userInvocable: true, body: 'Body' }];
transform(skills, TEST_DIR);
const parsed = parseFrontmatter(fs.readFileSync(skillPath(config, 'test'), 'utf-8'));
expect(parsed.frontmatter['user-invocable']).toBe(true);
});
test('should omit user-invocable for non-user-invocable skills', () => {
const skills = [{ name: 'test', description: 'Test', userInvocable: false, body: 'Body' }];
transform(skills, TEST_DIR);
const parsed = parseFrontmatter(fs.readFileSync(skillPath(config, 'test'), 'utf-8'));
expect(parsed.frontmatter['user-invocable']).toBeUndefined();
});
}
if (config.frontmatterFields.includes('argument-hint')) {
test('should emit argument-hint for user-invocable skills', () => {
const skills = [{ name: 'test', description: 'Test', userInvocable: true, argumentHint: '[target]', body: 'Body' }];
transform(skills, TEST_DIR);
const parsed = parseFrontmatter(fs.readFileSync(skillPath(config, 'test'), 'utf-8'));
expect(parsed.frontmatter['argument-hint']).toBe('[target]');
});
test('should not emit argument-hint for non-user-invocable skills', () => {
const skills = [{ name: 'test', description: 'Test', userInvocable: false, argumentHint: '[target]', body: 'Body' }];
transform(skills, TEST_DIR);
const parsed = parseFrontmatter(fs.readFileSync(skillPath(config, 'test'), 'utf-8'));
expect(parsed.frontmatter['argument-hint']).toBeUndefined();
});
}
if (config.frontmatterFields.includes('license')) {
test('should emit license when present', () => {
const skills = [{ name: 'test', description: 'Test', license: 'MIT', body: 'Body' }];
transform(skills, TEST_DIR);
const parsed = parseFrontmatter(fs.readFileSync(skillPath(config, 'test'), 'utf-8'));
expect(parsed.frontmatter.license).toBe('MIT');
});
test('should omit license when empty', () => {
const skills = [{ name: 'test', description: 'Test', license: '', body: 'Body' }];
transform(skills, TEST_DIR);
const parsed = parseFrontmatter(fs.readFileSync(skillPath(config, 'test'), 'utf-8'));
expect(parsed.frontmatter.license).toBeUndefined();
});
}
if (config.frontmatterFields.includes('compatibility')) {
test('should emit compatibility when present', () => {
const skills = [{ name: 'test', description: 'Test', compatibility: config.provider, body: 'Body' }];
transform(skills, TEST_DIR);
const content = fs.readFileSync(skillPath(config, 'test'), 'utf-8');
expect(content).toContain(`compatibility: ${config.provider}`);
});
}
if (config.frontmatterFields.includes('metadata')) {
test('should emit metadata when present', () => {
const skills = [{ name: 'test', description: 'Test', metadata: 'some-metadata', body: 'Body' }];
transform(skills, TEST_DIR);
const content = fs.readFileSync(skillPath(config, 'test'), 'utf-8');
expect(content).toContain('metadata: some-metadata');
});
}
if (config.frontmatterFields.includes('allowed-tools')) {
test('should emit allowed-tools when present', () => {
const skills = [{ name: 'test', description: 'Test', allowedTools: 'Bash,Edit', body: 'Body' }];
transform(skills, TEST_DIR);
const content = fs.readFileSync(skillPath(config, 'test'), 'utf-8');
expect(content).toContain('allowed-tools: Bash,Edit');
});
}
// Fields NOT in this provider's allowlist should not appear
const allOptionalFields = ['user-invocable', 'argument-hint', 'license', 'compatibility', 'metadata', 'allowed-tools'];
const excludedFields = allOptionalFields.filter(f => !config.frontmatterFields.includes(f));
if (excludedFields.length > 0) {
test('should not emit fields outside allowlist', () => {
const skills = [{
name: 'test',
description: 'Test',
userInvocable: true,
argumentHint: '[target]',
license: 'MIT',
compatibility: 'all',
metadata: 'meta',
allowedTools: 'Bash',
body: 'Body'
}];
transform(skills, TEST_DIR);
const content = fs.readFileSync(skillPath(config, 'test'), 'utf-8');
for (const field of excludedFields) {
const yamlKey = field; // field names match yaml keys
// Check the raw content doesn't contain the yaml key
const lines = content.split('\n');
const frontmatterLines = [];
let inFrontmatter = false;
for (const line of lines) {
if (line === '---') {
if (inFrontmatter) break;
inFrontmatter = true;
continue;
}
if (inFrontmatter) frontmatterLines.push(line);
}
const hasForbiddenField = frontmatterLines.some(l => l.startsWith(`${yamlKey}:`));
expect(hasForbiddenField).toBe(false);
}
});
}
});
}