Refine product and visual work lifecycle

This commit is contained in:
Paul Bakaus
2026-07-17 16:10:13 -07:00
parent bbed6eef08
commit 77c7d8e0fc
53 changed files with 1464 additions and 1761 deletions
+81 -29
View File
@@ -1,8 +1,10 @@
/**
* Context loader: prints PRODUCT.md (and DESIGN.md if present) as one
* markdown block on stdout, or prints a `NO_PRODUCT_MD:` message when no
* Context loader: prints PRODUCT.md, DESIGN.md when present, the matching
* persisted surface brief when one can be resolved, and native-platform
* guidance selected from PRODUCT.md. It prints a
* `NO_PRODUCT_MD:` message when no
* PRODUCT.md is found anywhere. The skill keys off that message to branch:
* from-scratch build commands (init / teach / craft / shape) and clear
* from-scratch build requests (plus init / teach / shape) and clear
* build/shape intent divert into the init flow, while scoped commands proceed
* using the existing code as context.
*
@@ -24,9 +26,11 @@ import path from 'node:path';
import { fileURLToPath } from 'node:url';
import { parseTargetOptions } from './lib/target-args.mjs';
import { IMPECCABLE_COMMAND } from './lib/provider.mjs';
import { resolveSurfaceBrief } from './lib/surface-briefs.mjs';
const PRODUCT_NAMES = ['PRODUCT.md', 'Product.md', 'product.md'];
const DESIGN_NAMES = ['DESIGN.md', 'Design.md', 'design.md'];
const SKILL_REFERENCE_DIR = path.resolve(path.dirname(fileURLToPath(import.meta.url)), '..', 'reference');
const FALLBACK_DIRS = ['.agents/context', 'docs'];
const MONOREPO_MARKER_FILES = ['pnpm-workspace.yaml', 'turbo.json', 'nx.json', 'lerna.json'];
const MONOREPO_FALLBACK_PROJECT_DIRS = ['apps', 'packages'];
@@ -75,6 +79,12 @@ export function loadContext(cwd = process.cwd(), options = {}) {
const designPath = resolved.designPath;
const product = productPath ? safeRead(productPath) : null;
const design = designPath ? safeRead(designPath) : null;
const platform = extractPlatform(product);
const surfaceResolution = resolveSurfaceBrief(
resolved.projectRoot,
hasTargetOption(options) ? options.targetPath : null,
);
const surfaceBrief = surfaceResolution.brief;
return {
hasProduct: !!product,
product,
@@ -85,7 +95,18 @@ export function loadContext(cwd = process.cwd(), options = {}) {
contextDir: resolved.contextDir,
productContextDir: productPath ? path.dirname(productPath) : null,
designContextDir: designPath ? path.dirname(designPath) : null,
hasSurfaceBrief: !!surfaceBrief,
surfaceBrief: surfaceBrief?.text ?? null,
surfaceBriefPath: surfaceBrief?.path ? path.relative(absCwd, surfaceBrief.path) : null,
surfaceBriefReason: surfaceResolution.reason,
surfaceBriefCandidates: surfaceResolution.candidates.map((brief) => ({
slug: brief.slug,
path: path.relative(absCwd, brief.path),
primaryTarget: brief.primaryTarget,
relatedTargets: brief.relatedTargets,
})),
hasVisualImplementation: hasVisualImplementation(resolved.projectRoot),
platform,
projectRoot: resolved.projectRoot,
repoRoot: resolved.repoRoot,
isMonorepo: resolved.isMonorepo,
@@ -698,6 +719,19 @@ function safeRead(p) {
}
}
function loadNativePlatformReferences(platform) {
const names = platform === 'adaptive'
? ['ios', 'android']
: platform === 'ios' || platform === 'android'
? [platform]
: [];
return names.flatMap((name) => {
const filePath = path.join(SKILL_REFERENCE_DIR, `${name}.md`);
const content = safeRead(filePath);
return content ? [{ name, filePath, content }] : [];
});
}
/**
* Best-effort evidence that the project already has an incumbent visual
* implementation. DESIGN.md is documentation, not the only source of design
@@ -989,13 +1023,13 @@ async function cli() {
const parts = ctx.hasVisualImplementation
? [
'NO_PRODUCT_MD: This project has no PRODUCT.md yet, but it does have an incumbent visual implementation. ' +
'For `init`, `teach`, `craft`, or `shape`, load reference/init.md and create PRODUCT.md with the user first. ' +
'For extension, init documents the incumbent system; for redesign/rebrand, init replaces it through a new ' +
'visual-world choice. Other ' +
'For `init`, `teach`, `shape`, or any request to create a new surface or replacement visual world, load reference/init.md and create PRODUCT.md with the user first. ' +
'After init writes PRODUCT.md, reference/new-work.md preserves and documents the incumbent system for an ' +
'extension or replaces it with the user for a redesign/rebrand. Other ' +
'narrow refinement commands may read the CSS, tokens, components, and assets and proceed without blocking, then ' +
`offer \`${IMPECCABLE_COMMAND} init\` as a follow-up.`,
'BUILD_INIT_REQUIRED: Before `craft` or `shape`, init must capture PRODUCT.md with the human or structured ' +
'simulated user. A redesign then replaces the visual world; an extension documents it.',
'BUILD_INIT_REQUIRED: Before shape or any new-surface/redesign flow, init must capture PRODUCT.md with the human or structured ' +
'simulated user. Init writes product truth only; reference/new-work.md owns every visual decision.',
'SCOPED_EXISTING_ALLOWED: Narrow refinement commands may use the incumbent implementation as authority without ' +
'blocking on context setup; they must preserve it and offer init afterward.',
'EXISTING_VISUAL_SYSTEM: For refinement or extension, code and assets are incumbent design authority and missing ' +
@@ -1004,17 +1038,18 @@ async function cli() {
]
: [
'NO_PRODUCT_MD: This project has no PRODUCT.md yet. ' +
'For `init`, `teach`, `craft`, `shape`, ' +
'For `init`, `teach`, `shape`, ' +
'or wording that clearly maps to a from-scratch build/shape flow, load ' +
'reference/init.md, complete its human or structured simulated-user interview, and write PRODUCT.md plus the ' +
'user-chosen seed DESIGN.md before building. If no answer mechanism truly exists, init may infer only from the ' +
'explicit brief, label its assumptions, and still write both files. For any other ' +
'reference/init.md, complete its human or structured simulated-user interview, and write PRODUCT.md before ' +
'designing. If no answer mechanism truly exists, init may infer only from the explicit brief and must label its ' +
'assumptions. It never writes DESIGN.md. For any other ' +
'(scoped) command against existing code, proceed using the code as ' +
`context and offer \`${IMPECCABLE_COMMAND} init\` as a suggestion (do not block).`,
'IDENTITY_INIT_REQUIRED: No committed product or visual world was found. New builds and redesigns ' +
'must finish reference/init.md before reference/new-work.md develops the task-specific surface concept. Scoped ' +
'PRODUCT_INIT_REQUIRED: No product context or visual authority was found. New builds and redesigns ' +
'must finish reference/init.md for PRODUCT.md, then reference/new-work.md establishes the world and surface. Scoped ' +
'fixes to existing code do not need the new-surface flow.',
];
appendSurfaceBriefContext(parts, ctx);
parts.push(buildResolvedContextDirective(ctx, cliOptions, { targetExists }));
if (shouldWarnMissingTarget(ctx, targetProvided, targetExists)) {
parts.push(buildMissingTargetDirective());
@@ -1027,30 +1062,28 @@ async function cli() {
if (ctx.hasDesign) {
parts.push(`# DESIGN.md\n\n${ctx.design.trim()}`);
}
appendSurfaceBriefContext(parts, ctx);
parts.push(buildResolvedContextDirective(ctx, cliOptions, { targetExists }));
if (shouldWarnMissingTarget(ctx, targetProvided, targetExists)) {
parts.push(buildMissingTargetDirective());
}
if (!ctx.hasDesign) {
parts.push(ctx.hasVisualImplementation
? 'BUILD_DESIGN_DOCUMENT_REQUIRED: PRODUCT.md exists and DESIGN.md is missing, but code contains incumbent visual decisions. ' +
'Before `craft` or `shape`, load reference/init.md Step 5. For extension, document CSS, tokens, components, and ' +
'assets as the incumbent world. For redesign/rebrand, replace the visual world with the user and treat the old ' +
'look only as evidence and anti-reference. Narrow refinement commands may proceed using the implementation directly.'
: 'IDENTITY_INIT_REQUIRED: PRODUCT.md exists but no DESIGN.md or incumbent visual implementation was found. ' +
'A new build or redesign must complete reference/init.md Step 5 with the human or structured simulated ' +
'user before reference/new-work.md develops the task-specific surface concept. Scoped fixes to existing code do not need it.');
? 'INCUMBENT_WORLD_UNDOCUMENTED: PRODUCT.md exists and DESIGN.md is missing, but code contains incumbent visual decisions. ' +
'For shape or a new-surface/redesign request, load reference/new-work.md: an extension documents and preserves the code-defined world; ' +
'a redesign replaces it with the user and uses the old look only as evidence and anti-reference. Narrow refinement ' +
'commands may proceed using the implementation directly.'
: 'WORLD_DISCOVERY_REQUIRED: PRODUCT.md exists but no DESIGN.md or incumbent visual implementation was found. ' +
'For a new build or redesign, load reference/new-work.md and establish the visual world with the human or structured ' +
'simulated user before developing the task concept. Scoped fixes to existing code do not need this flow.');
}
const platform = extractPlatform(ctx.product);
const nativeRefs =
platform === 'adaptive' ? ['ios', 'android'] : platform === 'ios' || platform === 'android' ? [platform] : [];
if (nativeRefs.length) {
const refList = nativeRefs.map(p => `\`reference/${p}.md\``).join(' and ');
const label = platform === 'adaptive' ? '`adaptive` (both iOS and Android)' : `\`${platform}\``;
const platformReferences = loadNativePlatformReferences(ctx.platform);
for (const reference of platformReferences) {
parts.push(
`NEXT STEP: This project targets ${label}. Also read ${refList} for native conventions, in addition to SKILL.md's mode guidance.`,
`# NATIVE PLATFORM REFERENCE: ${reference.name.toUpperCase()} (reference/${reference.name}.md)\n\n${reference.content.trim()}`,
);
} else if (!platform) {
}
if (!ctx.platform) {
// A `## Platform` section that names something we don't recognize (a
// toolchain like `flutter`, a typo) would otherwise silently fall back to
// web — the wrong default exactly when the user tried to say "native".
@@ -1087,10 +1120,29 @@ function buildResolvedContextDirective(ctx, options, { targetExists = null } = {
repoRoot: ctx.repoRoot,
productPath: ctx.productPath,
designPath: ctx.designPath,
surfaceBriefPath: ctx.surfaceBriefPath,
surfaceBriefReason: ctx.surfaceBriefReason,
surfaceBriefCandidates: ctx.surfaceBriefCandidates,
hasVisualImplementation: ctx.hasVisualImplementation,
platform: ctx.platform,
}, null, 2)}`;
}
function appendSurfaceBriefContext(parts, ctx) {
if (ctx.hasSurfaceBrief && ctx.surfaceBrief) {
parts.push(`# SURFACE BRIEF (${ctx.surfaceBriefPath})\n\n${ctx.surfaceBrief.trim()}`);
return;
}
if (!ctx.surfaceBriefCandidates?.length) return;
const helper = path.join(path.dirname(fileURLToPath(import.meta.url)), 'surface-brief.mjs');
parts.push(
'SURFACE_CONTEXT_AVAILABLE: Persisted surface briefs exist, but none was selected unambiguously for this invocation. ' +
'Resolve the requested surface to its concrete primary or related source path, then run ' +
`\`node ${helper} read <path>\` once before changing that surface. Candidates:\n` +
JSON.stringify(ctx.surfaceBriefCandidates, null, 2),
);
}
function shouldWarnMissingTarget(ctx, targetProvided, targetExists = null) {
if (ctx.isMonorepo && targetProvided && targetExists === false) return true;
return !!(