@b-apm/integration-copilot 0.1.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/LICENSE +21 -0
- package/README.md +66 -0
- package/dist/index.d.mts +17 -0
- package/dist/index.mjs +368 -0
- package/package.json +49 -0
package/LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 Sdju
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
package/README.md
ADDED
|
@@ -0,0 +1,66 @@
|
|
|
1
|
+
# @b-apm/integration-copilot
|
|
2
|
+
|
|
3
|
+
> **UNSTABLE:** Early public release. APIs and on-disk layouts may change without a major bump. Not production-ready.
|
|
4
|
+
|
|
5
|
+
GitHub Copilot **runtime** host for bapm (project-scope file primitives + home MCP translate).
|
|
6
|
+
|
|
7
|
+
## Opt-in load
|
|
8
|
+
|
|
9
|
+
```bash
|
|
10
|
+
npm i -D @b-apm/integration-copilot
|
|
11
|
+
```
|
|
12
|
+
|
|
13
|
+
```yaml
|
|
14
|
+
targets:
|
|
15
|
+
copilot: "@b-apm/integration-copilot"
|
|
16
|
+
active:
|
|
17
|
+
- copilot
|
|
18
|
+
```
|
|
19
|
+
|
|
20
|
+
```bash
|
|
21
|
+
bapm install --target copilot
|
|
22
|
+
bapm compile --target copilot
|
|
23
|
+
```
|
|
24
|
+
|
|
25
|
+
## Detection vs forced target
|
|
26
|
+
|
|
27
|
+
| Mode | When active | Creates roots? |
|
|
28
|
+
| ------------------------------- | ----------------------------------------------------------------------------------------------------------- | --------------------------------------------------- |
|
|
29
|
+
| **Auto-detect** | Any whitelist signal under `.github/` (`copilot-instructions.md` or instructions/agents/prompts/hooks dirs) | Only when materialize / configure runs after detect |
|
|
30
|
+
| **Forced** (`--target copilot`) | Explicit forced target id, even if detect is false | MAY `mkdir` `.github/` / `.agents/` as needed |
|
|
31
|
+
|
|
32
|
+
Auto-detect without a whitelist signal MUST NOT create roots solely for detection.
|
|
33
|
+
|
|
34
|
+
## Deploy roots
|
|
35
|
+
|
|
36
|
+
- `.github` — instructions, prompts, agents, hooks, compile output, ownership sidecar
|
|
37
|
+
- `.agents` — portable skills under `.agents/skills/<name>/`
|
|
38
|
+
|
|
39
|
+
Home MCP (`~/.copilot/mcp-config.json`, override with `COPILOT_HOME`) is outside project deploy roots.
|
|
40
|
+
|
|
41
|
+
## Materialize routing
|
|
42
|
+
|
|
43
|
+
| Primitive type | Destination |
|
|
44
|
+
| ---------------- | --------------------------------------------------------------------------------------------------- |
|
|
45
|
+
| instruction | `.github/instructions/<name>.instructions.md` (preserve `applyTo` / Copilot FM) |
|
|
46
|
+
| command / prompt | `.github/prompts/<name>.prompt.md` (never `.github/commands/`) |
|
|
47
|
+
| agent | `.github/agents/<name>.agent.md` |
|
|
48
|
+
| skill | `.agents/skills/<name>/` (never `.github/skills/`) |
|
|
49
|
+
| hook | per-file `.github/hooks/<pkg>-<stem>.json` + scripts under `.github/hooks/scripts/<pkg>/` + sidecar |
|
|
50
|
+
|
|
51
|
+
MCP is **not** written by materialize — only via `configureMcp`.
|
|
52
|
+
|
|
53
|
+
## MCP (translate)
|
|
54
|
+
|
|
55
|
+
- Config: `~/.copilot/mcp-config.json` (`mcpServers`)
|
|
56
|
+
- `mcpEnvMode: "translate"` — APM `${VAR}` / `${env:VAR}` / `<VAR>` stay as runtime `${VAR}` placeholders
|
|
57
|
+
- Install skips bake for this host; unrelated servers and top-level keys are preserved
|
|
58
|
+
- Never writes project `.vscode/mcp.json`
|
|
59
|
+
|
|
60
|
+
## Compile
|
|
61
|
+
|
|
62
|
+
Thin emitter → `.github/copilot-instructions.md`. Instruction primitives already deployed under `.github/instructions/` are omitted from the compile body. Honors `write` vs validate/preview.
|
|
63
|
+
|
|
64
|
+
## Exports
|
|
65
|
+
|
|
66
|
+
- `createCopilotIntegration` / `createIntegration` — runtime factory (`id: "copilot"`)
|
package/dist/index.d.mts
ADDED
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
import { BapmIntegration } from "@b-apm/integration-api";
|
|
2
|
+
//#region src/createCopilotIntegration.d.ts
|
|
3
|
+
/**
|
|
4
|
+
* Create the GitHub Copilot runtime target.
|
|
5
|
+
* Detect: APM SIGNAL_WHITELIST under `.github/` (any one signal; no mkdir).
|
|
6
|
+
* Materialize: instructions/prompts/agents/hooks under `.github/`, skills under `.agents/skills/`.
|
|
7
|
+
* MCP: home `~/.copilot/mcp-config.json` (`COPILOT_HOME`) with translate placeholders.
|
|
8
|
+
* Compile: thin `.github/copilot-instructions.md`, omitting instruction primitives.
|
|
9
|
+
*/
|
|
10
|
+
declare function createCopilotIntegration(options?: {
|
|
11
|
+
id?: string;
|
|
12
|
+
deployRoots?: string[];
|
|
13
|
+
}): BapmIntegration;
|
|
14
|
+
/** Normalize hook event names to camelCase (`session_start` / `SessionStart` → `sessionStart`). */
|
|
15
|
+
declare function toCamelCaseEvent(event: string): string;
|
|
16
|
+
//#endregion
|
|
17
|
+
export { createCopilotIntegration, createCopilotIntegration as createIntegration, toCamelCaseEvent };
|
package/dist/index.mjs
ADDED
|
@@ -0,0 +1,368 @@
|
|
|
1
|
+
import { existsSync, mkdirSync, readFileSync, statSync, writeFileSync } from "node:fs";
|
|
2
|
+
import { homedir } from "node:os";
|
|
3
|
+
import { basename, join, resolve } from "node:path";
|
|
4
|
+
import { assertUnderDeployRoots, compileMarkdownReport, copyHookScript, materializeSkill, primitivesList, primitivesMaterialize, readHookOwnershipSidecar, readPrimitiveContent, removeOwnedHookArtifacts, renderPrimitivesMarkdown, sanitizeName, writeDeployedFile, writeHookOwnershipSidecar } from "@b-apm/integration-api";
|
|
5
|
+
//#region src/createCopilotIntegration.ts
|
|
6
|
+
const DEFAULT_DEPLOY_ROOTS = [".github", ".agents"];
|
|
7
|
+
const HOOKS_OWNERSHIP_REL = ".github/bapm-hooks.json";
|
|
8
|
+
const COMPILE_DEFAULT = ".github/copilot-instructions.md";
|
|
9
|
+
const DETECT_DIRS = [
|
|
10
|
+
".github/instructions",
|
|
11
|
+
".github/agents",
|
|
12
|
+
".github/prompts",
|
|
13
|
+
".github/hooks"
|
|
14
|
+
];
|
|
15
|
+
/**
|
|
16
|
+
* Create the GitHub Copilot runtime target.
|
|
17
|
+
* Detect: APM SIGNAL_WHITELIST under `.github/` (any one signal; no mkdir).
|
|
18
|
+
* Materialize: instructions/prompts/agents/hooks under `.github/`, skills under `.agents/skills/`.
|
|
19
|
+
* MCP: home `~/.copilot/mcp-config.json` (`COPILOT_HOME`) with translate placeholders.
|
|
20
|
+
* Compile: thin `.github/copilot-instructions.md`, omitting instruction primitives.
|
|
21
|
+
*/
|
|
22
|
+
function createCopilotIntegration(options) {
|
|
23
|
+
const id = options?.id ?? "copilot";
|
|
24
|
+
const deployRoots = [...options?.deployRoots ?? DEFAULT_DEPLOY_ROOTS];
|
|
25
|
+
return {
|
|
26
|
+
id,
|
|
27
|
+
deployRoots,
|
|
28
|
+
mcpEnvMode: "translate",
|
|
29
|
+
detect: ({ cwd }) => detectCopilot(cwd),
|
|
30
|
+
getDeployRoots: () => [...deployRoots],
|
|
31
|
+
async compile(primitives, context) {
|
|
32
|
+
const content = renderPrimitivesMarkdown({
|
|
33
|
+
primitives: primitivesList(primitives),
|
|
34
|
+
title: "# GitHub Copilot instructions",
|
|
35
|
+
filter: (p) => !/instruction/i.test(String(p.type ?? ""))
|
|
36
|
+
});
|
|
37
|
+
return compileMarkdownReport({
|
|
38
|
+
cwd: context.cwd,
|
|
39
|
+
outputFile: context.outputFile ?? COMPILE_DEFAULT,
|
|
40
|
+
write: context.write,
|
|
41
|
+
content,
|
|
42
|
+
outsideCwdMessage: "Copilot compile output must be a cwd-relative file path"
|
|
43
|
+
});
|
|
44
|
+
},
|
|
45
|
+
async materialize(primitives, ctx) {
|
|
46
|
+
const cwd = resolve(ctx?.cwd ?? process.cwd());
|
|
47
|
+
const roots = ctx?.deployRoots?.length ? [...ctx.deployRoots] : deployRoots;
|
|
48
|
+
const deployedFiles = [];
|
|
49
|
+
const diagnostics = [];
|
|
50
|
+
const hookPrimitives = [];
|
|
51
|
+
await primitivesMaterialize(primitives, {
|
|
52
|
+
skill(p, { name }) {
|
|
53
|
+
deployedFiles.push(...materializeSkill({
|
|
54
|
+
primitive: p,
|
|
55
|
+
cwd,
|
|
56
|
+
deployRoots: roots,
|
|
57
|
+
destDir: join(".agents", "skills", name)
|
|
58
|
+
}));
|
|
59
|
+
},
|
|
60
|
+
instruction(p, { name }) {
|
|
61
|
+
deployedFiles.push(writeDeployedFile({
|
|
62
|
+
cwd,
|
|
63
|
+
deployRoots: roots,
|
|
64
|
+
destRel: join(".github", "instructions", `${name}.instructions.md`),
|
|
65
|
+
content: readPrimitiveContent(p),
|
|
66
|
+
primitive: {
|
|
67
|
+
name: String(p.name),
|
|
68
|
+
packageName: p.packageName
|
|
69
|
+
}
|
|
70
|
+
}));
|
|
71
|
+
},
|
|
72
|
+
agent(p, { name }) {
|
|
73
|
+
deployedFiles.push(writeDeployedFile({
|
|
74
|
+
cwd,
|
|
75
|
+
deployRoots: roots,
|
|
76
|
+
destRel: join(".github", "agents", `${name}.agent.md`),
|
|
77
|
+
content: readPrimitiveContent(p),
|
|
78
|
+
primitive: {
|
|
79
|
+
name: String(p.name),
|
|
80
|
+
packageName: p.packageName
|
|
81
|
+
}
|
|
82
|
+
}));
|
|
83
|
+
},
|
|
84
|
+
command(p, { name }) {
|
|
85
|
+
materializePromptLike(p, name, cwd, roots, deployedFiles);
|
|
86
|
+
},
|
|
87
|
+
hook(p) {
|
|
88
|
+
hookPrimitives.push(p);
|
|
89
|
+
},
|
|
90
|
+
unknown(p, { name, type }) {
|
|
91
|
+
if (/prompt/.test(type)) materializePromptLike(p, name, cwd, roots, deployedFiles);
|
|
92
|
+
}
|
|
93
|
+
});
|
|
94
|
+
if (hookPrimitives.length > 0) {
|
|
95
|
+
const hookDeployed = materializeCopilotHooks({
|
|
96
|
+
cwd,
|
|
97
|
+
roots,
|
|
98
|
+
hooks: hookPrimitives
|
|
99
|
+
});
|
|
100
|
+
deployedFiles.push(...hookDeployed.deployedFiles);
|
|
101
|
+
diagnostics.push(...hookDeployed.diagnostics);
|
|
102
|
+
}
|
|
103
|
+
return {
|
|
104
|
+
targetId: id,
|
|
105
|
+
deployedFiles,
|
|
106
|
+
...diagnostics.length > 0 ? { diagnostics } : {}
|
|
107
|
+
};
|
|
108
|
+
},
|
|
109
|
+
async configureMcp(servers, ctx) {
|
|
110
|
+
return writeCopilotMcpConfig(servers, {
|
|
111
|
+
cwd: resolve(ctx?.cwd ?? process.cwd()),
|
|
112
|
+
deployRoots: ctx?.deployRoots?.length ? [...ctx.deployRoots] : deployRoots,
|
|
113
|
+
targetId: ctx?.targetId ?? id
|
|
114
|
+
});
|
|
115
|
+
}
|
|
116
|
+
};
|
|
117
|
+
}
|
|
118
|
+
function detectCopilot(cwd) {
|
|
119
|
+
const instructions = join(cwd, ".github", "copilot-instructions.md");
|
|
120
|
+
if (existsSync(instructions) && statSync(instructions).isFile()) return true;
|
|
121
|
+
for (const rel of DETECT_DIRS) {
|
|
122
|
+
const abs = join(cwd, rel);
|
|
123
|
+
if (existsSync(abs) && statSync(abs).isDirectory()) return true;
|
|
124
|
+
}
|
|
125
|
+
return false;
|
|
126
|
+
}
|
|
127
|
+
function materializePromptLike(p, name, cwd, roots, deployedFiles) {
|
|
128
|
+
deployedFiles.push(writeDeployedFile({
|
|
129
|
+
cwd,
|
|
130
|
+
deployRoots: roots,
|
|
131
|
+
destRel: join(".github", "prompts", `${name}.prompt.md`),
|
|
132
|
+
content: readPrimitiveContent(p),
|
|
133
|
+
primitive: {
|
|
134
|
+
name: String(p.name),
|
|
135
|
+
packageName: p.packageName
|
|
136
|
+
}
|
|
137
|
+
}));
|
|
138
|
+
}
|
|
139
|
+
function resolveCopilotHome() {
|
|
140
|
+
const override = process.env.COPILOT_HOME?.trim();
|
|
141
|
+
if (override) return resolve(override);
|
|
142
|
+
return join(homedir(), ".copilot");
|
|
143
|
+
}
|
|
144
|
+
function writeCopilotMcpConfig(servers, ctx) {
|
|
145
|
+
const home = resolveCopilotHome();
|
|
146
|
+
mkdirSync(home, { recursive: true });
|
|
147
|
+
const destFile = join(home, "mcp-config.json");
|
|
148
|
+
const existingDoc = readExistingMcpDoc(destFile);
|
|
149
|
+
const existingServers = existingDoc.mcpServers && typeof existingDoc.mcpServers === "object" ? { ...existingDoc.mcpServers } : {};
|
|
150
|
+
const { entries: incoming, diagnostics } = normalizeServerEntries(servers);
|
|
151
|
+
const nextServers = { ...existingServers };
|
|
152
|
+
const writtenNames = [];
|
|
153
|
+
for (const [name, entry] of Object.entries(incoming)) {
|
|
154
|
+
const prior = existingServers[name];
|
|
155
|
+
const merged = prior && typeof prior === "object" ? {
|
|
156
|
+
...prior,
|
|
157
|
+
...entry
|
|
158
|
+
} : { ...entry };
|
|
159
|
+
delete merged.format;
|
|
160
|
+
delete merged.packageName;
|
|
161
|
+
delete merged.registry;
|
|
162
|
+
nextServers[name] = merged;
|
|
163
|
+
writtenNames.push(name);
|
|
164
|
+
}
|
|
165
|
+
const doc = {
|
|
166
|
+
...existingDoc,
|
|
167
|
+
mcpServers: nextServers
|
|
168
|
+
};
|
|
169
|
+
writeFileSync(destFile, `${JSON.stringify(doc, null, 2)}\n`, "utf8");
|
|
170
|
+
return {
|
|
171
|
+
targetId: ctx.targetId,
|
|
172
|
+
configPath: destFile,
|
|
173
|
+
servers: writtenNames,
|
|
174
|
+
deployedFiles: [],
|
|
175
|
+
diagnostics
|
|
176
|
+
};
|
|
177
|
+
}
|
|
178
|
+
function readExistingMcpDoc(path) {
|
|
179
|
+
if (!existsSync(path)) return {};
|
|
180
|
+
try {
|
|
181
|
+
const raw = JSON.parse(readFileSync(path, "utf8"));
|
|
182
|
+
return raw && typeof raw === "object" && !Array.isArray(raw) ? { ...raw } : {};
|
|
183
|
+
} catch {
|
|
184
|
+
return {};
|
|
185
|
+
}
|
|
186
|
+
}
|
|
187
|
+
function normalizeServerEntries(servers) {
|
|
188
|
+
const out = {};
|
|
189
|
+
const diagnostics = [];
|
|
190
|
+
const list = Array.isArray(servers) ? servers : Object.entries(servers).map(([name, value]) => ({
|
|
191
|
+
...value,
|
|
192
|
+
name: value.name ?? name
|
|
193
|
+
}));
|
|
194
|
+
for (const server of list) {
|
|
195
|
+
const name = String(server.name ?? "").trim();
|
|
196
|
+
if (!name) continue;
|
|
197
|
+
out[name] = toCopilotServerEntry(server);
|
|
198
|
+
}
|
|
199
|
+
return {
|
|
200
|
+
entries: out,
|
|
201
|
+
diagnostics
|
|
202
|
+
};
|
|
203
|
+
}
|
|
204
|
+
function translateEnvPlaceholders(env) {
|
|
205
|
+
const out = {};
|
|
206
|
+
for (const [key, raw] of Object.entries(env)) {
|
|
207
|
+
if (typeof raw !== "string") {
|
|
208
|
+
out[key] = String(raw);
|
|
209
|
+
continue;
|
|
210
|
+
}
|
|
211
|
+
out[key] = raw.replace(/\$\{env:([A-Za-z_][A-Za-z0-9_]*)\}/g, "${$1}").replace(/<([A-Z_][A-Z0-9_]*)>/g, "${$1}");
|
|
212
|
+
}
|
|
213
|
+
return out;
|
|
214
|
+
}
|
|
215
|
+
function toCopilotServerEntry(server) {
|
|
216
|
+
const transport = String(server.transport ?? server.type ?? "").toLowerCase();
|
|
217
|
+
const entry = {};
|
|
218
|
+
if (transport === "http" || transport === "sse" || typeof server.url === "string") {
|
|
219
|
+
if (server.url) entry.url = server.url;
|
|
220
|
+
if (transport) entry.type = transport === "sse" ? "sse" : "http";
|
|
221
|
+
} else {
|
|
222
|
+
if (server.command) entry.command = String(server.command);
|
|
223
|
+
if (Array.isArray(server.args)) entry.args = server.args;
|
|
224
|
+
entry.type = "stdio";
|
|
225
|
+
}
|
|
226
|
+
if (server.env && typeof server.env === "object") entry.env = translateEnvPlaceholders(server.env);
|
|
227
|
+
for (const [key, value] of Object.entries(server)) {
|
|
228
|
+
if ([
|
|
229
|
+
"name",
|
|
230
|
+
"transport",
|
|
231
|
+
"type",
|
|
232
|
+
"command",
|
|
233
|
+
"args",
|
|
234
|
+
"url",
|
|
235
|
+
"env",
|
|
236
|
+
"packageName",
|
|
237
|
+
"registry",
|
|
238
|
+
"format",
|
|
239
|
+
"cwd"
|
|
240
|
+
].includes(key)) continue;
|
|
241
|
+
if (value !== void 0) entry[key] = value;
|
|
242
|
+
}
|
|
243
|
+
return entry;
|
|
244
|
+
}
|
|
245
|
+
/** Normalize hook event names to camelCase (`session_start` / `SessionStart` → `sessionStart`). */
|
|
246
|
+
function toCamelCaseEvent(event) {
|
|
247
|
+
const trimmed = String(event ?? "").trim();
|
|
248
|
+
if (!trimmed) return trimmed;
|
|
249
|
+
if (trimmed.includes("_") || trimmed.includes("-")) return trimmed.split(/[_-]+/).filter(Boolean).map((part, index) => {
|
|
250
|
+
const lower = part.toLowerCase();
|
|
251
|
+
if (index === 0) return lower;
|
|
252
|
+
return lower.charAt(0).toUpperCase() + lower.slice(1);
|
|
253
|
+
}).join("");
|
|
254
|
+
return trimmed.charAt(0).toLowerCase() + trimmed.slice(1);
|
|
255
|
+
}
|
|
256
|
+
function packageKey(p) {
|
|
257
|
+
if (typeof p.packageName === "string" && p.packageName.trim()) return sanitizeName(p.packageName);
|
|
258
|
+
const source = String(p.source ?? "");
|
|
259
|
+
if (source.startsWith("dependency:")) return sanitizeName(source.slice(11));
|
|
260
|
+
return "local";
|
|
261
|
+
}
|
|
262
|
+
function materializeCopilotHooks(args) {
|
|
263
|
+
const { cwd, roots, hooks } = args;
|
|
264
|
+
const deployedFiles = [];
|
|
265
|
+
const diagnostics = [];
|
|
266
|
+
const ownershipPath = join(cwd, HOOKS_OWNERSHIP_REL);
|
|
267
|
+
assertUnderDeployRoots(cwd, ownershipPath, roots);
|
|
268
|
+
mkdirSync(join(cwd, ".github", "hooks"), { recursive: true });
|
|
269
|
+
const ownership = readHookOwnershipSidecar(ownershipPath);
|
|
270
|
+
removeOwnedHookArtifacts(cwd, ownership);
|
|
271
|
+
const nextOwned = {};
|
|
272
|
+
for (const p of hooks) {
|
|
273
|
+
const stem = sanitizeName(String(p.name));
|
|
274
|
+
const pkg = packageKey(p);
|
|
275
|
+
const hookRel = `.github/hooks/${pkg}-${stem}.json`;
|
|
276
|
+
const hookAbs = join(cwd, hookRel);
|
|
277
|
+
assertUnderDeployRoots(cwd, hookAbs, roots);
|
|
278
|
+
const srcPath = p.path ? resolve(p.path) : void 0;
|
|
279
|
+
if (!srcPath || !existsSync(srcPath) || !statSync(srcPath).isFile()) {
|
|
280
|
+
diagnostics.push({
|
|
281
|
+
code: "COPILOT_HOOK_SOURCE_MISSING",
|
|
282
|
+
message: `Hook primitive "${p.name}" has no readable source JSON`,
|
|
283
|
+
primitive: String(p.name)
|
|
284
|
+
});
|
|
285
|
+
continue;
|
|
286
|
+
}
|
|
287
|
+
let parsed;
|
|
288
|
+
try {
|
|
289
|
+
parsed = JSON.parse(readFileSync(srcPath, "utf8"));
|
|
290
|
+
} catch (cause) {
|
|
291
|
+
diagnostics.push({
|
|
292
|
+
code: "COPILOT_HOOK_JSON_INVALID",
|
|
293
|
+
message: `Hook "${p.name}" JSON is invalid: ${cause instanceof Error ? cause.message : String(cause)}`,
|
|
294
|
+
primitive: String(p.name)
|
|
295
|
+
});
|
|
296
|
+
continue;
|
|
297
|
+
}
|
|
298
|
+
const sourceHooks = parsed.hooks && typeof parsed.hooks === "object" ? parsed.hooks : {};
|
|
299
|
+
const outHooks = {};
|
|
300
|
+
const scripts = [];
|
|
301
|
+
for (const [event, entries] of Object.entries(sourceHooks)) {
|
|
302
|
+
if (!Array.isArray(entries)) continue;
|
|
303
|
+
const camel = toCamelCaseEvent(event);
|
|
304
|
+
const destList = [];
|
|
305
|
+
for (const entry of entries) {
|
|
306
|
+
if (!entry || typeof entry !== "object") continue;
|
|
307
|
+
const command = typeof entry.command === "string" ? entry.command.trim() : "";
|
|
308
|
+
const { _apm_source: _drop, ...clean } = entry;
|
|
309
|
+
if (!command) {
|
|
310
|
+
destList.push({ ...clean });
|
|
311
|
+
continue;
|
|
312
|
+
}
|
|
313
|
+
const rewritten = copyHookScript({
|
|
314
|
+
cwd,
|
|
315
|
+
deployRoots: roots,
|
|
316
|
+
hookFile: srcPath,
|
|
317
|
+
command,
|
|
318
|
+
alreadyDeployedNeedle: ".github/hooks/",
|
|
319
|
+
destRel: `.github/hooks/scripts/${pkg}/${basename(command.replace(/^\.\//, ""))}`,
|
|
320
|
+
commandAsDotSlash: true
|
|
321
|
+
});
|
|
322
|
+
destList.push({
|
|
323
|
+
...clean,
|
|
324
|
+
command: rewritten.commandRel
|
|
325
|
+
});
|
|
326
|
+
if (rewritten.scriptRel) {
|
|
327
|
+
scripts.push(rewritten.scriptRel);
|
|
328
|
+
deployedFiles.push({
|
|
329
|
+
path: rewritten.scriptRel,
|
|
330
|
+
primitive: {
|
|
331
|
+
name: String(p.name),
|
|
332
|
+
packageName: p.packageName
|
|
333
|
+
}
|
|
334
|
+
});
|
|
335
|
+
}
|
|
336
|
+
}
|
|
337
|
+
outHooks[camel] = destList;
|
|
338
|
+
}
|
|
339
|
+
writeFileSync(hookAbs, `${JSON.stringify({ hooks: outHooks }, null, 2)}\n`, "utf8");
|
|
340
|
+
deployedFiles.push({
|
|
341
|
+
path: hookRel,
|
|
342
|
+
primitive: {
|
|
343
|
+
name: String(p.name),
|
|
344
|
+
packageName: p.packageName
|
|
345
|
+
}
|
|
346
|
+
});
|
|
347
|
+
const ownedKey = `${pkg}-${stem}`;
|
|
348
|
+
nextOwned[ownedKey] = {
|
|
349
|
+
...p.packageName ? { packageName: p.packageName } : {},
|
|
350
|
+
hookFile: hookRel,
|
|
351
|
+
scripts
|
|
352
|
+
};
|
|
353
|
+
}
|
|
354
|
+
writeHookOwnershipSidecar(ownershipPath, { owned: nextOwned });
|
|
355
|
+
deployedFiles.push({
|
|
356
|
+
path: HOOKS_OWNERSHIP_REL,
|
|
357
|
+
...hooks[0] ? { primitive: {
|
|
358
|
+
name: String(hooks[0].name),
|
|
359
|
+
packageName: hooks[0].packageName
|
|
360
|
+
} } : {}
|
|
361
|
+
});
|
|
362
|
+
return {
|
|
363
|
+
deployedFiles,
|
|
364
|
+
diagnostics
|
|
365
|
+
};
|
|
366
|
+
}
|
|
367
|
+
//#endregion
|
|
368
|
+
export { createCopilotIntegration, createCopilotIntegration as createIntegration, toCamelCaseEvent };
|
package/package.json
ADDED
|
@@ -0,0 +1,49 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@b-apm/integration-copilot",
|
|
3
|
+
"version": "0.1.0",
|
|
4
|
+
"description": "GitHub Copilot runtime integration for bapm",
|
|
5
|
+
"license": "MIT",
|
|
6
|
+
"files": [
|
|
7
|
+
"dist"
|
|
8
|
+
],
|
|
9
|
+
"type": "module",
|
|
10
|
+
"exports": {
|
|
11
|
+
".": "./dist/index.mjs",
|
|
12
|
+
"./package.json": "./package.json"
|
|
13
|
+
},
|
|
14
|
+
"publishConfig": {
|
|
15
|
+
"access": "public"
|
|
16
|
+
},
|
|
17
|
+
"dependencies": {
|
|
18
|
+
"@b-apm/integration-api": "0.1.0"
|
|
19
|
+
},
|
|
20
|
+
"devDependencies": {
|
|
21
|
+
"@types/node": "^26.1.2",
|
|
22
|
+
"typescript": "^7.0.2",
|
|
23
|
+
"vite": "npm:@voidzero-dev/vite-plus-core@latest",
|
|
24
|
+
"vite-plus": "latest"
|
|
25
|
+
},
|
|
26
|
+
"engines": {
|
|
27
|
+
"node": ">=22.12.0"
|
|
28
|
+
},
|
|
29
|
+
"repository": {
|
|
30
|
+
"type": "git",
|
|
31
|
+
"url": "git+https://github.com/Sdju/bapm.git",
|
|
32
|
+
"directory": "packages/integration-copilot"
|
|
33
|
+
},
|
|
34
|
+
"homepage": "https://github.com/Sdju/bapm#readme",
|
|
35
|
+
"bugs": {
|
|
36
|
+
"url": "https://github.com/Sdju/bapm/issues"
|
|
37
|
+
},
|
|
38
|
+
"keywords": [
|
|
39
|
+
"unstable",
|
|
40
|
+
"bapm",
|
|
41
|
+
"b-apm"
|
|
42
|
+
],
|
|
43
|
+
"scripts": {
|
|
44
|
+
"build": "vp pack",
|
|
45
|
+
"dev": "vp pack --watch",
|
|
46
|
+
"test": "vp test",
|
|
47
|
+
"check": "vp check"
|
|
48
|
+
}
|
|
49
|
+
}
|