90 lines
4.5 KiB
JavaScript
90 lines
4.5 KiB
JavaScript
// Phase 6 acceptance: `npm run build:skill` must produce a runnable, readable,
|
|
// .ts-free skill artifact under dist/obelisk-skill. This guards ADR-0004 (ship
|
|
// readable non-bundled compiled JS) and catches import-rewriting / config drift
|
|
// that would only surface when the installed skill runs under plain Node (no
|
|
// type-stripping, no .ts resolution).
|
|
import { test } from 'node:test';
|
|
import assert from 'node:assert/strict';
|
|
import { execFileSync, spawnSync } from 'node:child_process';
|
|
import { readFileSync, readdirSync, mkdtempSync, mkdirSync, writeFileSync, rmSync, existsSync } from 'node:fs';
|
|
import { tmpdir } from 'node:os';
|
|
import { dirname, join, resolve } from 'node:path';
|
|
import { fileURLToPath } from 'node:url';
|
|
|
|
const repoRoot = resolve(dirname(fileURLToPath(import.meta.url)), '..');
|
|
const skillDir = join(repoRoot, 'dist', 'obelisk-skill');
|
|
|
|
function walk(dir) {
|
|
const out = [];
|
|
for (const entry of readdirSync(dir, { withFileTypes: true })) {
|
|
const full = join(dir, entry.name);
|
|
if (entry.isDirectory()) out.push(...walk(full));
|
|
else out.push(full);
|
|
}
|
|
return out;
|
|
}
|
|
|
|
test('build:skill produces a runnable, readable, .ts-free skill artifact', () => {
|
|
execFileSync('npm', ['run', 'build:skill'], { cwd: repoRoot, encoding: 'utf8', stdio: 'pipe' });
|
|
|
|
// Structure: compiled Core + schema + docs + package.json.
|
|
for (const rel of [
|
|
'package.json', 'SKILL.md', 'references/api-reference.md',
|
|
'scripts/core.js', 'scripts/persist.js', 'scripts/providers/claude.js',
|
|
'scripts/providers/codex.js', 'scripts/runtime.js', 'scripts/indexer.js',
|
|
'scripts/db.js', 'scripts/parsing.js', 'scripts/query.js', 'scripts/schema.sql',
|
|
]) {
|
|
assert.ok(existsSync(join(skillDir, rel)), `artifact missing ${rel}`);
|
|
}
|
|
assert.equal(JSON.parse(readFileSync(join(skillDir, 'package.json'), 'utf8')).type, 'module');
|
|
|
|
// Readable, not bundled: emitted files stay ~1:1 with source, and no relative
|
|
// import may still point at a .ts file (that would break under plain Node).
|
|
const jsFiles = walk(join(skillDir, 'scripts')).filter(f => f.endsWith('.js') || f.endsWith('.mjs'));
|
|
assert.ok(jsFiles.length >= 6, 'expected multiple un-bundled script files');
|
|
for (const file of jsFiles) {
|
|
const src = readFileSync(file, 'utf8');
|
|
assert.ok(!/from\s+['"][^'"]*\.ts['"]/.test(src), `${file} still imports a .ts module`);
|
|
assert.ok(!/import\(['"][^'"]*\.ts['"]\)/.test(src), `${file} still dynamic-imports a .ts module`);
|
|
}
|
|
|
|
// Runs end to end under plain Node against a fresh HOME (no type-stripping).
|
|
const home = mkdtempSync(join(tmpdir(), 'obelisk-skill-artifact-'));
|
|
try {
|
|
const projDir = join(home, '.claude', 'projects', '-tmp-proj');
|
|
mkdirSync(projDir, { recursive: true });
|
|
writeFileSync(join(projDir, 'smoke.jsonl'),
|
|
JSON.stringify({ uuid: 'm1', type: 'user', timestamp: '2026-06-10T10:00:00Z', cwd: '/tmp/proj', message: { role: 'user', content: 'hello artifact' } }) + '\n');
|
|
const env = { ...process.env, HOME: home };
|
|
const runtime = join(skillDir, 'scripts', 'runtime.js');
|
|
|
|
const build = spawnSync(process.execPath, [runtime, '--build'], { env, encoding: 'utf8' });
|
|
assert.equal(build.status, 0, build.stderr || build.stdout);
|
|
|
|
const search = spawnSync(process.execPath, [runtime, '--search', 'hello artifact'], { env, encoding: 'utf8' });
|
|
assert.equal(search.status, 0, search.stderr || search.stdout);
|
|
const hits = JSON.parse(search.stdout);
|
|
assert.equal(hits[0]?.message?.text, 'hello artifact', 'compiled artifact indexed and found the message');
|
|
|
|
const memoryPath = join(home, 'artifact-memory.md');
|
|
const attunePath = join(home, 'attune.mjs');
|
|
writeFileSync(memoryPath, '# Artifact memory\n');
|
|
writeFileSync(attunePath, `return remember(${JSON.stringify({
|
|
path: memoryPath,
|
|
session_id: 'smoke',
|
|
summary: 'Artifact release smoke memory',
|
|
})});`);
|
|
const attune = spawnSync(process.execPath, [runtime, '--attune', attunePath], { env, encoding: 'utf8' });
|
|
assert.equal(attune.status, 0, attune.stderr || attune.stdout);
|
|
assert.match(JSON.parse(attune.stdout).id, /^mem-/);
|
|
|
|
const queryPath = join(home, 'query.mjs');
|
|
writeFileSync(queryPath, "return memories({ sessionId: 'smoke', query: 'Artifact release smoke' });");
|
|
const query = spawnSync(process.execPath, [runtime, '--query', queryPath], { env, encoding: 'utf8' });
|
|
assert.equal(query.status, 0, query.stderr || query.stdout);
|
|
assert.equal(JSON.parse(query.stdout)[0]?.summary, 'Artifact release smoke memory');
|
|
} finally {
|
|
rmSync(home, { recursive: true, force: true });
|
|
}
|
|
});
|