diff --git a/src/local/entrypoint.test.ts b/src/local/entrypoint.test.ts index a44dd13f..3964882f 100644 --- a/src/local/entrypoint.test.ts +++ b/src/local/entrypoint.test.ts @@ -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'], diff --git a/src/local/entrypoint.ts b/src/local/entrypoint.ts index c12ed108..4567aed8 100644 --- a/src/local/entrypoint.ts +++ b/src/local/entrypoint.ts @@ -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',