chore: update vendored skills and record provenance

humanizer 2.2.0 -> 2.9.1 (blader/humanizer, MIT): adds a Voice
Calibration section and a passive-voice pattern, reworks negative
parallelisms and em dashes. Version moved to metadata.version upstream.

impeccable 3.6.0 -> 4.0.4 (pbakaus/impeccable, Apache-2.0): the repo
tags the skill and the npm CLI separately, so 3.6.0 was a real release
and npm's 3.5.0 was never the comparison. Adds native-platform reference
briefs.

Each now carries an UPSTREAM file; bin/check-vendored.sh reports drift.
This commit is contained in:
naps62
2026-08-01 14:25:26 +00:00
parent 2f7f512cec
commit 9023ed3ef9
144 changed files with 28302 additions and 5511 deletions
+125 -92
View File
@@ -2,7 +2,7 @@
* CLI helper: find an element in source and wrap it in a variant container.
*
* Usage:
* npx impeccable wrap --id SESSION_ID --count N --query "hero-combined-left" [--file path]
* node <scripts_path>/live-wrap.mjs --id SESSION_ID --count N --query "hero-combined-left" [--file path]
*
* Searches project files for the element matching the query (class name, ID, or
* text snippet), wraps it with the variant scaffolding, and prints the file path
@@ -14,14 +14,16 @@
import fs from 'node:fs';
import path from 'node:path';
import { isGeneratedFile } from './lib/is-generated.mjs';
import { resolveLiveTemplateExtensions } from './lib/template-extensions.mjs';
import { readBuffer as readManualEditsBuffer } from './live/manual-edits-buffer.mjs';
import { findSourceFile } from './live/source-search.mjs';
import { resolveSourceTraits } from './live/frameworks/index.mjs';
import {
buildSvelteComponentCssAuthoring,
scaffoldSvelteComponentSession,
shouldUseSvelteComponentInjection,
} from './live/svelte-component.mjs';
const EXTENSIONS = ['.html', '.jsx', '.tsx', '.vue', '.svelte', '.astro'];
import { enterLiveRoot } from './live/roots.mjs';
export async function wrapCli() {
const args = process.argv.slice(2);
@@ -68,6 +70,13 @@ The agent should insert variant HTML at insertLine.`);
const filePath = argVal(args, '--file');
const text = argVal(args, '--text');
const pageUrl = argVal(args, '--page-url');
// Preflight passes this for source-preview targets. It computes the scaffold
// (element location + wrapper text) but does NOT write it into source. The
// agent then writes the wrapper + all variants in one atomic edit. The
// premature server-side write full-reloaded the framework mid-generate and
// stranded the browser at 0/N (live-server.mjs missed-completion note). It is
// a no-op on the svelte-component path, which never writes the route source.
const deferSourceWrite = args.includes('--defer-source-write');
if (!id) { console.error('Missing --id'); process.exit(1); }
if (!elementId && !classes && !query) {
@@ -160,11 +169,29 @@ The agent should insert variant HTML at insertLine.`);
if (filtered.length === 1) {
match = filtered[0];
} else if (filtered.length === 0) {
// Source uses dynamic content (`<h1>{title}</h1>` etc.) so the
// browser-side textContent doesn't appear literally in source. Fall
// back to first-match rather than refusing — this is the same
// behavior unmodified callers see, just preserved.
match = candidates[0];
const normalizedText = String(text).replace(/\s+/g, ' ').trim();
if (normalizedText.length < 8) {
// Very short labels cannot disambiguate siblings reliably. Preserve
// the legacy behavior for these low-information picker events.
match = candidates[0];
} else {
// Rendered text that is absent from every candidate usually means
// the source uses expressions or component props. Picking the first
// same-class sibling silently edits the wrong instance (observed on
// Astro result cards), so stop and surface every candidate instead.
console.error(JSON.stringify({
error: 'element_ambiguous',
fallback: 'agent-driven',
reason: 'rendered_text_not_in_source',
file: path.relative(process.cwd(), targetFile),
candidates: candidates.map((c) => ({
startLine: c.startLine + 1,
endLine: c.endLine + 1,
})),
hint: 'Rendered text does not occur in any matching source branch. The element may use dynamic props or expressions; inspect the candidates and wrap the intended instance manually.',
}));
process.exit(1);
}
} else {
// Multiple candidates ALSO match the text. Truly ambiguous — refuse
// rather than pick wrong, and hand the agent the candidate locations
@@ -268,7 +295,10 @@ The agent should insert variant HTML at insertLine.`);
.join('\n');
const originalIndented = reindentOriginal(' ');
const relTargetFile = path.relative(process.cwd(), targetFile).split(path.sep).join('/');
const useSvelteComponent = shouldUseSvelteComponentInjection(targetFile);
// The registry says which files get component preview; the svelte-component
// module keeps the env escape hatch that turns it off.
const useSvelteComponent = resolveSourceTraits(targetFile).preview === 'component'
&& shouldUseSvelteComponentInjection(targetFile);
// Wrapper attributes differ by syntax. HTML allows plain string attrs;
// JSX requires object-literal style and parses string attrs as HTML (which
@@ -288,7 +318,7 @@ The agent should insert variant HTML at insertLine.`);
// replacement range to include the wrapper's `<div>` open / close lines
// so the entire scaffold gets removed cleanly.
const wrapperLines = isJsx ? [
indent + '<div data-impeccable-variants="' + id + '" data-impeccable-variant-count="' + count + '" ' + styleContents + '>',
indent + '<div data-impeccable-variants="' + id + '" data-impeccable-variant-count="' + count + '"' + ' ' + styleContents + '>',
indent + ' ' + commentSyntax.open + ' impeccable-variants-start ' + id + ' ' + commentSyntax.close,
indent + ' ' + commentSyntax.open + ' Original ' + commentSyntax.close,
indent + ' <div data-impeccable-variant="original">',
@@ -299,7 +329,7 @@ The agent should insert variant HTML at insertLine.`);
indent + '</div>',
] : [
indent + commentSyntax.open + ' impeccable-variants-start ' + id + ' ' + commentSyntax.close,
indent + '<div data-impeccable-variants="' + id + '" data-impeccable-variant-count="' + count + '" ' + styleContents + '>',
indent + '<div data-impeccable-variants="' + id + '" data-impeccable-variant-count="' + count + '"' + ' ' + styleContents + '>',
indent + ' ' + commentSyntax.open + ' Original ' + commentSyntax.close,
indent + ' <div data-impeccable-variant="original">',
originalIndented,
@@ -315,13 +345,20 @@ The agent should insert variant HTML at insertLine.`);
let outputEndLine = startLine + wrapperLines.length + (originalLines.length - 1);
let insertLine;
let svelteSession = null;
let deferredWrapper = null;
let sveltePreviewFallback = null;
if (useSvelteComponent) {
// Svelte/SvelteKit resets component-local state on markup HMR updates.
// Keep generation source-neutral: agents write real variant components
// under the generated componentDir, the browser mounts them into the live
// DOM, and live-accept.mjs inlines the accepted variant back into the route.
svelteSession = scaffoldSvelteComponentSession({
//
// The scaffold is AST-based and refuses markup a detached preview cannot
// support (component tags, bind:/use:, await blocks, bound nested each).
// Refusal falls back to the plain source-preview wrapper below: an
// HMR-resetting but CORRECT preview beats a detached wrong one.
const scaffolded = scaffoldSvelteComponentSession({
id,
count,
sourceFile: relTargetFile,
@@ -330,10 +367,32 @@ The agent should insert variant HTML at insertLine.`);
originalLines,
cwd: process.cwd(),
});
outputFile = path.resolve(process.cwd(), svelteSession.manifestFile);
outputStartLine = 1;
outputEndLine = 1;
insertLine = 1;
if (scaffolded && scaffolded.fallback === 'source-preview') {
sveltePreviewFallback = scaffolded.reason || 'unsupported markup';
} else {
svelteSession = scaffolded;
outputFile = path.resolve(process.cwd(), svelteSession.manifestFile);
outputStartLine = 1;
outputEndLine = 1;
insertLine = 1;
}
}
if (svelteSession) {
// component preview: outputs already set above
} else if (deferSourceWrite) {
// Deferred source write: compute the scaffold text but leave source
// untouched. The agent replaces the picked element's source range with
// `wrapperBlock` (variants spliced at the marker) in one edit. Writing the
// scaffold here first would reload the framework before the agent's write
// lands, and a browser caught mid-reload misses the `done` and sits at 0/N.
deferredWrapper = {
block: wrapperLines.join('\n'),
replaceStartLine: startLine + 1, // 1-indexed picked-element range the
replaceEndLine: endLine + 1, // agent's wrapper block replaces
};
// insertLine matches the final file position the wrapper occupies once the
// agent replaces the picked range, so downstream consumers stay consistent.
insertLine = startLine + 6 + (originalLines.length - 1) + 1;
} else {
// Replace the original element with the wrapper
const newLines = [
@@ -355,16 +414,31 @@ The agent should insert variant HTML at insertLine.`);
const outputRelFile = path.relative(process.cwd(), outputFile).split(path.sep).join('/');
const svelteComponentAuthoring = useSvelteComponent ? buildSvelteComponentCssAuthoring(count) : null;
const componentPreviewActive = !!svelteSession;
const svelteComponentAuthoring = componentPreviewActive ? buildSvelteComponentCssAuthoring(count) : null;
const componentSession = svelteSession;
const componentPreviewMode = componentPreviewActive ? 'svelte-component' : undefined;
const previewMode = componentPreviewMode;
console.log(JSON.stringify({
file: outputRelFile,
sourceFile: useSvelteComponent ? relTargetFile : undefined,
previewMode: useSvelteComponent ? 'svelte-component' : undefined,
componentDir: svelteSession?.componentDir,
propContract: svelteSession?.propContract,
sourceStartLine: useSvelteComponent ? startLine + 1 : undefined,
sourceEndLine: useSvelteComponent ? endLine + 1 : undefined,
sourceFile: componentPreviewActive ? relTargetFile : undefined,
previewMode,
previewFallback: sveltePreviewFallback
? { from: 'svelte-component', reason: sveltePreviewFallback }
: undefined,
// Deferred source write: the wrapper is NOT yet in source. The agent
// replaces [replaceStartLine, replaceEndLine] with `wrapperBlock` (variants
// spliced at the "insert below this line" marker) in one atomic edit.
sourceWritten: deferredWrapper ? false : undefined,
wrapperBlock: deferredWrapper ? deferredWrapper.block : undefined,
replaceStartLine: deferredWrapper ? deferredWrapper.replaceStartLine : undefined,
replaceEndLine: deferredWrapper ? deferredWrapper.replaceEndLine : undefined,
componentDir: componentSession?.componentDir,
propContract: componentSession?.propContract,
componentStubMarkup: componentSession?.stubMarkup,
sourceStartLine: componentPreviewActive ? startLine + 1 : undefined,
sourceEndLine: componentPreviewActive ? endLine + 1 : undefined,
startLine: outputStartLine, // 1-indexed for the agent
// wrapperLines is an array but one element (the original-content slot)
// is a `\n`-joined multi-line string, so the actual file-row count is
@@ -374,10 +448,10 @@ The agent should insert variant HTML at insertLine.`);
endLine: outputEndLine, // 1-indexed
insertLine, // 1-indexed: where variants go
commentSyntax: commentSyntax,
styleMode: useSvelteComponent ? 'svelte-component' : styleMode.mode,
styleTag: useSvelteComponent ? null : styleMode.styleTag,
cssSelectorPrefixExamples: useSvelteComponent ? [] : buildCssSelectorPrefixExamples(styleMode.mode, count),
cssAuthoring: useSvelteComponent ? svelteComponentAuthoring : buildCssAuthoring(styleMode, count),
styleMode: componentPreviewMode || styleMode.mode,
styleTag: componentPreviewActive ? null : styleMode.styleTag,
cssSelectorPrefixExamples: componentPreviewActive ? [] : buildCssSelectorPrefixExamples(styleMode.mode, count),
cssAuthoring: svelteComponentAuthoring || buildCssAuthoring(styleMode, count),
originalLineCount: originalLines.length,
}));
}
@@ -579,27 +653,22 @@ function attrEscapeDouble(str) {
.replace(/>/g, '&gt;');
}
/**
* Comment syntax, style mode, and preview strategy all come from the framework
* registry, keyed on the target file's extension: `.jsx`/`.tsx` author JSX
* comments, `.astro` needs global-prefixed preview CSS because Astro scopes
* component styles away from the generated wrappers, `.svelte` gets component
* preview. See live/frameworks/index.mjs for why extension and not project.
*/
function detectCommentSyntax(filePath) {
const ext = path.extname(filePath).toLowerCase();
if (ext === '.jsx' || ext === '.tsx') {
return { open: '{/*', close: '*/}' };
}
// HTML, Vue, Svelte, Astro all use HTML comments
return { open: '<!--', close: '-->' };
return resolveSourceTraits(filePath).commentSyntax === 'jsx'
? { open: '{/*', close: '*/}' }
: { open: '<!--', close: '-->' };
}
function detectStyleMode(filePath) {
const ext = path.extname(filePath).toLowerCase();
if (ext === '.astro') {
return {
mode: 'astro-global-prefixed',
styleTag: '<style is:inline data-impeccable-css="SESSION_ID">',
};
}
return {
mode: 'scoped',
styleTag: '<style data-impeccable-css="SESSION_ID">',
};
const traits = resolveSourceTraits(filePath);
return { mode: traits.styleMode, styleTag: traits.styleTag };
}
function buildCssSelectorPrefixExamples(styleMode, count) {
@@ -650,56 +719,19 @@ function buildCssAuthoring(styleMode, count) {
/**
* Search project files for the query string (class name, ID, etc.)
* Returns the first matching file path, or null.
*
* Only `node_modules`, `.git`, and `.impeccable` are skipped outright.
* dist/build/out are left to the isGeneratedFile guard so the
* `includeGenerated` second pass can still find the element there and report
* `generatedMatch`.
*/
function findFileWithQuery(query, cwd, genOpts = {}) {
const searchDirs = ['src', 'app', 'pages', 'components', 'public', 'views', 'templates', '.'];
const seen = new Set();
for (const dir of searchDirs) {
const absDir = path.join(cwd, dir);
if (!fs.existsSync(absDir)) continue;
const result = searchDir(absDir, query, seen, 0, genOpts);
if (result) return result;
}
return null;
}
function searchDir(dir, query, seen, depth, genOpts) {
if (depth > 5) return null; // don't go too deep
const realDir = fs.realpathSync(dir);
if (seen.has(realDir)) return null;
seen.add(realDir);
let entries;
try { entries = fs.readdirSync(dir, { withFileTypes: true }); }
catch { return null; }
// Check files first
for (const entry of entries) {
if (!entry.isFile()) continue;
const ext = path.extname(entry.name).toLowerCase();
if (!EXTENSIONS.includes(ext)) continue;
const filePath = path.join(dir, entry.name);
if (!genOpts.includeGenerated && isGeneratedFile(filePath, genOpts)) continue;
try {
const content = fs.readFileSync(filePath, 'utf-8');
if (content.includes(query)) return filePath;
} catch { /* skip unreadable files */ }
}
// Then recurse into directories. Always skip node_modules and .git (never
// project content). dist/build/out are left to the isGeneratedFile guard so
// the includeGenerated second-pass can still find the element there and
// report `generatedMatch`.
for (const entry of entries) {
if (!entry.isDirectory()) continue;
if (entry.name === 'node_modules' || entry.name === '.git') continue;
const result = searchDir(path.join(dir, entry.name), query, seen, depth + 1, genOpts);
if (result) return result;
}
return null;
return findSourceFile({
query,
cwd,
extensions: resolveLiveTemplateExtensions(cwd),
fileFilter: (filePath) => genOpts.includeGenerated || !isGeneratedFile(filePath, genOpts),
});
}
/**
@@ -876,6 +908,7 @@ function findClosingLine(lines, start) {
// Auto-execute when run directly (node live-wrap.mjs ...)
const _running = process.argv[1];
if (_running?.endsWith('live-wrap.mjs') || _running?.endsWith('live-wrap.mjs/')) {
enterLiveRoot();
wrapCli();
}