Skip to content
Open
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
53 changes: 30 additions & 23 deletions packages/opencode/src/session/instruction.ts
Original file line number Diff line number Diff line change
Expand Up @@ -35,7 +35,7 @@ export interface Interface {
readonly clear: (messageID: MessageID) => Effect.Effect<void>
readonly systemPaths: () => Effect.Effect<Set<string>, FSUtil.Error>
readonly system: () => Effect.Effect<string[], FSUtil.Error>
readonly find: (dir: string) => Effect.Effect<string | undefined, FSUtil.Error>
readonly find: (dir: string) => Effect.Effect<string[], FSUtil.Error>
readonly resolve: (
messages: SessionV1.WithParts[],
filepath: string,
Expand Down Expand Up @@ -65,6 +65,11 @@ export const layer: Layer.Layer<
"AGENTS.md",
...(!flags.disableClaudeCodePrompt ? ["CLAUDE.md"] : []),
"CONTEXT.md", // deprecated
"CONTRIBUTING.md",
".github/pull_request_template.md",
".github/PULL_REQUEST_TEMPLATE.md",
"pull_request_template.md",
"PULL_REQUEST_TEMPLATE.md",
]

const state = yield* InstanceState.make(
Expand Down Expand Up @@ -126,8 +131,7 @@ export const layer: Layer.Layer<
.findUp(file, ctx.directory, ctx.worktree)
.pipe(Effect.catch(() => Effect.succeed([])))
if (matches.length > 0) {
matches.forEach((item) => paths.add(path.resolve(item)))
break
paths.add(path.resolve(matches[0]))
}
}
}
Expand Down Expand Up @@ -169,11 +173,14 @@ export const layer: Layer.Layer<
})

const find = Effect.fn("Instruction.find")(function* (dir: string) {
const results: string[] = []
for (const file of instructionFiles) {
const filepath = path.resolve(path.join(dir, file))
if (yield* fs.existsSafe(filepath)) return filepath
if (yield* fs.existsSafe(filepath)) {
results.push(filepath)
}
}
return undefined
return results
})

const resolve = Effect.fn("Instruction.resolve")(function* (
Expand All @@ -192,26 +199,26 @@ export const layer: Layer.Layer<

// Walk upward from the file being read and attach nearby instruction files once per message.
while (current.startsWith(root) && current !== root) {
const found = yield* find(current)
if (!found || found === target || sys.has(found) || already.has(found)) {
current = path.dirname(current)
continue
}
const foundList = yield* find(current)
for (const found of foundList) {
if (found === target || sys.has(found) || already.has(found)) {
continue
}

let set = s.claims.get(messageID)
if (!set) {
set = new Set()
s.claims.set(messageID, set)
}
if (set.has(found)) {
current = path.dirname(current)
continue
}
let set = s.claims.get(messageID)
if (!set) {
set = new Set()
s.claims.set(messageID, set)
}
if (set.has(found)) {
continue
}

set.add(found)
const content = yield* read(found)
if (content) {
results.push({ filepath: found, content: `Instructions from: ${found}\n${content}` })
set.add(found)
const content = yield* read(found)
if (content) {
results.push({ filepath: found, content: `Instructions from: ${found}\n${content}` })
}
}

current = path.dirname(current)
Expand Down
5 changes: 5 additions & 0 deletions packages/opencode/src/session/prompt/anthropic.txt
Original file line number Diff line number Diff line change
Expand Up @@ -95,6 +95,11 @@ assistant: [Uses the Task tool]

IMPORTANT: Always use the TodoWrite tool to plan and track tasks throughout the conversation.

# Pull Requests and Conventions

- Before creating a pull request, you MUST read and follow the repository's pull request templates (e.g., `.github/pull_request_template.md` or `pull_request_template.md`) and contributing guidelines (e.g., `CONTRIBUTING.md`). Ensure the PR description follows the required format and answers all template questions.
- If a repository bot flags a PR or enforces rules with a deadline (e.g., a 2-hour edit/response window), prioritize honoring that deadline and edit the PR description or code immediately to resolve the issue.

# Code References

When referencing specific functions or pieces of code include the pattern `file_path:line_number` to allow the user to easily navigate to the source code location.
Expand Down
2 changes: 2 additions & 0 deletions packages/opencode/src/session/prompt/default.txt
Original file line number Diff line number Diff line change
Expand Up @@ -63,6 +63,8 @@ When making changes to files, first understand the file's code conventions. Mimi
- When you create a new component, first look at existing components to see how they're written; then consider framework choice, naming conventions, typing, and other conventions.
- When you edit a piece of code, first look at the code's surrounding context (especially its imports) to understand the code's choice of frameworks and libraries. Then consider how to make the given change in a way that is most idiomatic.
- Always follow security best practices. Never introduce code that exposes or logs secrets and keys. Never commit secrets or keys to the repository.
- Before creating a pull request, you MUST read and follow the repository's pull request templates (e.g., `.github/pull_request_template.md` or `pull_request_template.md`) and contributing guidelines (e.g., `CONTRIBUTING.md`). Ensure the PR description follows the required format and answers all template questions.
- If a repository bot flags a PR or enforces rules with a deadline (e.g., a 2-hour edit/response window), prioritize honoring that deadline and edit the PR description or code immediately to resolve the issue.

# Code style
- IMPORTANT: DO NOT ADD ***ANY*** COMMENTS unless asked
Expand Down
58 changes: 58 additions & 0 deletions packages/opencode/test/session/instruction.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -199,6 +199,41 @@ describe("Instruction.resolve", () => {
)

test.todo("fetches remote instructions from config URLs via HttpClient", () => {})

it.live("find returns all existing instruction files in the directory", () =>
withFiles({ "AGENTS.md": "# Agents", "CONTRIBUTING.md": "# Contributing" }, (dir) =>
Effect.gen(function* () {
const svc = yield* Instruction.Service
const files = yield* svc.find(dir)
expect(files).toHaveLength(2)
expect(files).toContain(path.join(dir, "AGENTS.md"))
expect(files).toContain(path.join(dir, "CONTRIBUTING.md"))
}),
),
)

it.live("resolve returns multiple instruction files from subdirectory", () =>
withFiles(
{
"subdir/AGENTS.md": "# Agents",
"subdir/CONTRIBUTING.md": "# Contributing",
"subdir/nested/file.ts": "const x = 1",
},
(dir) =>
Effect.gen(function* () {
const svc = yield* Instruction.Service
const results = yield* svc.resolve(
[],
path.join(dir, "subdir", "nested", "file.ts"),
MessageID.make("msg_message-test-multiresolve"),
)
expect(results.length).toBe(2)
const paths = results.map((r) => r.filepath)
expect(paths).toContain(path.join(dir, "subdir", "AGENTS.md"))
expect(paths).toContain(path.join(dir, "subdir", "CONTRIBUTING.md"))
}),
),
)
})

describe("Instruction.system", () => {
Expand All @@ -221,6 +256,29 @@ describe("Instruction.system", () => {
}),
)

it.live("loads multiple project instruction files (e.g. AGENTS.md and CONTRIBUTING.md) when both exist", () =>
Effect.gen(function* () {
const globalTmp = yield* tmpdirScoped()
const projectTmp = yield* tmpWithFiles({
"AGENTS.md": "# Project Agents",
"CONTRIBUTING.md": "# Project Contributing",
})

yield* Effect.gen(function* () {
const svc = yield* Instruction.Service
const paths = yield* svc.systemPaths()
expect(paths.has(path.join(projectTmp, "AGENTS.md"))).toBe(true)
expect(paths.has(path.join(projectTmp, "CONTRIBUTING.md"))).toBe(true)

const rules = yield* svc.system()
expect(rules).toHaveLength(2)
const ruleContents = rules.join("\n")
expect(ruleContents).toContain("# Project Agents")
expect(ruleContents).toContain("# Project Contributing")
}).pipe(provideInstance(projectTmp), provideInstruction({ home: globalTmp, config: globalTmp }))
}),
)

it.live("skips project and global CLAUDE.md when Claude Code prompt is disabled", () =>
Effect.gen(function* () {
const globalTmp = yield* tmpWithFiles({ ".claude/CLAUDE.md": "# Global Claude" })
Expand Down
Loading