@yagni-app/code 0.2.1 → 0.3.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.
Files changed (98) hide show
  1. package/dist/cli.d.ts +30 -0
  2. package/dist/cli.js +147 -3
  3. package/dist/connectClaudeCode.d.ts +77 -0
  4. package/dist/connectClaudeCode.js +228 -0
  5. package/dist/connectCodex.d.ts +75 -0
  6. package/dist/connectCodex.js +201 -0
  7. package/dist/doctor.d.ts +1 -1
  8. package/dist/doctor.js +1 -1
  9. package/dist/extension/advisor.d.ts +4 -4
  10. package/dist/extension/advisor.js +6 -7
  11. package/dist/extension/approvedPrefixes.d.ts +103 -0
  12. package/dist/extension/approvedPrefixes.js +282 -0
  13. package/dist/extension/askAdvisorTool.d.ts +20 -5
  14. package/dist/extension/askAdvisorTool.js +126 -20
  15. package/dist/extension/askYagniTool.d.ts +23 -0
  16. package/dist/extension/askYagniTool.js +91 -2
  17. package/dist/extension/branding.d.ts +34 -3
  18. package/dist/extension/branding.js +114 -13
  19. package/dist/extension/chipEditor.d.ts +30 -9
  20. package/dist/extension/chipEditor.js +173 -59
  21. package/dist/extension/claudeRules.d.ts +0 -2
  22. package/dist/extension/claudeRules.js +0 -8
  23. package/dist/extension/cmux/dispatcher.d.ts +25 -0
  24. package/dist/extension/cmux/dispatcher.js +266 -0
  25. package/dist/extension/cmux/hooks.d.ts +12 -0
  26. package/dist/extension/cmux/hooks.js +192 -0
  27. package/dist/extension/cmux/index.d.ts +3 -0
  28. package/dist/extension/cmux/index.js +155 -0
  29. package/dist/extension/cmux/naming.d.ts +5 -0
  30. package/dist/extension/cmux/naming.js +23 -0
  31. package/dist/extension/cmux/state.d.ts +33 -0
  32. package/dist/extension/cmux/state.js +142 -0
  33. package/dist/extension/config.d.ts +44 -1
  34. package/dist/extension/config.js +37 -4
  35. package/dist/extension/costHud.d.ts +16 -22
  36. package/dist/extension/costHud.js +8 -47
  37. package/dist/extension/crashReport.js +1 -3
  38. package/dist/extension/execPolicy.d.ts +135 -0
  39. package/dist/extension/execPolicy.js +936 -0
  40. package/dist/extension/flywheel.d.ts +44 -0
  41. package/dist/extension/flywheel.js +53 -0
  42. package/dist/extension/footer.d.ts +118 -0
  43. package/dist/extension/footer.js +308 -0
  44. package/dist/extension/guardian.d.ts +139 -0
  45. package/dist/extension/guardian.js +237 -0
  46. package/dist/extension/index.d.ts +35 -7
  47. package/dist/extension/index.js +338 -33
  48. package/dist/extension/mineBeat.d.ts +95 -0
  49. package/dist/extension/mineBeat.js +193 -0
  50. package/dist/extension/permission.d.ts +124 -10
  51. package/dist/extension/permission.js +603 -52
  52. package/dist/extension/pipeline/childRegistry.d.ts +41 -0
  53. package/dist/extension/pipeline/childRegistry.js +118 -0
  54. package/dist/extension/pipeline/finish.js +5 -1
  55. package/dist/extension/pipeline/goCommand.d.ts +1 -1
  56. package/dist/extension/pipeline/goCommand.js +40 -9
  57. package/dist/extension/pipeline/goStatusCommands.d.ts +10 -0
  58. package/dist/extension/pipeline/goStatusCommands.js +61 -1
  59. package/dist/extension/pipeline/personas.js +26 -1
  60. package/dist/extension/pipeline/resilience.d.ts +2 -1
  61. package/dist/extension/pipeline/resilience.js +21 -2
  62. package/dist/extension/pipeline/runRegistry.d.ts +23 -1
  63. package/dist/extension/pipeline/runRegistry.js +57 -1
  64. package/dist/extension/pipeline/runner.js +4 -0
  65. package/dist/extension/pipeline/verify.d.ts +4 -0
  66. package/dist/extension/pipeline/verify.js +48 -26
  67. package/dist/extension/recordDecisionTool.d.ts +8 -0
  68. package/dist/extension/recordDecisionTool.js +24 -0
  69. package/dist/extension/redact.d.ts +20 -0
  70. package/dist/extension/redact.js +64 -0
  71. package/dist/extension/rerouteNotice.d.ts +3 -4
  72. package/dist/extension/rerouteNotice.js +20 -11
  73. package/dist/extension/subagentRender.d.ts +129 -0
  74. package/dist/extension/subagentRender.js +441 -0
  75. package/dist/extension/subagents.d.ts +11 -8
  76. package/dist/extension/subagents.js +162 -37
  77. package/dist/extension/ticketTools.d.ts +37 -0
  78. package/dist/extension/ticketTools.js +117 -0
  79. package/dist/extension/todos.d.ts +28 -1
  80. package/dist/extension/todos.js +76 -1
  81. package/dist/extension/tokenProvider.js +46 -5
  82. package/dist/extension/ultra.d.ts +27 -0
  83. package/dist/extension/ultra.js +76 -0
  84. package/dist/launch.d.ts +7 -0
  85. package/dist/launch.js +24 -12
  86. package/dist/login.d.ts +4 -2
  87. package/dist/login.js +19 -4
  88. package/dist/padding.d.ts +22 -0
  89. package/dist/padding.js +25 -0
  90. package/dist/promptEnrichment.d.ts +40 -0
  91. package/dist/promptEnrichment.js +85 -0
  92. package/dist/signalForward.d.ts +60 -0
  93. package/dist/signalForward.js +130 -0
  94. package/dist/token.d.ts +25 -0
  95. package/dist/token.js +45 -0
  96. package/package.json +6 -5
  97. package/dist/extension/boostCommand.d.ts +0 -144
  98. package/dist/extension/boostCommand.js +0 -263
@@ -0,0 +1,193 @@
1
+ /**
2
+ * The opt-in repo seeding beat (org plane, Run 7).
3
+ *
4
+ * On the first interactive session in a repo whose repo-scoped decision
5
+ * ledger is empty (`/context?repo=` reports `repoDecisionCount: 0`), YAGNI
6
+ * Code offers — once — to read the repo's own decision record and seed the
7
+ * ledger: "Read this repo's ADRs and docs and seed the decision ledger?".
8
+ * One keystroke, then the backend extracts, dedupes, and banks at most 12
9
+ * `repo_mined` asserted rows, and the beat prints the banked count plus the
10
+ * Library review deep link.
11
+ *
12
+ * Never automatic, never a nag: a decline writes a per-repo marker so the
13
+ * beat does not recur; an accepted pass marks the repo only after the server
14
+ * confirms (a transport failure leaves the offer available next session).
15
+ * The corpus is collected at git HEAD (same echo-chamber guard as
16
+ * repoDocs.ts) — ADR directories, agent-instruction root docs, top-level
17
+ * docs/*.md, and recent merge subjects — bounded client-side to the same
18
+ * limits the backend enforces.
19
+ */
20
+ import { execFileSync } from "node:child_process";
21
+ import * as fs from "node:fs";
22
+ import { dirname, join } from "node:path";
23
+ import { METERED_POST_FETCH_POLICY, resilientFetch } from "./resilientFetch.js";
24
+ /**
25
+ * Client-side bounds on the mining corpus — mirror of the backend's
26
+ * MINE_INPUT_LIMITS so nothing is silently truncated server-side.
27
+ */
28
+ export const MINE_BEAT_LIMITS = {
29
+ maxDocs: 40,
30
+ maxPathChars: 300,
31
+ maxExcerptChars: 8_000,
32
+ maxCommitSubjects: 200,
33
+ maxSubjectChars: 200,
34
+ };
35
+ const GIT_OPTS = {
36
+ encoding: "utf8",
37
+ maxBuffer: 16 * 1024 * 1024,
38
+ stdio: ["ignore", "pipe", "ignore"],
39
+ };
40
+ function git(cwd, args) {
41
+ try {
42
+ return execFileSync("git", args, { cwd, ...GIT_OPTS });
43
+ }
44
+ catch {
45
+ return null;
46
+ }
47
+ }
48
+ /** Parse `owner/name` from an origin remote URL (ssh or https). */
49
+ export function parseRepoFullName(remote) {
50
+ if (!remote)
51
+ return undefined;
52
+ const m = remote.trim().match(/[:/]([^/:]+\/[^/]+?)(?:\.git)?$/);
53
+ return m ? m[1] : undefined;
54
+ }
55
+ export const defaultMineBeatGit = {
56
+ repoFullName: (cwd) => parseRepoFullName(git(cwd, ["config", "--get", "remote.origin.url"])),
57
+ listAtHead: (cwd) => {
58
+ const out = git(cwd, ["ls-tree", "-r", "--name-only", "HEAD"]);
59
+ return out === null ? null : out.split("\n").filter(Boolean);
60
+ },
61
+ readAtHead: (cwd, rel) => git(cwd, ["show", `HEAD:${rel}`]),
62
+ mergeSubjects: (cwd, max) => {
63
+ const merges = git(cwd, ["log", "--merges", `-n`, String(max), "--pretty=%s"]);
64
+ const chosen = merges && merges.trim().length > 0
65
+ ? merges
66
+ : // Squash-merge repos have no merge commits; plain subjects still
67
+ // carry the shipped-work trail. No further heuristics (spec flag).
68
+ git(cwd, ["log", `-n`, String(max), "--pretty=%s"]);
69
+ return (chosen ?? "").split("\n").map((s) => s.trim()).filter(Boolean).slice(0, max);
70
+ },
71
+ };
72
+ /** Is this HEAD-tracked path part of the repo's decision record? */
73
+ export function isMineCandidate(rel) {
74
+ if (/^(?:docs\/)?adrs?\/[^/]+\.md$/i.test(rel))
75
+ return true;
76
+ if (/^(?:AGENTS|CLAUDE|CONTRIBUTING|CONTEXT|ARCHITECTURE)\.md$/i.test(rel))
77
+ return true;
78
+ if (/^docs\/[^/]+\.md$/i.test(rel))
79
+ return true;
80
+ return false;
81
+ }
82
+ /** Collect the HEAD-era mining corpus, bounded to MINE_BEAT_LIMITS. */
83
+ export function collectMineCorpus(cwd, gitImpl = defaultMineBeatGit) {
84
+ const tracked = gitImpl.listAtHead(cwd);
85
+ if (tracked === null)
86
+ return { docs: [], commitSubjects: [] };
87
+ const docs = [];
88
+ for (const rel of tracked) {
89
+ if (docs.length >= MINE_BEAT_LIMITS.maxDocs)
90
+ break;
91
+ if (rel.length > MINE_BEAT_LIMITS.maxPathChars)
92
+ continue;
93
+ if (!isMineCandidate(rel))
94
+ continue;
95
+ const body = gitImpl.readAtHead(cwd, rel);
96
+ if (body === null || body.trim().length === 0)
97
+ continue;
98
+ docs.push({ path: rel, excerpt: body.trim().slice(0, MINE_BEAT_LIMITS.maxExcerptChars) });
99
+ }
100
+ const commitSubjects = gitImpl
101
+ .mergeSubjects(cwd, MINE_BEAT_LIMITS.maxCommitSubjects)
102
+ .map((s) => s.slice(0, MINE_BEAT_LIMITS.maxSubjectChars));
103
+ return { docs, commitSubjects };
104
+ }
105
+ /** Filesystem path of one repo's beat marker under the state home. */
106
+ export function mineMarkerPath(stateHome, repoFullName) {
107
+ return join(stateHome, "mine-beat", `${repoFullName.replace(/[/\\]/g, "__")}.json`);
108
+ }
109
+ export function fileMineBeatMarkers(stateHome) {
110
+ return {
111
+ has: (repo) => {
112
+ try {
113
+ return fs.existsSync(mineMarkerPath(stateHome, repo));
114
+ }
115
+ catch {
116
+ return false;
117
+ }
118
+ },
119
+ write: (repo, status) => {
120
+ try {
121
+ const p = mineMarkerPath(stateHome, repo);
122
+ fs.mkdirSync(dirname(p), { recursive: true });
123
+ fs.writeFileSync(p, JSON.stringify({ status, at: new Date().toISOString() }), "utf8");
124
+ }
125
+ catch {
126
+ // A marker failure must never break the session; worst case the beat
127
+ // asks once more next launch.
128
+ }
129
+ },
130
+ };
131
+ }
132
+ /**
133
+ * Offer (once per repo) and, on yes, run the seeding pass. Fail-soft
134
+ * throughout: any error surfaces as a notify at most, never a broken session.
135
+ */
136
+ export async function maybeOfferMiningBeat(ctx, opts) {
137
+ const { repoFullName } = opts;
138
+ // Only an EXPLICIT zero fires the beat: an absent count means an older
139
+ // backend or a count failure, and a nag on either would be wrong.
140
+ if (!repoFullName || opts.repoDecisionCount !== 0)
141
+ return { offered: false };
142
+ if (opts.markers.has(repoFullName))
143
+ return { offered: false };
144
+ if (!ctx.hasUI || typeof ctx.ui.confirm !== "function")
145
+ return { offered: false };
146
+ const collect = opts.collect ?? collectMineCorpus;
147
+ const corpus = collect(opts.cwd, opts.gitImpl ?? defaultMineBeatGit);
148
+ // Nothing to mine yet: skip WITHOUT a marker, so a later checkout that
149
+ // gains docs still gets its one offer.
150
+ if (corpus.docs.length === 0 && corpus.commitSubjects.length === 0) {
151
+ return { offered: false };
152
+ }
153
+ const accepted = await ctx.ui.confirm("Seed the decision ledger?", `Read this repo's ADRs and docs and bank up to 12 candidate decisions for ${repoFullName}? You review every one in the Library before it counts as confirmed.`);
154
+ if (!accepted) {
155
+ opts.markers.write(repoFullName, "declined");
156
+ return { offered: true, accepted: false };
157
+ }
158
+ try {
159
+ const res = await resilientFetch(`${opts.baseUrl}/api/yagni-code/decisions/mine`, {
160
+ method: "POST",
161
+ headers: {
162
+ authorization: `Bearer ${opts.getToken() ?? ""}`,
163
+ "content-type": "application/json",
164
+ },
165
+ body: JSON.stringify({
166
+ repo: repoFullName,
167
+ docs: corpus.docs,
168
+ commitSubjects: corpus.commitSubjects,
169
+ // Stable per repo: the write-spool replay contract makes a retry of
170
+ // the same pass idempotent server-side.
171
+ idempotencyKey: `mine:${repoFullName}`,
172
+ }),
173
+ }, { fetchImpl: opts.fetchImpl, policy: METERED_POST_FETCH_POLICY });
174
+ if (!res.ok) {
175
+ // No marker: the offer stays available next session.
176
+ ctx.ui.notify("Seeding the decision ledger failed; YAGNI Code will offer again next session.", "error");
177
+ return { offered: true, accepted: true };
178
+ }
179
+ const body = (await res.json());
180
+ opts.markers.write(repoFullName, "seeded");
181
+ if (body.alreadyMined)
182
+ return { offered: true, accepted: true, banked: 0 };
183
+ const banked = typeof body.banked === "number" ? body.banked : 0;
184
+ const noun = banked === 1 ? "decision" : "decisions";
185
+ ctx.ui.notify(`Banked ${banked} ${noun} read from this repo's docs. Review them: ${opts.baseUrl}/knowledge/decisions`, "info");
186
+ return { offered: true, accepted: true, banked };
187
+ }
188
+ catch {
189
+ ctx.ui.notify("Seeding the decision ledger failed; YAGNI Code will offer again next session.", "error");
190
+ return { offered: true, accepted: true };
191
+ }
192
+ }
193
+ //# sourceMappingURL=mineBeat.js.map
@@ -4,7 +4,8 @@
4
4
  * YAGNI Code registers no tool_call handler today, so the interactive session has
5
5
  * no plan/approval surface. P3 adds one on pi's documented `tool_call` block seam
6
6
  * plus a `/mode` command:
7
- * - auto (default): never blocks. Byte-identical to today, so this is additive.
7
+ * - auto (default): ordinary coding tools run directly; external tracker
8
+ * changes still require fresh human confirmation.
8
9
  * - plan : blocks write/edit/bash so the agent can explore + propose without
9
10
  * touching the tree.
10
11
  * - review : surfaces a three-way ctx.ui.select before a write/edit/bash; a
@@ -14,7 +15,7 @@
14
15
  * adds a session-scoped bless rule AND drafts a decision capture.
15
16
  *
16
17
  * `decideGate` is PURE; the live wiring holds the mode in a small closure (no
17
- * module-global state). The default auto mode remains fail-open, but stricter
18
+ * module-global state). The default auto mode remains direct for coding tools, but stricter
18
19
  * modes fail closed for side-effect tools if the gate itself errors. Bless rules
19
20
  * are session-scoped, path-prefix-bound, never persisted, and never consulted in
20
21
  * plan mode (plan blocks outright before isBlessed is reached).
@@ -26,20 +27,43 @@
26
27
  * the context so the model doesn't keep believing it is restricted.
27
28
  */
28
29
  import type { ExtensionAPI, ExtensionContext } from "@earendil-works/pi-coding-agent";
30
+ import { type ApprovedPrefixGrant } from "./approvedPrefixes.js";
29
31
  import { type BlessStore } from "./bless.js";
32
+ import { type ExecPolicy } from "./execPolicy.js";
33
+ import { type GuardianError, type GuardianRiskLevel } from "./guardian.js";
30
34
  export type PermissionMode = "auto" | "plan" | "review";
35
+ /**
36
+ * A shared, mutable holder for the current permission mode. Both
37
+ * `registerPermissionGate` (which writes on /mode) and the footer factory
38
+ * (which reads on every render) hold a reference, so mode changes appear
39
+ * immediately in the footer's line 2.
40
+ */
41
+ export interface ModeHolder {
42
+ get(): PermissionMode;
43
+ set(m: PermissionMode): void;
44
+ onSet(fn: (m: PermissionMode) => void): void;
45
+ }
46
+ export declare function createModeHolder(initial?: PermissionMode): ModeHolder;
31
47
  /** Which tools each tier acts on, plus the optional grounding-bless predicate. */
32
48
  export interface PermissionPolicy {
33
49
  /** Tools blocked outright in plan mode (write/exec). */
34
50
  planBlockTools: string[];
35
51
  /** Tools that prompt for confirmation in review mode. */
36
52
  reviewConfirmTools: string[];
53
+ /** Consequential external writes that require fresh consent in every mode. */
54
+ alwaysConfirmTools?: string[];
37
55
  /**
38
56
  * Optional: a recorded decision already blesses this action, so it auto-runs in
39
57
  * review mode instead of prompting. The hook for tying the gate to captured
40
58
  * judgment; default undefined (nothing pre-blessed).
41
59
  */
42
60
  isBlessed?: (toolName: string, params: Record<string, unknown>) => boolean;
61
+ /**
62
+ * Exec policy for bash command classification. When present, bash commands are
63
+ * classified by the exec policy engine before the tool-granular logic runs.
64
+ * Defaults to DEFAULT_EXEC_POLICY when absent.
65
+ */
66
+ execPolicy?: ExecPolicy;
43
67
  }
44
68
  export declare const DEFAULT_PERMISSION_POLICY: PermissionPolicy;
45
69
  /** A pure gate verdict: block outright, ask to confirm, or allow. */
@@ -48,13 +72,55 @@ export interface GateDecision {
48
72
  reason?: string;
49
73
  /** review mode only: the caller should ctx.ui.confirm before allowing. */
50
74
  confirm?: boolean;
75
+ /**
76
+ * Exec policy classification result for bash commands. When present, the
77
+ * tool_call handler should run the Guardian for "prompt" before falling
78
+ * through to the user confirm. Absent for non-bash tools or when no
79
+ * command string is available.
80
+ */
81
+ classify?: "allow" | "prompt" | "forbidden";
82
+ /**
83
+ * The exec policy's justification for a "prompt" classification — why the
84
+ * command was routed to the Guardian. Threaded into the consult prompt and
85
+ * the storage event (it is the layer-tuning signal, YAG-510).
86
+ */
87
+ classifyJustification?: string;
51
88
  }
52
89
  /**
53
- * Pure permission decision for one tool call under a mode + policy. auto always
54
- * allows; plan blocks the write/exec set; review marks writes for confirmation
90
+ * Pure permission decision for one tool call under a mode + policy. Auto allows
91
+ * ordinary tools; plan blocks the write/exec set; review marks writes for confirmation
55
92
  * unless a recorded decision blesses them.
56
93
  */
57
94
  export declare function decideGate(toolName: string, params: Record<string, unknown>, mode: PermissionMode, policy: PermissionPolicy): GateDecision;
95
+ /**
96
+ * Terminal outcome of one prompt-band decision (YAG-510). One storage event
97
+ * is emitted per terminal outcome; `consulted` says whether a Guardian LLM
98
+ * call actually happened (grants/cache hits skip it).
99
+ */
100
+ export type GuardianGateOutcome = "prefix_allow" | "cached_allow" | "allow" | "deny" | "ask_approved" | "ask_approved_remembered" | "ask_denied" | "ask_headless_blocked" | "breaker_ask_approved" | "breaker_blocked" | GuardianError;
101
+ /**
102
+ * Rich per-decision event for opt-in storage (YAG-510). Carries the RAW
103
+ * command — the wiring layer (index.ts) hashes/redacts per the workspace's
104
+ * storage tier before anything leaves the machine. Distinct from the
105
+ * sanitized GuardianDiagnosticEvent (local debug log), which never carries
106
+ * the command.
107
+ */
108
+ export interface GuardianGateEvent {
109
+ command: string;
110
+ outcome: GuardianGateOutcome;
111
+ /** Why the exec policy routed this command to the Guardian. */
112
+ execJustification?: string;
113
+ riskLevel?: GuardianRiskLevel;
114
+ rationale?: string;
115
+ mode: PermissionMode;
116
+ tier?: string;
117
+ durationMs?: number;
118
+ /** Whether a Guardian LLM consult actually ran for this decision. */
119
+ consulted: boolean;
120
+ /** Present when the terminal outcome followed a Guardian error (e.g. an
121
+ * error-fallback ask that the user then approved). */
122
+ guardianError?: GuardianError;
123
+ }
58
124
  /** What was blessed with "don't ask again", handed to the capture hook. */
59
125
  export interface BlessRememberInfo {
60
126
  tool: string;
@@ -73,16 +139,64 @@ export interface RegisterPermissionDeps {
73
139
  * never blocks the approved tool call.
74
140
  */
75
141
  onBlessRemember?: (ctx: ExtensionContext, info: BlessRememberInfo) => void | Promise<void>;
142
+ /** Shared holder so the footer can read the live mode on every render. */
143
+ modeHolder?: ModeHolder;
144
+ /**
145
+ * Guardian state handle for the session. When present, prompt-band bash
146
+ * commands are auto-reviewed by the Guardian LLM instead of interrupting
147
+ * the user. When absent, prompt-band commands fall through to the existing
148
+ * tool-granular behavior (review: user prompt, auto: allow).
149
+ */
150
+ guardianState?: import("./guardian.js").GuardianStateHandle;
151
+ /** Guardian limits (timeouts, circuit breaker). Defaults to DEFAULT_GUARDIAN_LIMITS. */
152
+ guardianLimits?: import("./guardian.js").GuardianLimits;
153
+ /** Override the Guardian model tier (default: efficient). */
154
+ guardianTier?: string;
155
+ /** Injectable Guardian review function (tests pass a stub). */
156
+ guardianReview?: (command: string, deps: import("./guardian.js").ReviewCommandDeps) => Promise<import("./guardian.js").ReviewResult>;
157
+ /**
158
+ * Called (fire-and-forget) after each Guardian review with a sanitized
159
+ * diagnostic event. Fail-soft; never blocks.
160
+ */
161
+ onGuardianReview?: (event: import("./guardian.js").GuardianDiagnosticEvent) => void;
162
+ /** Disable the Guardian entirely (env var YAGNI_DISABLE_GUARDIAN). */
163
+ guardianDisabled?: boolean;
164
+ /**
165
+ * Persisted "don't ask again" grants for this session's repo (YAG-510).
166
+ * Consulted ONLY after the exec policy classified a command as "prompt" —
167
+ * a grant can never override the forbidden band. Suppressed in review mode.
168
+ */
169
+ grants?: readonly ApprovedPrefixGrant[];
170
+ /** Resolve the grant scope key for a cwd (git remote origin URL fallback
171
+ * realpath). Lazily invoked on the first prompt-band bash call. */
172
+ resolveRepoKey?: (cwd: string) => string;
173
+ /** Persist a new grant (fire-and-forget; the in-memory list is updated
174
+ * either way). index.ts wires approvedPrefixes.appendGrant. */
175
+ persistGrant?: (grant: ApprovedPrefixGrant) => void;
176
+ /**
177
+ * Called (fire-and-forget) at every terminal prompt-band outcome with the
178
+ * rich storage event (raw command — the wiring layer redacts/hashes).
179
+ * Fail-soft; never blocks.
180
+ */
181
+ onGuardianEvent?: (event: GuardianGateEvent) => void;
76
182
  }
77
- /** The customType tag on injected plan-mode context (filterable later). */
78
- export declare const PLAN_CONTEXT_TYPE = "yagni-plan-context";
183
+ /** The customType tag on injected mode-context messages (filterable later). */
184
+ export declare const MODE_CONTEXT_TYPE = "yagni-mode-context";
185
+ /** Legacy alias — the original plan-mode tag, kept for backward compat. */
186
+ export declare const PLAN_CONTEXT_TYPE = "yagni-mode-context";
79
187
  export declare const PLAN_CONTEXT_MESSAGE = "[PLAN MODE ACTIVE]\nYou are in plan mode: explore and design, change nothing.\n- write, edit, and bash are held by the permission gate; do not attempt them.\n- Read, search, and ask_yagni freely to ground the plan in how this company works.\n- Produce a concrete numbered plan of the steps you would take, with the files involved.\n- End by asking the user to review the plan; they run /mode auto (or /mode review) to execute it.\n- Once executing, track the plan's steps with todo_write.";
188
+ /** Build the mode-awareness context message for the current permission mode. */
189
+ export declare function buildModeContextMessage(mode: PermissionMode): string;
80
190
  /**
81
- * Drop previously injected plan-mode context once plan mode is off, so the
82
- * model stops believing writes are held. Pure; returns the SAME array when
83
- * nothing needs filtering so callers can cheaply detect a no-op.
191
+ * Drop previously injected mode-context messages from a DIFFERENT mode so the
192
+ * model does not keep believing it is in a prior mode. Messages matching the
193
+ * current mode are kept (the fresh injection from before_agent_start should
194
+ * survive). Pure; returns the SAME array when nothing needs filtering so callers
195
+ * can cheaply detect a no-op.
84
196
  */
85
- export declare function filterStalePlanContext<T>(messages: T[]): T[];
197
+ export declare function filterStaleModeContext<T>(messages: T[], currentMode?: PermissionMode): T[];
198
+ /** Legacy alias — the original plan-mode filter name. */
199
+ export declare const filterStalePlanContext: typeof filterStaleModeContext;
86
200
  /**
87
201
  * Wire the tool_call gate + the /mode command onto a shared mode holder. Default
88
202
  * auto, so absent any /mode this is a no-op over today's behavior.