Skip to content

Commit 6ef19d1

Browse files
committed
feat(path-instructions): enable hot-reload for instructions
Instead of loading at startup, instruction files now load on each relevant tool execution. This enables hot-reloading, making changes to instruction files effective immediately. Injection is now also triggered by 'read' operations, providing context earlier.
1 parent 4f68f69 commit 6ef19d1

2 files changed

Lines changed: 7 additions & 23 deletions

File tree

README.md

Lines changed: 1 addition & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -59,17 +59,12 @@ The plugin will handle the rest, notifying you via the tool output whenever path
5959

6060
### Injection
6161

62-
When the AI performs an `edit` or `write` operation on a file, the plugin:
62+
When the AI performs an `edit`, `read` or `write` operation on a file, the plugin:
6363

6464
1. Finds all `*.instructions.md` files whose `applyTo` patterns match the target file path.
6565
2. Injects them **once per session** — subsequent operations on matching files won't repeat the injection.
6666
3. Appends the instructions to the tool output with a visible metadata header, so the AI can see which instructions were applied and follow them.
6767

68-
### Loading
69-
70-
- The plugin scans `.github/instructions/` and `.opencode/instructions/` recursively at startup.
71-
- Changes to instruction files are picked up on restart.
72-
7368
### Frontmatter notes
7469

7570
- `applyTo` accepts a comma-separated list of glob patterns. Values may be quoted with single or double quotes or left unquoted.

src/path-instructions.ts

Lines changed: 6 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -14,9 +14,9 @@
1414
* Your instructions here...
1515
*/
1616

17+
import type { Plugin } from '@opencode-ai/plugin'
1718
import * as fs from 'node:fs'
1819
import * as path from 'node:path'
19-
import type { Plugin } from '@opencode-ai/plugin'
2020

2121
interface InstructionFile {
2222
/** Glob patterns that determine when this instruction applies */
@@ -30,7 +30,7 @@ interface InstructionFile {
3030
}
3131

3232
/** File tools that operate on file paths */
33-
const FILE_TOOLS = new Set(['edit', 'write'])
33+
const FILE_TOOLS = new Set(['edit', 'read', 'write'])
3434

3535
/** Marker format used to track injected instructions in message history */
3636
const MARKER_PREFIX = 'path-instruction'
@@ -221,7 +221,6 @@ export const PathInstructionsPlugin: Plugin = async (ctx) => {
221221
}
222222

223223
const projectDir = directory
224-
const instructions = loadAllInstructionFiles(projectDir)
225224

226225
// -------------------------------------------------------------------------
227226
// Instance-level state (scoped to this plugin invocation)
@@ -243,18 +242,6 @@ export const PathInstructionsPlugin: Plugin = async (ctx) => {
243242
const toast = (message: string, variant: 'info' | 'success' | 'warning' | 'error' = 'info', duration = 5000) =>
244243
client.tui.showToast({ body: { title: '📋 Path Instructions', message, variant, duration } })
245244

246-
if (instructions.length === 0) {
247-
log('No path-specific instruction files found in .github/instructions or .opencode/instructions')
248-
toast('No instruction files found', 'warning', 3000)
249-
} else {
250-
const names = instructions.map(i => i.name).join(', ')
251-
log(`Loaded ${instructions.length} path instruction file(s):`)
252-
for (const instr of instructions) {
253-
log(` ${getRelativePath(projectDir, instr.filePath)} → applyTo: ${instr.applyTo.join(', ')}`)
254-
}
255-
toast(`Loaded ${instructions.length} instruction(s): ${names}`, 'success', 4000)
256-
}
257-
258245
return {
259246
'tool.execute.before': async (input, output) => {
260247
if (!FILE_TOOLS.has(input.tool)) return
@@ -269,8 +256,9 @@ export const PathInstructionsPlugin: Plugin = async (ctx) => {
269256
sessionInjected.set(sessionId, new Set())
270257
}
271258
const injected = sessionInjected.get(sessionId)!
259+
const currentInstructions = loadAllInstructionFiles(projectDir)
272260

273-
const matching = instructions.filter(
261+
const matching = currentInstructions.filter(
274262
instr =>
275263
!injected.has(instr.filePath) &&
276264
instr.applyTo.some(pattern => matchesGlob(pattern, relativePath)),
@@ -320,7 +308,8 @@ export const PathInstructionsPlugin: Plugin = async (ctx) => {
320308
const injected = sessionInjected.get(sessionId)
321309
if (!injected || injected.size === 0) return
322310

323-
const activeInstructions = instructions.filter(instr => injected.has(instr.filePath))
311+
const currentInstructions = loadAllInstructionFiles(projectDir)
312+
const activeInstructions = currentInstructions.filter(instr => injected.has(instr.filePath))
324313
if (activeInstructions.length === 0) return
325314

326315
const blocks = activeInstructions

0 commit comments

Comments
 (0)