dotnet-production-agent-skills 0.2.1 → 0.3.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/README.md CHANGED
@@ -59,7 +59,8 @@ skills/ (canonical portable Agent Skills)
59
59
  ├── Codex → .agents/skills
60
60
  ├── GitHub Copilot → .github/skills | .copilot/skills
61
61
  ├── Claude Code → .claude/skills
62
- └── Cursor → .cursor/skills | .agents/skills
62
+ ├── Cursor → .cursor/skills | .agents/skills
63
+ └── Antigravity → .agents/skills + .agents/hooks.json
63
64
  ```
64
65
 
65
66
  ### Core Engineering Invariant
@@ -286,9 +287,11 @@ External consumers are never assumed absent just because no internal repository
286
287
  | :--- | :--- | :--- |
287
288
  | Universal engineering rules | Managed section in project `.github/copilot-instructions.md` | Every Copilot coding task in that project |
288
289
  | Specialized .NET knowledge | Individual `SKILL.md` files | The coding agent selects relevant skills from their descriptions and the request |
289
- | Deterministic command enforcement | Optional host hooks/guardrails | Only when a supported host integration and policy are explicitly enabled |
290
+ | Deterministic command enforcement | Project hooks/guardrails | Installed for Codex, Copilot, Claude Code, Cursor, and Antigravity in project scope |
290
291
 
291
- The CLI installs and manages guidance; it does not route prompts or load every skill for every task. Clear skill descriptions help the coding agent select relevant knowledge. Universal rules belong in repository instructions. Command blocking belongs in host-supported hooks. Hook installation is currently an extension point in `src/copilot/hooks.ts`; no command hook is installed because a portable supported Copilot hook configuration has not been established.
292
+ The CLI installs and manages guidance; it does not route prompts or load every skill for every task. Clear skill descriptions help the coding agent select relevant knowledge. Universal rules belong in repository instructions. Command blocking belongs in host-supported hooks. Project installs now configure native hooks for every supported coding agent using one shared, dependency-free guard script. Existing unrelated hook entries are preserved.
293
+
294
+ The initial policy blocks force-push, destructive Git reset/clean/branch deletion, broad recursive deletion, and direct access to common credential-bearing files. Safe commands continue normally. Host configuration is written to `.codex/hooks.json`, `.github/hooks/dotnet-production-agent-skills.json`, `.claude/settings.json`, `.cursor/hooks.json`, or `.agents/hooks.json` for Antigravity.
292
295
 
293
296
  Copilot project installs manage only the text between `<!-- dotnet-production-agent-skills:start -->` and `<!-- dotnet-production-agent-skills:end -->`. Existing team instructions outside those markers remain intact. Uninstall removes only the managed section. User-scope Copilot installs install skills into `~/.copilot/skills`; repository instructions apply only to project scope.
294
297
 
@@ -319,7 +322,7 @@ Commands:
319
322
 
320
323
  | Flag | Description | Default |
321
324
  | :--- | :--- | :--- |
322
- | `--target <host>` | Target agent: `all`, `shared`, `codex`, `copilot`, `claude`, `cursor` | `all` |
325
+ | `--target <host>` | Target agent: `all`, `shared`, `codex`, `copilot`, `claude`, `cursor`, `antigravity` | `all` |
323
326
  | `--scope <scope>` | Installation scope: `user` (home dir) or `project` (repository dir) | `user` |
324
327
  | `--project <path>`| Explicit path to repository root (forces `--scope project`) | Current dir |
325
328
  | `--mode <mode>` | Installation mode: `copy` (portable) or `link` (symlinks) | `copy` |
@@ -336,6 +339,7 @@ Commands:
336
339
  | `copilot` | `~/.copilot/skills` | `<project>/.github/skills` |
337
340
  | `claude` | `~/.claude/skills` | `<project>/.claude/skills` |
338
341
  | `cursor` | `~/.cursor/skills` | `<project>/.cursor/skills` |
342
+ | `antigravity` | `~/.agents/skills` | `<project>/.agents/skills` |
339
343
  | `all` | `~/.agents/skills` + `~/.claude/skills` | `<project>/.agents/skills` + `<project>/.claude/skills` |
340
344
 
341
345
  ### Safety & Conflict Handling
@@ -355,6 +359,7 @@ Commands:
355
359
  | **GitHub Copilot** | `.github/skills`, `.agents/skills` | Installs to `.github/skills` (project) or `.copilot/skills` (user) | Surface capabilities vary across VS Code, Visual Studio, and JetBrains. |
356
360
  | **Claude Code** | Portable `SKILL.md` in `.claude/skills` | Direct copy or symlink into `.claude/skills` | Supports project & user scopes. Committed project skills recommended for team consistency. |
357
361
  | **Cursor** | `.cursor/skills` and `.agents/skills` | Installs to `.cursor/skills` or shared `.agents/skills` | Auto-detects compatible `.agents/skills` layouts. |
362
+ | **Google Antigravity** | `.agents/skills` and `.agents/hooks.json` | Uses shared skills plus a native project hook adapter | Works across Antigravity 2.0, CLI, and IDE workspace customization. |
358
363
 
359
364
  ---
360
365
 
@@ -371,7 +376,7 @@ GitHub Actions publishes the package from [`.github/workflows/publish-npm.yml`](
371
376
  - **Workflow dispatch**: choose `keep` to publish the current `package.json` version as-is, or `patch` / `minor` / `major` / `prerelease` to bump first. Optionally override the npm dist-tag, and optionally commit/tag changes back to the branch before publish.
372
377
  3. Dist-tags default from the version (`latest` for stable, `beta` / `next` / `alpha` / `rc` for matching prereleases) unless you override them on dispatch.
373
378
 
374
- `package.json` is the version source of truth. If `package-lock.json` root version drifts, the workflow aligns the lockfile to `package.json` (or the release tag) without inventing a new semver. `keep` does not bump; it only publishes the current version (after any lock sync). Republishing a version that already exists on npm is still rejected.
379
+ For GitHub release events, the semver release tag is authoritative and both package files are aligned to it before the npm availability check. For manual workflow runs, `package.json` remains the version source of truth. If `package-lock.json` drifts, the workflow aligns it without inventing another semver. `keep` does not bump; it only publishes the current version after lock synchronization. Republishing the resolved version is still rejected.
375
380
 
376
381
  Successful publishes are recorded under the repository **npm** environment (Deployments on the GitHub repo page) and link to the published package version on npmjs.com.
377
382
 
package/RELEASE_NOTES.md CHANGED
@@ -1,5 +1,24 @@
1
1
  # Release notes
2
2
 
3
+ ## v0.3.0 — Multi-agent hooks and Antigravity
4
+
5
+ ### New
6
+
7
+ - Added `antigravity` as a first-class CLI target using `.agents/skills` and native project hooks in `.agents/hooks.json`.
8
+ - Project installs can now configure command guardrails for Codex, GitHub Copilot, Claude Code, Cursor, and Google Antigravity. `--target all --scope project` installs all five adapters.
9
+ - Added a shared, dependency-free guard script that blocks force-push, destructive Git operations, broad recursive deletion, and direct access to common credential-bearing files.
10
+ - `list` and `doctor` report each project hook installation. Updates are idempotent, unrelated existing hook entries are preserved, and uninstall removes only package-managed entries.
11
+
12
+ ### Compatibility
13
+
14
+ - Existing targets, scopes, copy/link modes, dry runs, conflict handling, and managed Copilot instructions remain supported.
15
+ - Hooks are installed only for project scope. User-scope behavior is unchanged.
16
+
17
+ ### Release pipeline
18
+
19
+ - GitHub release tags are authoritative for release-triggered npm publishes. A `v0.3.0` release aligns both package files to `0.3.0` before checking npm availability or publishing, even when the tagged commit still contains an already-published version.
20
+ - The workflow now fails early if the resolved tag version and `package.json` ever disagree, preventing an old package version from being checked or published accidentally.
21
+
3
22
  ## Unreleased — Redis caching skill
4
23
 
5
24
  Proposed in [PR #6](https://github.com/dprakash2101/dotnet-production-agent-skills/pull/6); this change is not yet published to npm.
package/dist/src/cli.js CHANGED
@@ -7,11 +7,12 @@ import process from "node:process";
7
7
  import { fileURLToPath } from "node:url";
8
8
  import { createInterface } from "node:readline/promises";
9
9
  import { changeInstructions, inspectInstructions } from "./copilot/instructions.js";
10
+ import { changeHooks, hookTargets, inspectHooks, removeSharedHookScript } from "./hooks.js";
10
11
  import { validateSkills } from "./skills/validation.js";
11
12
  const PACKAGE_ROOT = path.resolve(path.dirname(fileURLToPath(import.meta.url)), "../..");
12
13
  const SKILLS_ROOT = path.join(PACKAGE_ROOT, "skills");
13
14
  const MANIFEST_NAME = ".dotnet-agent-skills.json";
14
- const SUPPORTED_TARGETS = new Set(["all", "shared", "codex", "copilot", "claude", "cursor"]);
15
+ const SUPPORTED_TARGETS = new Set(["all", "shared", "codex", "copilot", "claude", "cursor", "antigravity"]);
15
16
  function usage() {
16
17
  return `Usage: dotnet-production-agent-skills <command> [options]
17
18
 
@@ -23,7 +24,8 @@ Commands:
23
24
  uninstall Remove only skills tracked by this package
24
25
 
25
26
  Options:
26
- --target <all|shared|codex|copilot|claude|cursor> Default: all
27
+ --target <all|shared|codex|copilot|claude|cursor|antigravity>
28
+ Default: all
27
29
  --scope <user|project> Default: user
28
30
  --project <path> Project root; implies project scope
29
31
  --mode <copy|link> Default: copy
@@ -33,8 +35,9 @@ Options:
33
35
  --json JSON output for list/doctor
34
36
  -h, --help Show help
35
37
 
36
- For --target all, one shared .agents/skills installation serves Codex, Copilot,
37
- and Cursor; a second .claude/skills installation serves Claude Code.`;
38
+ For --target all, one shared .agents/skills installation serves compatible agents,
39
+ a second .claude/skills installation serves Claude Code, and project scope installs
40
+ native command-guardrail hooks for Codex, Copilot, Claude, Cursor, and Antigravity.`;
38
41
  }
39
42
  function requireValue(argv, index, option) {
40
43
  const value = argv[index + 1];
@@ -103,6 +106,7 @@ function destinations(options) {
103
106
  copilot: ".copilot/skills",
104
107
  claude: ".claude/skills",
105
108
  cursor: ".cursor/skills",
109
+ antigravity: ".agents/skills",
106
110
  }
107
111
  : {
108
112
  shared: ".agents/skills",
@@ -110,6 +114,7 @@ function destinations(options) {
110
114
  copilot: ".github/skills",
111
115
  claude: ".claude/skills",
112
116
  cursor: ".cursor/skills",
117
+ antigravity: ".agents/skills",
113
118
  };
114
119
  const targets = options.target === "all" ? ["shared", "claude"] : [options.target];
115
120
  const unique = new Map();
@@ -235,6 +240,9 @@ async function installOrUpdate(options, command) {
235
240
  if (instructions.state === "malformed")
236
241
  throw new Error(`malformed managed Copilot instructions: ${instructions.path}`);
237
242
  }
243
+ if (options.scope === "project") {
244
+ await Promise.all(hookTargets(options.target).map((target) => inspectHooks(options.project, PACKAGE_ROOT, target)));
245
+ }
238
246
  const packaged = new Set(skills);
239
247
  const packageJson = JSON.parse(await readFile(path.join(PACKAGE_ROOT, "package.json"), "utf8"));
240
248
  const destinationPlans = [];
@@ -328,6 +336,12 @@ async function installOrUpdate(options, command) {
328
336
  if (!options.dryRun)
329
337
  console.log("Copilot CLI: /skills list | /skills reload | /skills info <skill-name>");
330
338
  }
339
+ if (options.scope === "project") {
340
+ for (const target of hookTargets(options.target)) {
341
+ const action = await changeHooks(options.project, PACKAGE_ROOT, target, "install", options.dryRun);
342
+ console.log(`${action === "skip" ? "skip" : options.dryRun ? `would ${action}` : action} ${target} project hooks`);
343
+ }
344
+ }
331
345
  }
332
346
  async function uninstall(options) {
333
347
  if ((options.target === "copilot" || options.target === "all") && options.scope === "project") {
@@ -335,6 +349,9 @@ async function uninstall(options) {
335
349
  if (instructions.state === "malformed")
336
350
  throw new Error(`malformed managed Copilot instructions: ${instructions.path}`);
337
351
  }
352
+ if (options.scope === "project") {
353
+ await Promise.all(hookTargets(options.target).map((target) => inspectHooks(options.project, PACKAGE_ROOT, target)));
354
+ }
338
355
  const destinationPlans = [];
339
356
  for (const destination of destinations(options)) {
340
357
  const manifest = await readManifest(destination.directory);
@@ -380,6 +397,16 @@ async function uninstall(options) {
380
397
  if (action !== "skip")
381
398
  console.log(`${options.dryRun ? "would remove" : "remove"} Copilot production instructions`);
382
399
  }
400
+ if (options.scope === "project") {
401
+ const targets = hookTargets(options.target);
402
+ for (const target of targets) {
403
+ const action = await changeHooks(options.project, PACKAGE_ROOT, target, "uninstall", options.dryRun);
404
+ if (action !== "skip")
405
+ console.log(`${options.dryRun ? "would remove" : "remove"} ${target} project hooks`);
406
+ }
407
+ if (options.target === "all" && targets.length > 0)
408
+ await removeSharedHookScript(options.project, options.dryRun);
409
+ }
383
410
  }
384
411
  async function inspect(options) {
385
412
  const skills = await packagedSkills();
@@ -405,7 +432,11 @@ async function inspect(options) {
405
432
  }
406
433
  const copilotInstructions = (options.target === "copilot" || options.target === "all") && options.scope === "project"
407
434
  ? await inspectInstructions(options.project) : undefined;
408
- return { packagedSkills: skills, currentPackageVersion: packageJson.version, installations, ...(copilotInstructions ? { copilotInstructions } : {}) };
435
+ const hooks = options.scope === "project"
436
+ ? await Promise.all(hookTargets(options.target).map((target) => inspectHooks(options.project, PACKAGE_ROOT, target)))
437
+ : undefined;
438
+ return { packagedSkills: skills, currentPackageVersion: packageJson.version, installations,
439
+ ...(copilotInstructions ? { copilotInstructions } : {}), ...(hooks ? { hooks } : {}) };
409
440
  }
410
441
  async function validateCanonical() {
411
442
  return validateSkills(SKILLS_ROOT, await packagedSkills());
@@ -442,7 +473,8 @@ async function main() {
442
473
  const unhealthy = report.installations.flatMap((entry) => entry.skills)
443
474
  .filter((entry) => entry.state !== "unchanged" || entry.packaged === "outdated");
444
475
  const instructionsUnhealthy = report.copilotInstructions && !["unchanged", "unmanaged"].includes(report.copilotInstructions.state);
445
- const output = { ...report, validationErrors, healthy: validationErrors.length === 0 && unhealthy.length === 0 && !instructionsUnhealthy };
476
+ const hooksUnhealthy = report.hooks?.some((hook) => hook.state !== "unchanged") ?? false;
477
+ const output = { ...report, validationErrors, healthy: validationErrors.length === 0 && unhealthy.length === 0 && !instructionsUnhealthy && !hooksUnhealthy };
446
478
  if (parsed.options.json)
447
479
  console.log(JSON.stringify(output, null, 2));
448
480
  else {
@@ -459,6 +491,7 @@ async function main() {
459
491
  }
460
492
  if (report.copilotInstructions)
461
493
  console.log(`Copilot instructions: ${report.copilotInstructions.state} ${report.copilotInstructions.path}`);
494
+ report.hooks?.forEach((hook) => console.log(`${hook.target} hooks: ${hook.state} ${hook.config}`));
462
495
  validationErrors.forEach((error) => console.error(`error: ${error}`));
463
496
  }
464
497
  if (command === "doctor" && !output.healthy)
@@ -0,0 +1,138 @@
1
+ import { cp, mkdir, readFile, rm, writeFile } from "node:fs/promises";
2
+ import path from "node:path";
3
+ const OWNER = "dotnet-production-agent-skills";
4
+ const COMMAND = "node .agent-guardrails/guard.mjs";
5
+ const SCRIPT_DIRECTORY = ".agent-guardrails";
6
+ const SCRIPT_NAME = "guard.mjs";
7
+ function isObject(value) {
8
+ return typeof value === "object" && value !== null && !Array.isArray(value);
9
+ }
10
+ async function readJson(file) {
11
+ try {
12
+ const parsed = JSON.parse(await readFile(file, "utf8"));
13
+ if (!isObject(parsed))
14
+ throw new Error("root must be a JSON object");
15
+ return parsed;
16
+ }
17
+ catch (error) {
18
+ if (error instanceof Error && "code" in error && error.code === "ENOENT")
19
+ return {};
20
+ const detail = error instanceof Error ? error.message : String(error);
21
+ throw new Error(`invalid hook configuration ${file}: ${detail}`);
22
+ }
23
+ }
24
+ function locations(projectRoot, target) {
25
+ const configs = {
26
+ codex: ".codex/hooks.json",
27
+ copilot: ".github/hooks/dotnet-production-agent-skills.json",
28
+ claude: ".claude/settings.json",
29
+ cursor: ".cursor/hooks.json",
30
+ antigravity: ".agents/hooks.json",
31
+ };
32
+ return { config: path.join(projectRoot, configs[target]), script: path.join(projectRoot, SCRIPT_DIRECTORY, SCRIPT_NAME) };
33
+ }
34
+ function handler(target) {
35
+ return { type: "command", command: `${COMMAND} ${target}`, timeout: 10 };
36
+ }
37
+ function managedGroup(target) {
38
+ return { matcher: target === "antigravity" ? "run_command|view_file|write_to_file|replace_file_content|multi_replace_file_content" : "Bash|PowerShell|Read|Write|Edit", hooks: [handler(target)] };
39
+ }
40
+ function containsManaged(value) {
41
+ return JSON.stringify(value).includes(COMMAND);
42
+ }
43
+ function withoutManaged(value) {
44
+ return Array.isArray(value) ? value.filter((entry) => !containsManaged(entry)) : [];
45
+ }
46
+ function mergeConfig(current, target, install) {
47
+ if (target === "copilot") {
48
+ return install ? {
49
+ version: 1,
50
+ description: `${OWNER} project command guardrails`,
51
+ hooks: { preToolUse: [{ type: "command", command: `${COMMAND} copilot`, timeoutSec: 10 }] },
52
+ } : {};
53
+ }
54
+ if (target === "antigravity") {
55
+ const next = { ...current };
56
+ if (install)
57
+ next[OWNER] = { enabled: true, PreToolUse: [managedGroup(target)] };
58
+ else
59
+ delete next[OWNER];
60
+ return next;
61
+ }
62
+ const hooks = isObject(current.hooks) ? { ...current.hooks } : {};
63
+ const event = target === "cursor" ? "preToolUse" : "PreToolUse";
64
+ const entries = withoutManaged(hooks[event]);
65
+ if (install)
66
+ entries.push(target === "cursor"
67
+ ? { command: `${COMMAND} cursor`, timeout: 10, matcher: "Shell|Read|Write", failClosed: true }
68
+ : managedGroup(target));
69
+ if (entries.length)
70
+ hooks[event] = entries;
71
+ else
72
+ delete hooks[event];
73
+ const next = { ...current };
74
+ if (target === "cursor")
75
+ next.version = typeof current.version === "number" ? current.version : 1;
76
+ if (Object.keys(hooks).length)
77
+ next.hooks = hooks;
78
+ else
79
+ delete next.hooks;
80
+ return next;
81
+ }
82
+ async function expectedScript(packageRoot) {
83
+ return readFile(path.join(packageRoot, "hooks", SCRIPT_NAME), "utf8");
84
+ }
85
+ export async function inspectHooks(projectRoot, packageRoot, target) {
86
+ const files = locations(projectRoot, target);
87
+ try {
88
+ const [current, script] = await Promise.all([readJson(files.config), readFile(files.script, "utf8")]);
89
+ const expected = mergeConfig(current, target, true);
90
+ const configMatches = JSON.stringify(current) === JSON.stringify(expected);
91
+ return { target, state: configMatches && script === await expectedScript(packageRoot) ? "unchanged" : "modified", ...files };
92
+ }
93
+ catch (error) {
94
+ if (error instanceof Error && "code" in error && error.code === "ENOENT")
95
+ return { target, state: "missing", ...files };
96
+ throw error;
97
+ }
98
+ }
99
+ export async function changeHooks(projectRoot, packageRoot, target, action, dryRun) {
100
+ const files = locations(projectRoot, target);
101
+ const current = await readJson(files.config);
102
+ const next = mergeConfig(current, target, action === "install");
103
+ const hasConfigChange = JSON.stringify(current) !== JSON.stringify(next);
104
+ let scriptMatches = false;
105
+ try {
106
+ scriptMatches = await readFile(files.script, "utf8") === await expectedScript(packageRoot);
107
+ }
108
+ catch { /* missing */ }
109
+ if (action === "install") {
110
+ if (!hasConfigChange && scriptMatches)
111
+ return "skip";
112
+ if (!dryRun) {
113
+ await mkdir(path.dirname(files.config), { recursive: true });
114
+ await mkdir(path.dirname(files.script), { recursive: true });
115
+ await writeFile(files.config, `${JSON.stringify(next, null, 2)}\n`, "utf8");
116
+ await cp(path.join(packageRoot, "hooks", SCRIPT_NAME), files.script);
117
+ }
118
+ return Object.keys(current).length ? "update" : "install";
119
+ }
120
+ if (!hasConfigChange)
121
+ return "skip";
122
+ if (!dryRun) {
123
+ if (Object.keys(next).length)
124
+ await writeFile(files.config, `${JSON.stringify(next, null, 2)}\n`, "utf8");
125
+ else
126
+ await rm(files.config, { force: true });
127
+ }
128
+ return "remove";
129
+ }
130
+ export function hookTargets(target) {
131
+ if (target === "all")
132
+ return ["codex", "copilot", "claude", "cursor", "antigravity"];
133
+ return target === "shared" ? [] : [target];
134
+ }
135
+ export async function removeSharedHookScript(projectRoot, dryRun) {
136
+ if (!dryRun)
137
+ await rm(path.join(projectRoot, SCRIPT_DIRECTORY), { recursive: true, force: true });
138
+ }
@@ -0,0 +1,43 @@
1
+ #!/usr/bin/env node
2
+
3
+ const host = process.argv[2] ?? "unknown";
4
+ let input = {};
5
+ try {
6
+ const chunks = [];
7
+ for await (const chunk of process.stdin) chunks.push(chunk);
8
+ input = JSON.parse(Buffer.concat(chunks).toString("utf8") || "{}");
9
+ } catch {
10
+ process.stderr.write("Guardrail input was not valid JSON.\n");
11
+ process.exit(2);
12
+ }
13
+
14
+ const tool = input.tool_name ?? input.toolName ?? input.toolCall?.name ?? "";
15
+ const args = input.tool_input ?? input.toolArgs ?? input.toolCall?.args ?? {};
16
+ const command = String(args.command ?? args.CommandLine ?? args.script ?? "").trim();
17
+ const file = String(args.file_path ?? args.path ?? args.TargetFile ?? args.AbsolutePath ?? "").replaceAll("\\", "/");
18
+
19
+ const checks = [
20
+ [/\bgit\s+push\b[^\n;&|]*(?:--force(?:-with-lease)?|-f)\b/i, "Force-push is blocked. Use a normal push or request explicit human approval."],
21
+ [/\bgit\s+(?:reset\s+--hard|clean\s+-[^\s]*f[^\s]*|branch\s+-D)(?:\s|$)/i, "Destructive Git operations are blocked. Preserve work and request explicit human approval."],
22
+ [/(?:^|[;&|]\s*)(?:rm\s+-[^\s]*r[^\s]*f|Remove-Item\b[^\n;&|]*-Recurse[^\n;&|]*-Force)\s+(?:\/|~|\$HOME|\$env:USERPROFILE)(?:\s|$)/i, "Recursive deletion of a broad system or home path is blocked."],
23
+ [/(?:cat|type|Get-Content|more|less)\s+[^\n;&|]*(?:\.env(?:\.|\s|$)|id_(?:rsa|ed25519)|\.pem\b|\.pfx\b|secrets\.json\b)/i, "Reading credential-bearing files through the shell is blocked."],
24
+ ];
25
+
26
+ const sensitivePath = /(?:^|\/)(?:\.env(?:\.[^/]*)?|id_(?:rsa|ed25519)|[^/]*\.(?:pem|pfx)|secrets\.json)$/i;
27
+ let reason = sensitivePath.test(file) ? "Direct access to a credential-bearing file is blocked." : "";
28
+ if (!reason && command) reason = checks.find(([pattern]) => pattern.test(command))?.[1] ?? "";
29
+
30
+ function response(decision, message = "") {
31
+ if (host === "cursor") return decision === "deny"
32
+ ? { permission: "deny", user_message: message, agent_message: message }
33
+ : { permission: "allow" };
34
+ if (host === "antigravity") return { decision, ...(message ? { reason: message } : {}) };
35
+ if (host === "copilot") return decision === "deny"
36
+ ? { permissionDecision: "deny", permissionDecisionReason: message }
37
+ : { permissionDecision: "allow" };
38
+ return decision === "deny" ? {
39
+ hookSpecificOutput: { hookEventName: "PreToolUse", permissionDecision: "deny", permissionDecisionReason: message },
40
+ } : {};
41
+ }
42
+
43
+ process.stdout.write(`${JSON.stringify(response(reason ? "deny" : "allow", reason))}\n`);
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "dotnet-production-agent-skills",
3
- "version": "0.2.1",
3
+ "version": "0.3.0",
4
4
  "description": "Portable production .NET engineering skills for coding agents",
5
5
  "license": "MIT",
6
6
  "author": {
@@ -21,6 +21,7 @@
21
21
  },
22
22
  "files": [
23
23
  "dist/src",
24
+ "hooks",
24
25
  "copilot",
25
26
  "skills",
26
27
  "RELEASE_NOTES.md",
@@ -36,7 +37,7 @@
36
37
  },
37
38
  "scripts": {
38
39
  "build": "tsc -p tsconfig.json",
39
- "test": "npm run build && node --test dist/test/cli.test.js",
40
+ "test": "npm run build && node --test dist/test/cli.test.js scripts/*.test.mjs",
40
41
  "validate": "python3 scripts/validate_skills.py && npm test",
41
42
  "prepack": "npm run build"
42
43
  },
@@ -50,7 +51,8 @@
50
51
  "codex",
51
52
  "copilot",
52
53
  "claude-code",
53
- "cursor"
54
+ "cursor",
55
+ "antigravity"
54
56
  ],
55
57
  "dependencies": {
56
58
  "yaml": "^2.9.1"
@@ -1 +0,0 @@
1
- export {};