@microck/canonfig 2.0.0 → 2.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.
Files changed (57) hide show
  1. package/README.md +1 -1
  2. package/dist/cli/cli.js +1 -1
  3. package/dist/harness-configuration/adapters/amp.js +87 -0
  4. package/dist/harness-configuration/adapters/antigravity.js +50 -0
  5. package/dist/harness-configuration/adapters/claude.js +43 -0
  6. package/dist/harness-configuration/adapters/codex.js +60 -0
  7. package/dist/harness-configuration/adapters/copilot.js +79 -0
  8. package/dist/harness-configuration/adapters/cursor.js +67 -0
  9. package/dist/harness-configuration/adapters/descriptor.js +10 -0
  10. package/dist/harness-configuration/adapters/devin.js +66 -0
  11. package/dist/harness-configuration/adapters/droid.js +32 -0
  12. package/dist/harness-configuration/adapters/grok.js +44 -0
  13. package/dist/harness-configuration/adapters/hermes.js +105 -0
  14. package/dist/harness-configuration/adapters/index.js +50 -0
  15. package/dist/harness-configuration/adapters/kilo.js +18 -0
  16. package/dist/harness-configuration/adapters/kimi.js +148 -0
  17. package/dist/harness-configuration/adapters/omp.js +64 -0
  18. package/dist/harness-configuration/adapters/open-code-family.js +94 -0
  19. package/dist/harness-configuration/adapters/opencode.js +18 -0
  20. package/dist/harness-configuration/adapters/pi.js +93 -0
  21. package/dist/harness-configuration/adapters/qwen.js +164 -0
  22. package/dist/harness-configuration/adapters/shared-common.js +66 -0
  23. package/dist/harness-configuration/adapters/shared-documents.js +117 -0
  24. package/dist/harness-configuration/adapters/shared-hooks.js +186 -0
  25. package/dist/harness-configuration/adapters/shared-mcp.js +214 -0
  26. package/dist/harness-configuration/adapters/shared.js +4 -0
  27. package/dist/harness-configuration/adapters/tools.js +24 -0
  28. package/dist/harness-configuration/cli-arguments.js +105 -0
  29. package/dist/harness-configuration/cli-output.js +77 -0
  30. package/dist/harness-configuration/cli.js +196 -0
  31. package/dist/harness-configuration/core/compiler.js +175 -0
  32. package/dist/harness-configuration/core/config.js +74 -0
  33. package/dist/harness-configuration/core/diff.js +60 -0
  34. package/dist/harness-configuration/core/doctor.js +40 -0
  35. package/dist/harness-configuration/core/errors.js +13 -0
  36. package/dist/harness-configuration/core/filesystem.js +172 -0
  37. package/dist/harness-configuration/core/frontmatter.js +49 -0
  38. package/dist/harness-configuration/core/hash.js +4 -0
  39. package/dist/harness-configuration/core/path.js +50 -0
  40. package/dist/harness-configuration/core/planner.js +255 -0
  41. package/dist/harness-configuration/core/render-cleanup.js +91 -0
  42. package/dist/harness-configuration/core/render-json.js +195 -0
  43. package/dist/harness-configuration/core/render-text.js +134 -0
  44. package/dist/harness-configuration/core/render-utils.js +202 -0
  45. package/dist/harness-configuration/core/render.js +54 -0
  46. package/dist/harness-configuration/core/scaffold.js +103 -0
  47. package/dist/harness-configuration/core/schema-components.js +167 -0
  48. package/dist/harness-configuration/core/schema-config.js +98 -0
  49. package/dist/harness-configuration/core/schema-runtime.js +143 -0
  50. package/dist/harness-configuration/core/schema-types.js +8 -0
  51. package/dist/harness-configuration/core/schema.js +13 -0
  52. package/dist/harness-configuration/core/state.js +42 -0
  53. package/dist/harness-configuration/core/types.js +5 -0
  54. package/dist/harness-configuration/core/validation.js +113 -0
  55. package/dist/harness-configuration/templates/runtime.js +212 -0
  56. package/dist/runtime/main.js +20 -14
  57. package/package.json +5 -5
@@ -0,0 +1,164 @@
1
+ import { markdownWithFrontmatter } from "../core/frontmatter.js";
2
+ import { descriptor } from "./descriptor.js";
3
+ import { agentDocuments, commandDocuments, enabledHooks, enabledMcpServerEntries, hookCommand, secretValue, skillArtifacts, } from "./shared.js";
4
+ import { nativeTools } from "./tools.js";
5
+ const QWEN_EVENT_MAP = {
6
+ session_start: "SessionStart",
7
+ session_end: "SessionEnd",
8
+ prompt_submit: "UserPromptSubmit",
9
+ before_tool: "PreToolUse",
10
+ after_tool: "PostToolUse",
11
+ before_compact: "PreCompact",
12
+ stop: "Stop",
13
+ subagent_start: "SubagentStart",
14
+ subagent_stop: "SubagentStop",
15
+ };
16
+ const QWEN_MATCHER_EVENTS = new Set([
17
+ "SessionStart",
18
+ "SessionEnd",
19
+ "PreToolUse",
20
+ "PostToolUse",
21
+ "PreCompact",
22
+ "SubagentStart",
23
+ "SubagentStop",
24
+ ]);
25
+ function qwenHooks(context) {
26
+ const hooks = {};
27
+ const diagnostics = [];
28
+ for (const hook of enabledHooks(context)) {
29
+ const event = QWEN_EVENT_MAP[hook.event];
30
+ if (!event) {
31
+ diagnostics.push({
32
+ level: "warning",
33
+ code: "HOOK_EVENT_UNSUPPORTED",
34
+ target: "qwen",
35
+ message: `Qwen Code cannot directly map hook event ${hook.event}; it was skipped.`,
36
+ });
37
+ continue;
38
+ }
39
+ const entry = {
40
+ ...(QWEN_MATCHER_EVENTS.has(event) ? { matcher: "*" } : {}),
41
+ hooks: [{
42
+ type: "command",
43
+ command: hookCommand("qwen", hook),
44
+ timeout: hook.timeoutMs,
45
+ }],
46
+ };
47
+ (hooks[event] ??= []).push(entry);
48
+ }
49
+ return { hooks, diagnostics };
50
+ }
51
+ function qwenMcpServer(server) {
52
+ const common = {
53
+ ...(server.timeoutMs === undefined ? {} : { timeout: server.timeoutMs }),
54
+ ...(server.enabledTools?.length ? { includeTools: server.enabledTools } : {}),
55
+ ...(server.disabledTools?.length ? { excludeTools: server.disabledTools } : {}),
56
+ };
57
+ if (server.transport === "stdio") {
58
+ return {
59
+ command: server.command,
60
+ ...(server.args.length ? { args: server.args } : {}),
61
+ ...(server.cwd ? { cwd: server.cwd } : {}),
62
+ ...(Object.keys(server.env).length
63
+ ? {
64
+ env: Object.fromEntries(Object.entries(server.env).map(([key, value]) => [key, secretValue(value)])),
65
+ }
66
+ : {}),
67
+ ...common,
68
+ };
69
+ }
70
+ return {
71
+ ...(server.transport === "sse" ? { url: server.url } : { httpUrl: server.url }),
72
+ ...(Object.keys(server.headers).length
73
+ ? {
74
+ headers: Object.fromEntries(Object.entries(server.headers).map(([key, value]) => [key, secretValue(value)])),
75
+ }
76
+ : {}),
77
+ ...common,
78
+ };
79
+ }
80
+ function qwenMcpMap(context) {
81
+ return Object.fromEntries(enabledMcpServerEntries(context).map(([name, server]) => [
82
+ name,
83
+ qwenMcpServer(server),
84
+ ]));
85
+ }
86
+ export const qwenAdapter = {
87
+ descriptor: descriptor("qwen", "Qwen Code", ["qwen"], [
88
+ "https://github.com/QwenLM/qwen-code/blob/main/docs/users/features/skills.md",
89
+ "https://github.com/QwenLM/qwen-code/blob/main/docs/users/features/mcp.md",
90
+ "https://github.com/QwenLM/qwen-code/blob/main/docs/users/features/hooks.md",
91
+ "https://github.com/QwenLM/qwen-code/blob/main/docs/users/features/sub-agents.md",
92
+ "https://github.com/QwenLM/qwen-code/blob/main/docs/users/features/commands.md",
93
+ ], {
94
+ instructions: "portable",
95
+ rules: "portable",
96
+ skills: "native",
97
+ mcp: "native",
98
+ hooks: "native",
99
+ agents: "native",
100
+ commands: "native",
101
+ }, [
102
+ "Qwen Code stores project MCP servers and hooks together in .qwen/settings.json; Canonfig owns only the projected keys.",
103
+ "Canonical streamable HTTP servers map to Qwen's httpUrl field, while legacy SSE servers retain url.",
104
+ ], "2026-08-26"),
105
+ async build(context) {
106
+ const artifacts = [];
107
+ const diagnostics = [];
108
+ artifacts.push(...await skillArtifacts(context, ".qwen/skills", "qwen"));
109
+ if (enabledMcpServerEntries(context).length > 0) {
110
+ artifacts.push({
111
+ kind: "json",
112
+ path: ".qwen/settings.json",
113
+ owner: "qwen",
114
+ operations: [{
115
+ kind: "managed-map",
116
+ path: ["mcpServers"],
117
+ entries: qwenMcpMap(context),
118
+ collision: "error",
119
+ }],
120
+ });
121
+ }
122
+ const compiledHooks = qwenHooks(context);
123
+ diagnostics.push(...compiledHooks.diagnostics);
124
+ if (Object.keys(compiledHooks.hooks).length > 0) {
125
+ artifacts.push({
126
+ kind: "json",
127
+ path: ".qwen/settings.json",
128
+ owner: "qwen",
129
+ operations: [{
130
+ kind: "managed-hooks",
131
+ path: ["hooks"],
132
+ hooks: compiledHooks.hooks,
133
+ marker: ".canonfig/.runtime/hook-runner.mjs",
134
+ }],
135
+ });
136
+ }
137
+ for (const { agent, content } of await agentDocuments(context)) {
138
+ const tools = nativeTools("qwen", agent);
139
+ artifacts.push({
140
+ kind: "replace",
141
+ path: `.qwen/agents/${agent.id}.md`,
142
+ owner: "qwen",
143
+ content: markdownWithFrontmatter({
144
+ name: agent.id,
145
+ description: agent.description,
146
+ ...(agent.model === "inherit" ? {} : { model: agent.model }),
147
+ tools,
148
+ ...(!agent.writable
149
+ ? { disallowedTools: ["edit", "write_file"] }
150
+ : {}),
151
+ }, content),
152
+ });
153
+ }
154
+ for (const { command, content } of await commandDocuments(context)) {
155
+ artifacts.push({
156
+ kind: "replace",
157
+ path: `.qwen/commands/${command.id}.md`,
158
+ owner: "qwen",
159
+ content: markdownWithFrontmatter({ description: command.description }, content),
160
+ });
161
+ }
162
+ return { artifacts, diagnostics };
163
+ },
164
+ };
@@ -0,0 +1,66 @@
1
+ import { hookRegistryJson, hookRunnerSource } from "../templates/runtime.js";
2
+ import { readCanonfigText, skillArtifacts } from "./shared-documents.js";
3
+ import { enabledHooks } from "./shared-hooks.js";
4
+ import { hasEnabledMcpServers, standardMcpMap } from "./shared-mcp.js";
5
+ export async function commonArtifacts(context) {
6
+ const rootInstructions = await readCanonfigText(context, context.config.instructions.root);
7
+ const scoped = context.config.instructions.rules.length === 0 ? "" : [
8
+ "",
9
+ "## Scoped instruction sources",
10
+ "",
11
+ ...context.config.instructions.rules.map((rule) => {
12
+ const scope = rule.paths.length ? rule.paths.map((item) => `\`${item}\``).join(", ") : "all files";
13
+ return `- Read \`.canonfig/${rule.file}\` when working on ${scope}.`;
14
+ }),
15
+ ].join("\n");
16
+ const artifacts = [
17
+ {
18
+ kind: "managed-text",
19
+ path: "AGENTS.md",
20
+ owner: "common",
21
+ marker: "instructions",
22
+ comments: "html",
23
+ placement: "end",
24
+ content: `${rootInstructions.trim()}${scoped}`,
25
+ },
26
+ {
27
+ kind: "managed-text",
28
+ path: ".gitignore",
29
+ owner: "common",
30
+ marker: "state-ignore",
31
+ comments: "hash",
32
+ placement: "end",
33
+ content: ".canonfig/.harness-state.json",
34
+ },
35
+ ];
36
+ artifacts.push(...await skillArtifacts(context, ".agents/skills", "common"));
37
+ if (hasEnabledMcpServers(context)) {
38
+ artifacts.push({
39
+ kind: "json",
40
+ path: ".mcp.json",
41
+ owner: "common",
42
+ operations: [{
43
+ kind: "managed-map",
44
+ path: ["mcpServers"],
45
+ entries: standardMcpMap(context),
46
+ collision: "error",
47
+ }],
48
+ });
49
+ }
50
+ const hooks = enabledHooks(context);
51
+ if (hooks.length > 0) {
52
+ artifacts.push({
53
+ kind: "replace",
54
+ path: ".canonfig/.runtime/hook-runner.mjs",
55
+ owner: "common",
56
+ content: hookRunnerSource(),
57
+ mode: 0o755,
58
+ }, {
59
+ kind: "replace",
60
+ path: ".canonfig/.runtime/hooks.json",
61
+ owner: "common",
62
+ content: hookRegistryJson(hooks),
63
+ });
64
+ }
65
+ return artifacts;
66
+ }
@@ -0,0 +1,117 @@
1
+ import fs from "node:fs/promises";
2
+ import path from "node:path";
3
+ import { assertRealPathInside, assertSafeRelativePath, resolveInside, toPosix } from "../core/path.js";
4
+ import { markdownWithFrontmatter } from "../core/frontmatter.js";
5
+ import { walkFiles } from "../core/filesystem.js";
6
+ export const RUNTIME_MARKER = ".canonfig/.runtime/hook-runner.mjs";
7
+ export async function readCanonfigText(context, relativePath) {
8
+ const safe = assertSafeRelativePath(relativePath);
9
+ const absolute = resolveInside(context.canonfigDir, safe);
10
+ await assertRealPathInside(context.canonfigDir, absolute);
11
+ return fs.readFile(absolute, "utf8");
12
+ }
13
+ export async function copyDirectoryArtifacts(context, sourceRelative, destination, owner) {
14
+ const source = resolveInside(context.canonfigDir, assertSafeRelativePath(sourceRelative));
15
+ try {
16
+ await assertRealPathInside(context.canonfigDir, source);
17
+ await fs.access(source);
18
+ }
19
+ catch {
20
+ return [];
21
+ }
22
+ const files = await walkFiles(source);
23
+ const artifacts = [];
24
+ for (const file of files.sort()) {
25
+ const absolute = path.join(source, file);
26
+ const [content, stat] = await Promise.all([fs.readFile(absolute), fs.stat(absolute)]);
27
+ const executable = (stat.mode & 0o111) !== 0;
28
+ artifacts.push({
29
+ kind: "replace",
30
+ path: toPosix(path.posix.join(destination, toPosix(file))),
31
+ owner,
32
+ content,
33
+ ...(executable ? { mode: 0o755 } : {}),
34
+ });
35
+ }
36
+ return artifacts;
37
+ }
38
+ export async function skillArtifacts(context, destination, owner) {
39
+ const artifacts = [];
40
+ for (const root of context.config.skills.roots) {
41
+ artifacts.push(...await copyDirectoryArtifacts(context, root, destination, owner));
42
+ }
43
+ return artifacts;
44
+ }
45
+ export async function ruleDocuments(context) {
46
+ return Promise.all(context.config.instructions.rules.map(async (rule) => ({
47
+ rule,
48
+ content: await readCanonfigText(context, rule.file),
49
+ })));
50
+ }
51
+ export async function agentDocuments(context) {
52
+ return Promise.all(context.config.agents.map(async (agent) => ({
53
+ agent,
54
+ content: await readCanonfigText(context, agent.file),
55
+ })));
56
+ }
57
+ export async function commandDocuments(context) {
58
+ return Promise.all(context.config.commands.map(async (command) => ({
59
+ command,
60
+ content: await readCanonfigText(context, command.file),
61
+ })));
62
+ }
63
+ export function agentMarkdown(agent, content, tools) {
64
+ return markdownWithFrontmatter({
65
+ name: agent.id,
66
+ description: agent.description,
67
+ ...(agent.model === "inherit" ? {} : { model: agent.model }),
68
+ tools,
69
+ }, content);
70
+ }
71
+ export function commandMarkdown(command, content) {
72
+ return markdownWithFrontmatter({
73
+ description: command.description,
74
+ ...(command.argumentHint ? { "argument-hint": command.argumentHint } : {}),
75
+ }, content);
76
+ }
77
+ export function ruleMarkdown(rule, content, extra = {}) {
78
+ return markdownWithFrontmatter({
79
+ description: rule.description ?? `Canonfig rule: ${rule.id}`,
80
+ ...(rule.paths.length ? { globs: rule.paths } : {}),
81
+ ...extra,
82
+ }, content);
83
+ }
84
+ export function skillMarkdown(name, description, content, metadata = {}) {
85
+ return markdownWithFrontmatter({ name, description, ...metadata }, content);
86
+ }
87
+ function translatedSkillName(owner, kind, id) {
88
+ return `canonfig-${owner}-${kind}-${id}`;
89
+ }
90
+ export async function commandSkillArtifacts(context, destination, owner) {
91
+ const documents = await commandDocuments(context);
92
+ return documents.map(({ command, content }) => {
93
+ const name = translatedSkillName(owner, "command", command.id);
94
+ return {
95
+ kind: "replace",
96
+ path: `${destination}/${name}/SKILL.md`,
97
+ owner,
98
+ content: skillMarkdown(name, command.description, content, {
99
+ metadata: { canonfig: { kind: "command", sourceId: command.id, argumentHint: command.argumentHint ?? null } },
100
+ }),
101
+ };
102
+ });
103
+ }
104
+ export async function agentSkillArtifacts(context, destination, owner) {
105
+ const documents = await agentDocuments(context);
106
+ return documents.map(({ agent, content }) => {
107
+ const name = translatedSkillName(owner, "agent", agent.id);
108
+ return {
109
+ kind: "replace",
110
+ path: `${destination}/${name}/SKILL.md`,
111
+ owner,
112
+ content: skillMarkdown(name, agent.description, content, {
113
+ metadata: { canonfig: { kind: "agent", sourceId: agent.id, model: agent.model, tools: agent.tools } },
114
+ }),
115
+ };
116
+ });
117
+ }
@@ -0,0 +1,186 @@
1
+ export function hookCommand(target, hook) {
2
+ return `node \".canonfig/.runtime/hook-runner.mjs\" --hook ${hook.id} --target ${target} --event ${hook.event}`;
3
+ }
4
+ export function enabledHooks(context) {
5
+ return context.config.hooks.filter((hook) => hook.enabled);
6
+ }
7
+ function timeoutSeconds(timeoutMs) {
8
+ return Math.max(1, Math.ceil(timeoutMs / 1000));
9
+ }
10
+ export const CLAUDE_EVENT_MAP = {
11
+ session_start: "SessionStart",
12
+ session_end: "SessionEnd",
13
+ prompt_submit: "UserPromptSubmit",
14
+ before_agent: "PreInvocation",
15
+ after_agent: "PostInvocation",
16
+ before_tool: "PreToolUse",
17
+ after_tool: "PostToolUse",
18
+ before_compact: "PreCompact",
19
+ after_compact: "PostCompact",
20
+ stop: "Stop",
21
+ subagent_start: "SubagentStart",
22
+ subagent_stop: "SubagentStop",
23
+ };
24
+ export const CODEX_EVENT_MAP = {
25
+ session_start: "SessionStart",
26
+ session_end: "SessionEnd",
27
+ prompt_submit: "UserPromptSubmit",
28
+ before_tool: "PreToolUse",
29
+ after_tool: "PostToolUse",
30
+ before_compact: "PreCompact",
31
+ after_compact: "PostCompact",
32
+ stop: "Stop",
33
+ subagent_start: "SubagentStart",
34
+ subagent_stop: "SubagentStop",
35
+ };
36
+ export const DEVIN_EVENT_MAP = {
37
+ session_start: "SessionStart",
38
+ session_end: "SessionEnd",
39
+ prompt_submit: "UserPromptSubmit",
40
+ before_tool: "PreToolUse",
41
+ after_tool: "PostToolUse",
42
+ after_compact: "PostCompaction",
43
+ stop: "Stop",
44
+ };
45
+ export const GROK_EVENT_MAP = {
46
+ session_start: "SessionStart",
47
+ session_end: "SessionEnd",
48
+ prompt_submit: "UserPromptSubmit",
49
+ before_tool: "PreToolUse",
50
+ after_tool: "PostToolUse",
51
+ before_compact: "PreCompact",
52
+ after_compact: "PostCompact",
53
+ stop: "Stop",
54
+ subagent_start: "SubagentStart",
55
+ subagent_stop: "SubagentStop",
56
+ };
57
+ export function claudeStyleHooks(context, eventMap = CLAUDE_EVENT_MAP) {
58
+ const hooks = {};
59
+ const diagnostics = [];
60
+ for (const hook of enabledHooks(context)) {
61
+ const nativeEvent = eventMap[hook.event];
62
+ if (!nativeEvent) {
63
+ diagnostics.push({
64
+ level: "warning",
65
+ code: "HOOK_EVENT_UNSUPPORTED",
66
+ target: context.target,
67
+ message: `${context.target} cannot directly map hook event ${hook.event}; it was skipped.`,
68
+ });
69
+ continue;
70
+ }
71
+ const entry = {
72
+ matcher: ".*",
73
+ hooks: [{
74
+ type: "command",
75
+ command: hookCommand(context.target, hook),
76
+ timeout: timeoutSeconds(hook.timeoutMs),
77
+ }],
78
+ };
79
+ (hooks[nativeEvent] ??= []).push(entry);
80
+ }
81
+ return { hooks, diagnostics };
82
+ }
83
+ export function cursorHooks(context) {
84
+ const eventMap = {
85
+ session_start: "sessionStart",
86
+ session_end: "sessionEnd",
87
+ prompt_submit: "beforeSubmitPrompt",
88
+ before_tool: "preToolUse",
89
+ after_tool: "postToolUse",
90
+ before_compact: "preCompact",
91
+ stop: "stop",
92
+ subagent_start: "subagentStart",
93
+ subagent_stop: "subagentStop",
94
+ };
95
+ const hooks = {};
96
+ const diagnostics = [];
97
+ for (const hook of enabledHooks(context)) {
98
+ const event = eventMap[hook.event];
99
+ if (!event) {
100
+ diagnostics.push({
101
+ level: "warning",
102
+ code: "HOOK_EVENT_UNSUPPORTED",
103
+ target: "cursor",
104
+ message: `Cursor cannot directly map hook event ${hook.event}; it was skipped.`,
105
+ });
106
+ continue;
107
+ }
108
+ (hooks[event] ??= []).push({
109
+ command: hookCommand("cursor", hook),
110
+ ...(event === "preToolUse" || event === "postToolUse" ? { matcher: ".*" } : {}),
111
+ });
112
+ }
113
+ return { hooks, diagnostics };
114
+ }
115
+ export function copilotHooks(context) {
116
+ const eventMap = {
117
+ session_start: "sessionStart",
118
+ session_end: "sessionEnd",
119
+ prompt_submit: "userPromptSubmitted",
120
+ before_tool: "preToolUse",
121
+ after_tool: "postToolUse",
122
+ stop: "agentStop",
123
+ subagent_start: "subagentStart",
124
+ subagent_stop: "subagentStop",
125
+ before_compact: "preCompact",
126
+ };
127
+ const hooks = {};
128
+ const diagnostics = [];
129
+ for (const hook of enabledHooks(context)) {
130
+ const event = eventMap[hook.event];
131
+ if (!event) {
132
+ diagnostics.push({
133
+ level: "warning",
134
+ code: "HOOK_EVENT_UNSUPPORTED",
135
+ target: "copilot-cli",
136
+ message: `Copilot CLI cannot directly map hook event ${hook.event}; it was skipped.`,
137
+ });
138
+ continue;
139
+ }
140
+ const command = hookCommand("copilot-cli", hook);
141
+ (hooks[event] ??= []).push({
142
+ type: "command",
143
+ bash: command,
144
+ powershell: command,
145
+ cwd: ".",
146
+ timeoutSec: timeoutSeconds(hook.timeoutMs),
147
+ ...(event === "preToolUse" || event === "postToolUse" ? { matcher: ".*" } : {}),
148
+ });
149
+ }
150
+ return { hooks, diagnostics };
151
+ }
152
+ export function antigravityHooks(context) {
153
+ const eventMap = {
154
+ before_tool: "PreToolUse",
155
+ after_tool: "PostToolUse",
156
+ before_agent: "PreInvocation",
157
+ after_agent: "PostInvocation",
158
+ stop: "Stop",
159
+ };
160
+ const entries = {};
161
+ const diagnostics = [];
162
+ for (const hook of enabledHooks(context)) {
163
+ const event = eventMap[hook.event];
164
+ if (!event) {
165
+ diagnostics.push({
166
+ level: "warning",
167
+ code: "HOOK_EVENT_UNSUPPORTED",
168
+ target: "antigravity",
169
+ message: `Antigravity cannot map hook event ${hook.event}; it was skipped.`,
170
+ });
171
+ continue;
172
+ }
173
+ const handler = {
174
+ type: "command",
175
+ command: hookCommand("antigravity", hook),
176
+ timeout: timeoutSeconds(hook.timeoutMs),
177
+ };
178
+ entries[`canonfig-${hook.id}`] = {
179
+ enabled: true,
180
+ [event]: event === "PreToolUse" || event === "PostToolUse"
181
+ ? [{ matcher: ".*", hooks: [handler] }]
182
+ : [handler],
183
+ };
184
+ }
185
+ return { entries, diagnostics };
186
+ }