@jeger-ai/opengantry 3.7.0 → 3.8.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 (51) hide show
  1. package/README.md +15 -15
  2. package/dist/cli/commands/cage.d.ts +16 -0
  3. package/dist/cli/commands/cage.d.ts.map +1 -1
  4. package/dist/cli/commands/cage.js +77 -8
  5. package/dist/cli/commands/cage.js.map +1 -1
  6. package/dist/cli/lib/cage/cage-config.d.ts +29 -0
  7. package/dist/cli/lib/cage/cage-config.d.ts.map +1 -0
  8. package/dist/cli/lib/cage/cage-config.js +143 -0
  9. package/dist/cli/lib/cage/cage-config.js.map +1 -0
  10. package/dist/cli/lib/cage/cage-process.d.ts +1 -1
  11. package/dist/cli/lib/cage/cage-process.d.ts.map +1 -1
  12. package/dist/cli/lib/cage/cage-process.js +2 -2
  13. package/dist/cli/lib/cage/cage-process.js.map +1 -1
  14. package/dist/cli/lib/cage/cage-push-guard.d.ts +26 -0
  15. package/dist/cli/lib/cage/cage-push-guard.d.ts.map +1 -0
  16. package/dist/cli/lib/cage/cage-push-guard.js +114 -0
  17. package/dist/cli/lib/cage/cage-push-guard.js.map +1 -0
  18. package/dist/cli/lib/cage/cage-report.d.ts +37 -2
  19. package/dist/cli/lib/cage/cage-report.d.ts.map +1 -1
  20. package/dist/cli/lib/cage/cage-report.js +50 -4
  21. package/dist/cli/lib/cage/cage-report.js.map +1 -1
  22. package/dist/cli/lib/cage/cage-revert.d.ts +1 -1
  23. package/dist/cli/lib/cage/cage-rules.d.ts +49 -3
  24. package/dist/cli/lib/cage/cage-rules.d.ts.map +1 -1
  25. package/dist/cli/lib/cage/cage-rules.js +145 -7
  26. package/dist/cli/lib/cage/cage-rules.js.map +1 -1
  27. package/dist/cli/lib/cage/cage-run.d.ts +22 -2
  28. package/dist/cli/lib/cage/cage-run.d.ts.map +1 -1
  29. package/dist/cli/lib/cage/cage-run.js +85 -17
  30. package/dist/cli/lib/cage/cage-run.js.map +1 -1
  31. package/dist/cli/lib/cage/cage-snapshot.d.ts +11 -0
  32. package/dist/cli/lib/cage/cage-snapshot.d.ts.map +1 -1
  33. package/dist/cli/lib/cage/cage-snapshot.js +60 -18
  34. package/dist/cli/lib/cage/cage-snapshot.js.map +1 -1
  35. package/dist/cli/lib/cage/cage-suggest.d.ts +28 -0
  36. package/dist/cli/lib/cage/cage-suggest.d.ts.map +1 -0
  37. package/dist/cli/lib/cage/cage-suggest.js +164 -0
  38. package/dist/cli/lib/cage/cage-suggest.js.map +1 -0
  39. package/dist/cli/lib/cage/cage-watch.d.ts +36 -0
  40. package/dist/cli/lib/cage/cage-watch.d.ts.map +1 -0
  41. package/dist/cli/lib/cage/cage-watch.js +92 -0
  42. package/dist/cli/lib/cage/cage-watch.js.map +1 -0
  43. package/dist/cli/lib/version.gen.d.ts +1 -1
  44. package/dist/cli/lib/version.gen.js +1 -1
  45. package/dist/cli/program-cage.d.ts +9 -2
  46. package/dist/cli/program-cage.d.ts.map +1 -1
  47. package/dist/cli/program-cage.js +50 -6
  48. package/dist/cli/program-cage.js.map +1 -1
  49. package/package.json +1 -1
  50. package/templates/.gitagent/foreman/SUBSTRATE.version.json +1 -1
  51. package/templates/integrations/compatibility.json +1 -1
package/README.md CHANGED
@@ -141,30 +141,26 @@ See [`docs/DOMAINS.md`](docs/DOMAINS.md) for adapter details and [`docs/AGENT-GR
141
141
 
142
142
  ## Try it in 60 seconds: `gantry cage`
143
143
 
144
- No setup. Wrap any agent command:
144
+ No setup. Start your agent inside the cage, exactly as you normally would:
145
145
 
146
146
  ```bash
147
- npx -p @jeger-ai/opengantry opengantry-cage -- <agent command>
148
- # installed: gantry cage -- <agent command>
147
+ npx -p @jeger-ai/opengantry opengantry-cage -- claude
148
+ # installed (npm install -g @jeger-ai/opengantry): gantry cage -- claude, gantry cage -- aider
149
149
  ```
150
150
 
151
- Cage checks once, after the command exits, so use it for one-shot, headless agent runs rather than interactive sessions (install once; `npx` re-resolves the package on each launch):
151
+ While the session runs, cage checks about once a second and restores changes to CI configs, `.env` / `.env.*`, `.git/config`, `.git/hooks/` (and `core.hooksPath`) and your manifest `forbidden_zones` from an in-memory snapshot. Lockfile changes are listed but kept. A `git push` from inside the session is refused when the outgoing commits touch those paths, and the agent sees why. When you quit, cage prints one report: everything it restored, any refused pushes, exit `3` if anything was touched (otherwise the agent's own exit code).
152
152
 
153
- ```bash
154
- npm install -g @jeger-ai/opengantry
155
- gantry cage -- claude -p "Fix the failing test in auth.spec.ts"
156
- gantry cage -- aider --message "Refactor DB queries" --no-auto-commits --yes-always
153
+ Nothing is printed into the agent's screen while it runs: events go to a session log in your temp dir (path shown at start), with a terminal bell per restore. If the agent keeps rewriting the same file, cage stops restoring it after 3 tries, marks it contested and restores it once at exit.
157
154
 
158
- # single-task shell functions
159
- c-run() { gantry cage -- claude -p "$*"; }
160
- a-run() { gantry cage -- aider --message "$*" --no-auto-commits --yes-always; }
161
- ```
155
+ To protect more of your own repo (migrations, infrastructure, key files), let cage propose rules, review them, and commit `.cage.yaml`:
162
156
 
163
- Don't alias `claude` or `aider` themselves: both open an interactive REPL, and a protected-file edit early in a long session stays in place (and can be pushed) until you quit. Keep Aider's auto-commits off: cage restores the working tree, not git history or a remote, so a committed or pushed change needs a manual fix.
157
+ ```bash
158
+ gantry cage suggest --write # writes .cage.yaml.suggested; review it, keep what you want as .cage.yaml, commit
159
+ ```
164
160
 
165
- After the command exits, cage restores changes to CI configs, `.env` / `.env.*`, `.git/config`, `.git/hooks/` (and `core.hooksPath`), and your manifest `forbidden_zones`. Lockfile changes are listed but kept. Exit `3` means something was reverted; otherwise the command's own exit code passes through.
161
+ `.cage.yaml` can only add protection; it can never switch a built-in rule off. To relax one path for a single run, pass `--allow-override <path>`: cage then reports that path instead of restoring it, and says so at start and in the report.
166
162
 
167
- Cage detects and reverts **after** the command exits. It does not see reads, network calls, or writes outside the protected set. For scope enforcement during a run, use missions (below). Details: [`docs/FEATURES.md`](docs/FEATURES.md#zero-config-cage-gantry-cage).
163
+ Cage restores; it does not block. A change can be committed in the second before the next check, `git push --no-verify` skips the push guard, and a committed change stays in local history (cage restores the working tree only). Reads, network calls and writes outside the protected set are not seen. `--no-watch` checks once at exit instead. For scope enforced on every write, use missions (below). Details: [`docs/FEATURES.md`](docs/FEATURES.md#zero-config-cage-gantry-cage).
168
164
 
169
165
  ---
170
166
 
@@ -243,6 +239,10 @@ On failure, external agents ingest `findings[]`:
243
239
  }
244
240
  ```
245
241
 
242
+ **v3.8.0:** per-project cage rules: `.cage.yaml` adds paths and globs to the protected set (additive only, self-protected, fail closed), `gantry cage suggest` proposes them for review, and `--allow-override <path>` reports instead of restoring one path for a run, always announced ([ADR-0048](.gitagent/out-of-scope/ADR-0048-cage-trust-model.md)). See [Try it in 60 seconds](#try-it-in-60-seconds-gantry-cage).
243
+
244
+ **v3.7.1:** `gantry cage` restores protected files live during interactive agent sessions (default; `--no-watch` for exit-only) and refuses `git push` of protected paths from inside the session. See [Try it in 60 seconds](#try-it-in-60-seconds-gantry-cage).
245
+
246
246
  **v3.7.0:** zero-config `gantry cage -- <cmd>` (and `npx -p @jeger-ai/opengantry opengantry-cage -- <cmd>`) restores CI configs, `.env*`, git hooks/config and manifest forbidden zones after a one-shot agent run, reports lockfile changes, and exits 3 on tampering. See [Try it in 60 seconds](#try-it-in-60-seconds-gantry-cage).
247
247
 
248
248
  **v3.6.0:** `gate_adapter: tsc|eslint` fails closed on chained `gate_command`s (`GXT_GATE_ADAPTER_MISCONFIG`). `gantry hooks install` arms the tracked pre-commit hook so `gantry tmvc guard --strict` blocks commits outside the pinned mission. Optional local sqlite-vec contract drift index (`gantry contract embed`, `legislate --from-intent --embedding-file`). See [`docs/CHANGELOG.md`](docs/CHANGELOG.md) upgrade notes.
@@ -3,6 +3,22 @@ export interface CageCliOptions {
3
3
  json?: boolean;
4
4
  reportOnly?: boolean;
5
5
  maxFileBytes?: string;
6
+ /** Commander sets false for `--no-watch`. */
7
+ watch?: boolean;
8
+ watchIntervalMs?: string;
9
+ allowOverride?: string[];
6
10
  }
7
11
  export declare function runCageCommand(options: CageCliOptions): Promise<void>;
12
+ /** `gantry cage-guard pre-push --root <dir> [--plan <file>]`: called by the cage session pre-push hook. */
13
+ export declare function runCageGuardPrePush(options: {
14
+ root: string;
15
+ plan?: string;
16
+ }): Promise<void>;
17
+ export interface CageSuggestCliOptions {
18
+ write?: boolean;
19
+ force?: boolean;
20
+ json?: boolean;
21
+ }
22
+ /** `gantry cage suggest`: propose `.cage.yaml` entries; prints them, or writes `.cage.yaml.suggested`. */
23
+ export declare function runCageSuggestCommand(options: CageSuggestCliOptions): Promise<void>;
8
24
  //# sourceMappingURL=cage.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"cage.d.ts","sourceRoot":"","sources":["../../../src/cli/commands/cage.ts"],"names":[],"mappings":"AAKA,MAAM,WAAW,cAAc;IAC7B,OAAO,EAAE,MAAM,EAAE,CAAC;IAClB,IAAI,CAAC,EAAE,OAAO,CAAC;IACf,UAAU,CAAC,EAAE,OAAO,CAAC;IACrB,YAAY,CAAC,EAAE,MAAM,CAAC;CACvB;AASD,wBAAsB,cAAc,CAAC,OAAO,EAAE,cAAc,GAAG,OAAO,CAAC,IAAI,CAAC,CAuB3E"}
1
+ {"version":3,"file":"cage.d.ts","sourceRoot":"","sources":["../../../src/cli/commands/cage.ts"],"names":[],"mappings":"AAgBA,MAAM,WAAW,cAAc;IAC7B,OAAO,EAAE,MAAM,EAAE,CAAC;IAClB,IAAI,CAAC,EAAE,OAAO,CAAC;IACf,UAAU,CAAC,EAAE,OAAO,CAAC;IACrB,YAAY,CAAC,EAAE,MAAM,CAAC;IACtB,6CAA6C;IAC7C,KAAK,CAAC,EAAE,OAAO,CAAC;IAChB,eAAe,CAAC,EAAE,MAAM,CAAC;IACzB,aAAa,CAAC,EAAE,MAAM,EAAE,CAAC;CAC1B;AASD,wBAAsB,cAAc,CAAC,OAAO,EAAE,cAAc,GAAG,OAAO,CAAC,IAAI,CAAC,CA0C3E;AAED,2GAA2G;AAC3G,wBAAsB,mBAAmB,CAAC,OAAO,EAAE;IAAE,IAAI,EAAE,MAAM,CAAC;IAAC,IAAI,CAAC,EAAE,MAAM,CAAA;CAAE,GAAG,OAAO,CAAC,IAAI,CAAC,CAejG;AAED,MAAM,WAAW,qBAAqB;IACpC,KAAK,CAAC,EAAE,OAAO,CAAC;IAChB,KAAK,CAAC,EAAE,OAAO,CAAC;IAChB,IAAI,CAAC,EAAE,OAAO,CAAC;CAChB;AAED,0GAA0G;AAC1G,wBAAsB,qBAAqB,CAAC,OAAO,EAAE,qBAAqB,GAAG,OAAO,CAAC,IAAI,CAAC,CAmBzF"}
@@ -1,8 +1,14 @@
1
- import { logError, setExitCode } from "../lib/cli-io.js";
1
+ import fs from "node:fs";
2
+ import path from "node:path";
3
+ import { logError, readStdinIfEmpty, setExitCode } from "../lib/cli-io.js";
2
4
  import { emitCliJson, runUserCommandAsync } from "../lib/command-boundary.js";
3
- import { formatCageReport } from "../lib/cage/cage-report.js";
4
- import { runCage } from "../lib/cage/cage-run.js";
5
- function parseMaxFileBytes(raw) {
5
+ import { CAGE_SESSION_LOG_ENV, findPushViolations } from "../lib/cage/cage-push-guard.js";
6
+ import { formatCageOverrideBanner, formatCageReport } from "../lib/cage/cage-report.js";
7
+ import { buildCagePlan, parseSerializedCagePlan } from "../lib/cage/cage-rules.js";
8
+ import { resolveCageRoot, runCage } from "../lib/cage/cage-run.js";
9
+ import { assertOutsideCageSession, formatCageSuggestionsYaml, suggestCageRules, writeCageSuggestions, } from "../lib/cage/cage-suggest.js";
10
+ import { CAGE_CONFIG_FILE } from "../lib/cage/cage-config.js";
11
+ function parseNonNegativeInt(raw) {
6
12
  if (raw === undefined)
7
13
  return { ok: true, value: undefined };
8
14
  const t = raw.trim();
@@ -17,14 +23,34 @@ export async function runCageCommand(options) {
17
23
  setExitCode(2);
18
24
  return;
19
25
  }
20
- const max = parseMaxFileBytes(options.maxFileBytes);
21
- if (!max.ok) {
22
- logError("cage: --max-file-bytes must be a non-negative integer");
26
+ const max = parseNonNegativeInt(options.maxFileBytes);
27
+ const interval = parseNonNegativeInt(options.watchIntervalMs);
28
+ if (!max.ok || !interval.ok || interval.value === 0) {
29
+ logError("cage: --max-file-bytes must be a non-negative integer and --watch-interval-ms a positive integer");
23
30
  setExitCode(2);
24
31
  return;
25
32
  }
26
33
  await runUserCommandAsync({ json: options.json }, async () => {
27
- const report = await runCage({ command, reportOnly: options.reportOnly, maxFileBytes: max.value });
34
+ const report = await runCage({
35
+ command,
36
+ reportOnly: options.reportOnly,
37
+ maxFileBytes: max.value,
38
+ watch: options.watch !== false,
39
+ watchIntervalMs: interval.value,
40
+ allowOverride: options.allowOverride,
41
+ // The only lines printed before the command starts; nothing is written while it runs except a bell.
42
+ onStart: ({ sessionLog, protectedFiles, watch, overrides, configCommitted }) => {
43
+ const mode = watch ? "watching" : "checking at exit";
44
+ const logPart = sessionLog ? `; live events: ${sessionLog}` : "";
45
+ console.error(`cage: ${mode} ${String(protectedFiles)} protected file(s)${logPart}`);
46
+ if (configCommitted === false) {
47
+ console.error(`cage: warning: ${CAGE_CONFIG_FILE} is untracked or has uncommitted changes; review and commit it`);
48
+ }
49
+ const banner = formatCageOverrideBanner(overrides);
50
+ if (banner)
51
+ console.error(banner);
52
+ },
53
+ });
28
54
  if (options.json) {
29
55
  emitCliJson(report);
30
56
  }
@@ -36,4 +62,47 @@ export async function runCageCommand(options) {
36
62
  setExitCode(report.exit_code);
37
63
  });
38
64
  }
65
+ /** `gantry cage-guard pre-push --root <dir> [--plan <file>]`: called by the cage session pre-push hook. */
66
+ export async function runCageGuardPrePush(options) {
67
+ const stdin = await readStdinIfEmpty("");
68
+ // The session plan (with its overrides) when the hook passes one; a fresh plan otherwise.
69
+ const plan = options.plan ? parseSerializedCagePlan(fs.readFileSync(options.plan, "utf8")) : buildCagePlan(options.root);
70
+ const violations = findPushViolations(plan, stdin);
71
+ if (violations.length === 0)
72
+ return;
73
+ const list = violations.map((v) => `${v.path} (${v.rule})`).join(", ");
74
+ console.error(`gantry cage: push refused: outgoing commits change protected paths: ${list}`);
75
+ console.error("gantry cage: revert those commits, or push outside the cage session.");
76
+ const logPath = process.env[CAGE_SESSION_LOG_ENV];
77
+ if (logPath && fs.existsSync(logPath)) {
78
+ const event = { at: new Date().toISOString(), event: "push_refused", paths: violations };
79
+ fs.appendFileSync(logPath, `${JSON.stringify(event)}\n`);
80
+ }
81
+ setExitCode(1);
82
+ }
83
+ /** `gantry cage suggest`: propose `.cage.yaml` entries; prints them, or writes `.cage.yaml.suggested`. */
84
+ export async function runCageSuggestCommand(options) {
85
+ await runUserCommandAsync({ json: options.json }, () => {
86
+ assertOutsideCageSession();
87
+ const root = resolveCageRoot(process.cwd());
88
+ const result = suggestCageRules(root);
89
+ const yaml = formatCageSuggestionsYaml(result, fs.existsSync(path.join(root, CAGE_CONFIG_FILE)));
90
+ const n = result.suggestions.length;
91
+ let written = null;
92
+ if (options.write && n > 0)
93
+ written = writeCageSuggestions(root, yaml, options.force === true);
94
+ if (options.json) {
95
+ emitCliJson({ ...result, written: written ? path.basename(written) : null });
96
+ }
97
+ else if (!options.write) {
98
+ process.stdout.write(yaml);
99
+ }
100
+ const covered = `${String(result.already_covered)} already protected`;
101
+ if (written)
102
+ console.error(`cage suggest: wrote ${path.basename(written)} (${String(n)} entr${n === 1 ? "y" : "ies"}; ${covered}); review it, then move what you keep into ${CAGE_CONFIG_FILE}`);
103
+ else
104
+ console.error(`cage suggest: ${String(n)} new suggestion(s); ${covered}`);
105
+ return Promise.resolve();
106
+ });
107
+ }
39
108
  //# sourceMappingURL=cage.js.map
@@ -1 +1 @@
1
- {"version":3,"file":"cage.js","sourceRoot":"","sources":["../../../src/cli/commands/cage.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,QAAQ,EAAE,WAAW,EAAE,MAAM,kBAAkB,CAAC;AACzD,OAAO,EAAE,WAAW,EAAE,mBAAmB,EAAE,MAAM,4BAA4B,CAAC;AAC9E,OAAO,EAAE,gBAAgB,EAAE,MAAM,4BAA4B,CAAC;AAC9D,OAAO,EAAE,OAAO,EAAE,MAAM,yBAAyB,CAAC;AASlD,SAAS,iBAAiB,CAAC,GAAuB;IAChD,IAAI,GAAG,KAAK,SAAS;QAAE,OAAO,EAAE,EAAE,EAAE,IAAI,EAAE,KAAK,EAAE,SAAS,EAAE,CAAC;IAC7D,MAAM,CAAC,GAAG,GAAG,CAAC,IAAI,EAAE,CAAC;IACrB,IAAI,CAAC,OAAO,CAAC,IAAI,CAAC,CAAC,CAAC;QAAE,OAAO,EAAE,EAAE,EAAE,KAAK,EAAE,CAAC;IAC3C,OAAO,EAAE,EAAE,EAAE,IAAI,EAAE,KAAK,EAAE,MAAM,CAAC,QAAQ,CAAC,CAAC,EAAE,EAAE,CAAC,EAAE,CAAC;AACrD,CAAC;AAED,MAAM,CAAC,KAAK,UAAU,cAAc,CAAC,OAAuB;IAC1D,MAAM,OAAO,GAAG,OAAO,CAAC,OAAO,CAAC,CAAC,CAAC,KAAK,IAAI,CAAC,CAAC,CAAC,OAAO,CAAC,OAAO,CAAC,KAAK,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,OAAO,CAAC,OAAO,CAAC;IACzF,IAAI,OAAO,CAAC,MAAM,KAAK,CAAC,EAAE,CAAC;QACzB,QAAQ,CAAC,yDAAyD,CAAC,CAAC;QACpE,WAAW,CAAC,CAAC,CAAC,CAAC;QACf,OAAO;IACT,CAAC;IACD,MAAM,GAAG,GAAG,iBAAiB,CAAC,OAAO,CAAC,YAAY,CAAC,CAAC;IACpD,IAAI,CAAC,GAAG,CAAC,EAAE,EAAE,CAAC;QACZ,QAAQ,CAAC,uDAAuD,CAAC,CAAC;QAClE,WAAW,CAAC,CAAC,CAAC,CAAC;QACf,OAAO;IACT,CAAC;IAED,MAAM,mBAAmB,CAAC,EAAE,IAAI,EAAE,OAAO,CAAC,IAAI,EAAE,EAAE,KAAK,IAAI,EAAE;QAC3D,MAAM,MAAM,GAAG,MAAM,OAAO,CAAC,EAAE,OAAO,EAAE,UAAU,EAAE,OAAO,CAAC,UAAU,EAAE,YAAY,EAAE,GAAG,CAAC,KAAK,EAAE,CAAC,CAAC;QACnG,IAAI,OAAO,CAAC,IAAI,EAAE,CAAC;YACjB,WAAW,CAAC,MAAM,CAAC,CAAC;QACtB,CAAC;aAAM,CAAC;YACN,KAAK,MAAM,IAAI,IAAI,gBAAgB,CAAC,MAAM,CAAC;gBAAE,OAAO,CAAC,KAAK,CAAC,IAAI,CAAC,CAAC;QACnE,CAAC;QACD,IAAI,MAAM,CAAC,SAAS,KAAK,CAAC;YAAE,WAAW,CAAC,MAAM,CAAC,SAAS,CAAC,CAAC;IAC5D,CAAC,CAAC,CAAC;AACL,CAAC"}
1
+ {"version":3,"file":"cage.js","sourceRoot":"","sources":["../../../src/cli/commands/cage.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,29 @@
1
+ /** Per-project cage config at the cage root. Additive only: it can protect more, never less. */
2
+ export declare const CAGE_CONFIG_FILE = ".cage.yaml";
3
+ export type CageConfigMode = "revert" | "report";
4
+ /** One `protect:` entry: a root-relative path (file or directory tree) or a glob over root-relative paths. */
5
+ export interface CageProtectEntry {
6
+ kind: "path" | "glob";
7
+ /** Normalized POSIX value: no leading `./`, no trailing `/`. */
8
+ value: string;
9
+ mode: CageConfigMode;
10
+ }
11
+ export interface CageConfig {
12
+ present: boolean;
13
+ entries: CageProtectEntry[];
14
+ }
15
+ /** Root-relative POSIX path or glob; rejects absolute, `..`, and empty values. */
16
+ export declare function normalizeCageRelPath(raw: string): string | null;
17
+ /**
18
+ * Read `.cage.yaml` at the cage root. A missing file is the zero-config default; anything unreadable,
19
+ * malformed or subtractive throws so cage fails closed before the wrapped command runs.
20
+ */
21
+ export declare function loadCageConfig(root: string): CageConfig;
22
+ /**
23
+ * Whether `.cage.yaml` is on record: tracked and identical to `HEAD`. Null outside a git work tree.
24
+ * Unreviewed rules can only protect more, so cage warns rather than refuses.
25
+ */
26
+ export declare function cageConfigCommitted(root: string): boolean | null;
27
+ /** `*` and `?` stay inside one path segment; `**` spans any depth (`**\/x` also matches top-level `x`). */
28
+ export declare function cageGlobToRegExp(glob: string): RegExp;
29
+ //# sourceMappingURL=cage-config.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"cage-config.d.ts","sourceRoot":"","sources":["../../../../src/cli/lib/cage/cage-config.ts"],"names":[],"mappings":"AAMA,gGAAgG;AAChG,eAAO,MAAM,gBAAgB,eAAe,CAAC;AAE7C,MAAM,MAAM,cAAc,GAAG,QAAQ,GAAG,QAAQ,CAAC;AAEjD,8GAA8G;AAC9G,MAAM,WAAW,gBAAgB;IAC/B,IAAI,EAAE,MAAM,GAAG,MAAM,CAAC;IACtB,gEAAgE;IAChE,KAAK,EAAE,MAAM,CAAC;IACd,IAAI,EAAE,cAAc,CAAC;CACtB;AAED,MAAM,WAAW,UAAU;IACzB,OAAO,EAAE,OAAO,CAAC;IACjB,OAAO,EAAE,gBAAgB,EAAE,CAAC;CAC7B;AAoCD,kFAAkF;AAClF,wBAAgB,oBAAoB,CAAC,GAAG,EAAE,MAAM,GAAG,MAAM,GAAG,IAAI,CAK/D;AA2CD;;;GAGG;AACH,wBAAgB,cAAc,CAAC,IAAI,EAAE,MAAM,GAAG,UAAU,CAUvD;AAED;;;GAGG;AACH,wBAAgB,mBAAmB,CAAC,IAAI,EAAE,MAAM,GAAG,OAAO,GAAG,IAAI,CAMhE;AAED,2GAA2G;AAC3G,wBAAgB,gBAAgB,CAAC,IAAI,EAAE,MAAM,GAAG,MAAM,CAiBrD"}
@@ -0,0 +1,143 @@
1
+ import fs from "node:fs";
2
+ import path from "node:path";
3
+ import { parseDocument } from "yaml";
4
+ import { GantryUserError } from "../errors.js";
5
+ import { gitRun } from "../git/git.js";
6
+ /** Per-project cage config at the cage root. Additive only: it can protect more, never less. */
7
+ export const CAGE_CONFIG_FILE = ".cage.yaml";
8
+ /** Keys that would loosen protection. Named in the error so authors know relaxation is a runtime flag. */
9
+ const SUBTRACTIVE_KEYS = new Set([
10
+ "relax",
11
+ "exclude",
12
+ "excludes",
13
+ "allow",
14
+ "allow_override",
15
+ "allow_overrides",
16
+ "ignore",
17
+ "unprotect",
18
+ "override",
19
+ "overrides",
20
+ "disable",
21
+ "skip",
22
+ ]);
23
+ const ENTRY_KEYS = new Set(["path", "glob", "mode"]);
24
+ function configError(message) {
25
+ return new GantryUserError("GXT_CAGE_CONFIG_INVALID", `cage: ${CAGE_CONFIG_FILE}: ${message}`, `${CAGE_CONFIG_FILE} is additive only (protect: entries). Relax a rule for one run with --allow-override <path>.`, 2);
26
+ }
27
+ function unknownKey(key, where) {
28
+ if (SUBTRACTIVE_KEYS.has(key)) {
29
+ return configError(`${where}: "${key}" would remove protection; subtractive keys are not allowed`);
30
+ }
31
+ return configError(`${where}: unknown key "${key}"`);
32
+ }
33
+ /** Root-relative POSIX path or glob; rejects absolute, `..`, and empty values. */
34
+ export function normalizeCageRelPath(raw) {
35
+ const norm = raw.trim().replace(/\\/g, "/").replace(/^(\.\/)+/, "").replace(/\/+$/, "");
36
+ if (!norm || norm === "." || norm.startsWith("/") || /^[A-Za-z]:/.test(norm))
37
+ return null;
38
+ if (norm.split("/").some((seg) => seg === ".." || seg === "." || seg === ""))
39
+ return null;
40
+ return norm;
41
+ }
42
+ function parseEntry(raw, index) {
43
+ const where = `protect[${String(index)}]`;
44
+ if (!raw || typeof raw !== "object" || Array.isArray(raw)) {
45
+ throw configError(`${where}: expected a mapping with path: or glob:`);
46
+ }
47
+ const obj = raw;
48
+ for (const key of Object.keys(obj)) {
49
+ if (!ENTRY_KEYS.has(key))
50
+ throw unknownKey(key, where);
51
+ }
52
+ const hasPath = "path" in obj;
53
+ if (hasPath === "glob" in obj)
54
+ throw configError(`${where}: set exactly one of path: or glob:`);
55
+ const kind = hasPath ? "path" : "glob";
56
+ const rawValue = obj[kind];
57
+ const value = typeof rawValue === "string" ? normalizeCageRelPath(rawValue) : null;
58
+ if (!value)
59
+ throw configError(`${where}: ${kind} must be a root-relative path without ".." or a leading "/"`);
60
+ const mode = obj.mode ?? "revert";
61
+ if (mode !== "revert" && mode !== "report")
62
+ throw configError(`${where}: mode must be revert or report`);
63
+ return { kind, value, mode };
64
+ }
65
+ function parseConfigText(text) {
66
+ const doc = parseDocument(text, { uniqueKeys: true });
67
+ if (doc.errors.length > 0)
68
+ throw configError(`invalid YAML: ${doc.errors[0].message.split("\n")[0]}`);
69
+ let data;
70
+ try {
71
+ // No aliases: a protect list has no use for them, and they are the YAML expansion-bomb vector.
72
+ data = doc.toJS({ maxAliasCount: 0 });
73
+ }
74
+ catch (err) {
75
+ throw configError(`invalid YAML: ${err instanceof Error ? err.message : String(err)}`);
76
+ }
77
+ if (data === null || data === undefined)
78
+ return { present: true, entries: [] };
79
+ if (typeof data !== "object" || Array.isArray(data))
80
+ throw configError("expected a mapping with a protect: list");
81
+ const obj = data;
82
+ for (const key of Object.keys(obj)) {
83
+ if (key !== "protect")
84
+ throw unknownKey(key, "top level");
85
+ }
86
+ const protect = obj.protect ?? [];
87
+ if (!Array.isArray(protect))
88
+ throw configError("protect: must be a list");
89
+ return { present: true, entries: protect.map((e, i) => parseEntry(e, i)) };
90
+ }
91
+ /**
92
+ * Read `.cage.yaml` at the cage root. A missing file is the zero-config default; anything unreadable,
93
+ * malformed or subtractive throws so cage fails closed before the wrapped command runs.
94
+ */
95
+ export function loadCageConfig(root) {
96
+ const file = path.join(root, CAGE_CONFIG_FILE);
97
+ let text;
98
+ try {
99
+ text = fs.readFileSync(file, "utf8");
100
+ }
101
+ catch (err) {
102
+ if (err.code === "ENOENT")
103
+ return { present: false, entries: [] };
104
+ throw configError(`unreadable (${err.code ?? "ERROR"})`);
105
+ }
106
+ return parseConfigText(text);
107
+ }
108
+ /**
109
+ * Whether `.cage.yaml` is on record: tracked and identical to `HEAD`. Null outside a git work tree.
110
+ * Unreviewed rules can only protect more, so cage warns rather than refuses.
111
+ */
112
+ export function cageConfigCommitted(root) {
113
+ const tracked = gitRun(root, ["ls-files", "--", CAGE_CONFIG_FILE]);
114
+ if (!tracked.ok)
115
+ return null;
116
+ if (!tracked.stdout.trim())
117
+ return false;
118
+ const status = gitRun(root, ["status", "--porcelain", "--", CAGE_CONFIG_FILE]);
119
+ return status.ok ? status.stdout.trim() === "" : null;
120
+ }
121
+ /** `*` and `?` stay inside one path segment; `**` spans any depth (`**\/x` also matches top-level `x`). */
122
+ export function cageGlobToRegExp(glob) {
123
+ let re = "";
124
+ for (let i = 0; i < glob.length; i += 1) {
125
+ const ch = glob[i];
126
+ if (ch === "*" && glob[i + 1] === "*") {
127
+ const slash = glob[i + 2] === "/";
128
+ re += slash ? "(?:.*/)?" : ".*";
129
+ i += slash ? 2 : 1;
130
+ }
131
+ else if (ch === "*") {
132
+ re += "[^/]*";
133
+ }
134
+ else if (ch === "?") {
135
+ re += "[^/]";
136
+ }
137
+ else {
138
+ re += ch.replace(/[.+^${}()|[\]\\]/g, "\\$&");
139
+ }
140
+ }
141
+ return new RegExp(`^${re}$`);
142
+ }
143
+ //# sourceMappingURL=cage-config.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"cage-config.js","sourceRoot":"","sources":["../../../../src/cli/lib/cage/cage-config.ts"],"names":[],"mappings":"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"}
@@ -14,5 +14,5 @@ export declare function signalExitCode(signal: NodeJS.Signals): number;
14
14
  * shared process group, and forwarding it again would double-interrupt.
15
15
  * - SIGTERM / SIGHUP are forwarded to the child instead of killing the cage.
16
16
  */
17
- export declare function spawnCaged(argv: readonly string[], cwd: string): Promise<CageProcessResult>;
17
+ export declare function spawnCaged(argv: readonly string[], cwd: string, extraEnv?: NodeJS.ProcessEnv): Promise<CageProcessResult>;
18
18
  //# sourceMappingURL=cage-process.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"cage-process.d.ts","sourceRoot":"","sources":["../../../../src/cli/lib/cage/cage-process.ts"],"names":[],"mappings":"AAGA,MAAM,WAAW,iBAAiB;IAChC,IAAI,EAAE,MAAM,GAAG,IAAI,CAAC;IACpB,MAAM,EAAE,MAAM,CAAC,OAAO,GAAG,IAAI,CAAC;IAC9B,0DAA0D;IAC1D,UAAU,EAAE,MAAM,GAAG,IAAI,CAAC;CAC3B;AAED,kEAAkE;AAClE,wBAAgB,cAAc,CAAC,MAAM,EAAE,MAAM,CAAC,OAAO,GAAG,MAAM,CAG7D;AAID;;;;;;;GAOG;AACH,wBAAsB,UAAU,CAAC,IAAI,EAAE,SAAS,MAAM,EAAE,EAAE,GAAG,EAAE,MAAM,GAAG,OAAO,CAAC,iBAAiB,CAAC,CAkBjG"}
1
+ {"version":3,"file":"cage-process.d.ts","sourceRoot":"","sources":["../../../../src/cli/lib/cage/cage-process.ts"],"names":[],"mappings":"AAGA,MAAM,WAAW,iBAAiB;IAChC,IAAI,EAAE,MAAM,GAAG,IAAI,CAAC;IACpB,MAAM,EAAE,MAAM,CAAC,OAAO,GAAG,IAAI,CAAC;IAC9B,0DAA0D;IAC1D,UAAU,EAAE,MAAM,GAAG,IAAI,CAAC;CAC3B;AAED,kEAAkE;AAClE,wBAAgB,cAAc,CAAC,MAAM,EAAE,MAAM,CAAC,OAAO,GAAG,MAAM,CAG7D;AAID;;;;;;;GAOG;AACH,wBAAsB,UAAU,CAC9B,IAAI,EAAE,SAAS,MAAM,EAAE,EACvB,GAAG,EAAE,MAAM,EACX,QAAQ,GAAE,MAAM,CAAC,UAAe,GAC/B,OAAO,CAAC,iBAAiB,CAAC,CAkB5B"}
@@ -14,9 +14,9 @@ const FORWARDED_SIGNALS = ["SIGTERM", "SIGHUP"];
14
14
  * shared process group, and forwarding it again would double-interrupt.
15
15
  * - SIGTERM / SIGHUP are forwarded to the child instead of killing the cage.
16
16
  */
17
- export async function spawnCaged(argv, cwd) {
17
+ export async function spawnCaged(argv, cwd, extraEnv = {}) {
18
18
  const [command, ...args] = argv;
19
- const child = spawn(command, args, { cwd, stdio: "inherit", env: process.env });
19
+ const child = spawn(command, args, { cwd, stdio: "inherit", env: { ...process.env, ...extraEnv } });
20
20
  const ignoreInt = () => { };
21
21
  const forward = (sig) => {
22
22
  child.kill(sig);
@@ -1 +1 @@
1
- {"version":3,"file":"cage-process.js","sourceRoot":"","sources":["../../../../src/cli/lib/cage/cage-process.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,KAAK,EAAE,MAAM,oBAAoB,CAAC;AAC3C,OAAO,EAAE,MAAM,SAAS,CAAC;AASzB,kEAAkE;AAClE,MAAM,UAAU,cAAc,CAAC,MAAsB;IACnD,MAAM,CAAC,GAAG,EAAE,CAAC,SAAS,CAAC,OAAO,CAAC,MAAM,CAAC,CAAC;IACvC,OAAO,OAAO,CAAC,KAAK,QAAQ,CAAC,CAAC,CAAC,GAAG,GAAG,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC;AAC7C,CAAC;AAED,MAAM,iBAAiB,GAA8B,CAAC,SAAS,EAAE,QAAQ,CAAC,CAAC;AAE3E;;;;;;;GAOG;AACH,MAAM,CAAC,KAAK,UAAU,UAAU,CAAC,IAAuB,EAAE,GAAW;IACnE,MAAM,CAAC,OAAO,EAAE,GAAG,IAAI,CAAC,GAAG,IAAI,CAAC;IAChC,MAAM,KAAK,GAAG,KAAK,CAAC,OAAQ,EAAE,IAAI,EAAE,EAAE,GAAG,EAAE,KAAK,EAAE,SAAS,EAAE,GAAG,EAAE,OAAO,CAAC,GAAG,EAAE,CAAC,CAAC;IACjF,MAAM,SAAS,GAAG,GAAS,EAAE,GAAE,CAAC,CAAC;IACjC,MAAM,OAAO,GAAG,CAAC,GAAmB,EAAQ,EAAE;QAC5C,KAAK,CAAC,IAAI,CAAC,GAAG,CAAC,CAAC;IAClB,CAAC,CAAC;IACF,OAAO,CAAC,EAAE,CAAC,QAAQ,EAAE,SAAS,CAAC,CAAC;IAChC,KAAK,MAAM,GAAG,IAAI,iBAAiB;QAAE,OAAO,CAAC,EAAE,CAAC,GAAG,EAAE,OAAO,CAAC,CAAC;IAC9D,IAAI,CAAC;QACH,OAAO,MAAM,IAAI,OAAO,CAAoB,CAAC,OAAO,EAAE,EAAE;YACtD,KAAK,CAAC,EAAE,CAAC,OAAO,EAAE,CAAC,GAAG,EAAE,EAAE,CAAC,OAAO,CAAC,EAAE,IAAI,EAAE,IAAI,EAAE,MAAM,EAAE,IAAI,EAAE,UAAU,EAAE,GAAG,CAAC,OAAO,EAAE,CAAC,CAAC,CAAC;YAC3F,KAAK,CAAC,EAAE,CAAC,MAAM,EAAE,CAAC,IAAI,EAAE,MAAM,EAAE,EAAE,CAAC,OAAO,CAAC,EAAE,IAAI,EAAE,MAAM,EAAE,UAAU,EAAE,IAAI,EAAE,CAAC,CAAC,CAAC;QAClF,CAAC,CAAC,CAAC;IACL,CAAC;YAAS,CAAC;QACT,OAAO,CAAC,GAAG,CAAC,QAAQ,EAAE,SAAS,CAAC,CAAC;QACjC,KAAK,MAAM,GAAG,IAAI,iBAAiB;YAAE,OAAO,CAAC,GAAG,CAAC,GAAG,EAAE,OAAO,CAAC,CAAC;IACjE,CAAC;AACH,CAAC"}
1
+ {"version":3,"file":"cage-process.js","sourceRoot":"","sources":["../../../../src/cli/lib/cage/cage-process.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,KAAK,EAAE,MAAM,oBAAoB,CAAC;AAC3C,OAAO,EAAE,MAAM,SAAS,CAAC;AASzB,kEAAkE;AAClE,MAAM,UAAU,cAAc,CAAC,MAAsB;IACnD,MAAM,CAAC,GAAG,EAAE,CAAC,SAAS,CAAC,OAAO,CAAC,MAAM,CAAC,CAAC;IACvC,OAAO,OAAO,CAAC,KAAK,QAAQ,CAAC,CAAC,CAAC,GAAG,GAAG,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC;AAC7C,CAAC;AAED,MAAM,iBAAiB,GAA8B,CAAC,SAAS,EAAE,QAAQ,CAAC,CAAC;AAE3E;;;;;;;GAOG;AACH,MAAM,CAAC,KAAK,UAAU,UAAU,CAC9B,IAAuB,EACvB,GAAW,EACX,WAA8B,EAAE;IAEhC,MAAM,CAAC,OAAO,EAAE,GAAG,IAAI,CAAC,GAAG,IAAI,CAAC;IAChC,MAAM,KAAK,GAAG,KAAK,CAAC,OAAQ,EAAE,IAAI,EAAE,EAAE,GAAG,EAAE,KAAK,EAAE,SAAS,EAAE,GAAG,EAAE,EAAE,GAAG,OAAO,CAAC,GAAG,EAAE,GAAG,QAAQ,EAAE,EAAE,CAAC,CAAC;IACrG,MAAM,SAAS,GAAG,GAAS,EAAE,GAAE,CAAC,CAAC;IACjC,MAAM,OAAO,GAAG,CAAC,GAAmB,EAAQ,EAAE;QAC5C,KAAK,CAAC,IAAI,CAAC,GAAG,CAAC,CAAC;IAClB,CAAC,CAAC;IACF,OAAO,CAAC,EAAE,CAAC,QAAQ,EAAE,SAAS,CAAC,CAAC;IAChC,KAAK,MAAM,GAAG,IAAI,iBAAiB;QAAE,OAAO,CAAC,EAAE,CAAC,GAAG,EAAE,OAAO,CAAC,CAAC;IAC9D,IAAI,CAAC;QACH,OAAO,MAAM,IAAI,OAAO,CAAoB,CAAC,OAAO,EAAE,EAAE;YACtD,KAAK,CAAC,EAAE,CAAC,OAAO,EAAE,CAAC,GAAG,EAAE,EAAE,CAAC,OAAO,CAAC,EAAE,IAAI,EAAE,IAAI,EAAE,MAAM,EAAE,IAAI,EAAE,UAAU,EAAE,GAAG,CAAC,OAAO,EAAE,CAAC,CAAC,CAAC;YAC3F,KAAK,CAAC,EAAE,CAAC,MAAM,EAAE,CAAC,IAAI,EAAE,MAAM,EAAE,EAAE,CAAC,OAAO,CAAC,EAAE,IAAI,EAAE,MAAM,EAAE,UAAU,EAAE,IAAI,EAAE,CAAC,CAAC,CAAC;QAClF,CAAC,CAAC,CAAC;IACL,CAAC;YAAS,CAAC;QACT,OAAO,CAAC,GAAG,CAAC,QAAQ,EAAE,SAAS,CAAC,CAAC;QACjC,KAAK,MAAM,GAAG,IAAI,iBAAiB;YAAE,OAAO,CAAC,GAAG,CAAC,GAAG,EAAE,OAAO,CAAC,CAAC;IACjE,CAAC;AACH,CAAC"}
@@ -0,0 +1,26 @@
1
+ import { type CagePlan, type CageRuleId } from "./cage-rules.js";
2
+ /** Env var the session hook reads to find the cage session log. */
3
+ export declare const CAGE_SESSION_LOG_ENV = "GANTRY_CAGE_SESSION_LOG";
4
+ export interface CagePushGuard {
5
+ hooksDir: string;
6
+ /** Env additions for the caged process tree (GIT_CONFIG_* appends, never replaces, existing entries). */
7
+ env: NodeJS.ProcessEnv;
8
+ dispose(): void;
9
+ }
10
+ /**
11
+ * Create a temporary hooks dir outside the repo and the env that points the caged process tree at it.
12
+ * Returns null outside a git work tree. Nothing is written into the repository.
13
+ */
14
+ export declare function createCagePushGuard(plan: CagePlan, sessionLogPath: string | null): CagePushGuard | null;
15
+ /**
16
+ * Rule for a repo-relative path in an outgoing commit, resolved exactly like the live snapshot.
17
+ * Report-mode paths (lockfiles, report entries, `--allow-override`) never block a push.
18
+ */
19
+ export declare function pushGuardRule(plan: CagePlan, rel: string): CageRuleId | null;
20
+ export interface PushGuardViolation {
21
+ path: string;
22
+ rule: CageRuleId;
23
+ }
24
+ /** Parse pre-push stdin (`<local ref> <local sha> <remote ref> <remote sha>` per line) and list protected paths. */
25
+ export declare function findPushViolations(plan: CagePlan, prePushStdin: string): PushGuardViolation[];
26
+ //# sourceMappingURL=cage-push-guard.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"cage-push-guard.d.ts","sourceRoot":"","sources":["../../../../src/cli/lib/cage/cage-push-guard.ts"],"names":[],"mappings":"AAKA,OAAO,EAAuC,KAAK,QAAQ,EAAE,KAAK,UAAU,EAAE,MAAM,iBAAiB,CAAC;AA2BtG,mEAAmE;AACnE,eAAO,MAAM,oBAAoB,4BAA4B,CAAC;AAK9D,MAAM,WAAW,aAAa;IAC5B,QAAQ,EAAE,MAAM,CAAC;IACjB,yGAAyG;IACzG,GAAG,EAAE,MAAM,CAAC,UAAU,CAAC;IACvB,OAAO,IAAI,IAAI,CAAC;CACjB;AAwCD;;;GAGG;AACH,wBAAgB,mBAAmB,CAAC,IAAI,EAAE,QAAQ,EAAE,cAAc,EAAE,MAAM,GAAG,IAAI,GAAG,aAAa,GAAG,IAAI,CAavG;AAED;;;GAGG;AACH,wBAAgB,aAAa,CAAC,IAAI,EAAE,QAAQ,EAAE,GAAG,EAAE,MAAM,GAAG,UAAU,GAAG,IAAI,CAG5E;AAMD,MAAM,WAAW,kBAAkB;IACjC,IAAI,EAAE,MAAM,CAAC;IACb,IAAI,EAAE,UAAU,CAAC;CAClB;AAED,oHAAoH;AACpH,wBAAgB,kBAAkB,CAAC,IAAI,EAAE,QAAQ,EAAE,YAAY,EAAE,MAAM,GAAG,kBAAkB,EAAE,CAa7F"}
@@ -0,0 +1,114 @@
1
+ import fs from "node:fs";
2
+ import os from "node:os";
3
+ import path from "node:path";
4
+ import { fileURLToPath } from "node:url";
5
+ import { gitRun } from "../git/git.js";
6
+ import { classifyCagePath, serializeCagePlan } from "./cage-rules.js";
7
+ /** Standard client-side git hooks; each session wrapper chains to the repo's real hook of the same name. */
8
+ const GIT_HOOK_NAMES = [
9
+ "applypatch-msg",
10
+ "pre-applypatch",
11
+ "post-applypatch",
12
+ "pre-commit",
13
+ "pre-merge-commit",
14
+ "prepare-commit-msg",
15
+ "commit-msg",
16
+ "post-commit",
17
+ "pre-rebase",
18
+ "post-checkout",
19
+ "post-merge",
20
+ "pre-push",
21
+ "pre-auto-gc",
22
+ "post-rewrite",
23
+ "sendemail-validate",
24
+ "fsmonitor-watchman",
25
+ "post-index-change",
26
+ "reference-transaction",
27
+ "push-to-checkout",
28
+ ];
29
+ const ZERO_SHA = /^0+$/;
30
+ /** Env var the session hook reads to find the cage session log. */
31
+ export const CAGE_SESSION_LOG_ENV = "GANTRY_CAGE_SESSION_LOG";
32
+ /** The session plan, written next to the hooks so the push guard checks exactly what the session protects. */
33
+ const PLAN_FILE = "cage-plan.json";
34
+ function shellQuote(s) {
35
+ return `'${s.replace(/'/g, `'\\''`)}'`;
36
+ }
37
+ function activeHooksDir(root) {
38
+ const r = gitRun(root, ["rev-parse", "--path-format=absolute", "--git-path", "hooks"]);
39
+ const out = r.stdout.trim();
40
+ return r.ok && out ? path.resolve(out) : null;
41
+ }
42
+ /** The CLI entry the session pre-push hook calls (`dist/cli/index.js`, next to this module's `lib/`). */
43
+ function cliEntry() {
44
+ return fileURLToPath(new URL("../../index.js", import.meta.url));
45
+ }
46
+ function hookScript(name, realHooks, root, planFile) {
47
+ const real = shellQuote(path.join(realHooks, name));
48
+ const guard = name === "pre-push"
49
+ ? `stdin_copy="$(cat)"\nprintf '%s\\n' "$stdin_copy" | ${shellQuote(process.execPath)} ${shellQuote(cliEntry())} cage-guard pre-push --root ${shellQuote(root)} --plan ${shellQuote(planFile)} || exit 1\n`
50
+ : "";
51
+ const chain = name === "pre-push"
52
+ ? `if [ -x ${real} ]; then printf '%s\\n' "$stdin_copy" | ${real} "$@"; exit $?; fi\n`
53
+ : `if [ -x ${real} ]; then exec ${real} "$@"; fi\n`;
54
+ return `#!/bin/sh\n# gantry cage session hook (temporary; removed when the cage session ends)\n${guard}${chain}exit 0\n`;
55
+ }
56
+ function gitConfigEnv(hooksDir) {
57
+ const raw = process.env.GIT_CONFIG_COUNT;
58
+ const n = raw && /^\d+$/.test(raw) ? Number.parseInt(raw, 10) : 0;
59
+ return {
60
+ GIT_CONFIG_COUNT: String(n + 1),
61
+ [`GIT_CONFIG_KEY_${String(n)}`]: "core.hooksPath",
62
+ [`GIT_CONFIG_VALUE_${String(n)}`]: hooksDir,
63
+ };
64
+ }
65
+ /**
66
+ * Create a temporary hooks dir outside the repo and the env that points the caged process tree at it.
67
+ * Returns null outside a git work tree. Nothing is written into the repository.
68
+ */
69
+ export function createCagePushGuard(plan, sessionLogPath) {
70
+ const root = plan.root;
71
+ const realHooks = activeHooksDir(root);
72
+ if (!realHooks)
73
+ return null;
74
+ const hooksDir = fs.mkdtempSync(path.join(os.tmpdir(), "gantry-cage-hooks-"));
75
+ const planFile = path.join(hooksDir, PLAN_FILE);
76
+ fs.writeFileSync(planFile, serializeCagePlan(plan), { mode: 0o600 });
77
+ for (const name of GIT_HOOK_NAMES) {
78
+ fs.writeFileSync(path.join(hooksDir, name), hookScript(name, realHooks, root, planFile), { mode: 0o755 });
79
+ }
80
+ const env = { ...gitConfigEnv(hooksDir) };
81
+ if (sessionLogPath)
82
+ env[CAGE_SESSION_LOG_ENV] = sessionLogPath;
83
+ return { hooksDir, env, dispose: () => fs.rmSync(hooksDir, { recursive: true, force: true }) };
84
+ }
85
+ /**
86
+ * Rule for a repo-relative path in an outgoing commit, resolved exactly like the live snapshot.
87
+ * Report-mode paths (lockfiles, report entries, `--allow-override`) never block a push.
88
+ */
89
+ export function pushGuardRule(plan, rel) {
90
+ const hit = classifyCagePath(plan, rel);
91
+ return hit && hit.mode === "revert" ? hit.rule : null;
92
+ }
93
+ function outgoingRange(localSha, remoteSha) {
94
+ return ZERO_SHA.test(remoteSha) ? [localSha, "--not", "--remotes"] : [`${remoteSha}..${localSha}`];
95
+ }
96
+ /** Parse pre-push stdin (`<local ref> <local sha> <remote ref> <remote sha>` per line) and list protected paths. */
97
+ export function findPushViolations(plan, prePushStdin) {
98
+ const hits = new Map();
99
+ for (const line of prePushStdin.split(/\r?\n/)) {
100
+ const [, localSha, , remoteSha] = line.trim().split(/\s+/);
101
+ if (!localSha || !remoteSha || ZERO_SHA.test(localSha))
102
+ continue;
103
+ const r = gitRun(plan.root, ["log", "--format=", "--name-only", ...outgoingRange(localSha, remoteSha)]);
104
+ if (!r.ok)
105
+ continue;
106
+ for (const rel of r.stdout.split(/\r?\n/).map((s) => s.trim()).filter(Boolean)) {
107
+ const rule = pushGuardRule(plan, rel);
108
+ if (rule)
109
+ hits.set(rel, rule);
110
+ }
111
+ }
112
+ return [...hits].map(([p, rule]) => ({ path: p, rule })).sort((a, b) => a.path.localeCompare(b.path));
113
+ }
114
+ //# sourceMappingURL=cage-push-guard.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"cage-push-guard.js","sourceRoot":"","sources":["../../../../src/cli/lib/cage/cage-push-guard.ts"],"names":[],"mappings":"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"}