Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 4 additions & 1 deletion overlays/skills/impeccable/scripts/impeccable.cmd
Original file line number Diff line number Diff line change
@@ -1,3 +1,6 @@
@echo off
node "%~dp0..\..\..\src\impeccable-launcher.mjs" %*
setlocal EnableExtensions
rem Do not set PLUGIN_ROOT here; that env var selects the Codex host in resolveHost.
for %%I in ("%~dp0..\..\..") do set "DESIGN_LAUNCHER_ROOT=%%~fI"
node "%DESIGN_LAUNCHER_ROOT%\src\impeccable-launcher.mjs" %*
exit /b %errorlevel%
21 changes: 14 additions & 7 deletions scripts/build-plugin-targets.mjs
Original file line number Diff line number Diff line change
Expand Up @@ -211,8 +211,10 @@ function narrowModules(destination, host) {
}

function replaceRequired(source, search, replacement, label) {
if (source.split(search).length !== 2) throw new Error(`portable skill projection anchor must occur exactly once: ${label}`);
return source.replace(search, replacement);
// Projection anchors are authored with LF; normalize Windows CRLF checkouts first.
const normalized = source.replace(/\r\n/g, "\n");
if (normalized.split(search).length !== 2) throw new Error(`portable skill projection anchor must occur exactly once: ${label}`);
return normalized.replace(search, replacement);
}

const portableImpeccableTransforms = [
Expand Down Expand Up @@ -343,7 +345,7 @@ function adaptAgentPluginSkills(destination) {
for (const skillName of ["design", "impeccable"]) rmSync(join(skillsRoot, skillName, "agents"), { recursive: true, force: true });

const designPath = join(skillsRoot, "design", "SKILL.md");
let design = readFileSync(designPath, "utf8");
let design = readProjectedText(designPath);
design = replaceRequired(
design,
"description: Use when the user explicitly invokes /design in Cursor or $design in Codex for project setup, status, diagnostics, stakeholder questionnaires, explicit local detector scans, change-scoped interface review, or curated website and web-app design work. Routes general design work to the bundled Impeccable skill and narrower work to registered curated modules.",
Expand Down Expand Up @@ -379,7 +381,8 @@ function adaptAgentPluginSkills(destination) {
writeFileSync(designPath, design);

const impeccablePath = join(skillsRoot, "impeccable", "SKILL.md");
let impeccable = readFileSync(impeccablePath, "utf8");
// SKILL.md anchors below are LF-authored; normalize CRLF from Windows checkouts.
let impeccable = readProjectedText(impeccablePath);
const start = "## Geldmacher Design host contract\n\n";
const end = "\n\nThis skill gives you the tools and permission to create design";
const startIndex = impeccable.indexOf(start);
Expand Down Expand Up @@ -430,9 +433,8 @@ function adaptAgentPluginSkills(destination) {
"portable live panel operation notation"));

const hostPath = join(destination, "src", "host.mjs");
const host = readFileSync(hostPath, "utf8");
writeFileSync(hostPath, replaceRequired(
host,
readProjectedText(hostPath),
" return skill;\n}",
" return `the loaded ${skill} skill's`;\n}",
"portable lifecycle operation notation",
Expand Down Expand Up @@ -595,6 +597,11 @@ function assertSourceRoot(sourceRoot) {
return resolved;
}

function readProjectedText(file) {
// Windows checkouts may materialize CRLF; projection anchors are authored as LF.
return readFileSync(file, 'utf8').replace(/\r\n/g, '\n');
}

function projectSelfContainedReferences(destination) {
const transformations = [
{
Expand All @@ -615,7 +622,7 @@ function projectSelfContainedReferences(destination) {
];
for (const item of transformations) {
const file = join(destination, 'skills/impeccable', item.path);
writeFileSync(file, replaceRequired(readFileSync(file, 'utf8'), item.search, item.replacement, `self-contained ${item.path}`));
writeFileSync(file, replaceRequired(readProjectedText(file), item.search, item.replacement, `self-contained ${item.path}`));
}
}

Expand Down
7 changes: 5 additions & 2 deletions scripts/lib/impeccable-candidate-runtime.mjs
Original file line number Diff line number Diff line change
Expand Up @@ -36,13 +36,16 @@ export function verifyCandidateRuntime(root, projection, destinations) {
const nativeScript = !file.endsWith('.mjs');
const executable = nativeScript ? (process.platform === 'win32' ? 'cmd.exe' : file) : process.execPath;
const argv = nativeScript ? (process.platform === 'win32' ? ['/d', '/s', '/c', `"${[file, ...args].map(value => `"${value}"`).join(' ')}"`] : args) : [file, ...args];
const result = spawnSync(executable, argv, { cwd: project, env, input, encoding: 'utf8', timeout: 10000, shell: false });
const result = spawnSync(executable, argv, {
cwd: project, env, input, encoding: 'utf8', timeout: 10000, shell: false,
windowsVerbatimArguments: process.platform === 'win32' && nativeScript,
});
if (result.error || result.status !== 0) throw new Error(`Candidate ${host} entrypoint failed (${args.join(' ')}): ${result.error?.message || result.stderr || result.status}`);
return result.stdout;
};
const launcher = join(target, 'skills/impeccable/scripts', process.platform === 'win32' ? 'impeccable.cmd' : 'impeccable');
if (run(launcher, ['engine-probe']).trim() !== `impeccable-engine ${runtime.engineVersion}`) throw new Error('Engine handshake differs from the skill pin.');
const context = run(launcher, ['context']);
const context = run(launcher, ['context']).replace(/\r\n/g, '\n');
if (!context.includes('RESOLVED_CONTEXT:') || !context.includes(`PLUGIN_HOOK_STATE: ${host}:`)) throw new Error('Candidate plugin context contract missing.');
const doctor = JSON.parse(run(launcher, ['doctor']));
if (!Array.isArray(doctor.findings) || doctor.pluginHook?.host !== host) throw new Error('Candidate plugin doctor contract missing.');
Expand Down
5 changes: 4 additions & 1 deletion skills/impeccable/scripts/impeccable.cmd
Original file line number Diff line number Diff line change
@@ -1,3 +1,6 @@
@echo off
node "%~dp0..\..\..\src\impeccable-launcher.mjs" %*
setlocal EnableExtensions
rem Do not set PLUGIN_ROOT here; that env var selects the Codex host in resolveHost.
for %%I in ("%~dp0..\..\..") do set "DESIGN_LAUNCHER_ROOT=%%~fI"
node "%DESIGN_LAUNCHER_ROOT%\src\impeccable-launcher.mjs" %*
exit /b %errorlevel%
6 changes: 5 additions & 1 deletion src/impeccable-engine.mjs
Original file line number Diff line number Diff line change
Expand Up @@ -57,6 +57,10 @@ export function resolveEngine(root, { platform = process.platform, arch = proces
if (bytes.length !== asset.size || createHash('sha256').update(bytes).digest('hex') !== asset.sha256) {
throw new Error(`Bundled engine hash or size mismatch: ${key}`);
}
if (platform !== 'win32' && !(fs.statSync(file).mode & 0o111)) throw new Error(`Bundled engine is not executable: ${key}`);
// Unix execute bits are authoritative on POSIX hosts only. Windows checkouts often
// drop +x on foreign engine binaries, and win32 itself does not use mode 0111.
if (process.platform !== 'win32' && key !== 'windows-x64' && !(fs.statSync(file).mode & 0o111)) {
throw new Error(`Bundled engine is not executable: ${key}`);
}
return { file, relativeScript: relative, engineVersion: pin.engine.version, platform: key, pluginRoot: fs.realpathSync(root) };
}
2 changes: 2 additions & 0 deletions src/impeccable-plugin-commands.mjs
Original file line number Diff line number Diff line change
Expand Up @@ -140,6 +140,8 @@ function protectProjectContext(stdout, cwd) {
}

export function projectEngineOutput({ command, stdout, host, cwd }) {
// Windows engine/console captures may use CRLF; projection anchors are LF-authored.
stdout = stdout.replace(/\r\n/g, '\n');
if (command === 'context') {
if (stdout.startsWith('TARGET_SELECTION_REQUIRED:\n')) {
const parts = stdout.trim().split('\n\n');
Expand Down
10 changes: 8 additions & 2 deletions tests/impeccable-engine.test.mjs
Original file line number Diff line number Diff line change
Expand Up @@ -156,10 +156,16 @@ test('thin launcher and generated commands work from a plugin path containing sp
}
const launcher = path.join(copy, 'skills/impeccable/scripts', process.platform === 'win32' ? 'impeccable.cmd' : 'impeccable');
const env = impeccableRuntimeEnvironment('codex', copy);
// On Windows, pass a /s /c line with every token quoted and disable Node's
// extra argv escaping (windowsVerbatimArguments). Without that, cmd.exe
// rewrites paths with spaces into a UNC-looking fragment and fails with
// "The network path was not found."
const result = process.platform === 'win32'
? spawnSync('cmd.exe', ['/d', '/s', '/c', `""${launcher}" engine-probe"`], { cwd, env, encoding: 'utf8', shell: false })
? spawnSync('cmd.exe', ['/d', '/s', '/c', `"${[launcher, 'engine-probe'].map((value) => `"${value}"`).join(' ')}"`], {
cwd, env, encoding: 'utf8', shell: false, windowsVerbatimArguments: true,
})
: spawnSync(launcher, ['engine-probe'], { cwd, env, encoding: 'utf8', shell: false });
assert.equal(result.status, 0, result.stderr);
assert.equal(result.status, 0, result.stderr || result.stdout);
assert.equal(result.stdout.trim(), `impeccable-engine ${pin.engine.version}`);
const context = runBundledImpeccable({ pluginRoot: copy, cwd, host: 'codex', command: 'context' });
assert.equal(context.status, 0, context.error);
Expand Down
8 changes: 6 additions & 2 deletions tests/portable-runtime.test.mjs
Original file line number Diff line number Diff line change
Expand Up @@ -206,7 +206,9 @@ test("built targets run self-contained lifecycle and hook simulations", (t) => {
for (const [file, body] of Object.entries(projectTexts)) writeFileSync(join(project, file), body);
const launcher = join(portable, 'skills', 'impeccable', 'scripts', process.platform === 'win32' ? 'impeccable.cmd' : 'impeccable');
const preserved = process.platform === 'win32'
? spawnSync('cmd.exe', ['/d', '/s', '/c', `""${launcher}" context"`], { cwd: project, env: portableEnv, encoding: 'utf8', shell: false })
? spawnSync('cmd.exe', ['/d', '/s', '/c', `"${[launcher, 'context'].map((value) => `"${value}"`).join(' ')}"`], {
cwd: project, env: portableEnv, encoding: 'utf8', shell: false, windowsVerbatimArguments: true,
})
: spawnSync(launcher, ['context'], { cwd: project, env: portableEnv, encoding: 'utf8', shell: false });
assert.equal(preserved.status, 0, preserved.stderr);
for (const [file, body] of Object.entries(projectTexts)) {
Expand Down Expand Up @@ -234,7 +236,9 @@ test("built targets run self-contained lifecycle and hook simulations", (t) => {
const child = join(monorepo, 'apps', app);
for (const [cwd, args] of [[child, ['context']], [monorepo, ['context', '--target', `apps/${app}`]]]) {
const result = process.platform === 'win32'
? spawnSync('cmd.exe', ['/d', '/s', '/c', `"${[launcher, ...args].map(value => `"${value}"`).join(' ')}"`], { cwd, env, encoding: 'utf8', shell: false })
? spawnSync('cmd.exe', ['/d', '/s', '/c', `"${[launcher, ...args].map(value => `"${value}"`).join(' ')}"`], {
cwd, env, encoding: 'utf8', shell: false, windowsVerbatimArguments: true,
})
: spawnSync(launcher, args, { cwd, env, encoding: 'utf8', shell: false });
assert.equal(result.status, 0, `${host} ${cwd}: ${result.stderr}`);
for (const [file, body] of Object.entries(app === 'one' ? projectTexts : inherited)) {
Expand Down
4 changes: 2 additions & 2 deletions upstream/impeccable.lock.json
Original file line number Diff line number Diff line change
Expand Up @@ -35,7 +35,7 @@
"evidence-based-usability-guidance"
],
"patch": "upstream/patches/impeccable-plugin.patch",
"patchSha256": "85f4fe5700c18fcb0e1b0343e790d9c646f4d7b4abddbc3588d29f5aa7767f4a",
"patchSha256": "9cd4cad609aaa125f9a4a621d5489266d8ff64472b321a7e6aff028e93426bf6",
"files": [
{
"source": ".cursor/agents/impeccable-asset-producer.md",
Expand Down Expand Up @@ -439,7 +439,7 @@
"source": ".cursor/skills/impeccable/scripts/impeccable.cmd",
"destination": "skills/impeccable/scripts/impeccable.cmd",
"sourceSha256": "759bab1d8adc071fdde2efcf6e2157aa4026f3c2b4f3c342a8070bf328011616",
"vendoredSha256": "0eed70b974fe25a84de65b7a96409ce562cbf58d84ad7d82aeae8eee5601cd3c",
"vendoredSha256": "073975e9a2bd302bad9a06310c3a71c8353ac128a8dc11969c0a289ae5ccd2c8",
"transformations": [
"native-engine-launcher"
]
Expand Down
7 changes: 5 additions & 2 deletions upstream/patches/impeccable-plugin.patch
Original file line number Diff line number Diff line change
Expand Up @@ -1012,7 +1012,7 @@ diff --git a/skills/impeccable/scripts/impeccable.cmd b/skills/impeccable/script
index 7ebe419..0be0a0d 100644
--- a/skills/impeccable/scripts/impeccable.cmd
+++ b/skills/impeccable/scripts/impeccable.cmd
@@ -1,214 +1,3 @@
@@ -1,214 +1,6 @@
@echo off
-setlocal
-rem Impeccable launcher (Windows). Runs bin\windows-<arch>\impeccable.exe next
Expand Down Expand Up @@ -1227,7 +1227,10 @@ index 7ebe419..0be0a0d 100644
-echo impeccable: no engine binary found (looked in %bin%, %cached%, PATH). 1>&2
-echo Download impeccable-windows-%arch%.exe from https://github.com/pbakaus/impeccable/releases (tag engine-v%version%) and save it as %cached%, or set IMPECCABLE_BIN to a preinstalled engine binary. Docs: https://impeccable.style 1>&2
-exit /b 127
+node "%~dp0..\..\..\src\impeccable-launcher.mjs" %*
+setlocal EnableExtensions
+rem Do not set PLUGIN_ROOT here; that env var selects the Codex host in resolveHost.
+for %%I in ("%~dp0..\..\..") do set "DESIGN_LAUNCHER_ROOT=%%~fI"
+node "%DESIGN_LAUNCHER_ROOT%\src\impeccable-launcher.mjs" %*
+exit /b %errorlevel%
diff --git a/agents/impeccable-asset-producer.md b/agents/impeccable-asset-producer.md
index 005c61c..d1080f5 100644
Expand Down
Loading