Files
pbakaus_impeccable/skill/scripts/context.mjs
T
Paul BakausandClaude Opus 4.7 33467e5f0f skill: simplify context loading and inline register directive
Replaces load-context.mjs's JSON output with a tight markdown block from
the renamed context.mjs. The script now extracts PRODUCT.md's `## Register`
field and appends a `NEXT STEP:` directive naming the matching reference
(brand.md / product.md), which moved Gemini from skipping the register
load entirely to honoring it. Drops the `.impeccable.md` auto-migration;
makes IMPECCABLE_CONTEXT_DIR a lazy escape hatch consulted only when the
default paths come up empty.

Setup is now four bullets in one list. The DESIGN.md nudge is gone; in
its place, a "familiarize with the existing design system" step that
calls out CSS / tokens / running app as authoritative sources alongside
DESIGN.md. The standalone `### Register` H3 stays for the cascade rules
(task cue → surface → register field).

New LLM-backed test suite at tests/skill-behavior/ runs five scenarios
against claude-haiku-4-5, gpt-5.4-mini, and gemini-3.1-flash-lite via
Vercel AI SDK. Captures real tool traces, asserts on context.mjs calls,
brand.md loads, and teach.md fallback. Skips cleanly when API keys are
unset. 13-14/15 pass; only stable failure is the v3.2.0-era gpt-mini S4
"don't re-run" regression. Adds @ai-sdk/google as devDep and the
test:skill-behavior npm script.

Touches em-dashes in skill/SKILL.md and four reference files so
`bun run build:skills` passes its skill-prose validator. teach.md and
document.md drop their "re-run the loader to refresh session cache"
steps since the agent's own write is now the freshest source.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-20 15:25:31 -07:00

126 lines
4.2 KiB
JavaScript

/**
* Context loader: prints PRODUCT.md (and DESIGN.md if present) as one
* markdown block on stdout, or exits with empty stdout when no PRODUCT.md
* is found anywhere. The skill keys off "empty stdout" to branch into the
* teach flow.
*
* Path resolution (first match wins):
* 1. cwd, if PRODUCT.md or DESIGN.md is there
* 2. .agents/context/ then docs/
* 3. $IMPECCABLE_CONTEXT_DIR (absolute or cwd-relative) — power-user
* escape hatch, only consulted when defaults are empty
* 4. cwd as a "nothing found" default
*
* `resolveContextDir()` and `loadContext()` are also exported for the
* server-side scripts (live.mjs, live-server.mjs) that need the structured
* shape rather than the markdown block.
*/
import fs from 'node:fs';
import path from 'node:path';
const PRODUCT_NAMES = ['PRODUCT.md', 'Product.md', 'product.md'];
const DESIGN_NAMES = ['DESIGN.md', 'Design.md', 'design.md'];
const FALLBACK_DIRS = ['.agents/context', 'docs'];
export function resolveContextDir(cwd = process.cwd()) {
if (firstExisting(cwd, [...PRODUCT_NAMES, ...DESIGN_NAMES])) {
return cwd;
}
for (const rel of FALLBACK_DIRS) {
const candidate = path.resolve(cwd, rel);
if (firstExisting(candidate, [...PRODUCT_NAMES, ...DESIGN_NAMES])) {
return candidate;
}
}
const envDir = process.env.IMPECCABLE_CONTEXT_DIR;
if (envDir && envDir.trim()) {
const trimmed = envDir.trim();
return path.isAbsolute(trimmed) ? trimmed : path.resolve(cwd, trimmed);
}
return cwd;
}
export function loadContext(cwd = process.cwd()) {
const contextDir = resolveContextDir(cwd);
const productPath = firstExisting(contextDir, PRODUCT_NAMES);
const designPath = firstExisting(contextDir, DESIGN_NAMES);
const product = productPath ? safeRead(productPath) : null;
const design = designPath ? safeRead(designPath) : null;
return {
hasProduct: !!product,
product,
productPath: productPath ? path.relative(cwd, productPath) : null,
hasDesign: !!design,
design,
designPath: designPath ? path.relative(cwd, designPath) : null,
contextDir,
};
}
function firstExisting(dir, names) {
for (const name of names) {
const abs = path.join(dir, name);
if (fs.existsSync(abs)) return abs;
}
return null;
}
function safeRead(p) {
try {
return fs.readFileSync(p, 'utf-8');
} catch {
return null;
}
}
/**
* Pull the register (`brand` or `product`) out of PRODUCT.md by looking
* for a `## Register` section and reading the first non-empty line that
* follows it. Returns null when the file is legacy / register-less.
*/
function extractRegister(product) {
if (!product) return null;
const lines = product.split('\n');
for (let i = 0; i < lines.length; i++) {
if (/^##\s+Register\b/i.test(lines[i].trim())) {
for (let j = i + 1; j < lines.length; j++) {
const next = lines[j].trim();
if (!next) continue;
const word = next.toLowerCase();
if (word === 'brand' || word === 'product') return word;
return null;
}
}
}
return null;
}
function cli() {
const ctx = loadContext(process.cwd());
if (!ctx.hasProduct) {
// Direct stdout message instead of relying on empty output as a signal
// — cheap models miss the empty case more often than the explicit one.
process.stdout.write(
'NO_PRODUCT_MD: This project has no PRODUCT.md yet. ' +
'Stop the current task, load reference/teach.md, and follow its ' +
'instructions to write PRODUCT.md before resuming.\n',
);
process.exit(0);
}
const parts = [`# PRODUCT.md\n\n${ctx.product.trim()}`];
if (ctx.hasDesign) {
parts.push(`# DESIGN.md\n\n${ctx.design.trim()}`);
}
const register = extractRegister(ctx.product);
const next = register
? `NEXT STEP: This project's register is \`${register}\`. You MUST now read \`reference/${register}.md\` before producing any design output.`
: `NEXT STEP: You MUST now read the matching register reference (\`reference/brand.md\` or \`reference/product.md\`) before producing any design output. Pick based on PRODUCT.md above.`;
parts.push(next);
process.stdout.write(parts.join('\n\n---\n\n') + '\n');
}
const _running = process.argv[1];
if (_running?.endsWith('context.mjs') || _running?.endsWith('context.mjs/')) {
cli();
}