@b-apm/integration-antigravity 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 +33 -0
- package/dist/index.d.mts +21 -0
- package/dist/index.mjs +483 -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,33 @@
|
|
|
1
|
+
# `@b-apm/integration-antigravity`
|
|
2
|
+
|
|
3
|
+
> **UNSTABLE:** Early public release. APIs and on-disk layouts may change without a major bump. Not production-ready.
|
|
4
|
+
|
|
5
|
+
Antigravity CLI (`agy`) project-scope runtime for bapm.
|
|
6
|
+
|
|
7
|
+
## Activation (explicit-only)
|
|
8
|
+
|
|
9
|
+
| Mode | Condition | Notes |
|
|
10
|
+
| ----------------------------------- | -------------------------------- | --------------------------------------------------------------------------------------- |
|
|
11
|
+
| **Auto-detect** | Never | `detect` always returns `false` — shared `.agents/` is not an Antigravity-unique signal |
|
|
12
|
+
| **Forced** (`--target antigravity`) | Explicit CLI/manifest object-map | MAY `mkdir` `.agents/` for rules/skills/hooks |
|
|
13
|
+
|
|
14
|
+
Not part of `--target all` alone (same APM model as `agent-skills`). Load via:
|
|
15
|
+
|
|
16
|
+
```yaml
|
|
17
|
+
targets:
|
|
18
|
+
antigravity: "@b-apm/integration-antigravity"
|
|
19
|
+
```
|
|
20
|
+
|
|
21
|
+
## Layout
|
|
22
|
+
|
|
23
|
+
Writes only under `.agents/` (plus project-root `AGENTS.md` compile):
|
|
24
|
+
|
|
25
|
+
- instructions → `.agents/rules/<name>.md` (`trigger` / `globs` from `applyTo`)
|
|
26
|
+
- skills → `.agents/skills/<name>/SKILL.md`
|
|
27
|
+
- hooks → `.agents/hooks.json` (agy schema under top-level `bapm`) + `.agents/bapm-hooks.json` sidecar
|
|
28
|
+
- MCP → `.agents/mcp_config.json` (**opt-in**: `.agents/` must already exist)
|
|
29
|
+
- compile → `AGENTS.md` (omits instruction primitives already deployed as rules)
|
|
30
|
+
|
|
31
|
+
**Skipped:** agents, commands (APM N).
|
|
32
|
+
|
|
33
|
+
**Out of scope:** user `~/.gemini/**`; claiming unrelated `.agents/` trees beyond rules/skills/hooks/mcp (coexists with agent-skills skills path).
|
package/dist/index.d.mts
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
import { BapmIntegration } from "@b-apm/integration-api";
|
|
2
|
+
//#region src/createAntigravityIntegration.d.ts
|
|
3
|
+
/**
|
|
4
|
+
* Create the Antigravity CLI (agy) target.
|
|
5
|
+
* Detect: always false (explicit-only; shared `.agents/` is not a signal).
|
|
6
|
+
* Materialize: instructions → `.agents/rules/` (trigger/globs),
|
|
7
|
+
* skills → `.agents/skills/`, hooks → `.agents/hooks.json` (agy schema),
|
|
8
|
+
* agents/commands → skip.
|
|
9
|
+
* MCP: opt-in `.agents/mcp_config.json` when `.agents/` exists (`serverUrl` for remotes).
|
|
10
|
+
* Compile: project-root `AGENTS.md`, omitting instruction primitives.
|
|
11
|
+
*/
|
|
12
|
+
declare function createAntigravityIntegration(options?: {
|
|
13
|
+
id?: string;
|
|
14
|
+
deployRoots?: string[];
|
|
15
|
+
}): BapmIntegration;
|
|
16
|
+
/**
|
|
17
|
+
* Map portable `applyTo` frontmatter to Antigravity `trigger: glob` + `globs`.
|
|
18
|
+
*/
|
|
19
|
+
declare function transformAntigravityRulesMarkdown(source: string): string;
|
|
20
|
+
//#endregion
|
|
21
|
+
export { createAntigravityIntegration, createAntigravityIntegration as createIntegration, transformAntigravityRulesMarkdown };
|
package/dist/index.mjs
ADDED
|
@@ -0,0 +1,483 @@
|
|
|
1
|
+
import { cpSync, existsSync, mkdirSync, readFileSync, statSync, writeFileSync } from "node:fs";
|
|
2
|
+
import { basename, dirname, join, resolve } from "node:path";
|
|
3
|
+
import { assertUnderDeployRoots, compileMarkdownReport, findPackageRoot, materializeSkill, primitivesList, primitivesMaterialize, readHookOwnershipSidecar, readPrimitiveContent, removeOwnedHookArtifacts, renderPrimitivesMarkdown, sanitizeName, writeDeployedFile, writeHookOwnershipSidecar } from "@b-apm/integration-api";
|
|
4
|
+
//#region src/createAntigravityIntegration.ts
|
|
5
|
+
const DEFAULT_DEPLOY_ROOTS = [".agents", "."];
|
|
6
|
+
const MCP_JSON_REL = ".agents/mcp_config.json";
|
|
7
|
+
const HOOKS_JSON_REL = ".agents/hooks.json";
|
|
8
|
+
const HOOKS_OWNERSHIP_REL = ".agents/bapm-hooks.json";
|
|
9
|
+
const BAPM_HOOK_CONTAINER = "bapm";
|
|
10
|
+
/** Antigravity nested matcher/hooks[] events (APM hook_native_formats). */
|
|
11
|
+
const NESTED_EVENTS = /* @__PURE__ */ new Set(["PreToolUse", "PostToolUse"]);
|
|
12
|
+
/**
|
|
13
|
+
* Create the Antigravity CLI (agy) target.
|
|
14
|
+
* Detect: always false (explicit-only; shared `.agents/` is not a signal).
|
|
15
|
+
* Materialize: instructions → `.agents/rules/` (trigger/globs),
|
|
16
|
+
* skills → `.agents/skills/`, hooks → `.agents/hooks.json` (agy schema),
|
|
17
|
+
* agents/commands → skip.
|
|
18
|
+
* MCP: opt-in `.agents/mcp_config.json` when `.agents/` exists (`serverUrl` for remotes).
|
|
19
|
+
* Compile: project-root `AGENTS.md`, omitting instruction primitives.
|
|
20
|
+
*/
|
|
21
|
+
function createAntigravityIntegration(options) {
|
|
22
|
+
const id = options?.id ?? "antigravity";
|
|
23
|
+
const deployRoots = [...options?.deployRoots ?? DEFAULT_DEPLOY_ROOTS];
|
|
24
|
+
return {
|
|
25
|
+
id,
|
|
26
|
+
deployRoots,
|
|
27
|
+
detect: () => false,
|
|
28
|
+
getDeployRoots: () => [...deployRoots],
|
|
29
|
+
async compile(primitives, context) {
|
|
30
|
+
const content = renderPrimitivesMarkdown({
|
|
31
|
+
primitives: primitivesList(primitives),
|
|
32
|
+
title: "# AGENTS.md",
|
|
33
|
+
filter: (p) => !/instruction/i.test(String(p.type ?? ""))
|
|
34
|
+
});
|
|
35
|
+
return compileMarkdownReport({
|
|
36
|
+
cwd: context.cwd,
|
|
37
|
+
outputFile: context.outputFile ?? "AGENTS.md",
|
|
38
|
+
write: context.write,
|
|
39
|
+
content,
|
|
40
|
+
outsideCwdMessage: "Antigravity compile output must be a cwd-relative file path"
|
|
41
|
+
});
|
|
42
|
+
},
|
|
43
|
+
async materialize(primitives, ctx) {
|
|
44
|
+
const cwd = resolve(ctx?.cwd ?? process.cwd());
|
|
45
|
+
const roots = ctx?.deployRoots?.length ? [...ctx.deployRoots] : deployRoots;
|
|
46
|
+
if (!roots.some((r) => r === ".agents" || r.startsWith(".agents"))) throw new Error("antigravity target missing .agents deploy root");
|
|
47
|
+
const deployedFiles = [];
|
|
48
|
+
const diagnostics = [];
|
|
49
|
+
const hookPrimitives = [];
|
|
50
|
+
await primitivesMaterialize(primitives, {
|
|
51
|
+
skill(p, { name }) {
|
|
52
|
+
deployedFiles.push(...materializeSkill({
|
|
53
|
+
primitive: p,
|
|
54
|
+
cwd,
|
|
55
|
+
deployRoots: roots,
|
|
56
|
+
destDir: join(".agents", "skills", name)
|
|
57
|
+
}));
|
|
58
|
+
},
|
|
59
|
+
instruction(p, { name }) {
|
|
60
|
+
deployedFiles.push(writeDeployedFile({
|
|
61
|
+
cwd,
|
|
62
|
+
deployRoots: roots,
|
|
63
|
+
destRel: join(".agents", "rules", `${name}.md`),
|
|
64
|
+
content: transformAntigravityRulesMarkdown(readPrimitiveContent(p)),
|
|
65
|
+
primitive: {
|
|
66
|
+
name: String(p.name),
|
|
67
|
+
packageName: p.packageName
|
|
68
|
+
}
|
|
69
|
+
}));
|
|
70
|
+
},
|
|
71
|
+
agent(p) {
|
|
72
|
+
diagnostics.push({
|
|
73
|
+
code: "ANTIGRAVITY_PRIMITIVE_UNSUPPORTED",
|
|
74
|
+
message: `Antigravity does not materialize agent primitives: "${p.name}"`,
|
|
75
|
+
primitive: String(p.name),
|
|
76
|
+
kind: "agent"
|
|
77
|
+
});
|
|
78
|
+
},
|
|
79
|
+
command(p) {
|
|
80
|
+
diagnostics.push({
|
|
81
|
+
code: "ANTIGRAVITY_PRIMITIVE_UNSUPPORTED",
|
|
82
|
+
message: `Antigravity does not materialize command primitives: "${p.name}"`,
|
|
83
|
+
primitive: String(p.name),
|
|
84
|
+
kind: "command"
|
|
85
|
+
});
|
|
86
|
+
},
|
|
87
|
+
hook(p) {
|
|
88
|
+
hookPrimitives.push(p);
|
|
89
|
+
}
|
|
90
|
+
});
|
|
91
|
+
if (hookPrimitives.length > 0) {
|
|
92
|
+
const hookDeployed = materializeAntigravityHooks({
|
|
93
|
+
cwd,
|
|
94
|
+
roots,
|
|
95
|
+
hooks: hookPrimitives
|
|
96
|
+
});
|
|
97
|
+
deployedFiles.push(...hookDeployed.deployedFiles);
|
|
98
|
+
diagnostics.push(...hookDeployed.diagnostics);
|
|
99
|
+
}
|
|
100
|
+
return {
|
|
101
|
+
targetId: id,
|
|
102
|
+
deployedFiles,
|
|
103
|
+
...diagnostics.length > 0 ? { diagnostics } : {}
|
|
104
|
+
};
|
|
105
|
+
},
|
|
106
|
+
async configureMcp(servers, ctx) {
|
|
107
|
+
return writeAntigravityMcpConfig(servers, {
|
|
108
|
+
cwd: resolve(ctx?.cwd ?? process.cwd()),
|
|
109
|
+
deployRoots: ctx?.deployRoots?.length ? [...ctx.deployRoots] : deployRoots,
|
|
110
|
+
targetId: ctx?.targetId ?? id
|
|
111
|
+
});
|
|
112
|
+
}
|
|
113
|
+
};
|
|
114
|
+
}
|
|
115
|
+
/**
|
|
116
|
+
* Map portable `applyTo` frontmatter to Antigravity `trigger: glob` + `globs`.
|
|
117
|
+
*/
|
|
118
|
+
function transformAntigravityRulesMarkdown(source) {
|
|
119
|
+
const match = source.match(/^---\r?\n([\s\S]*?)\r?\n---\r?\n?([\s\S]*)$/);
|
|
120
|
+
if (!match) return source;
|
|
121
|
+
const rawFm = match[1] ?? "";
|
|
122
|
+
const body = match[2] ?? "";
|
|
123
|
+
const lines = rawFm.split(/\r?\n/);
|
|
124
|
+
const kept = [];
|
|
125
|
+
const globs = [];
|
|
126
|
+
let i = 0;
|
|
127
|
+
while (i < lines.length) {
|
|
128
|
+
const line = lines[i];
|
|
129
|
+
const keyMatch = line.match(/^([A-Za-z0-9_-]+)\s*:(.*)$/);
|
|
130
|
+
if (!keyMatch) {
|
|
131
|
+
if (line.trim()) kept.push(line);
|
|
132
|
+
i += 1;
|
|
133
|
+
continue;
|
|
134
|
+
}
|
|
135
|
+
const key = keyMatch[1];
|
|
136
|
+
const rest = keyMatch[2] ?? "";
|
|
137
|
+
if (key === "applyTo" || key === "globs" || key === "trigger") {
|
|
138
|
+
if (key === "trigger") {
|
|
139
|
+
i += 1;
|
|
140
|
+
continue;
|
|
141
|
+
}
|
|
142
|
+
const inline = rest.trim();
|
|
143
|
+
if (inline.startsWith("[") && inline.endsWith("]")) {
|
|
144
|
+
for (const part of inline.slice(1, -1).split(",")) {
|
|
145
|
+
const v = part.trim().replace(/^["']|["']$/g, "");
|
|
146
|
+
if (v) globs.push(v);
|
|
147
|
+
}
|
|
148
|
+
i += 1;
|
|
149
|
+
continue;
|
|
150
|
+
}
|
|
151
|
+
if (inline && inline !== "|" && inline !== ">") {
|
|
152
|
+
for (const part of inline.replace(/^["']|["']$/g, "").split(",")) {
|
|
153
|
+
const v = part.trim().replace(/^["']|["']$/g, "");
|
|
154
|
+
if (v) globs.push(v);
|
|
155
|
+
}
|
|
156
|
+
i += 1;
|
|
157
|
+
continue;
|
|
158
|
+
}
|
|
159
|
+
i += 1;
|
|
160
|
+
while (i < lines.length) {
|
|
161
|
+
const listMatch = lines[i].match(/^[ \t]*-[ \t]*(.+)$/);
|
|
162
|
+
if (!listMatch) break;
|
|
163
|
+
globs.push(listMatch[1].trim().replace(/^["']|["']$/g, ""));
|
|
164
|
+
i += 1;
|
|
165
|
+
}
|
|
166
|
+
continue;
|
|
167
|
+
}
|
|
168
|
+
kept.push(line);
|
|
169
|
+
i += 1;
|
|
170
|
+
}
|
|
171
|
+
const fmLines = [...kept];
|
|
172
|
+
if (globs.length > 0) {
|
|
173
|
+
fmLines.push("trigger: glob");
|
|
174
|
+
if (globs.length === 1) fmLines.push(`globs: ${JSON.stringify(globs[0])}`);
|
|
175
|
+
else {
|
|
176
|
+
fmLines.push("globs:");
|
|
177
|
+
for (const g of globs) fmLines.push(` - ${JSON.stringify(g)}`);
|
|
178
|
+
}
|
|
179
|
+
}
|
|
180
|
+
if (fmLines.length === 0) return body.replace(/\s+$/, "\n") || body;
|
|
181
|
+
return `---\n${fmLines.join("\n")}\n---\n${body}`.replace(/\s+$/, "\n");
|
|
182
|
+
}
|
|
183
|
+
function writeAntigravityMcpConfig(servers, ctx) {
|
|
184
|
+
const { cwd, deployRoots } = ctx;
|
|
185
|
+
const agentsDir = join(cwd, ".agents");
|
|
186
|
+
if (!existsSync(agentsDir) || !statSync(agentsDir).isDirectory()) return {
|
|
187
|
+
targetId: ctx.targetId,
|
|
188
|
+
configPath: MCP_JSON_REL,
|
|
189
|
+
servers: [],
|
|
190
|
+
deployedFiles: [],
|
|
191
|
+
diagnostics: [{
|
|
192
|
+
code: "ANTIGRAVITY_MCP_SKIP_NO_AGENTS_DIR",
|
|
193
|
+
message: "Skipping project MCP write: .agents/ directory is absent (Antigravity project-scope MCP is opt-in)"
|
|
194
|
+
}]
|
|
195
|
+
};
|
|
196
|
+
if (!deployRoots.some((r) => r === ".agents" || r.startsWith(".agents"))) throw new Error("antigravity configureMcp requires a registered .agents deploy root");
|
|
197
|
+
const destFile = join(cwd, MCP_JSON_REL);
|
|
198
|
+
assertUnderDeployRoots(cwd, destFile, deployRoots);
|
|
199
|
+
mkdirSync(agentsDir, { recursive: true });
|
|
200
|
+
const existingDoc = readExistingJsonDoc(destFile);
|
|
201
|
+
const existingServers = existingDoc.mcpServers && typeof existingDoc.mcpServers === "object" ? { ...existingDoc.mcpServers } : {};
|
|
202
|
+
const { entries: incoming, diagnostics } = normalizeServerEntries(servers);
|
|
203
|
+
const nextServers = { ...existingServers };
|
|
204
|
+
const writtenNames = [];
|
|
205
|
+
for (const [name, entry] of Object.entries(incoming)) {
|
|
206
|
+
nextServers[name] = { ...entry };
|
|
207
|
+
writtenNames.push(name);
|
|
208
|
+
}
|
|
209
|
+
const doc = {
|
|
210
|
+
...existingDoc,
|
|
211
|
+
mcpServers: nextServers
|
|
212
|
+
};
|
|
213
|
+
writeFileSync(destFile, `${JSON.stringify(doc, null, 2)}\n`, "utf8");
|
|
214
|
+
return {
|
|
215
|
+
targetId: ctx.targetId,
|
|
216
|
+
configPath: MCP_JSON_REL,
|
|
217
|
+
servers: writtenNames,
|
|
218
|
+
deployedFiles: [{ path: MCP_JSON_REL }],
|
|
219
|
+
diagnostics
|
|
220
|
+
};
|
|
221
|
+
}
|
|
222
|
+
function readExistingJsonDoc(path) {
|
|
223
|
+
if (!existsSync(path)) return {};
|
|
224
|
+
try {
|
|
225
|
+
const raw = JSON.parse(readFileSync(path, "utf8"));
|
|
226
|
+
return raw && typeof raw === "object" && !Array.isArray(raw) ? { ...raw } : {};
|
|
227
|
+
} catch {
|
|
228
|
+
return {};
|
|
229
|
+
}
|
|
230
|
+
}
|
|
231
|
+
function normalizeServerEntries(servers) {
|
|
232
|
+
const out = {};
|
|
233
|
+
const diagnostics = [];
|
|
234
|
+
const list = Array.isArray(servers) ? servers : Object.entries(servers).map(([name, value]) => ({
|
|
235
|
+
...value,
|
|
236
|
+
name: value.name ?? name
|
|
237
|
+
}));
|
|
238
|
+
for (const server of list) {
|
|
239
|
+
const name = String(server.name ?? "").trim();
|
|
240
|
+
if (!name) continue;
|
|
241
|
+
const entry = toAntigravityServerEntry(server);
|
|
242
|
+
if (!entry) {
|
|
243
|
+
diagnostics.push({
|
|
244
|
+
code: "ANTIGRAVITY_MCP_UNSUPPORTED",
|
|
245
|
+
message: `Antigravity cannot faithfully represent MCP server "${name}"`,
|
|
246
|
+
server: name
|
|
247
|
+
});
|
|
248
|
+
continue;
|
|
249
|
+
}
|
|
250
|
+
out[name] = entry;
|
|
251
|
+
}
|
|
252
|
+
return {
|
|
253
|
+
entries: out,
|
|
254
|
+
diagnostics
|
|
255
|
+
};
|
|
256
|
+
}
|
|
257
|
+
function toAntigravityServerEntry(server) {
|
|
258
|
+
const transport = String(server.transport ?? server.type ?? "").toLowerCase();
|
|
259
|
+
const entry = {};
|
|
260
|
+
const remoteUrl = typeof server.url === "string" ? server.url : typeof server.httpUrl === "string" ? String(server.httpUrl) : typeof server.serverUrl === "string" ? String(server.serverUrl) : void 0;
|
|
261
|
+
if (remoteUrl && (transport === "http" || transport === "sse" || transport === "streamable-http" || !server.command)) entry.serverUrl = remoteUrl;
|
|
262
|
+
else if (server.command) {
|
|
263
|
+
entry.command = server.command;
|
|
264
|
+
if (Array.isArray(server.args)) entry.args = server.args;
|
|
265
|
+
} else return;
|
|
266
|
+
if (server.env && typeof server.env === "object") entry.env = server.env;
|
|
267
|
+
for (const [key, value] of Object.entries(server)) {
|
|
268
|
+
if ([
|
|
269
|
+
"name",
|
|
270
|
+
"transport",
|
|
271
|
+
"type",
|
|
272
|
+
"command",
|
|
273
|
+
"args",
|
|
274
|
+
"url",
|
|
275
|
+
"httpUrl",
|
|
276
|
+
"serverUrl",
|
|
277
|
+
"env",
|
|
278
|
+
"packageName",
|
|
279
|
+
"registry",
|
|
280
|
+
"format",
|
|
281
|
+
"cwd"
|
|
282
|
+
].includes(key)) continue;
|
|
283
|
+
if (value !== void 0) entry[key] = value;
|
|
284
|
+
}
|
|
285
|
+
return entry;
|
|
286
|
+
}
|
|
287
|
+
function materializeAntigravityHooks(args) {
|
|
288
|
+
const { cwd, roots, hooks } = args;
|
|
289
|
+
const deployedFiles = [];
|
|
290
|
+
const diagnostics = [];
|
|
291
|
+
const hooksPath = join(cwd, HOOKS_JSON_REL);
|
|
292
|
+
const ownershipPath = join(cwd, HOOKS_OWNERSHIP_REL);
|
|
293
|
+
assertUnderDeployRoots(cwd, hooksPath, roots);
|
|
294
|
+
assertUnderDeployRoots(cwd, ownershipPath, roots);
|
|
295
|
+
mkdirSync(join(cwd, ".agents"), { recursive: true });
|
|
296
|
+
const doc = readAgyHooksDoc(hooksPath);
|
|
297
|
+
const ownership = readHookOwnershipSidecar(ownershipPath);
|
|
298
|
+
removeOwnedHookArtifacts(cwd, ownership);
|
|
299
|
+
delete doc[BAPM_HOOK_CONTAINER];
|
|
300
|
+
const container = {};
|
|
301
|
+
const nextOwned = {};
|
|
302
|
+
for (const p of hooks) {
|
|
303
|
+
const name = sanitizeName(String(p.name));
|
|
304
|
+
const pkg = sanitizeName(String(p.packageName ?? p.name));
|
|
305
|
+
const srcPath = p.path ? resolve(p.path) : void 0;
|
|
306
|
+
if (!srcPath || !existsSync(srcPath) || !statSync(srcPath).isFile()) {
|
|
307
|
+
diagnostics.push({
|
|
308
|
+
code: "ANTIGRAVITY_HOOK_SOURCE_MISSING",
|
|
309
|
+
message: `Hook primitive "${p.name}" has no readable source JSON`,
|
|
310
|
+
primitive: String(p.name)
|
|
311
|
+
});
|
|
312
|
+
continue;
|
|
313
|
+
}
|
|
314
|
+
let parsed;
|
|
315
|
+
try {
|
|
316
|
+
parsed = JSON.parse(readFileSync(srcPath, "utf8"));
|
|
317
|
+
} catch (cause) {
|
|
318
|
+
diagnostics.push({
|
|
319
|
+
code: "ANTIGRAVITY_HOOK_JSON_INVALID",
|
|
320
|
+
message: `Hook "${p.name}" JSON is invalid: ${cause instanceof Error ? cause.message : String(cause)}`,
|
|
321
|
+
primitive: String(p.name)
|
|
322
|
+
});
|
|
323
|
+
continue;
|
|
324
|
+
}
|
|
325
|
+
const sourceHooks = parsed.hooks && typeof parsed.hooks === "object" ? parsed.hooks : {};
|
|
326
|
+
const ownedEntries = [];
|
|
327
|
+
const scripts = [];
|
|
328
|
+
for (const [rawEvent, entries] of Object.entries(sourceHooks)) {
|
|
329
|
+
if (!Array.isArray(entries)) continue;
|
|
330
|
+
const event = normalizeAgyHookEvent(rawEvent);
|
|
331
|
+
const converted = toAgyEventEntries(event, entries, {
|
|
332
|
+
cwd,
|
|
333
|
+
roots,
|
|
334
|
+
pkg,
|
|
335
|
+
hookFile: srcPath,
|
|
336
|
+
ownedEntries,
|
|
337
|
+
scripts,
|
|
338
|
+
deployedFiles,
|
|
339
|
+
primitive: p
|
|
340
|
+
});
|
|
341
|
+
const prior = container[event];
|
|
342
|
+
if (Array.isArray(prior)) prior.push(...converted);
|
|
343
|
+
else container[event] = converted;
|
|
344
|
+
}
|
|
345
|
+
nextOwned[name] = {
|
|
346
|
+
...p.packageName ? { packageName: p.packageName } : {},
|
|
347
|
+
entries: ownedEntries,
|
|
348
|
+
scripts
|
|
349
|
+
};
|
|
350
|
+
}
|
|
351
|
+
doc[BAPM_HOOK_CONTAINER] = container;
|
|
352
|
+
writeFileSync(hooksPath, `${JSON.stringify(doc, null, 2)}\n`, "utf8");
|
|
353
|
+
writeHookOwnershipSidecar(ownershipPath, { owned: nextOwned });
|
|
354
|
+
deployedFiles.push({
|
|
355
|
+
path: HOOKS_JSON_REL,
|
|
356
|
+
...hooks[0] ? { primitive: {
|
|
357
|
+
name: String(hooks[0].name),
|
|
358
|
+
packageName: hooks[0].packageName
|
|
359
|
+
} } : {}
|
|
360
|
+
});
|
|
361
|
+
deployedFiles.push({
|
|
362
|
+
path: HOOKS_OWNERSHIP_REL,
|
|
363
|
+
...hooks[0] ? { primitive: {
|
|
364
|
+
name: String(hooks[0].name),
|
|
365
|
+
packageName: hooks[0].packageName
|
|
366
|
+
} } : {}
|
|
367
|
+
});
|
|
368
|
+
return {
|
|
369
|
+
deployedFiles,
|
|
370
|
+
diagnostics
|
|
371
|
+
};
|
|
372
|
+
}
|
|
373
|
+
function toAgyEventEntries(event, entries, ctx) {
|
|
374
|
+
if (NESTED_EVENTS.has(event)) {
|
|
375
|
+
const groups = [];
|
|
376
|
+
for (const entry of entries) {
|
|
377
|
+
if (!entry || typeof entry !== "object") continue;
|
|
378
|
+
const obj = entry;
|
|
379
|
+
if (Array.isArray(obj.hooks)) {
|
|
380
|
+
const group = { hooks: obj.hooks.filter((h) => !!h && typeof h === "object").map((h) => rewriteHandler(h, event, ctx)) };
|
|
381
|
+
if (typeof obj.matcher === "string") group.matcher = obj.matcher;
|
|
382
|
+
groups.push(group);
|
|
383
|
+
} else groups.push({ hooks: [rewriteHandler(obj, event, ctx)] });
|
|
384
|
+
}
|
|
385
|
+
return groups;
|
|
386
|
+
}
|
|
387
|
+
const flat = [];
|
|
388
|
+
for (const entry of entries) {
|
|
389
|
+
if (!entry || typeof entry !== "object") continue;
|
|
390
|
+
const obj = entry;
|
|
391
|
+
if (Array.isArray(obj.hooks)) {
|
|
392
|
+
for (const h of obj.hooks) if (h && typeof h === "object") flat.push(rewriteHandler(h, event, ctx));
|
|
393
|
+
} else flat.push(rewriteHandler(obj, event, ctx));
|
|
394
|
+
}
|
|
395
|
+
return flat;
|
|
396
|
+
}
|
|
397
|
+
function rewriteHandler(handler, event, ctx) {
|
|
398
|
+
const next = { ...handler };
|
|
399
|
+
delete next.timeoutSec;
|
|
400
|
+
delete next._apm_source;
|
|
401
|
+
delete next._bapm_source;
|
|
402
|
+
const timeoutSec = typeof handler.timeoutSec === "number" ? handler.timeoutSec : typeof handler.timeout === "number" ? handler.timeout : void 0;
|
|
403
|
+
if (timeoutSec !== void 0) next.timeout = timeoutSec;
|
|
404
|
+
const command = typeof handler.command === "string" ? handler.command.trim() : "";
|
|
405
|
+
if (command) {
|
|
406
|
+
const rewritten = copyHookScript({
|
|
407
|
+
cwd: ctx.cwd,
|
|
408
|
+
roots: ctx.roots,
|
|
409
|
+
pkg: ctx.pkg,
|
|
410
|
+
hookFile: ctx.hookFile,
|
|
411
|
+
command
|
|
412
|
+
});
|
|
413
|
+
next.command = rewritten.commandRel;
|
|
414
|
+
ctx.ownedEntries.push({
|
|
415
|
+
event,
|
|
416
|
+
command: rewritten.commandRel
|
|
417
|
+
});
|
|
418
|
+
if (rewritten.scriptRel) {
|
|
419
|
+
ctx.scripts.push(rewritten.scriptRel);
|
|
420
|
+
ctx.deployedFiles.push({
|
|
421
|
+
path: rewritten.scriptRel,
|
|
422
|
+
primitive: {
|
|
423
|
+
name: String(ctx.primitive.name),
|
|
424
|
+
packageName: ctx.primitive.packageName
|
|
425
|
+
}
|
|
426
|
+
});
|
|
427
|
+
}
|
|
428
|
+
}
|
|
429
|
+
if (typeof next.type !== "string") next.type = "command";
|
|
430
|
+
return next;
|
|
431
|
+
}
|
|
432
|
+
function normalizeAgyHookEvent(event) {
|
|
433
|
+
const trimmed = event.trim();
|
|
434
|
+
if (!trimmed) return trimmed;
|
|
435
|
+
if (/^[A-Z][A-Za-z0-9]*$/.test(trimmed)) return trimmed;
|
|
436
|
+
return trimmed.split(/[-_\s]+/).filter(Boolean).map((part) => part.charAt(0).toUpperCase() + part.slice(1)).join("");
|
|
437
|
+
}
|
|
438
|
+
function copyHookScript(args) {
|
|
439
|
+
const { cwd, roots, pkg, hookFile, command } = args;
|
|
440
|
+
const tokens = command.split(/\s+/).filter(Boolean);
|
|
441
|
+
let scriptToken = tokens.find((t) => t.includes("/") || /\.(py|sh|js|mjs|cjs|ts)$/i.test(t));
|
|
442
|
+
if (!scriptToken && tokens.length === 1) scriptToken = tokens[0];
|
|
443
|
+
if (!scriptToken) return { commandRel: command };
|
|
444
|
+
const cleaned = scriptToken.replace(/^\.\//, "").replace(/^\$\{PLUGIN_ROOT\}\/?/, "");
|
|
445
|
+
const packageRoot = findPackageRoot(hookFile);
|
|
446
|
+
const hookDir = dirname(hookFile);
|
|
447
|
+
const source = [
|
|
448
|
+
resolve(hookDir, cleaned),
|
|
449
|
+
resolve(hookDir, basename(cleaned)),
|
|
450
|
+
resolve(packageRoot, cleaned),
|
|
451
|
+
resolve(packageRoot, basename(cleaned)),
|
|
452
|
+
resolve(packageRoot, "hooks", basename(cleaned)),
|
|
453
|
+
resolve(dirname(packageRoot), cleaned),
|
|
454
|
+
resolve(dirname(packageRoot), basename(cleaned))
|
|
455
|
+
].find((p) => {
|
|
456
|
+
try {
|
|
457
|
+
return existsSync(p) && statSync(p).isFile();
|
|
458
|
+
} catch {
|
|
459
|
+
return false;
|
|
460
|
+
}
|
|
461
|
+
});
|
|
462
|
+
if (!source) return { commandRel: command };
|
|
463
|
+
const destRel = `.agents/hooks/${pkg}/hooks/${basename(source)}`;
|
|
464
|
+
const destAbs = join(cwd, destRel);
|
|
465
|
+
assertUnderDeployRoots(cwd, destAbs, roots);
|
|
466
|
+
mkdirSync(dirname(destAbs), { recursive: true });
|
|
467
|
+
cpSync(source, destAbs);
|
|
468
|
+
return {
|
|
469
|
+
commandRel: tokens.map((t) => t === scriptToken || t.replace(/^\.\//, "") === cleaned ? destRel : t).join(" "),
|
|
470
|
+
scriptRel: destRel
|
|
471
|
+
};
|
|
472
|
+
}
|
|
473
|
+
function readAgyHooksDoc(path) {
|
|
474
|
+
if (!existsSync(path)) return {};
|
|
475
|
+
try {
|
|
476
|
+
const raw = JSON.parse(readFileSync(path, "utf8"));
|
|
477
|
+
return raw && typeof raw === "object" && !Array.isArray(raw) ? { ...raw } : {};
|
|
478
|
+
} catch {
|
|
479
|
+
return {};
|
|
480
|
+
}
|
|
481
|
+
}
|
|
482
|
+
//#endregion
|
|
483
|
+
export { createAntigravityIntegration, createAntigravityIntegration as createIntegration, transformAntigravityRulesMarkdown };
|
package/package.json
ADDED
|
@@ -0,0 +1,49 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@b-apm/integration-antigravity",
|
|
3
|
+
"version": "0.1.0",
|
|
4
|
+
"description": "Antigravity CLI (agy) 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-antigravity"
|
|
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
|
+
}
|