diff --git a/.agents/skills/impeccable/SKILL.md b/.agents/skills/impeccable/SKILL.md index ca57daafc..78a7a5b74 100644 --- a/.agents/skills/impeccable/SKILL.md +++ b/.agents/skills/impeccable/SKILL.md @@ -152,11 +152,14 @@ Plus two management commands: `pin ` and `unpin `, detailed be Reason over the signals; there is no score to obey: - `setup.hasDesign` false while `setup.hasCode` true → `document` (capture the visual system). + - `critique.latest` is `null` → the project has never been critiqued; for a set-up project with a real surface, offering `$impeccable critique ` is a strong default. - `critique.latest` with a low `score` or non-zero `p0` / `p1` → `polish` (it reads that snapshot as its backlog), or re-run `critique` if the snapshot looks stale. - `git.changedFiles` pointing at one surface → scope `audit` or `polish` to those files specifically, naming them. - `devServer.running` true → `live` is available for in-browser iteration; if false, don't lead with `live`. - Otherwise group by intent exactly as init's "Recommend starting points" step does (build new / improve what's there / iterate visually), tailored to `setup.register`. + **If `scan.detectTarget` is set, run `node .agents/skills/impeccable/scripts/detect.mjs --json ` once** (the bundled detector: no network, no npx, full coverage) and fold the hits into your picks: many quality / contrast hits → `audit` or `polish`; a specific slop family → the matching command (gradient text or eyebrows → `quieter` / `typeset`, flat or gray palette → `colorize`, and so on). It's a real, current signal that beats guessing. If detect errors, skip it and recommend the user run `audit` themselves; never block the suggestion on it. + Keep it to 2-3 pointed picks with the exact command to type. The menu stays the fallback; the recommendation is the lede. 2. **First word matches a command**: load its reference file and follow its instructions. Everything after the command name is the target. 3. **First word doesn't match, but the intent clearly maps to one command** (e.g. "fix the spacing" → `layout`, "rewrite this error message" → `clarify`, "the colors feel flat" → `colorize`): load that command's reference and proceed as if invoked. If two commands could fit, ask once which. diff --git a/.agents/skills/impeccable/scripts/context-signals.mjs b/.agents/skills/impeccable/scripts/context-signals.mjs index cd54793af..1792d1a19 100644 --- a/.agents/skills/impeccable/scripts/context-signals.mjs +++ b/.agents/skills/impeccable/scripts/context-signals.mjs @@ -150,8 +150,35 @@ async function devServerSignals() { return { running: open.length > 0, ports: open }; } +/** + * What the agent could point the bundled detector (`detect.mjs`) at. The + * detector is HTML/CSS oriented, so a rendered page (dev server) or a static + * HTML entry is a far better target than a raw source tree. This script does + * NOT run the detector itself — it just surfaces the target so the agent can + * run `node /detect.mjs --json ` (bundled, dep-free, fast) + * and fold the hits into its recommendation. + */ +function scanTarget(cwd, devServer) { + if (devServer.running && devServer.ports.length) { + return { detectTarget: `http://localhost:${devServer.ports[0]}`, via: 'dev-server' }; + } + for (const c of ['index.html', 'public/index.html', 'dist/index.html', 'build/index.html']) { + if (fs.existsSync(path.join(cwd, c))) return { detectTarget: c, via: 'html' }; + } + for (const dir of ['.', 'public', 'dist', 'build']) { + try { + const abs = path.join(cwd, dir); + if (!fs.existsSync(abs)) continue; + const html = fs.readdirSync(abs).find((f) => f.endsWith('.html')); + if (html) return { detectTarget: path.join(dir, html), via: 'html' }; + } catch { /* ignore unreadable dir */ } + } + return { detectTarget: null, via: null }; +} + export async function gatherSignals(cwd = process.cwd()) { const ctx = loadContext(cwd); + const devServer = await devServerSignals(); return { setup: { hasProduct: ctx.hasProduct, @@ -163,7 +190,8 @@ export async function gatherSignals(cwd = process.cwd()) { }, critique: { latest: latestCritique(cwd) }, git: gitSignals(cwd), - devServer: await devServerSignals(), + devServer, + scan: scanTarget(cwd, devServer), }; } diff --git a/.claude/skills/impeccable/SKILL.md b/.claude/skills/impeccable/SKILL.md index c61880528..589bee044 100644 --- a/.claude/skills/impeccable/SKILL.md +++ b/.claude/skills/impeccable/SKILL.md @@ -146,11 +146,14 @@ Plus two management commands: `pin ` and `unpin `, detailed be Reason over the signals; there is no score to obey: - `setup.hasDesign` false while `setup.hasCode` true → `document` (capture the visual system). + - `critique.latest` is `null` → the project has never been critiqued; for a set-up project with a real surface, offering `/impeccable critique ` is a strong default. - `critique.latest` with a low `score` or non-zero `p0` / `p1` → `polish` (it reads that snapshot as its backlog), or re-run `critique` if the snapshot looks stale. - `git.changedFiles` pointing at one surface → scope `audit` or `polish` to those files specifically, naming them. - `devServer.running` true → `live` is available for in-browser iteration; if false, don't lead with `live`. - Otherwise group by intent exactly as init's "Recommend starting points" step does (build new / improve what's there / iterate visually), tailored to `setup.register`. + **If `scan.detectTarget` is set, run `node .claude/skills/impeccable/scripts/detect.mjs --json ` once** (the bundled detector: no network, no npx, full coverage) and fold the hits into your picks: many quality / contrast hits → `audit` or `polish`; a specific slop family → the matching command (gradient text or eyebrows → `quieter` / `typeset`, flat or gray palette → `colorize`, and so on). It's a real, current signal that beats guessing. If detect errors, skip it and recommend the user run `audit` themselves; never block the suggestion on it. + Keep it to 2-3 pointed picks with the exact command to type. The menu stays the fallback; the recommendation is the lede. 2. **First word matches a command**: load its reference file and follow its instructions. Everything after the command name is the target. 3. **First word doesn't match, but the intent clearly maps to one command** (e.g. "fix the spacing" → `layout`, "rewrite this error message" → `clarify`, "the colors feel flat" → `colorize`): load that command's reference and proceed as if invoked. If two commands could fit, ask once which. diff --git a/.claude/skills/impeccable/scripts/context-signals.mjs b/.claude/skills/impeccable/scripts/context-signals.mjs index cd54793af..1792d1a19 100644 --- a/.claude/skills/impeccable/scripts/context-signals.mjs +++ b/.claude/skills/impeccable/scripts/context-signals.mjs @@ -150,8 +150,35 @@ async function devServerSignals() { return { running: open.length > 0, ports: open }; } +/** + * What the agent could point the bundled detector (`detect.mjs`) at. The + * detector is HTML/CSS oriented, so a rendered page (dev server) or a static + * HTML entry is a far better target than a raw source tree. This script does + * NOT run the detector itself — it just surfaces the target so the agent can + * run `node /detect.mjs --json ` (bundled, dep-free, fast) + * and fold the hits into its recommendation. + */ +function scanTarget(cwd, devServer) { + if (devServer.running && devServer.ports.length) { + return { detectTarget: `http://localhost:${devServer.ports[0]}`, via: 'dev-server' }; + } + for (const c of ['index.html', 'public/index.html', 'dist/index.html', 'build/index.html']) { + if (fs.existsSync(path.join(cwd, c))) return { detectTarget: c, via: 'html' }; + } + for (const dir of ['.', 'public', 'dist', 'build']) { + try { + const abs = path.join(cwd, dir); + if (!fs.existsSync(abs)) continue; + const html = fs.readdirSync(abs).find((f) => f.endsWith('.html')); + if (html) return { detectTarget: path.join(dir, html), via: 'html' }; + } catch { /* ignore unreadable dir */ } + } + return { detectTarget: null, via: null }; +} + export async function gatherSignals(cwd = process.cwd()) { const ctx = loadContext(cwd); + const devServer = await devServerSignals(); return { setup: { hasProduct: ctx.hasProduct, @@ -163,7 +190,8 @@ export async function gatherSignals(cwd = process.cwd()) { }, critique: { latest: latestCritique(cwd) }, git: gitSignals(cwd), - devServer: await devServerSignals(), + devServer, + scan: scanTarget(cwd, devServer), }; } diff --git a/.cursor/skills/impeccable/SKILL.md b/.cursor/skills/impeccable/SKILL.md index 83dcdfbd9..5b9c9f3df 100644 --- a/.cursor/skills/impeccable/SKILL.md +++ b/.cursor/skills/impeccable/SKILL.md @@ -142,11 +142,14 @@ Plus two management commands: `pin ` and `unpin `, detailed be Reason over the signals; there is no score to obey: - `setup.hasDesign` false while `setup.hasCode` true → `document` (capture the visual system). + - `critique.latest` is `null` → the project has never been critiqued; for a set-up project with a real surface, offering `/impeccable critique ` is a strong default. - `critique.latest` with a low `score` or non-zero `p0` / `p1` → `polish` (it reads that snapshot as its backlog), or re-run `critique` if the snapshot looks stale. - `git.changedFiles` pointing at one surface → scope `audit` or `polish` to those files specifically, naming them. - `devServer.running` true → `live` is available for in-browser iteration; if false, don't lead with `live`. - Otherwise group by intent exactly as init's "Recommend starting points" step does (build new / improve what's there / iterate visually), tailored to `setup.register`. + **If `scan.detectTarget` is set, run `node .cursor/skills/impeccable/scripts/detect.mjs --json ` once** (the bundled detector: no network, no npx, full coverage) and fold the hits into your picks: many quality / contrast hits → `audit` or `polish`; a specific slop family → the matching command (gradient text or eyebrows → `quieter` / `typeset`, flat or gray palette → `colorize`, and so on). It's a real, current signal that beats guessing. If detect errors, skip it and recommend the user run `audit` themselves; never block the suggestion on it. + Keep it to 2-3 pointed picks with the exact command to type. The menu stays the fallback; the recommendation is the lede. 2. **First word matches a command**: load its reference file and follow its instructions. Everything after the command name is the target. 3. **First word doesn't match, but the intent clearly maps to one command** (e.g. "fix the spacing" → `layout`, "rewrite this error message" → `clarify`, "the colors feel flat" → `colorize`): load that command's reference and proceed as if invoked. If two commands could fit, ask once which. diff --git a/.cursor/skills/impeccable/scripts/context-signals.mjs b/.cursor/skills/impeccable/scripts/context-signals.mjs index cd54793af..1792d1a19 100644 --- a/.cursor/skills/impeccable/scripts/context-signals.mjs +++ b/.cursor/skills/impeccable/scripts/context-signals.mjs @@ -150,8 +150,35 @@ async function devServerSignals() { return { running: open.length > 0, ports: open }; } +/** + * What the agent could point the bundled detector (`detect.mjs`) at. The + * detector is HTML/CSS oriented, so a rendered page (dev server) or a static + * HTML entry is a far better target than a raw source tree. This script does + * NOT run the detector itself — it just surfaces the target so the agent can + * run `node /detect.mjs --json ` (bundled, dep-free, fast) + * and fold the hits into its recommendation. + */ +function scanTarget(cwd, devServer) { + if (devServer.running && devServer.ports.length) { + return { detectTarget: `http://localhost:${devServer.ports[0]}`, via: 'dev-server' }; + } + for (const c of ['index.html', 'public/index.html', 'dist/index.html', 'build/index.html']) { + if (fs.existsSync(path.join(cwd, c))) return { detectTarget: c, via: 'html' }; + } + for (const dir of ['.', 'public', 'dist', 'build']) { + try { + const abs = path.join(cwd, dir); + if (!fs.existsSync(abs)) continue; + const html = fs.readdirSync(abs).find((f) => f.endsWith('.html')); + if (html) return { detectTarget: path.join(dir, html), via: 'html' }; + } catch { /* ignore unreadable dir */ } + } + return { detectTarget: null, via: null }; +} + export async function gatherSignals(cwd = process.cwd()) { const ctx = loadContext(cwd); + const devServer = await devServerSignals(); return { setup: { hasProduct: ctx.hasProduct, @@ -163,7 +190,8 @@ export async function gatherSignals(cwd = process.cwd()) { }, critique: { latest: latestCritique(cwd) }, git: gitSignals(cwd), - devServer: await devServerSignals(), + devServer, + scan: scanTarget(cwd, devServer), }; } diff --git a/.gemini/skills/impeccable/SKILL.md b/.gemini/skills/impeccable/SKILL.md index 2e753039f..931e402b9 100644 --- a/.gemini/skills/impeccable/SKILL.md +++ b/.gemini/skills/impeccable/SKILL.md @@ -143,11 +143,14 @@ Plus two management commands: `pin ` and `unpin `, detailed be Reason over the signals; there is no score to obey: - `setup.hasDesign` false while `setup.hasCode` true → `document` (capture the visual system). + - `critique.latest` is `null` → the project has never been critiqued; for a set-up project with a real surface, offering `/impeccable critique ` is a strong default. - `critique.latest` with a low `score` or non-zero `p0` / `p1` → `polish` (it reads that snapshot as its backlog), or re-run `critique` if the snapshot looks stale. - `git.changedFiles` pointing at one surface → scope `audit` or `polish` to those files specifically, naming them. - `devServer.running` true → `live` is available for in-browser iteration; if false, don't lead with `live`. - Otherwise group by intent exactly as init's "Recommend starting points" step does (build new / improve what's there / iterate visually), tailored to `setup.register`. + **If `scan.detectTarget` is set, run `node .gemini/skills/impeccable/scripts/detect.mjs --json ` once** (the bundled detector: no network, no npx, full coverage) and fold the hits into your picks: many quality / contrast hits → `audit` or `polish`; a specific slop family → the matching command (gradient text or eyebrows → `quieter` / `typeset`, flat or gray palette → `colorize`, and so on). It's a real, current signal that beats guessing. If detect errors, skip it and recommend the user run `audit` themselves; never block the suggestion on it. + Keep it to 2-3 pointed picks with the exact command to type. The menu stays the fallback; the recommendation is the lede. 2. **First word matches a command**: load its reference file and follow its instructions. Everything after the command name is the target. 3. **First word doesn't match, but the intent clearly maps to one command** (e.g. "fix the spacing" → `layout`, "rewrite this error message" → `clarify`, "the colors feel flat" → `colorize`): load that command's reference and proceed as if invoked. If two commands could fit, ask once which. diff --git a/.gemini/skills/impeccable/scripts/context-signals.mjs b/.gemini/skills/impeccable/scripts/context-signals.mjs index cd54793af..1792d1a19 100644 --- a/.gemini/skills/impeccable/scripts/context-signals.mjs +++ b/.gemini/skills/impeccable/scripts/context-signals.mjs @@ -150,8 +150,35 @@ async function devServerSignals() { return { running: open.length > 0, ports: open }; } +/** + * What the agent could point the bundled detector (`detect.mjs`) at. The + * detector is HTML/CSS oriented, so a rendered page (dev server) or a static + * HTML entry is a far better target than a raw source tree. This script does + * NOT run the detector itself — it just surfaces the target so the agent can + * run `node /detect.mjs --json ` (bundled, dep-free, fast) + * and fold the hits into its recommendation. + */ +function scanTarget(cwd, devServer) { + if (devServer.running && devServer.ports.length) { + return { detectTarget: `http://localhost:${devServer.ports[0]}`, via: 'dev-server' }; + } + for (const c of ['index.html', 'public/index.html', 'dist/index.html', 'build/index.html']) { + if (fs.existsSync(path.join(cwd, c))) return { detectTarget: c, via: 'html' }; + } + for (const dir of ['.', 'public', 'dist', 'build']) { + try { + const abs = path.join(cwd, dir); + if (!fs.existsSync(abs)) continue; + const html = fs.readdirSync(abs).find((f) => f.endsWith('.html')); + if (html) return { detectTarget: path.join(dir, html), via: 'html' }; + } catch { /* ignore unreadable dir */ } + } + return { detectTarget: null, via: null }; +} + export async function gatherSignals(cwd = process.cwd()) { const ctx = loadContext(cwd); + const devServer = await devServerSignals(); return { setup: { hasProduct: ctx.hasProduct, @@ -163,7 +190,8 @@ export async function gatherSignals(cwd = process.cwd()) { }, critique: { latest: latestCritique(cwd) }, git: gitSignals(cwd), - devServer: await devServerSignals(), + devServer, + scan: scanTarget(cwd, devServer), }; } diff --git a/.github/skills/impeccable/SKILL.md b/.github/skills/impeccable/SKILL.md index 6ee98f389..c107bc2a7 100644 --- a/.github/skills/impeccable/SKILL.md +++ b/.github/skills/impeccable/SKILL.md @@ -144,11 +144,14 @@ Plus two management commands: `pin ` and `unpin `, detailed be Reason over the signals; there is no score to obey: - `setup.hasDesign` false while `setup.hasCode` true → `document` (capture the visual system). + - `critique.latest` is `null` → the project has never been critiqued; for a set-up project with a real surface, offering `/impeccable critique ` is a strong default. - `critique.latest` with a low `score` or non-zero `p0` / `p1` → `polish` (it reads that snapshot as its backlog), or re-run `critique` if the snapshot looks stale. - `git.changedFiles` pointing at one surface → scope `audit` or `polish` to those files specifically, naming them. - `devServer.running` true → `live` is available for in-browser iteration; if false, don't lead with `live`. - Otherwise group by intent exactly as init's "Recommend starting points" step does (build new / improve what's there / iterate visually), tailored to `setup.register`. + **If `scan.detectTarget` is set, run `node .github/skills/impeccable/scripts/detect.mjs --json ` once** (the bundled detector: no network, no npx, full coverage) and fold the hits into your picks: many quality / contrast hits → `audit` or `polish`; a specific slop family → the matching command (gradient text or eyebrows → `quieter` / `typeset`, flat or gray palette → `colorize`, and so on). It's a real, current signal that beats guessing. If detect errors, skip it and recommend the user run `audit` themselves; never block the suggestion on it. + Keep it to 2-3 pointed picks with the exact command to type. The menu stays the fallback; the recommendation is the lede. 2. **First word matches a command**: load its reference file and follow its instructions. Everything after the command name is the target. 3. **First word doesn't match, but the intent clearly maps to one command** (e.g. "fix the spacing" → `layout`, "rewrite this error message" → `clarify`, "the colors feel flat" → `colorize`): load that command's reference and proceed as if invoked. If two commands could fit, ask once which. diff --git a/.github/skills/impeccable/scripts/context-signals.mjs b/.github/skills/impeccable/scripts/context-signals.mjs index cd54793af..1792d1a19 100644 --- a/.github/skills/impeccable/scripts/context-signals.mjs +++ b/.github/skills/impeccable/scripts/context-signals.mjs @@ -150,8 +150,35 @@ async function devServerSignals() { return { running: open.length > 0, ports: open }; } +/** + * What the agent could point the bundled detector (`detect.mjs`) at. The + * detector is HTML/CSS oriented, so a rendered page (dev server) or a static + * HTML entry is a far better target than a raw source tree. This script does + * NOT run the detector itself — it just surfaces the target so the agent can + * run `node /detect.mjs --json ` (bundled, dep-free, fast) + * and fold the hits into its recommendation. + */ +function scanTarget(cwd, devServer) { + if (devServer.running && devServer.ports.length) { + return { detectTarget: `http://localhost:${devServer.ports[0]}`, via: 'dev-server' }; + } + for (const c of ['index.html', 'public/index.html', 'dist/index.html', 'build/index.html']) { + if (fs.existsSync(path.join(cwd, c))) return { detectTarget: c, via: 'html' }; + } + for (const dir of ['.', 'public', 'dist', 'build']) { + try { + const abs = path.join(cwd, dir); + if (!fs.existsSync(abs)) continue; + const html = fs.readdirSync(abs).find((f) => f.endsWith('.html')); + if (html) return { detectTarget: path.join(dir, html), via: 'html' }; + } catch { /* ignore unreadable dir */ } + } + return { detectTarget: null, via: null }; +} + export async function gatherSignals(cwd = process.cwd()) { const ctx = loadContext(cwd); + const devServer = await devServerSignals(); return { setup: { hasProduct: ctx.hasProduct, @@ -163,7 +190,8 @@ export async function gatherSignals(cwd = process.cwd()) { }, critique: { latest: latestCritique(cwd) }, git: gitSignals(cwd), - devServer: await devServerSignals(), + devServer, + scan: scanTarget(cwd, devServer), }; } diff --git a/.kiro/skills/impeccable/SKILL.md b/.kiro/skills/impeccable/SKILL.md index d0e6e0528..4aa5b2679 100644 --- a/.kiro/skills/impeccable/SKILL.md +++ b/.kiro/skills/impeccable/SKILL.md @@ -142,11 +142,14 @@ Plus two management commands: `pin ` and `unpin `, detailed be Reason over the signals; there is no score to obey: - `setup.hasDesign` false while `setup.hasCode` true → `document` (capture the visual system). + - `critique.latest` is `null` → the project has never been critiqued; for a set-up project with a real surface, offering `/impeccable critique ` is a strong default. - `critique.latest` with a low `score` or non-zero `p0` / `p1` → `polish` (it reads that snapshot as its backlog), or re-run `critique` if the snapshot looks stale. - `git.changedFiles` pointing at one surface → scope `audit` or `polish` to those files specifically, naming them. - `devServer.running` true → `live` is available for in-browser iteration; if false, don't lead with `live`. - Otherwise group by intent exactly as init's "Recommend starting points" step does (build new / improve what's there / iterate visually), tailored to `setup.register`. + **If `scan.detectTarget` is set, run `node .kiro/skills/impeccable/scripts/detect.mjs --json ` once** (the bundled detector: no network, no npx, full coverage) and fold the hits into your picks: many quality / contrast hits → `audit` or `polish`; a specific slop family → the matching command (gradient text or eyebrows → `quieter` / `typeset`, flat or gray palette → `colorize`, and so on). It's a real, current signal that beats guessing. If detect errors, skip it and recommend the user run `audit` themselves; never block the suggestion on it. + Keep it to 2-3 pointed picks with the exact command to type. The menu stays the fallback; the recommendation is the lede. 2. **First word matches a command**: load its reference file and follow its instructions. Everything after the command name is the target. 3. **First word doesn't match, but the intent clearly maps to one command** (e.g. "fix the spacing" → `layout`, "rewrite this error message" → `clarify`, "the colors feel flat" → `colorize`): load that command's reference and proceed as if invoked. If two commands could fit, ask once which. diff --git a/.kiro/skills/impeccable/scripts/context-signals.mjs b/.kiro/skills/impeccable/scripts/context-signals.mjs index cd54793af..1792d1a19 100644 --- a/.kiro/skills/impeccable/scripts/context-signals.mjs +++ b/.kiro/skills/impeccable/scripts/context-signals.mjs @@ -150,8 +150,35 @@ async function devServerSignals() { return { running: open.length > 0, ports: open }; } +/** + * What the agent could point the bundled detector (`detect.mjs`) at. The + * detector is HTML/CSS oriented, so a rendered page (dev server) or a static + * HTML entry is a far better target than a raw source tree. This script does + * NOT run the detector itself — it just surfaces the target so the agent can + * run `node /detect.mjs --json ` (bundled, dep-free, fast) + * and fold the hits into its recommendation. + */ +function scanTarget(cwd, devServer) { + if (devServer.running && devServer.ports.length) { + return { detectTarget: `http://localhost:${devServer.ports[0]}`, via: 'dev-server' }; + } + for (const c of ['index.html', 'public/index.html', 'dist/index.html', 'build/index.html']) { + if (fs.existsSync(path.join(cwd, c))) return { detectTarget: c, via: 'html' }; + } + for (const dir of ['.', 'public', 'dist', 'build']) { + try { + const abs = path.join(cwd, dir); + if (!fs.existsSync(abs)) continue; + const html = fs.readdirSync(abs).find((f) => f.endsWith('.html')); + if (html) return { detectTarget: path.join(dir, html), via: 'html' }; + } catch { /* ignore unreadable dir */ } + } + return { detectTarget: null, via: null }; +} + export async function gatherSignals(cwd = process.cwd()) { const ctx = loadContext(cwd); + const devServer = await devServerSignals(); return { setup: { hasProduct: ctx.hasProduct, @@ -163,7 +190,8 @@ export async function gatherSignals(cwd = process.cwd()) { }, critique: { latest: latestCritique(cwd) }, git: gitSignals(cwd), - devServer: await devServerSignals(), + devServer, + scan: scanTarget(cwd, devServer), }; } diff --git a/.opencode/skills/impeccable/SKILL.md b/.opencode/skills/impeccable/SKILL.md index eea811708..7cc0abe66 100644 --- a/.opencode/skills/impeccable/SKILL.md +++ b/.opencode/skills/impeccable/SKILL.md @@ -146,11 +146,14 @@ Plus two management commands: `pin ` and `unpin `, detailed be Reason over the signals; there is no score to obey: - `setup.hasDesign` false while `setup.hasCode` true → `document` (capture the visual system). + - `critique.latest` is `null` → the project has never been critiqued; for a set-up project with a real surface, offering `/impeccable critique ` is a strong default. - `critique.latest` with a low `score` or non-zero `p0` / `p1` → `polish` (it reads that snapshot as its backlog), or re-run `critique` if the snapshot looks stale. - `git.changedFiles` pointing at one surface → scope `audit` or `polish` to those files specifically, naming them. - `devServer.running` true → `live` is available for in-browser iteration; if false, don't lead with `live`. - Otherwise group by intent exactly as init's "Recommend starting points" step does (build new / improve what's there / iterate visually), tailored to `setup.register`. + **If `scan.detectTarget` is set, run `node .opencode/skills/impeccable/scripts/detect.mjs --json ` once** (the bundled detector: no network, no npx, full coverage) and fold the hits into your picks: many quality / contrast hits → `audit` or `polish`; a specific slop family → the matching command (gradient text or eyebrows → `quieter` / `typeset`, flat or gray palette → `colorize`, and so on). It's a real, current signal that beats guessing. If detect errors, skip it and recommend the user run `audit` themselves; never block the suggestion on it. + Keep it to 2-3 pointed picks with the exact command to type. The menu stays the fallback; the recommendation is the lede. 2. **First word matches a command**: load its reference file and follow its instructions. Everything after the command name is the target. 3. **First word doesn't match, but the intent clearly maps to one command** (e.g. "fix the spacing" → `layout`, "rewrite this error message" → `clarify`, "the colors feel flat" → `colorize`): load that command's reference and proceed as if invoked. If two commands could fit, ask once which. diff --git a/.opencode/skills/impeccable/scripts/context-signals.mjs b/.opencode/skills/impeccable/scripts/context-signals.mjs index cd54793af..1792d1a19 100644 --- a/.opencode/skills/impeccable/scripts/context-signals.mjs +++ b/.opencode/skills/impeccable/scripts/context-signals.mjs @@ -150,8 +150,35 @@ async function devServerSignals() { return { running: open.length > 0, ports: open }; } +/** + * What the agent could point the bundled detector (`detect.mjs`) at. The + * detector is HTML/CSS oriented, so a rendered page (dev server) or a static + * HTML entry is a far better target than a raw source tree. This script does + * NOT run the detector itself — it just surfaces the target so the agent can + * run `node /detect.mjs --json ` (bundled, dep-free, fast) + * and fold the hits into its recommendation. + */ +function scanTarget(cwd, devServer) { + if (devServer.running && devServer.ports.length) { + return { detectTarget: `http://localhost:${devServer.ports[0]}`, via: 'dev-server' }; + } + for (const c of ['index.html', 'public/index.html', 'dist/index.html', 'build/index.html']) { + if (fs.existsSync(path.join(cwd, c))) return { detectTarget: c, via: 'html' }; + } + for (const dir of ['.', 'public', 'dist', 'build']) { + try { + const abs = path.join(cwd, dir); + if (!fs.existsSync(abs)) continue; + const html = fs.readdirSync(abs).find((f) => f.endsWith('.html')); + if (html) return { detectTarget: path.join(dir, html), via: 'html' }; + } catch { /* ignore unreadable dir */ } + } + return { detectTarget: null, via: null }; +} + export async function gatherSignals(cwd = process.cwd()) { const ctx = loadContext(cwd); + const devServer = await devServerSignals(); return { setup: { hasProduct: ctx.hasProduct, @@ -163,7 +190,8 @@ export async function gatherSignals(cwd = process.cwd()) { }, critique: { latest: latestCritique(cwd) }, git: gitSignals(cwd), - devServer: await devServerSignals(), + devServer, + scan: scanTarget(cwd, devServer), }; } diff --git a/.pi/skills/impeccable/SKILL.md b/.pi/skills/impeccable/SKILL.md index e4833e0fd..8a066527d 100644 --- a/.pi/skills/impeccable/SKILL.md +++ b/.pi/skills/impeccable/SKILL.md @@ -144,11 +144,14 @@ Plus two management commands: `pin ` and `unpin `, detailed be Reason over the signals; there is no score to obey: - `setup.hasDesign` false while `setup.hasCode` true → `document` (capture the visual system). + - `critique.latest` is `null` → the project has never been critiqued; for a set-up project with a real surface, offering `/impeccable critique ` is a strong default. - `critique.latest` with a low `score` or non-zero `p0` / `p1` → `polish` (it reads that snapshot as its backlog), or re-run `critique` if the snapshot looks stale. - `git.changedFiles` pointing at one surface → scope `audit` or `polish` to those files specifically, naming them. - `devServer.running` true → `live` is available for in-browser iteration; if false, don't lead with `live`. - Otherwise group by intent exactly as init's "Recommend starting points" step does (build new / improve what's there / iterate visually), tailored to `setup.register`. + **If `scan.detectTarget` is set, run `node .pi/skills/impeccable/scripts/detect.mjs --json ` once** (the bundled detector: no network, no npx, full coverage) and fold the hits into your picks: many quality / contrast hits → `audit` or `polish`; a specific slop family → the matching command (gradient text or eyebrows → `quieter` / `typeset`, flat or gray palette → `colorize`, and so on). It's a real, current signal that beats guessing. If detect errors, skip it and recommend the user run `audit` themselves; never block the suggestion on it. + Keep it to 2-3 pointed picks with the exact command to type. The menu stays the fallback; the recommendation is the lede. 2. **First word matches a command**: load its reference file and follow its instructions. Everything after the command name is the target. 3. **First word doesn't match, but the intent clearly maps to one command** (e.g. "fix the spacing" → `layout`, "rewrite this error message" → `clarify`, "the colors feel flat" → `colorize`): load that command's reference and proceed as if invoked. If two commands could fit, ask once which. diff --git a/.pi/skills/impeccable/scripts/context-signals.mjs b/.pi/skills/impeccable/scripts/context-signals.mjs index cd54793af..1792d1a19 100644 --- a/.pi/skills/impeccable/scripts/context-signals.mjs +++ b/.pi/skills/impeccable/scripts/context-signals.mjs @@ -150,8 +150,35 @@ async function devServerSignals() { return { running: open.length > 0, ports: open }; } +/** + * What the agent could point the bundled detector (`detect.mjs`) at. The + * detector is HTML/CSS oriented, so a rendered page (dev server) or a static + * HTML entry is a far better target than a raw source tree. This script does + * NOT run the detector itself — it just surfaces the target so the agent can + * run `node /detect.mjs --json ` (bundled, dep-free, fast) + * and fold the hits into its recommendation. + */ +function scanTarget(cwd, devServer) { + if (devServer.running && devServer.ports.length) { + return { detectTarget: `http://localhost:${devServer.ports[0]}`, via: 'dev-server' }; + } + for (const c of ['index.html', 'public/index.html', 'dist/index.html', 'build/index.html']) { + if (fs.existsSync(path.join(cwd, c))) return { detectTarget: c, via: 'html' }; + } + for (const dir of ['.', 'public', 'dist', 'build']) { + try { + const abs = path.join(cwd, dir); + if (!fs.existsSync(abs)) continue; + const html = fs.readdirSync(abs).find((f) => f.endsWith('.html')); + if (html) return { detectTarget: path.join(dir, html), via: 'html' }; + } catch { /* ignore unreadable dir */ } + } + return { detectTarget: null, via: null }; +} + export async function gatherSignals(cwd = process.cwd()) { const ctx = loadContext(cwd); + const devServer = await devServerSignals(); return { setup: { hasProduct: ctx.hasProduct, @@ -163,7 +190,8 @@ export async function gatherSignals(cwd = process.cwd()) { }, critique: { latest: latestCritique(cwd) }, git: gitSignals(cwd), - devServer: await devServerSignals(), + devServer, + scan: scanTarget(cwd, devServer), }; } diff --git a/.qoder/skills/impeccable/SKILL.md b/.qoder/skills/impeccable/SKILL.md index a6fecfaf8..9afbff322 100644 --- a/.qoder/skills/impeccable/SKILL.md +++ b/.qoder/skills/impeccable/SKILL.md @@ -146,11 +146,14 @@ Plus two management commands: `pin ` and `unpin `, detailed be Reason over the signals; there is no score to obey: - `setup.hasDesign` false while `setup.hasCode` true → `document` (capture the visual system). + - `critique.latest` is `null` → the project has never been critiqued; for a set-up project with a real surface, offering `/impeccable critique ` is a strong default. - `critique.latest` with a low `score` or non-zero `p0` / `p1` → `polish` (it reads that snapshot as its backlog), or re-run `critique` if the snapshot looks stale. - `git.changedFiles` pointing at one surface → scope `audit` or `polish` to those files specifically, naming them. - `devServer.running` true → `live` is available for in-browser iteration; if false, don't lead with `live`. - Otherwise group by intent exactly as init's "Recommend starting points" step does (build new / improve what's there / iterate visually), tailored to `setup.register`. + **If `scan.detectTarget` is set, run `node .qoder/skills/impeccable/scripts/detect.mjs --json ` once** (the bundled detector: no network, no npx, full coverage) and fold the hits into your picks: many quality / contrast hits → `audit` or `polish`; a specific slop family → the matching command (gradient text or eyebrows → `quieter` / `typeset`, flat or gray palette → `colorize`, and so on). It's a real, current signal that beats guessing. If detect errors, skip it and recommend the user run `audit` themselves; never block the suggestion on it. + Keep it to 2-3 pointed picks with the exact command to type. The menu stays the fallback; the recommendation is the lede. 2. **First word matches a command**: load its reference file and follow its instructions. Everything after the command name is the target. 3. **First word doesn't match, but the intent clearly maps to one command** (e.g. "fix the spacing" → `layout`, "rewrite this error message" → `clarify`, "the colors feel flat" → `colorize`): load that command's reference and proceed as if invoked. If two commands could fit, ask once which. diff --git a/.qoder/skills/impeccable/scripts/context-signals.mjs b/.qoder/skills/impeccable/scripts/context-signals.mjs index cd54793af..1792d1a19 100644 --- a/.qoder/skills/impeccable/scripts/context-signals.mjs +++ b/.qoder/skills/impeccable/scripts/context-signals.mjs @@ -150,8 +150,35 @@ async function devServerSignals() { return { running: open.length > 0, ports: open }; } +/** + * What the agent could point the bundled detector (`detect.mjs`) at. The + * detector is HTML/CSS oriented, so a rendered page (dev server) or a static + * HTML entry is a far better target than a raw source tree. This script does + * NOT run the detector itself — it just surfaces the target so the agent can + * run `node /detect.mjs --json ` (bundled, dep-free, fast) + * and fold the hits into its recommendation. + */ +function scanTarget(cwd, devServer) { + if (devServer.running && devServer.ports.length) { + return { detectTarget: `http://localhost:${devServer.ports[0]}`, via: 'dev-server' }; + } + for (const c of ['index.html', 'public/index.html', 'dist/index.html', 'build/index.html']) { + if (fs.existsSync(path.join(cwd, c))) return { detectTarget: c, via: 'html' }; + } + for (const dir of ['.', 'public', 'dist', 'build']) { + try { + const abs = path.join(cwd, dir); + if (!fs.existsSync(abs)) continue; + const html = fs.readdirSync(abs).find((f) => f.endsWith('.html')); + if (html) return { detectTarget: path.join(dir, html), via: 'html' }; + } catch { /* ignore unreadable dir */ } + } + return { detectTarget: null, via: null }; +} + export async function gatherSignals(cwd = process.cwd()) { const ctx = loadContext(cwd); + const devServer = await devServerSignals(); return { setup: { hasProduct: ctx.hasProduct, @@ -163,7 +190,8 @@ export async function gatherSignals(cwd = process.cwd()) { }, critique: { latest: latestCritique(cwd) }, git: gitSignals(cwd), - devServer: await devServerSignals(), + devServer, + scan: scanTarget(cwd, devServer), }; } diff --git a/.rovodev/skills/impeccable/SKILL.md b/.rovodev/skills/impeccable/SKILL.md index 6a6619645..ac7e279ee 100644 --- a/.rovodev/skills/impeccable/SKILL.md +++ b/.rovodev/skills/impeccable/SKILL.md @@ -146,11 +146,14 @@ Plus two management commands: `pin ` and `unpin `, detailed be Reason over the signals; there is no score to obey: - `setup.hasDesign` false while `setup.hasCode` true → `document` (capture the visual system). + - `critique.latest` is `null` → the project has never been critiqued; for a set-up project with a real surface, offering `/impeccable critique ` is a strong default. - `critique.latest` with a low `score` or non-zero `p0` / `p1` → `polish` (it reads that snapshot as its backlog), or re-run `critique` if the snapshot looks stale. - `git.changedFiles` pointing at one surface → scope `audit` or `polish` to those files specifically, naming them. - `devServer.running` true → `live` is available for in-browser iteration; if false, don't lead with `live`. - Otherwise group by intent exactly as init's "Recommend starting points" step does (build new / improve what's there / iterate visually), tailored to `setup.register`. + **If `scan.detectTarget` is set, run `node .rovodev/skills/impeccable/scripts/detect.mjs --json ` once** (the bundled detector: no network, no npx, full coverage) and fold the hits into your picks: many quality / contrast hits → `audit` or `polish`; a specific slop family → the matching command (gradient text or eyebrows → `quieter` / `typeset`, flat or gray palette → `colorize`, and so on). It's a real, current signal that beats guessing. If detect errors, skip it and recommend the user run `audit` themselves; never block the suggestion on it. + Keep it to 2-3 pointed picks with the exact command to type. The menu stays the fallback; the recommendation is the lede. 2. **First word matches a command**: load its reference file and follow its instructions. Everything after the command name is the target. 3. **First word doesn't match, but the intent clearly maps to one command** (e.g. "fix the spacing" → `layout`, "rewrite this error message" → `clarify`, "the colors feel flat" → `colorize`): load that command's reference and proceed as if invoked. If two commands could fit, ask once which. diff --git a/.rovodev/skills/impeccable/scripts/context-signals.mjs b/.rovodev/skills/impeccable/scripts/context-signals.mjs index cd54793af..1792d1a19 100644 --- a/.rovodev/skills/impeccable/scripts/context-signals.mjs +++ b/.rovodev/skills/impeccable/scripts/context-signals.mjs @@ -150,8 +150,35 @@ async function devServerSignals() { return { running: open.length > 0, ports: open }; } +/** + * What the agent could point the bundled detector (`detect.mjs`) at. The + * detector is HTML/CSS oriented, so a rendered page (dev server) or a static + * HTML entry is a far better target than a raw source tree. This script does + * NOT run the detector itself — it just surfaces the target so the agent can + * run `node /detect.mjs --json ` (bundled, dep-free, fast) + * and fold the hits into its recommendation. + */ +function scanTarget(cwd, devServer) { + if (devServer.running && devServer.ports.length) { + return { detectTarget: `http://localhost:${devServer.ports[0]}`, via: 'dev-server' }; + } + for (const c of ['index.html', 'public/index.html', 'dist/index.html', 'build/index.html']) { + if (fs.existsSync(path.join(cwd, c))) return { detectTarget: c, via: 'html' }; + } + for (const dir of ['.', 'public', 'dist', 'build']) { + try { + const abs = path.join(cwd, dir); + if (!fs.existsSync(abs)) continue; + const html = fs.readdirSync(abs).find((f) => f.endsWith('.html')); + if (html) return { detectTarget: path.join(dir, html), via: 'html' }; + } catch { /* ignore unreadable dir */ } + } + return { detectTarget: null, via: null }; +} + export async function gatherSignals(cwd = process.cwd()) { const ctx = loadContext(cwd); + const devServer = await devServerSignals(); return { setup: { hasProduct: ctx.hasProduct, @@ -163,7 +190,8 @@ export async function gatherSignals(cwd = process.cwd()) { }, critique: { latest: latestCritique(cwd) }, git: gitSignals(cwd), - devServer: await devServerSignals(), + devServer, + scan: scanTarget(cwd, devServer), }; } diff --git a/.trae-cn/skills/impeccable/SKILL.md b/.trae-cn/skills/impeccable/SKILL.md index e93bbdd3c..fd2c6d784 100644 --- a/.trae-cn/skills/impeccable/SKILL.md +++ b/.trae-cn/skills/impeccable/SKILL.md @@ -144,11 +144,14 @@ Plus two management commands: `pin ` and `unpin `, detailed be Reason over the signals; there is no score to obey: - `setup.hasDesign` false while `setup.hasCode` true → `document` (capture the visual system). + - `critique.latest` is `null` → the project has never been critiqued; for a set-up project with a real surface, offering `/impeccable critique ` is a strong default. - `critique.latest` with a low `score` or non-zero `p0` / `p1` → `polish` (it reads that snapshot as its backlog), or re-run `critique` if the snapshot looks stale. - `git.changedFiles` pointing at one surface → scope `audit` or `polish` to those files specifically, naming them. - `devServer.running` true → `live` is available for in-browser iteration; if false, don't lead with `live`. - Otherwise group by intent exactly as init's "Recommend starting points" step does (build new / improve what's there / iterate visually), tailored to `setup.register`. + **If `scan.detectTarget` is set, run `node .trae-cn/skills/impeccable/scripts/detect.mjs --json ` once** (the bundled detector: no network, no npx, full coverage) and fold the hits into your picks: many quality / contrast hits → `audit` or `polish`; a specific slop family → the matching command (gradient text or eyebrows → `quieter` / `typeset`, flat or gray palette → `colorize`, and so on). It's a real, current signal that beats guessing. If detect errors, skip it and recommend the user run `audit` themselves; never block the suggestion on it. + Keep it to 2-3 pointed picks with the exact command to type. The menu stays the fallback; the recommendation is the lede. 2. **First word matches a command**: load its reference file and follow its instructions. Everything after the command name is the target. 3. **First word doesn't match, but the intent clearly maps to one command** (e.g. "fix the spacing" → `layout`, "rewrite this error message" → `clarify`, "the colors feel flat" → `colorize`): load that command's reference and proceed as if invoked. If two commands could fit, ask once which. diff --git a/.trae-cn/skills/impeccable/scripts/context-signals.mjs b/.trae-cn/skills/impeccable/scripts/context-signals.mjs index cd54793af..1792d1a19 100644 --- a/.trae-cn/skills/impeccable/scripts/context-signals.mjs +++ b/.trae-cn/skills/impeccable/scripts/context-signals.mjs @@ -150,8 +150,35 @@ async function devServerSignals() { return { running: open.length > 0, ports: open }; } +/** + * What the agent could point the bundled detector (`detect.mjs`) at. The + * detector is HTML/CSS oriented, so a rendered page (dev server) or a static + * HTML entry is a far better target than a raw source tree. This script does + * NOT run the detector itself — it just surfaces the target so the agent can + * run `node /detect.mjs --json ` (bundled, dep-free, fast) + * and fold the hits into its recommendation. + */ +function scanTarget(cwd, devServer) { + if (devServer.running && devServer.ports.length) { + return { detectTarget: `http://localhost:${devServer.ports[0]}`, via: 'dev-server' }; + } + for (const c of ['index.html', 'public/index.html', 'dist/index.html', 'build/index.html']) { + if (fs.existsSync(path.join(cwd, c))) return { detectTarget: c, via: 'html' }; + } + for (const dir of ['.', 'public', 'dist', 'build']) { + try { + const abs = path.join(cwd, dir); + if (!fs.existsSync(abs)) continue; + const html = fs.readdirSync(abs).find((f) => f.endsWith('.html')); + if (html) return { detectTarget: path.join(dir, html), via: 'html' }; + } catch { /* ignore unreadable dir */ } + } + return { detectTarget: null, via: null }; +} + export async function gatherSignals(cwd = process.cwd()) { const ctx = loadContext(cwd); + const devServer = await devServerSignals(); return { setup: { hasProduct: ctx.hasProduct, @@ -163,7 +190,8 @@ export async function gatherSignals(cwd = process.cwd()) { }, critique: { latest: latestCritique(cwd) }, git: gitSignals(cwd), - devServer: await devServerSignals(), + devServer, + scan: scanTarget(cwd, devServer), }; } diff --git a/.trae/skills/impeccable/SKILL.md b/.trae/skills/impeccable/SKILL.md index d8316ae21..b2cf7deb4 100644 --- a/.trae/skills/impeccable/SKILL.md +++ b/.trae/skills/impeccable/SKILL.md @@ -144,11 +144,14 @@ Plus two management commands: `pin ` and `unpin `, detailed be Reason over the signals; there is no score to obey: - `setup.hasDesign` false while `setup.hasCode` true → `document` (capture the visual system). + - `critique.latest` is `null` → the project has never been critiqued; for a set-up project with a real surface, offering `/impeccable critique ` is a strong default. - `critique.latest` with a low `score` or non-zero `p0` / `p1` → `polish` (it reads that snapshot as its backlog), or re-run `critique` if the snapshot looks stale. - `git.changedFiles` pointing at one surface → scope `audit` or `polish` to those files specifically, naming them. - `devServer.running` true → `live` is available for in-browser iteration; if false, don't lead with `live`. - Otherwise group by intent exactly as init's "Recommend starting points" step does (build new / improve what's there / iterate visually), tailored to `setup.register`. + **If `scan.detectTarget` is set, run `node .trae/skills/impeccable/scripts/detect.mjs --json ` once** (the bundled detector: no network, no npx, full coverage) and fold the hits into your picks: many quality / contrast hits → `audit` or `polish`; a specific slop family → the matching command (gradient text or eyebrows → `quieter` / `typeset`, flat or gray palette → `colorize`, and so on). It's a real, current signal that beats guessing. If detect errors, skip it and recommend the user run `audit` themselves; never block the suggestion on it. + Keep it to 2-3 pointed picks with the exact command to type. The menu stays the fallback; the recommendation is the lede. 2. **First word matches a command**: load its reference file and follow its instructions. Everything after the command name is the target. 3. **First word doesn't match, but the intent clearly maps to one command** (e.g. "fix the spacing" → `layout`, "rewrite this error message" → `clarify`, "the colors feel flat" → `colorize`): load that command's reference and proceed as if invoked. If two commands could fit, ask once which. diff --git a/.trae/skills/impeccable/scripts/context-signals.mjs b/.trae/skills/impeccable/scripts/context-signals.mjs index cd54793af..1792d1a19 100644 --- a/.trae/skills/impeccable/scripts/context-signals.mjs +++ b/.trae/skills/impeccable/scripts/context-signals.mjs @@ -150,8 +150,35 @@ async function devServerSignals() { return { running: open.length > 0, ports: open }; } +/** + * What the agent could point the bundled detector (`detect.mjs`) at. The + * detector is HTML/CSS oriented, so a rendered page (dev server) or a static + * HTML entry is a far better target than a raw source tree. This script does + * NOT run the detector itself — it just surfaces the target so the agent can + * run `node /detect.mjs --json ` (bundled, dep-free, fast) + * and fold the hits into its recommendation. + */ +function scanTarget(cwd, devServer) { + if (devServer.running && devServer.ports.length) { + return { detectTarget: `http://localhost:${devServer.ports[0]}`, via: 'dev-server' }; + } + for (const c of ['index.html', 'public/index.html', 'dist/index.html', 'build/index.html']) { + if (fs.existsSync(path.join(cwd, c))) return { detectTarget: c, via: 'html' }; + } + for (const dir of ['.', 'public', 'dist', 'build']) { + try { + const abs = path.join(cwd, dir); + if (!fs.existsSync(abs)) continue; + const html = fs.readdirSync(abs).find((f) => f.endsWith('.html')); + if (html) return { detectTarget: path.join(dir, html), via: 'html' }; + } catch { /* ignore unreadable dir */ } + } + return { detectTarget: null, via: null }; +} + export async function gatherSignals(cwd = process.cwd()) { const ctx = loadContext(cwd); + const devServer = await devServerSignals(); return { setup: { hasProduct: ctx.hasProduct, @@ -163,7 +190,8 @@ export async function gatherSignals(cwd = process.cwd()) { }, critique: { latest: latestCritique(cwd) }, git: gitSignals(cwd), - devServer: await devServerSignals(), + devServer, + scan: scanTarget(cwd, devServer), }; } diff --git a/plugin/skills/impeccable/SKILL.md b/plugin/skills/impeccable/SKILL.md index c61880528..589bee044 100644 --- a/plugin/skills/impeccable/SKILL.md +++ b/plugin/skills/impeccable/SKILL.md @@ -146,11 +146,14 @@ Plus two management commands: `pin ` and `unpin `, detailed be Reason over the signals; there is no score to obey: - `setup.hasDesign` false while `setup.hasCode` true → `document` (capture the visual system). + - `critique.latest` is `null` → the project has never been critiqued; for a set-up project with a real surface, offering `/impeccable critique ` is a strong default. - `critique.latest` with a low `score` or non-zero `p0` / `p1` → `polish` (it reads that snapshot as its backlog), or re-run `critique` if the snapshot looks stale. - `git.changedFiles` pointing at one surface → scope `audit` or `polish` to those files specifically, naming them. - `devServer.running` true → `live` is available for in-browser iteration; if false, don't lead with `live`. - Otherwise group by intent exactly as init's "Recommend starting points" step does (build new / improve what's there / iterate visually), tailored to `setup.register`. + **If `scan.detectTarget` is set, run `node .claude/skills/impeccable/scripts/detect.mjs --json ` once** (the bundled detector: no network, no npx, full coverage) and fold the hits into your picks: many quality / contrast hits → `audit` or `polish`; a specific slop family → the matching command (gradient text or eyebrows → `quieter` / `typeset`, flat or gray palette → `colorize`, and so on). It's a real, current signal that beats guessing. If detect errors, skip it and recommend the user run `audit` themselves; never block the suggestion on it. + Keep it to 2-3 pointed picks with the exact command to type. The menu stays the fallback; the recommendation is the lede. 2. **First word matches a command**: load its reference file and follow its instructions. Everything after the command name is the target. 3. **First word doesn't match, but the intent clearly maps to one command** (e.g. "fix the spacing" → `layout`, "rewrite this error message" → `clarify`, "the colors feel flat" → `colorize`): load that command's reference and proceed as if invoked. If two commands could fit, ask once which. diff --git a/plugin/skills/impeccable/scripts/context-signals.mjs b/plugin/skills/impeccable/scripts/context-signals.mjs index cd54793af..1792d1a19 100644 --- a/plugin/skills/impeccable/scripts/context-signals.mjs +++ b/plugin/skills/impeccable/scripts/context-signals.mjs @@ -150,8 +150,35 @@ async function devServerSignals() { return { running: open.length > 0, ports: open }; } +/** + * What the agent could point the bundled detector (`detect.mjs`) at. The + * detector is HTML/CSS oriented, so a rendered page (dev server) or a static + * HTML entry is a far better target than a raw source tree. This script does + * NOT run the detector itself — it just surfaces the target so the agent can + * run `node /detect.mjs --json ` (bundled, dep-free, fast) + * and fold the hits into its recommendation. + */ +function scanTarget(cwd, devServer) { + if (devServer.running && devServer.ports.length) { + return { detectTarget: `http://localhost:${devServer.ports[0]}`, via: 'dev-server' }; + } + for (const c of ['index.html', 'public/index.html', 'dist/index.html', 'build/index.html']) { + if (fs.existsSync(path.join(cwd, c))) return { detectTarget: c, via: 'html' }; + } + for (const dir of ['.', 'public', 'dist', 'build']) { + try { + const abs = path.join(cwd, dir); + if (!fs.existsSync(abs)) continue; + const html = fs.readdirSync(abs).find((f) => f.endsWith('.html')); + if (html) return { detectTarget: path.join(dir, html), via: 'html' }; + } catch { /* ignore unreadable dir */ } + } + return { detectTarget: null, via: null }; +} + export async function gatherSignals(cwd = process.cwd()) { const ctx = loadContext(cwd); + const devServer = await devServerSignals(); return { setup: { hasProduct: ctx.hasProduct, @@ -163,7 +190,8 @@ export async function gatherSignals(cwd = process.cwd()) { }, critique: { latest: latestCritique(cwd) }, git: gitSignals(cwd), - devServer: await devServerSignals(), + devServer, + scan: scanTarget(cwd, devServer), }; } diff --git a/skill/SKILL.src.md b/skill/SKILL.src.md index f82f70b7b..08eb358e8 100644 --- a/skill/SKILL.src.md +++ b/skill/SKILL.src.md @@ -165,11 +165,14 @@ Plus two management commands: `pin ` and `unpin `, detailed be Reason over the signals; there is no score to obey: - `setup.hasDesign` false while `setup.hasCode` true → `document` (capture the visual system). + - `critique.latest` is `null` → the project has never been critiqued; for a set-up project with a real surface, offering `/impeccable critique ` is a strong default. - `critique.latest` with a low `score` or non-zero `p0` / `p1` → `polish` (it reads that snapshot as its backlog), or re-run `critique` if the snapshot looks stale. - `git.changedFiles` pointing at one surface → scope `audit` or `polish` to those files specifically, naming them. - `devServer.running` true → `live` is available for in-browser iteration; if false, don't lead with `live`. - Otherwise group by intent exactly as init's "Recommend starting points" step does (build new / improve what's there / iterate visually), tailored to `setup.register`. + **If `scan.detectTarget` is set, run `node {{scripts_path}}/detect.mjs --json ` once** (the bundled detector: no network, no npx, full coverage) and fold the hits into your picks: many quality / contrast hits → `audit` or `polish`; a specific slop family → the matching command (gradient text or eyebrows → `quieter` / `typeset`, flat or gray palette → `colorize`, and so on). It's a real, current signal that beats guessing. If detect errors, skip it and recommend the user run `audit` themselves; never block the suggestion on it. + Keep it to 2-3 pointed picks with the exact command to type. The menu stays the fallback; the recommendation is the lede. 2. **First word matches a command**: load its reference file and follow its instructions. Everything after the command name is the target. 3. **First word doesn't match, but the intent clearly maps to one command** (e.g. "fix the spacing" → `layout`, "rewrite this error message" → `clarify`, "the colors feel flat" → `colorize`): load that command's reference and proceed as if invoked. If two commands could fit, ask once which. diff --git a/skill/scripts/context-signals.mjs b/skill/scripts/context-signals.mjs index cd54793af..1792d1a19 100644 --- a/skill/scripts/context-signals.mjs +++ b/skill/scripts/context-signals.mjs @@ -150,8 +150,35 @@ async function devServerSignals() { return { running: open.length > 0, ports: open }; } +/** + * What the agent could point the bundled detector (`detect.mjs`) at. The + * detector is HTML/CSS oriented, so a rendered page (dev server) or a static + * HTML entry is a far better target than a raw source tree. This script does + * NOT run the detector itself — it just surfaces the target so the agent can + * run `node /detect.mjs --json ` (bundled, dep-free, fast) + * and fold the hits into its recommendation. + */ +function scanTarget(cwd, devServer) { + if (devServer.running && devServer.ports.length) { + return { detectTarget: `http://localhost:${devServer.ports[0]}`, via: 'dev-server' }; + } + for (const c of ['index.html', 'public/index.html', 'dist/index.html', 'build/index.html']) { + if (fs.existsSync(path.join(cwd, c))) return { detectTarget: c, via: 'html' }; + } + for (const dir of ['.', 'public', 'dist', 'build']) { + try { + const abs = path.join(cwd, dir); + if (!fs.existsSync(abs)) continue; + const html = fs.readdirSync(abs).find((f) => f.endsWith('.html')); + if (html) return { detectTarget: path.join(dir, html), via: 'html' }; + } catch { /* ignore unreadable dir */ } + } + return { detectTarget: null, via: null }; +} + export async function gatherSignals(cwd = process.cwd()) { const ctx = loadContext(cwd); + const devServer = await devServerSignals(); return { setup: { hasProduct: ctx.hasProduct, @@ -163,7 +190,8 @@ export async function gatherSignals(cwd = process.cwd()) { }, critique: { latest: latestCritique(cwd) }, git: gitSignals(cwd), - devServer: await devServerSignals(), + devServer, + scan: scanTarget(cwd, devServer), }; } diff --git a/tests/context-signals.test.mjs b/tests/context-signals.test.mjs index b44efb0c2..28f778b97 100644 --- a/tests/context-signals.test.mjs +++ b/tests/context-signals.test.mjs @@ -108,6 +108,27 @@ describe('gatherSignals', () => { assert.equal(typeof s.devServer.running, 'boolean'); assert.ok(Array.isArray(s.devServer.ports)); }); + + it('points scan.detectTarget at an HTML entry when one exists', async () => { + write('public/index.html', 'x'); + const s = await gatherSignals(scratch); + // A live dev server (if one happens to run on the host) wins; otherwise + // the static HTML entry is the target. + if (!s.devServer.running) { + assert.equal(s.scan.detectTarget, 'public/index.html'); + assert.equal(s.scan.via, 'html'); + } else { + assert.equal(s.scan.via, 'dev-server'); + } + }); + + it('has a null scan.detectTarget when there is nothing scannable', async () => { + const s = await gatherSignals(scratch); + if (!s.devServer.running) { + assert.equal(s.scan.detectTarget, null); + assert.equal(s.scan.via, null); + } + }); }); describe('context-signals CLI', () => {