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.js CLI 整測、用 node:test 內建。
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。升或用 polyfill。
煙測驗 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+ 而非硬碼數。
生命測驗 建 → 驗 → 刪 含清:
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 skillsnode:test 默以陳序行套、然套內測或無序。生命套(建 → 驗 → 刪)置於單 describe 以保序after() 仍行。然 before() 拋→後測與 after() 或不行。before() 保簡execSync。或前置 echo y |、或於測恆傳 --yes.claude/skills/ 或 .agents/skills/ 之測改工樹。CI 或敗於「污工目」察。常清scaffold-cli-command — 建此測所驗之命build-cli-plugin — 建步四所測之適design-cli-output — 測所斷之出模