@yagni-app/code-staging 1.0.6-staging.1242.1 → 1.0.6-staging.1245.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.
- package/README.md +30 -6
- package/dist/doctor.d.ts +8 -3
- package/dist/doctor.js +38 -11
- package/dist/extension/index.js +99 -0
- package/dist/extension/permission/gate.d.ts +53 -0
- package/dist/extension/permission/gate.js +269 -2
- package/dist/extension/permissionRules/bashFileArgs.d.ts +39 -0
- package/dist/extension/permissionRules/bashFileArgs.js +236 -0
- package/dist/extension/permissionRules/engine.d.ts +50 -0
- package/dist/extension/permissionRules/engine.js +238 -0
- package/dist/extension/permissionRules/loadConfig.d.ts +53 -0
- package/dist/extension/permissionRules/loadConfig.js +90 -0
- package/dist/extension/permissionRules/parser.d.ts +38 -0
- package/dist/extension/permissionRules/parser.js +136 -0
- package/dist/extension/permissionRules/pathRules.d.ts +58 -0
- package/dist/extension/permissionRules/pathRules.js +120 -0
- package/dist/extension/permissionRules/shellRules.d.ts +52 -0
- package/dist/extension/permissionRules/shellRules.js +221 -0
- package/dist/extension/vendor/IGNORE-LICENSE-MIT +21 -0
- package/dist/extension/vendor/ignore.d.ts +86 -0
- package/dist/extension/vendor/ignore.js +788 -0
- package/dist/otel.d.ts +36 -7
- package/dist/otel.js +90 -12
- package/package.json +2 -2
package/README.md
CHANGED
|
@@ -179,11 +179,16 @@ children and subagents — emits a per-prompt span tree: interaction → LLM
|
|
|
179
179
|
request → tool calls, following the OTel GenAI semantic conventions. Nothing is
|
|
180
180
|
exported unless you configure an endpoint.
|
|
181
181
|
|
|
182
|
-
Enable it one of
|
|
183
|
-
|
|
184
|
-
- `OTEL_EXPORTER_OTLP_ENDPOINT
|
|
185
|
-
|
|
186
|
-
|
|
182
|
+
Enable it one of four ways (first match wins):
|
|
183
|
+
|
|
184
|
+
- `OTEL_EXPORTER_OTLP_ENDPOINT=…` in the environment (a personal override —
|
|
185
|
+
handy for pointing one session at a scratch collector), or
|
|
186
|
+
- `OTEL_EXPORTER_OTLP_TRACES_ENDPOINT=…` in the environment — the per-signal
|
|
187
|
+
form Claude Code setups use (e.g.
|
|
188
|
+
`OTEL_EXPORTER_OTLP_TRACES_ENDPOINT=https://otlp.datadoghq.com/v1/traces`
|
|
189
|
+
with `OTEL_EXPORTER_OTLP_TRACES_PROTOCOL=http/protobuf`); the launcher
|
|
190
|
+
bridges it to the generic var pi-otel reads, so a Claude Code OTel env
|
|
191
|
+
lights up YAGNI Code traces with no extra config, or
|
|
187
192
|
- **workspace settings** (the zero-setup path): a workspace admin sets the
|
|
188
193
|
endpoint, protocol, and any collector headers (e.g. a Datadog API key) once
|
|
189
194
|
in the web app under Settings → YAGNI Code → Trace export. Every session in
|
|
@@ -197,7 +202,26 @@ Enable it one of three ways (first match wins):
|
|
|
197
202
|
Standard OTel env vars are honored (`OTEL_EXPORTER_OTLP_PROTOCOL`,
|
|
198
203
|
`OTEL_EXPORTER_OTLP_HEADERS`, `OTEL_SERVICE_NAME` — defaults to `yagni-code`),
|
|
199
204
|
and `PI_OTEL_DISABLED=1` is the kill switch. `yagni doctor` shows the current
|
|
200
|
-
export state
|
|
205
|
+
export state and probes the endpoint live (unreachable → a warning with a
|
|
206
|
+
hint, never an exit-code flip).
|
|
207
|
+
|
|
208
|
+
Two recipes for common collectors:
|
|
209
|
+
|
|
210
|
+
- **Datadog, direct SaaS (no agent):** endpoint
|
|
211
|
+
`https://otlp.datadoghq.com/v1/traces` (US1; other sites have their own
|
|
212
|
+
host — `otlp.us3.datadoghq.com`, `otlp.us5.datadoghq.com`,
|
|
213
|
+
`otlp.datadoghq.eu`, `otlp.ap1.datadoghq.com`), protocol `http/protobuf`
|
|
214
|
+
(Datadog's direct trace intake does not accept gRPC), header
|
|
215
|
+
`dd-api-key=<your Datadog API key>`. Use the full path, not `/v1/` — the
|
|
216
|
+
endpoint is passed through verbatim, no path is appended for you.
|
|
217
|
+
- **Datadog Agent:** endpoint `http://<agent-host>:4317`, protocol `grpc`, no
|
|
218
|
+
key (the agent forwards with its own). OTLP ingest ships in the agent but
|
|
219
|
+
is off by default — enable it with
|
|
220
|
+
`otlp_config.receiver.protocols.grpc.endpoint: 0.0.0.0:4317` in
|
|
221
|
+
`datadog.yaml`.
|
|
222
|
+
- **Grafana Cloud:** endpoint `https://otlp-gateway-prod-…-0.grafana.net:443/otlp`,
|
|
223
|
+
protocol `http/protobuf`, header `authorization=<the raw MTgx… value>` (the
|
|
224
|
+
`Basic ` scheme is added for you).
|
|
201
225
|
|
|
202
226
|
Two things are enforced and not configurable:
|
|
203
227
|
|
package/dist/doctor.d.ts
CHANGED
|
@@ -13,7 +13,7 @@
|
|
|
13
13
|
* Advisory checks (loose perms, missing `gh`) never flip the exit code.
|
|
14
14
|
*/
|
|
15
15
|
import { type TokenExpiryStatus } from "./launch.js";
|
|
16
|
-
import { type OtelLaunchConfig } from "./otel.js";
|
|
16
|
+
import { probeOtelEndpoint, type OtelLaunchConfig } from "./otel.js";
|
|
17
17
|
import { type Profile } from "./profiles.js";
|
|
18
18
|
export type CheckStatus = "ok" | "warn" | "fail";
|
|
19
19
|
export interface CheckResult {
|
|
@@ -63,10 +63,15 @@ export declare function checkCliUpdate(probe: {
|
|
|
63
63
|
export declare function checkGh(onPath: boolean): CheckResult;
|
|
64
64
|
/**
|
|
65
65
|
* Advisory OTel-export line: says whether sessions will stream traces to an
|
|
66
|
-
* OTLP collector,
|
|
66
|
+
* OTLP collector, from which config source, and — when a collector is
|
|
67
|
+
* configured — whether the endpoint answers TCP right now (scheme-default
|
|
68
|
+
* ports included: https with no port probes 443). Never flips the exit code —
|
|
67
69
|
* most machines have no collector, and that is the healthy default.
|
|
68
70
|
*/
|
|
69
|
-
export declare function checkOtelExport(config: OtelLaunchConfig | undefined
|
|
71
|
+
export declare function checkOtelExport(config: OtelLaunchConfig | undefined, deps?: {
|
|
72
|
+
probeEndpoint?: typeof probeOtelEndpoint;
|
|
73
|
+
probeTimeoutMs?: number;
|
|
74
|
+
}): Promise<CheckResult>;
|
|
70
75
|
/** Config-only MCP snapshot (no connections — health checks live in `mcp list`/`get`). */
|
|
71
76
|
export interface McpProbe {
|
|
72
77
|
/** YAGNI_CODE_MCP_DISABLED kill-switch is active. */
|
package/dist/doctor.js
CHANGED
|
@@ -17,7 +17,7 @@ import { delimiter, join } from "node:path";
|
|
|
17
17
|
import { credentialsDir } from "./credentials.js";
|
|
18
18
|
import { currentCliVersion, fetchLatestVersion, isNewerVersion } from "./upgrade.js";
|
|
19
19
|
import { classifyTokenExpiry } from "./launch.js";
|
|
20
|
-
import { resolveOtelLaunchWithWorkspace } from "./otel.js";
|
|
20
|
+
import { probeOtelEndpoint, resolveOtelLaunchWithWorkspace, } from "./otel.js";
|
|
21
21
|
import { resolveExtensionPath, resolvePiCliPath, resolvePiPackageDir } from "./paths.js";
|
|
22
22
|
import { readActiveProfile } from "./profiles.js";
|
|
23
23
|
import { resolveMcpConfigPath } from "./mcpCommand.js";
|
|
@@ -202,10 +202,12 @@ export function checkGh(onPath) {
|
|
|
202
202
|
}
|
|
203
203
|
/**
|
|
204
204
|
* Advisory OTel-export line: says whether sessions will stream traces to an
|
|
205
|
-
* OTLP collector,
|
|
205
|
+
* OTLP collector, from which config source, and — when a collector is
|
|
206
|
+
* configured — whether the endpoint answers TCP right now (scheme-default
|
|
207
|
+
* ports included: https with no port probes 443). Never flips the exit code —
|
|
206
208
|
* most machines have no collector, and that is the healthy default.
|
|
207
209
|
*/
|
|
208
|
-
export function checkOtelExport(config) {
|
|
210
|
+
export async function checkOtelExport(config, deps = {}) {
|
|
209
211
|
if (!config) {
|
|
210
212
|
return {
|
|
211
213
|
name: "otel export (optional)",
|
|
@@ -215,16 +217,41 @@ export function checkOtelExport(config) {
|
|
|
215
217
|
};
|
|
216
218
|
}
|
|
217
219
|
const source = config.source === "env"
|
|
218
|
-
? "OTEL_EXPORTER_OTLP_ENDPOINT"
|
|
220
|
+
? (config.envVar ?? "OTEL_EXPORTER_OTLP_ENDPOINT")
|
|
219
221
|
: config.source === "workspace"
|
|
220
222
|
? "workspace settings"
|
|
221
223
|
: ".pi/settings.json";
|
|
222
|
-
|
|
223
|
-
|
|
224
|
-
|
|
225
|
-
|
|
226
|
-
|
|
227
|
-
|
|
224
|
+
const base = `on → ${config.endpoint} (${source}, metadata-only)`;
|
|
225
|
+
const doProbe = deps.probeEndpoint ?? probeOtelEndpoint;
|
|
226
|
+
try {
|
|
227
|
+
const reachable = await doProbe(config.endpoint, deps.probeTimeoutMs ?? 1_000);
|
|
228
|
+
return reachable
|
|
229
|
+
? {
|
|
230
|
+
name: "otel export (optional)",
|
|
231
|
+
status: "ok",
|
|
232
|
+
detail: `${base}, TCP reachable`,
|
|
233
|
+
required: false,
|
|
234
|
+
}
|
|
235
|
+
: {
|
|
236
|
+
name: "otel export (optional)",
|
|
237
|
+
status: "warn",
|
|
238
|
+
detail: `${base}, endpoint not reachable`,
|
|
239
|
+
hint: "the collector is down, unreachable from this machine, or the URL is wrong — traces will not export until it answers",
|
|
240
|
+
required: false,
|
|
241
|
+
};
|
|
242
|
+
}
|
|
243
|
+
catch {
|
|
244
|
+
// The probe itself errored (not "endpoint down" — the check could not
|
|
245
|
+
// run). Distinct from a verified-healthy line so nobody reads an
|
|
246
|
+
// unverified config as checked-and-good.
|
|
247
|
+
return {
|
|
248
|
+
name: "otel export (optional)",
|
|
249
|
+
status: "warn",
|
|
250
|
+
detail: `${base}, reachability probe failed`,
|
|
251
|
+
hint: "the reachability check could not run (network error) — export is configured but unverified; traces may still export normally",
|
|
252
|
+
required: false,
|
|
253
|
+
};
|
|
254
|
+
}
|
|
228
255
|
}
|
|
229
256
|
/**
|
|
230
257
|
* Advisory (never required) MCP line: reports config health without connecting
|
|
@@ -489,7 +516,7 @@ export async function gatherChecks(deps = {}) {
|
|
|
489
516
|
checks.push(checkStateDir(probeStateDir()));
|
|
490
517
|
checks.push(checkGh(ghOnPath()));
|
|
491
518
|
checks.push(checkMcpConfig(await probeMcp()));
|
|
492
|
-
checks.push(checkOtelExport(await resolveOtelLaunchWithWorkspace({
|
|
519
|
+
checks.push(await checkOtelExport(await resolveOtelLaunchWithWorkspace({
|
|
493
520
|
env: process.env,
|
|
494
521
|
cwd: process.cwd(),
|
|
495
522
|
creds: profile.token ? { baseUrl: profile.baseUrl, token: profile.token } : null,
|
package/dist/extension/index.js
CHANGED
|
@@ -1,4 +1,6 @@
|
|
|
1
1
|
import { createHash } from "node:crypto";
|
|
2
|
+
import { existsSync, readFileSync, renameSync, writeFileSync } from "node:fs";
|
|
3
|
+
import { join } from "node:path";
|
|
2
4
|
import { Text } from "@earendil-works/pi-tui";
|
|
3
5
|
import { DEFAULT_ADVISOR_LIMITS, formatAdvisorSubtotal, makeAdvisorState } from "./advisor.js";
|
|
4
6
|
import { makeChildUsageState } from "./childUsage.js";
|
|
@@ -35,6 +37,7 @@ import { registerGoCommand } from "./pipeline/goCommand.js";
|
|
|
35
37
|
import { registerGoCompareCommand } from "./pipeline/goCompareCommand.js";
|
|
36
38
|
import { DEFAULT_PERMISSION_POLICY, createModeHolder, registerPermissionGate } from "./permission/gate.js";
|
|
37
39
|
import { loadHooksConfig, makeHookRunner, registerHooks } from "./hooks.js";
|
|
40
|
+
import { loadPermissionRules } from "./permissionRules/loadConfig.js";
|
|
38
41
|
import { registerSubagents } from "./subagents.js";
|
|
39
42
|
import { createUltraHolder, registerUltraCommand } from "./ultra.js";
|
|
40
43
|
import { registerTodos } from "./todos.js";
|
|
@@ -334,6 +337,8 @@ export async function registerYagni(pi, deps = {}) {
|
|
|
334
337
|
// The static queue only carries non-connectivity notices (e.g. the
|
|
335
338
|
// `.mcp.json` approval prompt), which stay one-shot.
|
|
336
339
|
let connectivityNoticesFlushed = false;
|
|
340
|
+
// permission-rule notices are one-shot like connectivity notices.
|
|
341
|
+
let rulesNoticesFlushed = false;
|
|
337
342
|
pi.on("after_provider_response", (_event, ctx) => {
|
|
338
343
|
try {
|
|
339
344
|
if (!ctx.hasUI)
|
|
@@ -347,6 +352,21 @@ export async function registerYagni(pi, deps = {}) {
|
|
|
347
352
|
while (mcpApprovalNotices.length > 0) {
|
|
348
353
|
ctx.ui.notify(mcpApprovalNotices.shift(), "info");
|
|
349
354
|
}
|
|
355
|
+
// one-shot permission-rule config warnings (bad files,
|
|
356
|
+
// never-consulted tools). One consolidated line, never a wall.
|
|
357
|
+
if (!rulesNoticesFlushed) {
|
|
358
|
+
rulesNoticesFlushed = true;
|
|
359
|
+
const parts = [];
|
|
360
|
+
if (loadedRules.diagnostics.warnings.length > 0) {
|
|
361
|
+
parts.push(`${loadedRules.diagnostics.warnings.length} permission-rule config warning(s)`);
|
|
362
|
+
}
|
|
363
|
+
if (loadedRules.diagnostics.neverConsultedTools.length > 0) {
|
|
364
|
+
parts.push(`rules for unknown tools: ${loadedRules.diagnostics.neverConsultedTools.join(", ")} (ignored)`);
|
|
365
|
+
}
|
|
366
|
+
if (parts.length > 0) {
|
|
367
|
+
ctx.ui.notify(`Permission rules: ${parts.join(" · ")}`, "warning");
|
|
368
|
+
}
|
|
369
|
+
}
|
|
350
370
|
}
|
|
351
371
|
catch {
|
|
352
372
|
// A notice must never break a turn.
|
|
@@ -408,6 +428,25 @@ export async function registerYagni(pi, deps = {}) {
|
|
|
408
428
|
// the startup load is the trust boundary; live reload was reviewed and
|
|
409
429
|
// rejected as a same-session self-authorization path, PR #1698).
|
|
410
430
|
const sessionGrants = evalMode ? [] : loadGrants();
|
|
431
|
+
// settings-based permission rules, loaded once at startup (same
|
|
432
|
+
// trust-boundary posture as grants: no mid-session reload). User config
|
|
433
|
+
// (~/.yagni-code/config.json) + project config (.yagni-code/config.json);
|
|
434
|
+
// project allow rules are trust-gated at evaluation time, deny/ask always.
|
|
435
|
+
const loadedRules = evalMode
|
|
436
|
+
? { rules: [], diagnostics: { warnings: [], neverConsultedTools: [] } }
|
|
437
|
+
: loadPermissionRules({ cwd: process.cwd() });
|
|
438
|
+
for (const w of loadedRules.diagnostics.warnings) {
|
|
439
|
+
logEvent({ source: "permission-rules", level: "warn", event: "config_warning", fields: { warning: w } });
|
|
440
|
+
}
|
|
441
|
+
if (loadedRules.diagnostics.neverConsultedTools.length > 0) {
|
|
442
|
+
logEvent({
|
|
443
|
+
source: "permission-rules",
|
|
444
|
+
level: "warn",
|
|
445
|
+
event: "never_consulted_tools",
|
|
446
|
+
fields: { tools: loadedRules.diagnostics.neverConsultedTools.join(", ") },
|
|
447
|
+
});
|
|
448
|
+
}
|
|
449
|
+
const rulesStateHome = codeStateHome(null, env);
|
|
411
450
|
const GUARDIAN_EVENT_TIMEOUT_MS = 5_000;
|
|
412
451
|
// YAG-506: load user-configurable lifecycle hooks config and create the
|
|
413
452
|
// hook runner for the permission gate. Skipped in eval mode.
|
|
@@ -420,6 +459,27 @@ export async function registerYagni(pi, deps = {}) {
|
|
|
420
459
|
guardianState,
|
|
421
460
|
guardianLimits,
|
|
422
461
|
guardianTier,
|
|
462
|
+
...(loadedRules.rules.length > 0
|
|
463
|
+
? {
|
|
464
|
+
permissionRules: loadedRules.rules,
|
|
465
|
+
rulesUserStateHome: rulesStateHome,
|
|
466
|
+
rulesProjectRoot: process.cwd(),
|
|
467
|
+
onRuleVerdict: (ev) => {
|
|
468
|
+
logEvent({
|
|
469
|
+
source: "permission-rules",
|
|
470
|
+
level: "debug",
|
|
471
|
+
event: `rule_${ev.verdict}`,
|
|
472
|
+
sessionId: env.YAGNI_SESSION_ID ?? undefined,
|
|
473
|
+
fields: {
|
|
474
|
+
tool: ev.toolName,
|
|
475
|
+
via: ev.matchedVia,
|
|
476
|
+
source: ev.rule.source,
|
|
477
|
+
rule: ev.rule.raw,
|
|
478
|
+
},
|
|
479
|
+
});
|
|
480
|
+
},
|
|
481
|
+
}
|
|
482
|
+
: {}),
|
|
423
483
|
guardianDisabled,
|
|
424
484
|
childUsage,
|
|
425
485
|
guardianReview: (command, deps) => reviewCommand(command, { ...deps, modelTier: guardianTier }),
|
|
@@ -471,6 +531,45 @@ export async function registerYagni(pi, deps = {}) {
|
|
|
471
531
|
if (!evalMode)
|
|
472
532
|
appendGrant(grant);
|
|
473
533
|
},
|
|
534
|
+
// persist a user-level allow rule (Guardian ask dialog's third
|
|
535
|
+
// option). Atomic write, never overwrites other keys, fail-soft.
|
|
536
|
+
persistUserRule: (ruleString) => {
|
|
537
|
+
if (evalMode)
|
|
538
|
+
return;
|
|
539
|
+
try {
|
|
540
|
+
const userPath = join(rulesStateHome, "config.json");
|
|
541
|
+
let parsed = {};
|
|
542
|
+
if (existsSync(userPath)) {
|
|
543
|
+
parsed = JSON.parse(readFileSync(userPath, "utf-8"));
|
|
544
|
+
if (!parsed || typeof parsed !== "object" || Array.isArray(parsed))
|
|
545
|
+
return;
|
|
546
|
+
}
|
|
547
|
+
const perms = (parsed.permissions ?? {});
|
|
548
|
+
const allow = Array.isArray(perms.allow) ? perms.allow : [];
|
|
549
|
+
if (!allow.includes(ruleString))
|
|
550
|
+
allow.push(ruleString);
|
|
551
|
+
perms.allow = allow;
|
|
552
|
+
parsed.permissions = perms;
|
|
553
|
+
const tmp = join(rulesStateHome, `.config.json.yagni-${process.pid}-${Date.now()}.tmp`);
|
|
554
|
+
writeFileSync(tmp, `${JSON.stringify(parsed, null, 2)}\n`, { mode: 0o600 });
|
|
555
|
+
renameSync(tmp, userPath);
|
|
556
|
+
logEvent({
|
|
557
|
+
source: "permission-rules",
|
|
558
|
+
level: "info",
|
|
559
|
+
event: "user_rule_saved",
|
|
560
|
+
sessionId: env.YAGNI_SESSION_ID ?? undefined,
|
|
561
|
+
fields: { rule: ruleString },
|
|
562
|
+
});
|
|
563
|
+
}
|
|
564
|
+
catch (err) {
|
|
565
|
+
logEvent({
|
|
566
|
+
source: "permission-rules",
|
|
567
|
+
level: "warn",
|
|
568
|
+
event: "user_rule_save_failed",
|
|
569
|
+
fields: { message: err instanceof Error ? err.message : "unknown" },
|
|
570
|
+
});
|
|
571
|
+
}
|
|
572
|
+
},
|
|
474
573
|
// Opt-in storage stream (YAG-510). Tier decides what leaves the machine:
|
|
475
574
|
// "off" → nothing (not even sent); "hash" → sha256 + family prefix +
|
|
476
575
|
// metadata, no command content; "raw" → adds client-REDACTED command and
|
|
@@ -94,6 +94,33 @@ export interface GateDecision {
|
|
|
94
94
|
*/
|
|
95
95
|
classifyJustification?: string;
|
|
96
96
|
}
|
|
97
|
+
/**
|
|
98
|
+
* What the hard floors say about an ALLOW-rule verdict — the three invariants
|
|
99
|
+
* that outrank any user/project permission rule. Extracted from the inline
|
|
100
|
+
* allow path so the gate reads linearly and the floors are directly testable.
|
|
101
|
+
* PURE — no I/O, no session state:
|
|
102
|
+
*
|
|
103
|
+
* "allow" no floor applies — the rule's allow short-circuits everything
|
|
104
|
+
* "block" the exec-policy forbidden band holds despite the allow rule
|
|
105
|
+
* "confirm" alwaysConfirmTools keeps its fresh-consent contract
|
|
106
|
+
* "hold" plan mode's no-mutation contract outranks the allow rule —
|
|
107
|
+
* fall through to the normal plan-mode gate
|
|
108
|
+
*
|
|
109
|
+
* Order is deliberate: plan-mode first (its outcome routes to the EXISTING
|
|
110
|
+
* plan-mode machinery, not a bespoke block); then the terminal forbidden-band
|
|
111
|
+
* block; then the confirm deferral.
|
|
112
|
+
*/
|
|
113
|
+
export type AllowRuleFloorOutcome = {
|
|
114
|
+
kind: "allow";
|
|
115
|
+
} | {
|
|
116
|
+
kind: "block";
|
|
117
|
+
reason: string;
|
|
118
|
+
} | {
|
|
119
|
+
kind: "confirm";
|
|
120
|
+
} | {
|
|
121
|
+
kind: "hold";
|
|
122
|
+
};
|
|
123
|
+
export declare function allowRuleFloorVerdict(toolName: string, params: Record<string, unknown>, mode: PermissionMode, policy: PermissionPolicy): AllowRuleFloorOutcome;
|
|
97
124
|
/**
|
|
98
125
|
* Pure permission decision for one tool call under a mode + policy. Auto allows
|
|
99
126
|
* ordinary tools; plan blocks the write/exec set; review marks writes for confirmation
|
|
@@ -187,12 +214,38 @@ export interface RegisterPermissionDeps {
|
|
|
187
214
|
/** Persist a new grant (fire-and-forget; the in-memory list is updated
|
|
188
215
|
* either way). index.ts wires approvedPrefixes.appendGrant. */
|
|
189
216
|
persistGrant?: (grant: ApprovedPrefixGrant) => void;
|
|
217
|
+
/**
|
|
218
|
+
* persist a user-level permission rule string (e.g.
|
|
219
|
+
* `Bash(git push:*)`) into ~/.yagni-code/config.json permissions.allow.
|
|
220
|
+
* Wired by index.ts; offered as a third option on Guardian ask dialogs.
|
|
221
|
+
* Fail-soft: the in-session approval applies even if the write fails.
|
|
222
|
+
*/
|
|
223
|
+
persistUserRule?: (ruleString: string) => void;
|
|
190
224
|
/**
|
|
191
225
|
* Called (fire-and-forget) at every terminal prompt-band outcome with the
|
|
192
226
|
* rich storage event (raw command — the wiring layer redacts/hashes).
|
|
193
227
|
* Fail-soft; never blocks.
|
|
194
228
|
*/
|
|
195
229
|
onGuardianEvent?: (event: GuardianGateEvent) => void;
|
|
230
|
+
/**
|
|
231
|
+
* settings-based permission rules (user + project config.json).
|
|
232
|
+
* When present, evaluated at the TOP of the tool_call handler, before
|
|
233
|
+
* hooks: deny → ask → allow, first match wins. Deny/ask are final; allow
|
|
234
|
+
* short-circuits the Guardian but can never lift the exec-policy forbidden
|
|
235
|
+
* band or the alwaysConfirmTools contract (both re-checked below).
|
|
236
|
+
*/
|
|
237
|
+
permissionRules?: readonly import("../permissionRules/loadConfig.js").PermissionRule[];
|
|
238
|
+
/** ~/.yagni-code — the user `/`-anchor base for path rules. */
|
|
239
|
+
rulesUserStateHome?: string;
|
|
240
|
+
/** Project root for project-source `/`-anchored path rules; null outside a repo. */
|
|
241
|
+
rulesProjectRoot?: string | null;
|
|
242
|
+
/** Overrides ~ expansion for path rules (tests). */
|
|
243
|
+
rulesHomeDir?: string;
|
|
244
|
+
/** Called (fire-and-soft) after every rule verdict for debug logging. */
|
|
245
|
+
onRuleVerdict?: (event: import("../permissionRules/engine.js").RuleEvaluation & {
|
|
246
|
+
toolName: string;
|
|
247
|
+
cwd: string;
|
|
248
|
+
}) => void;
|
|
196
249
|
/**
|
|
197
250
|
* User-configurable lifecycle hooks (YAG-506). When present, PreToolUse
|
|
198
251
|
* hooks run before decideGate and can short-circuit (allow/deny/ask),
|