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
26 changes: 26 additions & 0 deletions src/local/entrypoint.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2206,6 +2206,32 @@ describe('runLocal', () => {
expect(result.logs.some((l) => l.includes('[local] workflow generation'))).toBe(false);
});

it('routes a non-executable CLI specFile to generate even when the spec body mentions a workflow path', async () => {
const localExecutor = memoryLocalExecutorOptions();
const designDoc = [
'# Relay-backed review runtime',
'',
'The durable deep-review path in `workflows/runtime/deep-review-pr.ts` is the foundation we want to extend.',
'Run, launch, and start references are scattered through the runtime narrative below.',
'Author a workflow that wires the new runtime end-to-end.',
].join('\n');
const result = await runLocal(
{
source: 'cli',
spec: designDoc,
specFile: '/specs/relay-backed-review-runtime-spec.md',
stageMode: 'generate',
cliMetadata: { argv: ['ricky', '--mode', 'local', '--spec-file', '/specs/relay-backed-review-runtime-spec.md'] },
},
{ localExecutor },
);

expect(result.ok).toBe(true);
expect(result.logs.some((l) => l.includes('[local] spec intake route: generate'))).toBe(true);
expect(result.logs.some((l) => l.includes('[local] spec intake route: clarify'))).toBe(false);
expect(result.logs.some((l) => l.includes('[local] workflow generation: passed'))).toBe(true);
});

it('returns local artifact and runtime log shape for an injected runtime adapter', async () => {
const localExecutor = memoryLocalExecutorOptions({
stdout: ['local workflow completed'],
Expand Down
18 changes: 12 additions & 6 deletions src/local/entrypoint.ts
Original file line number Diff line number Diff line change
Expand Up @@ -1375,13 +1375,19 @@ function toRawSpecPayload(
};
}

// Explicit CLI description handoff — the user handed over prose, not a
// workflow path. Bypass the keyword-based intent classifier (which
// Explicit CLI description handoff — the user handed Ricky prose (or a
// spec file) rather than an executable workflow path, so the request is
// to author one. Bypass the keyword-based intent classifier, which
// over-fires on words like "run" / "execute" that appear naturally in
// feature specs) and route straight to generate. Only applies when the
// spec text contains no workflow file reference; otherwise the spec is
// a "run X" request and the natural-language classifier handles it.
if (request.source === 'cli' && !mentionsWorkflowFile(request.spec)) {
// feature specs.
//
// When --spec-file points at a non-executable path, force generate
// regardless of what the file mentions: a workflow path inside a design
// doc is context, not an invocation target (executable specPaths are
// already handled above). For inline --spec prose, keep the older guard
// so "run workflows/foo.ts" still flows to the natural-language
// classifier.
if (request.source === 'cli' && (request.specPath || !mentionsWorkflowFile(request.spec))) {
return {
...base,
kind: 'structured_json',
Expand Down