fix(setup): SSF-002 follow-up — step children: silence logger info, force clack colors

QA on the VM showed two leaks in the effect:step tee: the host logger's
INFO lines (it always emits ANSI and defaults to info) landed on the
wizard screen, and the child's clack card drew colorless because
picocolors disables ANSI on a piped stdout, clashing with the wizard
theme.

hostExecStream now spawns steps with LOG_LEVEL=warn (operator-set level
wins; warnings/errors still pass) and FORCE_COLOR=1 when the operator's
terminal is a real TTY.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01HB8rQxJN7itcFhNYVDntpm
This commit is contained in:
Koshkoshinsk
2026-07-06 16:20:59 +03:00
parent 1a3c3eaf9b
commit 405643f8a7
2 changed files with 28 additions and 1 deletions
+14
View File
@@ -148,6 +148,20 @@ describe('thin skill driver', () => {
expect(out.fields.PLATFORM_ID).toBe('telegram:42');
});
it('hostExecStream children run with LOG_LEVEL=warn — host logger info noise stays off the wizard', async () => {
const root = mkdtempSync(join(tmpdir(), 'driver-loglevel-'));
const prev = process.env.LOG_LEVEL;
delete process.env.LOG_LEVEL; // simulate an operator who didn't set one
try {
const out = await hostExecStream(root)(
'echo "=== NANOCLAW SETUP: ENV ==="; echo "STATUS: success"; echo "LVL: $LOG_LEVEL"; echo "=== END ==="',
);
expect(out.fields.LVL).toBe('warn');
} finally {
if (prev !== undefined) process.env.LOG_LEVEL = prev;
}
});
function reuseScratch(): { root: string; skill: string } {
const root = mkdtempSync(join(tmpdir(), 'reuse-'));
const skill = mkdtempSync(join(tmpdir(), 'reuse-skill-'));
+14 -1
View File
@@ -282,7 +282,20 @@ export function hostExecStream(projectRoot: string): (cmd: string) => Promise<St
new Promise((resolve) => {
const child = spawn('bash', ['-c', cmd], {
cwd: projectRoot,
env: { ...process.env, PATH: `${join(projectRoot, 'bin')}:${process.env.PATH ?? ''}` },
env: {
...process.env,
PATH: `${join(projectRoot, 'bin')}:${process.env.PATH ?? ''}`,
// A step renders curated operator UI (a code card, a QR) — the host
// logger's info noise doesn't belong on the wizard screen, and it
// always emits ANSI so it can't be filtered by stream. Warnings and
// errors still pass. An operator-set LOG_LEVEL wins (debugging).
LOG_LEVEL: process.env.LOG_LEVEL ?? 'warn',
// The child's stdout is a pipe, so picocolors would strip its clack
// rendering to bare box chars that clash with the wizard theme.
// When the OPERATOR's terminal is a real TTY, the teed lines land
// there — force color so the child's card matches the parent.
...(process.stdout.isTTY ? { FORCE_COLOR: '1' } : {}),
},
stdio: ['inherit', 'pipe', 'pipe'],
});
const blocks: Array<{ fields: Record<string, string> }> = [];