Files
nanoclaw/scripts/skill-inputs.ts
T
gavrielc a517ecee07 test(skills): conformance suite — every fence-carrying skill applies programmatically
scripts/skill-conformance.test.ts auto-discovers each .claude/skills/*/SKILL.md
with nc: fences (15 today) and asserts, per skill: lint clean (errors and
warn-lints); per branch-scenario from a colocated apply-fixtures.json, a full
applySkill run with stubbed exec/execStream/resolveRemote goes green — nothing
deferred, nothing degraded to agent, every var bound; every when: guard value
is exercised by some scenario (coverage read from ApplyResult.vars, with
documented exclusions); static effect ordering (mutations before build, build
before test, restart only after both); and a run-health sabotage probe — a
failing fetch/check/external run must gate every later restart/step/wire.

Fixture values satisfy the skills' real validate: regexes, so a fixture can't
paper over a broken prompt. Skills with prompts but no fixture fail with an
actionable message; prompt-less skills get a default empty scenario.

scripts/skill-inputs.ts adds the inputsFromEnv helper from the seam spec §6
(NC_INPUT_<VAR>, collision error); its test round-trips env-supplied inputs
through a real skill apply.

Runs in the existing CI vitest step — no workflow change needed.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-07-13 17:11:25 +03:00

33 lines
1.5 KiB
TypeScript

// inputsFromEnv — the pipeline consumer's input path (docs/skill-engine-seam.md §6).
//
// A CI/pipeline caller supplies prompt answers as environment variables using
// the `NC_INPUT_<VAR>` convention (prompt var uppercased). This helper parses
// the skill's prompt vars via parseDirectives and returns the `inputs` record
// applySkill consumes. It is a helper, not an engine feature — the engine never
// reads process.env for inputs; `inputs` stays the only env-agnostic seam.
//
// Var names are case-sensitive in the grammar, so uppercasing can collide
// (`bot_token` vs `Bot_Token` both map to NC_INPUT_BOT_TOKEN); a collision is
// an error, never a silent last-writer-wins.
import { parseDirectives, promptVar } from './skill-directives.js';
export function inputsFromEnv(md: string, env: Record<string, string | undefined> = process.env): Record<string, string> {
const inputs: Record<string, string> = {};
const byKey = new Map<string, string>(); // NC_INPUT_<VAR> → the prompt var that claimed it
for (const d of parseDirectives(md)) {
if (d.kind !== 'prompt') continue;
const v = promptVar(d);
if (!v) continue;
const key = `NC_INPUT_${v.toUpperCase()}`;
const prior = byKey.get(key);
if (prior !== undefined && prior !== v) {
throw new Error(`inputsFromEnv: prompt vars "${prior}" and "${v}" both map to ${key} — rename one (var names must be case-insensitively unique)`);
}
byKey.set(key, v);
const val = env[key];
if (val !== undefined) inputs[v] = val;
}
return inputs;
}