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
4 changes: 4 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
@@ -1,5 +1,9 @@
node_modules/
dist/
# Fixture decoys carry real ignored-directory names so the related-files
# scan's pruning is tested against the genuine article — re-include them
!fixtures/workspace/node_modules/
!fixtures/workspace/dist/
.env
*.log
.DS_Store
Expand Down
6 changes: 3 additions & 3 deletions AGENTS.md
Original file line number Diff line number Diff line change
Expand Up @@ -19,10 +19,10 @@ src/
main.ts # entrypoint — collects/validates inputs; pipeline + outputs land next PR
config.ts # action inputs → validated ActionConfig
logger.ts # structured JSON logger — levels, child contexts, lazy props
github/ # GitHub I/O: event payload → PrContext (octokit wrappers planned — next PR)
openrouter/ # (planned — next PR) OpenRouter I/O: @openrouter/sdk wrapper, structured-output ladder
github/ # GitHub I/O: event payload → PrContext, octokit wrappers (diff fetch, review posting)
openrouter/ # OpenRouter I/O: @openrouter/sdk wrapper, structured-output retry ladder, cost summary
diff/ # pure transforms over parse-diff output
context/ # (planned — next PR) workspace reads: conventions file, changed files, related files
context/ # workspace I/O: conventions file, changed files, related-files reverse-import scan
review/ # pure review logic: finding schema, phases, prompt, selection, comment mapping
orchestrate.ts # (planned — next PR) the pipeline — fully testable with stub clients
```
Expand Down
15 changes: 15 additions & 0 deletions fixtures/openrouter.chat-result.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
{
"id": "gen-fixture-0001",
"object": "chat.completion",
"model": "openai/gpt-5-mini",
"choices": [
{
"finishReason": "stop",
"message": {
"role": "assistant",
"content": "{\"analysis\":\"src/greeter.ts: verified the empty-key guard and traced register() callers.\",\"findings\":[]}"
}
}
],
"usage": { "promptTokens": 12000, "completionTokens": 800, "cost": 0.0421 }
}
15 changes: 15 additions & 0 deletions fixtures/openrouter.invalid-json.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
{
"id": "gen-fixture-0002",
"object": "chat.completion",
"model": "openai/gpt-5-mini",
"choices": [
{
"finishReason": "length",
"message": {
"role": "assistant",
"content": "The model rambled instead of emitting JSON {"
}
}
],
"usage": { "promptTokens": 11800, "completionTokens": 42, "cost": null }
}
15 changes: 15 additions & 0 deletions fixtures/openrouter.schema-mismatch.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
{
"id": "gen-fixture-0003",
"object": "chat.completion",
"model": "openai/gpt-5-mini",
"choices": [
{
"finishReason": "stop",
"message": {
"role": "assistant",
"content": "{\"verdict\":\"lgtm\"}"
}
}
],
"usage": { "promptTokens": 11900, "completionTokens": 12, "cost": 0.001 }
}
12 changes: 12 additions & 0 deletions fixtures/pull.get.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
{
"number": 7,
"state": "open",
"title": "feat: trim names before greeting",
"body": "Trims whitespace from names and validates registry keys.",
"head": {
"sha": "abc123def456abc123def456abc123def456abc1",
"ref": "feat/trim-names"
},
"base": { "ref": "main" },
"html_url": "https://github.com/aliasunder/fixture/pull/7"
}
3 changes: 3 additions & 0 deletions fixtures/workspace/AGENTS.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
# Fixture conventions

Use exact assertions. Never mock what you can stub.
3 changes: 3 additions & 0 deletions fixtures/workspace/dist/stale.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
import { greet } from "../src/greeter.js"

export const staleGreeting = greet("stale")
3 changes: 3 additions & 0 deletions fixtures/workspace/node_modules/decoy/index.js

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

3 changes: 3 additions & 0 deletions fixtures/workspace/src/__tests__/greeter.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
import { greet } from "../greeter.js"

export const greeting = greet("fixture")
3 changes: 3 additions & 0 deletions fixtures/workspace/src/barrel-consumer.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
import { LIB_NAME } from "./lib"
Comment thread
coderabbitai[bot] marked this conversation as resolved.

export const describeLib = (): string => `lib: ${LIB_NAME}`
6 changes: 6 additions & 0 deletions fixtures/workspace/src/caller.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
import { greet } from "./greeter.js"
import { registry } from "./registry.js"

/** Fixture importer that touches two changed files. */
export const greetAll = (): string[] =>
[...registry.keys()].map((registeredName) => greet(registeredName))
3 changes: 3 additions & 0 deletions fixtures/workspace/src/consumer.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
import { greet } from "./greeter.js"

export const shout = (name: string): string => greet(name).toUpperCase()
Binary file added fixtures/workspace/src/data.bin
Binary file not shown.
1 change: 1 addition & 0 deletions fixtures/workspace/src/greeter.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
export const greet = (name: string): string => `Hello, ${name.trim()}!`
1 change: 1 addition & 0 deletions fixtures/workspace/src/hub.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
export const HUB = "hub"
3 changes: 3 additions & 0 deletions fixtures/workspace/src/importers/importer-1.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
import { HUB } from "../hub.js"

export const importerOne = HUB
3 changes: 3 additions & 0 deletions fixtures/workspace/src/importers/importer-2.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
import { HUB } from "../hub.js"

export const importerTwo = HUB
3 changes: 3 additions & 0 deletions fixtures/workspace/src/importers/importer-3.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
import { HUB } from "../hub.js"

export const importerThree = HUB
3 changes: 3 additions & 0 deletions fixtures/workspace/src/importers/importer-4.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
import { HUB } from "../hub.js"

export const importerFour = HUB
3 changes: 3 additions & 0 deletions fixtures/workspace/src/importers/importer-5.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
import { HUB } from "../hub.js"

export const importerFive = HUB
3 changes: 3 additions & 0 deletions fixtures/workspace/src/importers/importer-6.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
import { HUB } from "../hub.js"

export const importerSix = HUB
3 changes: 3 additions & 0 deletions fixtures/workspace/src/importers/importer-7.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
import { HUB } from "../hub.js"

export const importerSeven = HUB
3 changes: 3 additions & 0 deletions fixtures/workspace/src/importers/importer-8.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
import { HUB } from "../hub.js"

export const importerEight = HUB
3 changes: 3 additions & 0 deletions fixtures/workspace/src/importers/importer-9.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
import { HUB } from "../hub.js"

export const importerNine = HUB
1 change: 1 addition & 0 deletions fixtures/workspace/src/lib/index.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
export const LIB_NAME = "fixture-lib"
1 change: 1 addition & 0 deletions fixtures/workspace/src/registry.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
export const registry = new Map<string, string>()
120 changes: 120 additions & 0 deletions src/context/__tests__/import-resolution.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,120 @@
import { describe, expect, it } from "vitest"
import {
extractImportSpecifiers,
resolveImportSpecifier,
} from "../import-resolution.js"

describe("extractImportSpecifiers", () => {
it("extracts static, dynamic, and require specifiers from one source", () => {
const source = [
`import { a } from "./a.js"`,
`import type { B } from '../b.js'`,
`const lazy = await import("./lazy.js")`,
`const legacy = require("./legacy.cjs")`,
`import zod from "zod"`,
].join("\n")

const specifiers = extractImportSpecifiers(source)

expect(specifiers).toEqual([
"./a.js",
"../b.js",
"./lazy.js",
"./legacy.cjs",
"zod",
])
})

it("extracts a side-effect import specifier", () => {
const specifiers = extractImportSpecifiers(`import "./register.js"`)

expect(specifiers).toEqual(["./register.js"])
})

it("returns an empty list for a source with no imports", () => {
const specifiers = extractImportSpecifiers(`export const answer = 42`)

expect(specifiers).toEqual([])
})

it("extracts a quoted specifier inside a comment (grep-style by design)", () => {
const specifiers = extractImportSpecifiers(`// import { x } from "./x.js"`)

expect(specifiers).toEqual(["./x.js"])
})
})

describe("resolveImportSpecifier", () => {
const scenarios = [
{
name: "remaps a sibling .js specifier to .ts and .tsx",
importerPath: "src/caller.ts",
specifier: "./greeter.js",
expected: ["src/greeter.js", "src/greeter.ts", "src/greeter.tsx"],
},
{
name: "resolves a parent-directory .js specifier",
importerPath: "src/a/b.ts",
specifier: "../util.js",
expected: ["src/util.js", "src/util.ts", "src/util.tsx"],
},
{
name: "remaps .mjs to .mts",
importerPath: "src/caller.ts",
specifier: "./worker.mjs",
expected: ["src/worker.mjs", "src/worker.mts"],
},
{
name: "remaps .cjs to .cts",
importerPath: "src/caller.ts",
specifier: "./legacy.cjs",
expected: ["src/legacy.cjs", "src/legacy.cts"],
},
{
name: "keeps an explicit .ts specifier as the only candidate",
importerPath: "src/caller.ts",
specifier: "./greeter.ts",
expected: ["src/greeter.ts"],
},
{
name: "expands an extensionless specifier with suffixes and index files",
importerPath: "src/caller.ts",
specifier: "./lib",
expected: [
"src/lib",
"src/lib.ts",
"src/lib.tsx",
"src/lib.js",
"src/lib.jsx",
"src/lib/index.ts",
"src/lib/index.tsx",
"src/lib/index.js",
"src/lib/index.jsx",
],
},
{
name: "returns no candidates for a package specifier",
importerPath: "src/caller.ts",
specifier: "zod",
expected: [],
},
{
name: "returns no candidates for a node built-in",
importerPath: "src/caller.ts",
specifier: "node:path",
expected: [],
},
{
name: "returns no candidates when the join escapes the workspace root",
importerPath: "src/caller.ts",
specifier: "../../outside.js",
expected: [],
},
]

it.each(scenarios)("$name", ({ importerPath, specifier, expected }) => {
const candidates = resolveImportSpecifier({ importerPath, specifier })

expect(candidates).toEqual(expected)
})
})
Loading