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
2 changes: 1 addition & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -280,7 +280,7 @@ Pass `--server` to start the Copilot server automatically as part of the run:
cat ./program.ts | node skills/rig/rig.ts --server
```

Pass `--typecheck` to typecheck the rig program before execution:
Pass `--typecheck` to typecheck the rig program and exit without executing it:

```bash
cat ./program.ts | node skills/rig/rig.ts --typecheck
Expand Down
2 changes: 1 addition & 1 deletion skills/rig/SKILL.md
Original file line number Diff line number Diff line change
Expand Up @@ -312,7 +312,7 @@ Pass `--server` to have the harness start the Copilot server automatically befor
echo "Review this diff" | node skills/rig/rig.ts src/program.ts --server
```

Pass `--typecheck` to typecheck the rig program before execution:
Pass `--typecheck` to typecheck the rig program and exit without executing it:

```bash
cat <<'RIG' | node skills/rig/rig.ts --typecheck
Expand Down
33 changes: 24 additions & 9 deletions skills/rig/rig.ts
Original file line number Diff line number Diff line change
Expand Up @@ -863,7 +863,20 @@ function withInjectedRigImport(programCode: string): string {
if (/\bfrom\s*["']rig["']/.test(programCode)) {
return programCode;
}
return `import { agent, p, s } from "rig";\n\n${programCode}`;
const names: string[] = [];
if (/\bagent\s*\(/u.test(programCode)) {
names.push("agent");
}
if (/\bp(?:\s*`|\s*\.)/u.test(programCode)) {
names.push("p");
}
if (/\bs\s*\./u.test(programCode)) {
names.push("s");
}
if (names.length === 0) {
return programCode;
}
return `import { ${names.join(", ")} } from "rig";\n\n${programCode}`;
}

function withInjectedDefaultRootAgent(programCode: string): string {
Expand Down Expand Up @@ -909,7 +922,7 @@ function renderStdout(value: unknown): string {
return JSON.stringify(value);
}

async function typecheckProgram(programPath: string, cwd: string): Promise<void> {
async function typecheckProgram(programPath: string, cwd: string, displayPath = programPath): Promise<void> {
const execFileAsync = promisify(execFile);
const skillTsconfigPath = resolve(dirname(fileURLToPath(import.meta.url)), "tsconfig.json");
const candidateTsconfigPaths = [resolve(cwd, "tsconfig.json"), skillTsconfigPath];
Expand Down Expand Up @@ -955,7 +968,7 @@ async function typecheckProgram(programPath: string, cwd: string): Promise<void>
.join("\n")
.trim();
const detail = diagnostics ? `\n${diagnostics}` : "";
throw new Error(`Typecheck failed for ${programPath}.${detail}`);
throw new Error(`Typecheck failed for ${displayPath}.${detail}`);
} finally {
await rm(tempDir, { recursive: true, force: true });
}
Expand All @@ -967,15 +980,16 @@ async function runRootAgentFromStdin(
io: LauncherIo,
scriptName: string,
): Promise<void> {
const prompt = await readStdin(io.stdin);
if (!prompt.trim()) {
throw new Error(`Usage: ${scriptName} <program-file>`);
}

const cwd = options.cwd ?? process.cwd();
const resolvedPath = isAbsolute(programPath) ? programPath : resolve(cwd, programPath);
if (options.typecheck) {
await typecheckProgram(resolvedPath, cwd);
return;
}

const prompt = await readStdin(io.stdin);
if (!prompt.trim()) {
throw new Error(`Usage: ${scriptName} <program-file>`);
}

configureAgent(copilotEngine(resolveCopilotOptions(cwd, options)));
Expand Down Expand Up @@ -1008,7 +1022,8 @@ async function runProgramCodeFromStdin(
await writeFile(tempProgramPath, transformedProgramCode, "utf8");
try {
if (options.typecheck) {
await typecheckProgram(tempProgramPath, cwd);
await typecheckProgram(tempProgramPath, cwd, "<stdin>");
return;
}
configureAgent(copilotEngine(resolveCopilotOptions(cwd, options)));
const mod = await import(pathToFileURL(tempProgramPath).href);
Expand Down
30 changes: 27 additions & 3 deletions src/launcher.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -240,7 +240,7 @@ it("accepts --server flag without rejecting", async () => {
expect(mocks.forStdio).toHaveBeenCalled();
});

it("accepts --typecheck flag without rejecting", async () => {
it("typechecks a program file without executing it", async () => {
const fixturePath = resolve(dirname(fileURLToPath(import.meta.url)), "./launcher.stdin.fixture.ts");
const stdin = Readable.from(["Review this patch"]);
const output: string[] = [];
Expand All @@ -253,7 +253,8 @@ it("accepts --typecheck flag without rejecting", async () => {

await runLauncherCli([fixturePath, "--typecheck"], {}, { stdin, stdout });

expect(output.join("")).toBe("done");
expect(output.join("")).toBe("");
expect(mocks.createSession).not.toHaveBeenCalled();
});

it("falls back to the skill tsconfig when cwd tsconfig is missing", async () => {
Expand All @@ -270,7 +271,8 @@ it("falls back to the skill tsconfig when cwd tsconfig is missing", async () =>

await runLauncherCli([fixturePath, "--typecheck"], { cwd: skillDirCwd }, { stdin, stdout });

expect(output.join("")).toBe("done");
expect(output.join("")).toBe("");
expect(mocks.createSession).not.toHaveBeenCalled();
});

it("rejects --typecheck when inline program fails typecheck", async () => {
Expand All @@ -292,6 +294,28 @@ export default root;
);
});

it("typechecks an inline program from stdin without executing it", async () => {
const stdin = Readable.from([`
const root = agent({
name: "launcher-stdin-program",
instructions: "Write a short note.",
});
export default root;
`]);
const output: string[] = [];
const stdout = new Writable({
write(chunk, _encoding, callback) {
output.push(chunk.toString());
callback();
},
});

await runLauncherCli(["--typecheck"], {}, { stdin, stdout });

expect(output.join("")).toBe("");
expect(mocks.createSession).not.toHaveBeenCalled();
});

it("rejects --typecheck when program file fails typecheck before execution", async () => {
const tempDir = await mkdtemp(resolve(tmpdir(), "rig-launcher-test-"));
const fixturePath = resolve(tempDir, "typecheck-fail.ts");
Expand Down
Loading