@dlacaille/opencode-copilot-instructions 0.2.0 → 0.4.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@dlacaille/opencode-copilot-instructions",
3
- "version": "0.2.0",
3
+ "version": "0.4.0",
4
4
  "description": "OpenCode plugin that loads GitHub Copilot custom instruction files",
5
5
  "type": "module",
6
6
  "license": "MIT",
package/src/index.ts CHANGED
@@ -25,7 +25,25 @@ function getRepoRelativePath(
25
25
  path.isAbsolute(relative)
26
26
  )
27
27
  return null;
28
- return relative;
28
+ return relative.split(path.sep).join("/");
29
+ }
30
+
31
+ function appendInstructions<Content>(
32
+ result: { content?: string | ReadonlyArray<Content>; output?: unknown },
33
+ text: string,
34
+ ): string | ReadonlyArray<Content | { type: "text"; text: string }> {
35
+ const { content, output } = result;
36
+ if (Array.isArray(content)) return [...content, { type: "text", text }];
37
+ // Without content, the model would otherwise see only the output, so keep it in the text.
38
+ const existing =
39
+ typeof content === "string"
40
+ ? content
41
+ : output === undefined
42
+ ? ""
43
+ : typeof output === "string"
44
+ ? output
45
+ : JSON.stringify(output, null, 2);
46
+ return existing ? `${existing}\n\n${text}` : text;
29
47
  }
30
48
 
31
49
  export default Plugin.define({
@@ -82,9 +100,6 @@ export default Plugin.define({
82
100
  });
83
101
  if (matching.length === 0) return;
84
102
 
85
- for (const instruction of matching)
86
- state.markFileInjected(event.sessionID, instruction.file);
87
-
88
103
  const text = matching
89
104
  .map((instruction) => {
90
105
  const filename = path.basename(instruction.file);
@@ -93,19 +108,23 @@ export default Plugin.define({
93
108
  })
94
109
  .join("\n\n");
95
110
 
96
- state.setPending(event.id, text);
111
+ state.setPending(event.id, {
112
+ sessionId: event.sessionID,
113
+ files: matching.map((instruction) => instruction.file),
114
+ text,
115
+ });
97
116
  });
98
117
 
99
118
  await ctx.tool.hook("execute.after", (event) => {
100
- const text = state.consumePending(event.id);
101
- if (!text) return;
119
+ const pending = state.consumePending(event.id);
120
+ if (!pending) return;
102
121
  if (event.status !== "completed") return;
103
122
 
104
- const existing =
105
- typeof event.result.content === "string" ? event.result.content : "";
123
+ for (const file of pending.files)
124
+ state.markFileInjected(pending.sessionId, file);
106
125
  event.result = {
107
126
  ...event.result,
108
- content: existing ? `${existing}\n\n${text}` : text,
127
+ content: appendInstructions(event.result, pending.text),
109
128
  };
110
129
  });
111
130
 
package/src/matcher.ts CHANGED
@@ -4,7 +4,7 @@ export type Matcher = (path: string) => boolean;
4
4
 
5
5
  export function createMatcher(patterns: string[]): Matcher {
6
6
  if (patterns.length === 0) return () => false;
7
- const isMatch = picomatch(patterns);
7
+ const isMatch = picomatch(patterns, { dot: true });
8
8
  return (path) => isMatch(path);
9
9
  }
10
10
 
@@ -4,9 +4,15 @@
4
4
  * Tracks which path-specific instructions have been injected per session, and
5
5
  * pending instructions keyed by tool call ID.
6
6
  */
7
+ export interface PendingInstructions {
8
+ sessionId: string;
9
+ files: string[];
10
+ text: string;
11
+ }
12
+
7
13
  export class SessionState {
8
14
  private injectedPerSession = new Map<string, Set<string>>();
9
- private pendingInstructions = new Map<string, string>();
15
+ private pendingInstructions = new Map<string, PendingInstructions>();
10
16
 
11
17
  isFileInjected(sessionId: string, file: string): boolean {
12
18
  return this.injectedPerSession.get(sessionId)?.has(file) ?? false;
@@ -23,15 +29,19 @@ export class SessionState {
23
29
 
24
30
  clearSession(sessionId: string): void {
25
31
  this.injectedPerSession.delete(sessionId);
32
+ for (const [callId, pending] of this.pendingInstructions) {
33
+ if (pending.sessionId === sessionId)
34
+ this.pendingInstructions.delete(callId);
35
+ }
26
36
  }
27
37
 
28
- setPending(callId: string, text: string): void {
29
- this.pendingInstructions.set(callId, text);
38
+ setPending(callId: string, pending: PendingInstructions): void {
39
+ this.pendingInstructions.set(callId, pending);
30
40
  }
31
41
 
32
- consumePending(callId: string): string | undefined {
33
- const text = this.pendingInstructions.get(callId);
42
+ consumePending(callId: string): PendingInstructions | undefined {
43
+ const pending = this.pendingInstructions.get(callId);
34
44
  this.pendingInstructions.delete(callId);
35
- return text;
45
+ return pending;
36
46
  }
37
47
  }