mirror of
https://github.com/pbakaus/impeccable.git
synced 2026-09-15 15:46:30 +03:00
Add /impeccable generate: agent-initiated live variants (Node-era squash)
Squash of the ten commits reviewed on PR #626, plus the last review round's connection-aware roll call, before the rebase onto the Rust engine: the generate command reference and router row, the overlay's agent-target handling (roll call, leases, replay, rescue), the Node-era live-server routes and live-generate CLI, the hook stand-down, the pricing cards e2e fixture, and the unit, contract, e2e, and skill-behavior tests. The server, CLI, hook, and pin halves are ported to the engine crates in the commits that follow. AI-assisted: implemented and tested with Claude Code under maintainer direction. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
This commit is contained in:
committed by
Abdul Wahab
co-authored by
Claude Fable 5
parent
1c043ea7c9
commit
fc89b0ed62
@@ -322,6 +322,9 @@ results remain the completed measurements.
|
||||
| 17 | existing surface; asks whether critique is required before polish | completes read-only advice distinguishing assessment from implementation and explaining critique is optional; reference coverage is diagnostic |
|
||||
| 18 | existing surface; explicitly requests polish followed by a next-command recommendation | loads `polish.md` rather than substituting workflow advice for the requested work |
|
||||
| 19 | tiny spacing edit with PRODUCT.md + DESIGN.md; Bash denied, a real-loader success control, a denied-launcher planning-only case, and a denied-launcher documentation case (PRODUCT.md + index.html, no DESIGN.md) | edits require successful playbook/craft-floor reads and a pre-edit denial warning; planning stays read-only and skips craft-floor; documentation requires successful document.md and source reads before any DESIGN.md write, with the denial disclosed before the first tool call after the denied launcher |
|
||||
| 20 | PRODUCT.md + DESIGN.md + `index.html`; prompt is `/impeccable generate 2 bold variants of the hero heading` | loads `reference/generate.md`, and before any `live.md` read (live.md alone is the misroute) |
|
||||
| 21 | same fixture; prompt is natural language with no command word ("Show me a few quieter versions of the hero heading in the browser so I can pick one.") | infers `reference/generate.md` before any `live.md` read |
|
||||
| 22 | same fixture; prompt is `Make the hero heading bolder.` | does **not** load `reference/generate.md` (a plain refinement stays out of live); which playbook the refinement lands on is existing routing's business, not this guard's |
|
||||
|
||||
## Setup launcher-failure branch (2026-09-06, PR #750)
|
||||
|
||||
|
||||
@@ -14,6 +14,7 @@
|
||||
import { describe, it } from 'node:test';
|
||||
import assert from 'node:assert/strict';
|
||||
import path from 'node:path';
|
||||
import { execFileSync } from 'node:child_process';
|
||||
|
||||
import {
|
||||
prepareWorkspace,
|
||||
@@ -79,6 +80,40 @@ function loadedBeforeImplementationWrite(trace, filename) {
|
||||
return loadIndex >= 0 && (writeIndex < 0 || loadIndex < writeIndex);
|
||||
}
|
||||
|
||||
/**
|
||||
* True when `first` was loaded, and loaded before `second` whenever `second`
|
||||
* was loaded at all. generate.md hands off to live.md, so a run that reaches
|
||||
* live.md must have gone through generate.md first; live.md alone is the
|
||||
* misroute.
|
||||
*/
|
||||
function loadedBefore(trace, first, second) {
|
||||
const indexOf = (filename) => {
|
||||
const needle = filename.toLowerCase();
|
||||
return trace.toolCalls.findIndex(({ name, input }) => {
|
||||
if (name === 'read') return input?.path?.toLowerCase().includes(needle);
|
||||
if (name === 'bash') return input?.command?.toLowerCase().includes(needle);
|
||||
return false;
|
||||
});
|
||||
};
|
||||
const firstIndex = indexOf(first);
|
||||
const secondIndex = indexOf(second);
|
||||
return firstIndex >= 0 && (secondIndex < 0 || firstIndex < secondIndex);
|
||||
}
|
||||
|
||||
/**
|
||||
* A generate scenario that reaches the boot leaves a detached live helper
|
||||
* behind; stop it (idempotent) before the workspace goes away.
|
||||
*/
|
||||
function stopLiveHelper(workspace) {
|
||||
try {
|
||||
execFileSync(
|
||||
process.execPath,
|
||||
[path.join(workspace, '.claude/skills/impeccable/scripts/live-server.mjs'), 'stop'],
|
||||
{ cwd: workspace, stdio: 'ignore', timeout: 10_000 },
|
||||
);
|
||||
} catch { /* nothing was running */ }
|
||||
}
|
||||
|
||||
function executedUpdateCommands(trace) {
|
||||
const executableSegments = trace.bashCommands.flatMap((command) =>
|
||||
command
|
||||
@@ -782,5 +817,86 @@ for (const modelId of resolveModelList()) {
|
||||
cleanupWorkspace(workspace);
|
||||
}
|
||||
});
|
||||
|
||||
it('scenario 20: explicit generate request routes to generate.md', async () => {
|
||||
// "generate N <direction> variants of <element>" is the command's whole
|
||||
// grammar. The route must land on generate.md; bolder.md is the
|
||||
// direction's own playbook and live.md loads it later, so neither
|
||||
// counts as the route.
|
||||
const workspace = prepareWorkspace({
|
||||
files: { 'PRODUCT.md': PRODUCT_MD_SAMPLE, 'DESIGN.md': DESIGN_MD_SAMPLE, 'index.html': MINIMAL_LANDING_HTML },
|
||||
});
|
||||
try {
|
||||
const { trace, text } = await runTurn({
|
||||
workspace,
|
||||
model,
|
||||
userPrompt: '/impeccable generate 2 bold variants of the hero heading',
|
||||
maxSteps: 6,
|
||||
});
|
||||
logTrace('S20', 'generate-explicit', modelId, trace, { textSample: text.slice(0, 400) });
|
||||
assert.ok(
|
||||
loadedBefore(trace, 'generate.md', 'live.md'),
|
||||
`agent should load generate.md for an explicit generate request, before any live.md read.\n` +
|
||||
`Trace: ${JSON.stringify(summarizeTrace(trace), null, 2)}`,
|
||||
);
|
||||
} finally {
|
||||
stopLiveHelper(workspace);
|
||||
cleanupWorkspace(workspace);
|
||||
}
|
||||
});
|
||||
|
||||
it('scenario 21: natural-language variant request infers generate', async () => {
|
||||
// No command word and no "generate": the intent is carried by
|
||||
// "versions", "in the browser", and "pick one". A model that reads
|
||||
// that as a source-side bolder or quieter edit misroutes.
|
||||
const workspace = prepareWorkspace({
|
||||
files: { 'PRODUCT.md': PRODUCT_MD_SAMPLE, 'DESIGN.md': DESIGN_MD_SAMPLE, 'index.html': MINIMAL_LANDING_HTML },
|
||||
});
|
||||
try {
|
||||
const { trace, text } = await runTurn({
|
||||
workspace,
|
||||
model,
|
||||
userPrompt: 'Show me a few quieter versions of the hero heading in the browser so I can pick one.',
|
||||
maxSteps: 6,
|
||||
});
|
||||
logTrace('S21', 'generate-implicit', modelId, trace, { textSample: text.slice(0, 400) });
|
||||
assert.ok(
|
||||
loadedBefore(trace, 'generate.md', 'live.md'),
|
||||
`agent should infer generate.md from a versions-to-pick-from request, before any live.md read.\n` +
|
||||
`Trace: ${JSON.stringify(summarizeTrace(trace), null, 2)}`,
|
||||
);
|
||||
} finally {
|
||||
stopLiveHelper(workspace);
|
||||
cleanupWorkspace(workspace);
|
||||
}
|
||||
});
|
||||
|
||||
it('scenario 22: a plain refinement request stays out of generate', async () => {
|
||||
// The inverse guard: "make it bolder" asks for one edit in source, not
|
||||
// for variants to choose from in a browser. Over-triggering generate
|
||||
// here would drag every refinement into a live session. Which playbook
|
||||
// the refinement itself lands on is the existing sub-command routing's
|
||||
// business, not this guard's.
|
||||
const workspace = prepareWorkspace({
|
||||
files: { 'PRODUCT.md': PRODUCT_MD_SAMPLE, 'DESIGN.md': DESIGN_MD_SAMPLE, 'index.html': MINIMAL_LANDING_HTML },
|
||||
});
|
||||
try {
|
||||
const { trace, text } = await runTurn({
|
||||
workspace,
|
||||
model,
|
||||
userPrompt: 'Make the hero heading bolder.',
|
||||
maxSteps: 6,
|
||||
});
|
||||
logTrace('S22', 'refinement-not-generate', modelId, trace, { textSample: text.slice(0, 400) });
|
||||
assert.equal(
|
||||
fileLoaded(trace, 'generate.md'),
|
||||
false,
|
||||
`a plain refinement must not route into generate.md.\n` +
|
||||
`Trace: ${JSON.stringify(summarizeTrace(trace), null, 2)}`,
|
||||
);
|
||||
} finally {
|
||||
cleanupWorkspace(workspace);
|
||||
}
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user