brainstorming
brainstorming
You MUST use this before any creative work - creating features, building components, adding functionality, modifying behavior, or planning anything new, in software or out of it (a talk, a business, a renovation).
test-cli-applicationWrite integration tests for a Node.js CLI application using the built-in node:test module. Covers the exec helper pattern, output assertions, filesystem state verification, cleanup hooks, JSON output parsing, error case testing, and state restoration after destructive tests. Use when adding tests to an existing CLI, testing a new command, verifying adapter …
Independent GitHub snapshot · Automated classification · Original documentation governs
以內置 node:test 之模與 execSync 撰 Node.js CLI 之合試。
cli/index.js)import { describe, it, before, after } from 'node:test';
import assert from 'node:assert/strict';
import { execSync } from 'child_process';
import { existsSync, rmSync } from 'fs';
import { resolve } from 'path';
const CLI = 'node cli/index.js';
const ROOT = process.cwd();
function run(args) {
return execSync(`${CLI} ${args}`, {
cwd: ROOT,
encoding: 'utf8',
timeout: 10000,
});
}
要設之決:
node:test 為內置——無須試行依execSync 行 CLI 為子程——試實之二進,非內函encoding: 'utf8' 給字符出為正則匹ROOT 為可復得:自 node:test 引之試文,有可行之 run() 助。
敗則:若 node:test 不可得,Node.js 之版於 18 以下。升或用墊。
煙試驗 CLI 啟、析參、生期之出形:
describe('meta', () => {
it('shows version', () => {
const out = run('--version');
assert.match(out, /\d+\.\d+\.\d+/);
});
it('shows help with all commands', () => {
const out = run('--help');
assert.match(out, /install/);
assert.match(out, /list/);
assert.match(out, /detect/);
});
});
describe('registry', () => {
it('list shows expected counts', () => {
const out = run('list --domains');
assert.match(out, /\d+ domains/);
});
it('search finds known items', () => {
const out = run('search "docker"');
assert.match(out, /result\(s\) for "docker"/);
});
it('search returns 0 for nonsense', () => {
const out = run('search "xyzzy-nonexistent"');
assert.match(out, /0 result/);
});
});
煙試之模:
--version 與 --help 恆行得:煙試確 CLI 可行而資已載。
敗則:若註冊計頻變,用 \d+ 而非硬編號。
生命試驗 create → verify → delete 之序與清:
describe('install', () => {
const testPath = resolve(ROOT, '.agents/skills/commit-changes');
after(() => {
// Always clean up, even if tests fail
try { rmSync(testPath); } catch {}
try { rmSync(resolve(ROOT, '.agents/skills'), { recursive: true }); } catch {}
try { rmSync(resolve(ROOT, '.agents'), { recursive: true }); } catch {}
});
it('dry-run does not create files', () => {
const out = run('install commit-changes --dry-run');
assert.match(out, /DRY RUN/);
assert.ok(!existsSync(testPath));
});
it('installs creates the target', () => {
run('install commit-changes');
assert.ok(existsSync(testPath));
});
it('skips already installed', () => {
const out = run('install commit-changes');
assert.match(out, /skipped/);
});
it('uninstall removes the target', () => {
run('uninstall commit-changes');
assert.ok(!existsSync(testPath));
});
});
清之則:
after() 而非 afterEach()——生命試相建try/catch——清不可敗試套得:試於 describe 內順行,敗時清亦行。
敗則:若試並行(node:test 默非),以 { concurrency: 1 } 強為序。
試各適器之目路而不變:
describe('adapter: cursor (dry-run)', () => {
it('targets .cursor/skills/ path', () => {
const out = run('install commit-changes --framework cursor --dry-run');
assert.match(out, /\.cursor\/skills/i);
});
});
describe('adapter: copilot (dry-run)', () => {
it('targets .github/ path', () => {
const out = run('install commit-changes --framework copilot --dry-run');
assert.match(out, /\.github/i);
});
});
此模可廣於任數適器。各試:
--framework 越自察--dry-run 不立文得:每適器一 describe 塊,各至少有路斷。
敗則:若適器於項目不存,試敗於「Unknown framework」。此正——適器試唯為已實之適器存。
describe('errors', () => {
it('rejects unknown items', () => {
assert.throws(
() => run('install nonexistent-skill-xyz'),
/No matching items|Unknown/,
);
});
it('rejects unknown framework', () => {
assert.throws(
() => run('install commit-changes --framework nonexistent'),
/Unknown framework/,
);
});
it('handles missing state gracefully', () => {
assert.throws(
() => run('scatter nonexistent-team'),
/not burning|Unknown/,
);
});
});
誤試之模:
assert.throws 捕 execSync 之非零出碼得:諸誤路生非零出碼與助辭。
敗則:execSync 於非零出投。誤之 stderr 或 stdout 含辭。若 assert.throws 之正則不匹,察 error.stdout。
describe('json output', () => {
it('campfire --json outputs valid JSON', () => {
const out = run('campfire --json');
const data = JSON.parse(out);
assert.ok(typeof data.totalTeams === 'number');
assert.ok(Array.isArray(data.fires));
});
it('gather --dry-run --json outputs structured data', () => {
const out = run('gather tending --dry-run --json');
// JSON may follow a DRY RUN header — extract from first '{'
const jsonStart = out.indexOf('{');
assert.ok(jsonStart >= 0, 'Should contain JSON');
const data = JSON.parse(out.slice(jsonStart));
assert.equal(data.team, 'tending');
});
});
JSON 試之患:
{ 字符得:JSON 之出可析而含期之鍵。
敗則:若 JSON.parse 敗,命或混人文於 JSON。或修命使 --json 模出純 JSON,或取 JSON 之子串。
describe('stateful commands', () => {
const stateDir = resolve(ROOT, '.agent-almanac');
after(() => {
// Remove state file created by tests
try { rmSync(stateDir, { recursive: true }); } catch {}
});
// Tests that create/modify state...
});
// Restore symlinks that destructive tests may remove
describe('destructive tests', () => {
after(() => {
// Restore symlinks that scatter/uninstall removed
const skills = ['heal', 'meditate', 'remote-viewing'];
for (const skill of skills) {
const link = resolve(ROOT, `.claude/skills/${skill}`);
if (!existsSync(link)) {
try {
execSync(`ln -s ../../skills/${skill} ${link}`, { cwd: ROOT });
} catch {}
}
}
});
});
態復之則:
.agent-almanac/state.json)試後須清scatter/uninstall 除之符鏈須復init 立之清單文(agent-almanac.yml)須除after() 鉤反聲明序行——復鉤宜末聲得:試套留項目於初狀。
敗則:若 CI 報試後留文,加清於 after()。試後用 git status 察漏態。
node --test cli/test/cli.test.js 行--version、--help、註冊載\d+ 正則或動讀計,非斷 329 skills。node:test 默以聲序行套,然套內試或不。用生命套(create → verify → delete)於單 describe 以保序。after() 仍行。然若於 before() 投,後試與 after() 或不行。before() 宜小。execSync。或管 echo y | 或恆於試傳 --yes。.claude/skills/ 或 .agents/skills/ 之試動工樹。CI 或敗於「dirty working directory」之察。恆清。scaffold-cli-command — 建此試所驗之命build-cli-plugin — 建第四步所試之適器design-cli-output — 試所斷之出模