@yagni-app/code-staging 0.3.0-staging.1064.1 → 0.3.0-staging.1071.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.
@@ -27,6 +27,9 @@
27
27
  * the context so the model doesn't keep believing it is restricted.
28
28
  */
29
29
  import { makeBlessStore as defaultMakeBlessStore } from "./bless.js";
30
+ import { classifyCommand, DEFAULT_EXEC_POLICY } from "./execPolicy.js";
31
+ import { isDebug } from "./diagnostics.js";
32
+ import { buildDiagnosticEvent, checkCircuitBreaker, DEFAULT_GUARDIAN_LIMITS, } from "./guardian.js";
30
33
  export function createModeHolder(initial = "auto") {
31
34
  let current = initial;
32
35
  return {
@@ -57,6 +60,33 @@ export function decideGate(toolName, params, mode, policy) {
57
60
  if (policy.alwaysConfirmTools?.includes(toolName)) {
58
61
  return { block: false, confirm: true };
59
62
  }
63
+ // Exec policy: classify bash commands before the tool-granular logic.
64
+ if (toolName === "bash") {
65
+ const command = typeof params.command === "string" ? params.command.trim() : "";
66
+ if (command) {
67
+ try {
68
+ const execPolicy = policy.execPolicy ?? DEFAULT_EXEC_POLICY;
69
+ const classification = classifyCommand(command, execPolicy);
70
+ if (classification.decision === "allow")
71
+ return { block: false };
72
+ if (classification.decision === "forbidden") {
73
+ return { block: true, reason: classification.justification };
74
+ }
75
+ // prompt — signal to the handler so it can run the Guardian.
76
+ // In auto mode the handler runs the Guardian; in review mode the
77
+ // handler runs the Guardian first, then falls back to user confirm.
78
+ if (mode === "auto")
79
+ return { block: false, classify: "prompt" };
80
+ // review mode
81
+ if (policy.isBlessed?.(toolName, params))
82
+ return { block: false };
83
+ return { block: false, confirm: true, classify: "prompt" };
84
+ }
85
+ catch {
86
+ // classifyCommand threw — fall through to tool-granular logic (graceful degradation).
87
+ }
88
+ }
89
+ }
60
90
  if (mode === "auto")
61
91
  return { block: false };
62
92
  // review
@@ -67,9 +97,13 @@ export function decideGate(toolName, params, mode, policy) {
67
97
  }
68
98
  return { block: false };
69
99
  }
70
- /** The customType tag on injected plan-mode context (filterable later). */
71
- export const PLAN_CONTEXT_TYPE = "yagni-plan-context";
100
+ /** The customType tag on injected mode-context messages (filterable later). */
101
+ export const MODE_CONTEXT_TYPE = "yagni-mode-context";
102
+ /** Legacy alias — the original plan-mode tag, kept for backward compat. */
103
+ export const PLAN_CONTEXT_TYPE = MODE_CONTEXT_TYPE;
72
104
  const PLAN_MARKER = "[PLAN MODE ACTIVE]";
105
+ const AUTO_MARKER = "[AUTO MODE]";
106
+ const REVIEW_MARKER = "[REVIEW MODE]";
73
107
  export const PLAN_CONTEXT_MESSAGE = `${PLAN_MARKER}
74
108
  You are in plan mode: explore and design, change nothing.
75
109
  - write, edit, and bash are held by the permission gate; do not attempt them.
@@ -77,31 +111,84 @@ You are in plan mode: explore and design, change nothing.
77
111
  - Produce a concrete numbered plan of the steps you would take, with the files involved.
78
112
  - End by asking the user to review the plan; they run /mode auto (or /mode review) to execute it.
79
113
  - Once executing, track the plan's steps with todo_write.`;
80
- function mentionsPlanMarker(content) {
114
+ const AUTO_CONTEXT_MESSAGE = `${AUTO_MARKER}
115
+ You are in auto mode. Coding commands run directly.
116
+ - Proactively verify your work: run tests, lint, and typecheck after changes.
117
+ - Destructive commands (rm -rf, git reset --hard, git push --force) are blocked by the exec policy.
118
+ - Ambiguous commands are reviewed by the Guardian before running.`;
119
+ const REVIEW_CONTEXT_MESSAGE = `${REVIEW_MARKER}
120
+ You are in review mode. Each bash, write, and edit may be confirmed before running.
121
+ - Safe commands (ls, cat, git status) run without prompting.
122
+ - Ambiguous commands are reviewed by the Guardian first; if the Guardian allows, they run without prompting.
123
+ - If the Guardian denies or is unavailable, you will be asked to confirm.
124
+ - Propose verification steps but wait for approval before running them.`;
125
+ /** Build the mode-awareness context message for the current permission mode. */
126
+ export function buildModeContextMessage(mode) {
127
+ switch (mode) {
128
+ case "plan":
129
+ return PLAN_CONTEXT_MESSAGE;
130
+ case "review":
131
+ return REVIEW_CONTEXT_MESSAGE;
132
+ case "auto":
133
+ return AUTO_CONTEXT_MESSAGE;
134
+ }
135
+ }
136
+ const MODE_MARKERS = {
137
+ plan: PLAN_MARKER,
138
+ auto: AUTO_MARKER,
139
+ review: REVIEW_MARKER,
140
+ };
141
+ function mentionsModeMarker(content) {
81
142
  if (typeof content === "string")
82
- return content.includes(PLAN_MARKER);
143
+ return Object.values(MODE_MARKERS).some((m) => content.includes(m));
83
144
  if (Array.isArray(content)) {
84
145
  return content.some((c) => typeof c?.text === "string" &&
85
- (c.text.includes(PLAN_MARKER)));
146
+ Object.values(MODE_MARKERS).some((m) => c.text.includes(m)));
86
147
  }
87
148
  return false;
88
149
  }
150
+ /** Which mode marker does this message carry, if any? */
151
+ function modeMarkerFor(content) {
152
+ const text = typeof content === "string"
153
+ ? content
154
+ : Array.isArray(content)
155
+ ? content.find((c) => typeof c?.text === "string")?.text
156
+ : undefined;
157
+ if (!text)
158
+ return null;
159
+ for (const [mode, marker] of Object.entries(MODE_MARKERS)) {
160
+ if (text.includes(marker))
161
+ return marker;
162
+ }
163
+ return null;
164
+ }
89
165
  /**
90
- * Drop previously injected plan-mode context once plan mode is off, so the
91
- * model stops believing writes are held. Pure; returns the SAME array when
92
- * nothing needs filtering so callers can cheaply detect a no-op.
166
+ * Drop previously injected mode-context messages from a DIFFERENT mode so the
167
+ * model does not keep believing it is in a prior mode. Messages matching the
168
+ * current mode are kept (the fresh injection from before_agent_start should
169
+ * survive). Pure; returns the SAME array when nothing needs filtering so callers
170
+ * can cheaply detect a no-op.
93
171
  */
94
- export function filterStalePlanContext(messages) {
172
+ export function filterStaleModeContext(messages, currentMode) {
173
+ const currentMarker = currentMode ? MODE_MARKERS[currentMode] : undefined;
95
174
  const keep = messages.filter((m) => {
96
175
  const msg = m;
97
- if (msg?.customType === PLAN_CONTEXT_TYPE)
98
- return false;
99
- if (msg?.role === "user" && mentionsPlanMarker(msg.content))
100
- return false;
176
+ if (msg?.customType === MODE_CONTEXT_TYPE) {
177
+ // Keep if it matches the current mode; strip if from a different mode
178
+ // (or if we don't know the current mode — strip all to be safe).
179
+ const marker = modeMarkerFor(msg.content);
180
+ return currentMarker !== undefined && marker === currentMarker;
181
+ }
182
+ if (msg?.role === "user" && mentionsModeMarker(msg.content)) {
183
+ const marker = modeMarkerFor(msg.content);
184
+ return currentMarker !== undefined && marker === currentMarker;
185
+ }
101
186
  return true;
102
187
  });
103
188
  return keep.length === messages.length ? messages : keep;
104
189
  }
190
+ /** Legacy alias — the original plan-mode filter name. */
191
+ export const filterStalePlanContext = filterStaleModeContext;
105
192
  const MODE_STATUS = {
106
193
  auto: undefined,
107
194
  plan: "⏸ plan",
@@ -143,6 +230,15 @@ function externalTrackerPrompt(toolName, input) {
143
230
  }
144
231
  return "Confirm external tracker change";
145
232
  }
233
+ function guardianErrorMessage(error) {
234
+ switch (error) {
235
+ case "timeout": return "review timed out";
236
+ case "malformed": return "unclear verdict";
237
+ case "empty": return "no response";
238
+ case "network": return "service unavailable";
239
+ default: return "unknown error";
240
+ }
241
+ }
146
242
  /**
147
243
  * Wire the tool_call gate + the /mode command onto a shared mode holder. Default
148
244
  * auto, so absent any /mode this is a no-op over today's behavior.
@@ -162,12 +258,125 @@ export function registerPermissionGate(pi, deps = {}) {
162
258
  isBlessed: basePolicy.isBlessed ?? ((tool, params) => blessStore?.isBlessed(tool, params) ?? false),
163
259
  };
164
260
  const sideEffects = sideEffectTools(effectivePolicy);
261
+ const guardianState = deps.guardianState;
262
+ const guardianLimits = deps.guardianLimits;
263
+ const guardianDisabled = deps.guardianDisabled ?? false;
264
+ const guardianReview = deps.guardianReview;
265
+ const guardianTier = deps.guardianTier;
165
266
  pi.on("tool_call", async (event, ctx) => {
166
267
  try {
167
268
  const input = event.input ?? {};
168
269
  const decision = decideGate(event.toolName, input, mode, effectivePolicy);
169
270
  if (decision.block)
170
271
  return { block: true, reason: decision.reason };
272
+ // Guardian: when the exec policy classified a bash command as "prompt",
273
+ // run the Guardian LLM review instead of interrupting the user (if enabled).
274
+ if (decision.classify === "prompt" && guardianState && !guardianDisabled && guardianReview) {
275
+ const command = typeof input.command === "string"
276
+ ? input.command
277
+ : "";
278
+ // Session cap check — prevents unlimited Guardian consults.
279
+ const limits = guardianLimits ?? DEFAULT_GUARDIAN_LIMITS;
280
+ if (guardianState.read().reviews >= limits.maxReviews) {
281
+ if (ctx?.hasUI)
282
+ ctx.ui.notify(`Guardian review cap reached (${limits.maxReviews} this session).`, "warning");
283
+ return { block: true, reason: `Guardian review cap reached (${limits.maxReviews} this session). Switch to /mode review to approve manually.` };
284
+ }
285
+ // Circuit breaker check.
286
+ const breaker = checkCircuitBreaker(guardianState.read(), limits);
287
+ if (breaker.tripped) {
288
+ if (ctx?.hasUI)
289
+ ctx.ui.notify(breaker.reason ?? "Guardian circuit breaker tripped.", "warning");
290
+ return { block: true, reason: breaker.reason };
291
+ }
292
+ // Show the reviewing chip.
293
+ if (ctx?.hasUI)
294
+ ctx.ui.setStatus?.("yagni-guardian", "🛡 reviewing");
295
+ const startMs = Date.now();
296
+ let reviewResult;
297
+ try {
298
+ reviewResult = await guardianReview(command, {
299
+ cwd: ctx?.cwd ?? ".",
300
+ ...(ctx?.signal ? { signal: ctx.signal } : {}),
301
+ ...(guardianTier ? { modelTier: guardianTier } : {}),
302
+ });
303
+ }
304
+ catch {
305
+ reviewResult = { verdict: null, error: "network", cost: 0 };
306
+ }
307
+ finally {
308
+ if (ctx?.hasUI)
309
+ ctx.ui.setStatus?.("yagni-guardian", undefined);
310
+ }
311
+ const durationMs = Date.now() - startMs;
312
+ const state = guardianState.read();
313
+ if (reviewResult.verdict?.outcome === "allow") {
314
+ guardianState.recordReview("allow");
315
+ if (deps.onGuardianReview) {
316
+ void Promise.resolve(deps.onGuardianReview(buildDiagnosticEvent("allow", {
317
+ durationMs,
318
+ tier: guardianTier,
319
+ rationale: reviewResult.verdict.rationale,
320
+ debug: isDebug(),
321
+ }))).catch(() => { });
322
+ }
323
+ return {};
324
+ }
325
+ if (reviewResult.verdict?.outcome === "deny") {
326
+ guardianState.recordReview("deny");
327
+ const rationale = reviewResult.verdict.rationale;
328
+ if (deps.onGuardianReview) {
329
+ void Promise.resolve(deps.onGuardianReview(buildDiagnosticEvent("deny", {
330
+ durationMs,
331
+ tier: guardianTier,
332
+ rationale,
333
+ debug: isDebug(),
334
+ }))).catch(() => { });
335
+ }
336
+ // Check circuit breaker after recording.
337
+ const breaker2 = checkCircuitBreaker(guardianState.read(), guardianLimits ?? DEFAULT_GUARDIAN_LIMITS);
338
+ if (breaker2.tripped) {
339
+ if (ctx?.hasUI)
340
+ ctx.ui.notify(breaker2.reason ?? "Guardian circuit breaker tripped.", "warning");
341
+ }
342
+ else if (ctx?.hasUI) {
343
+ ctx.ui.notify(`Guardian denied: ${rationale}`, "warning");
344
+ }
345
+ return {
346
+ block: true,
347
+ reason: `Guardian denied: ${rationale} Find a safer alternative or ask the user to proceed.`,
348
+ };
349
+ }
350
+ // Guardian failed (timeout/malformed/network/empty).
351
+ if (deps.onGuardianReview) {
352
+ void Promise.resolve(deps.onGuardianReview(buildDiagnosticEvent(reviewResult.error ?? "network", {
353
+ durationMs,
354
+ tier: guardianTier,
355
+ debug: isDebug(),
356
+ }))).catch(() => { });
357
+ }
358
+ // Graceful degradation per mode.
359
+ if (mode === "review" && decision.confirm) {
360
+ // Fall through to the existing user confirm flow below.
361
+ }
362
+ else {
363
+ // Auto mode (or review without confirm): fail closed.
364
+ const errorMsg = guardianErrorMessage(reviewResult.error);
365
+ if (ctx?.hasUI)
366
+ ctx.ui.notify(`Guardian unavailable: ${errorMsg}`, "warning");
367
+ return {
368
+ block: true,
369
+ reason: `Guardian unavailable (${errorMsg}). Switch to /mode review to approve manually.`,
370
+ };
371
+ }
372
+ }
373
+ // Guardian disabled + prompt band: auto mode blocks, review falls through to confirm.
374
+ if (decision.classify === "prompt" && (!guardianState || guardianDisabled) && mode === "auto") {
375
+ return {
376
+ block: true,
377
+ reason: "Ambiguous command blocked (Guardian disabled). Switch to /mode review to approve manually.",
378
+ };
379
+ }
171
380
  if (decision.confirm) {
172
381
  // Review mode needs a confirmation. With no dialog-capable UI (headless),
173
382
  // fail CLOSED: the user explicitly chose a stricter mode, so a write we
@@ -218,22 +427,22 @@ export function registerPermissionGate(pi, deps = {}) {
218
427
  return {};
219
428
  }
220
429
  });
221
- // Model awareness: while plan mode is on, every agent start carries a hidden
222
- // plan-mode context message so the model plans instead of attempting held
223
- // writes. Off-plan turns inject nothing.
430
+ // Model awareness: every agent turn carries a hidden mode-context message so
431
+ // the model knows what it can do (auto: verify proactively, review: wait for
432
+ // approval, plan: hold writes). The context hook strips stale mode context
433
+ // from prior turns so old mode messages don't accumulate.
224
434
  pi.on("before_agent_start", async () => {
225
- if (mode !== "plan")
226
- return;
435
+ // Reset the Guardian circuit breaker at the start of each turn so
436
+ // consecutive denials don't accumulate across turns.
437
+ deps.guardianState?.resetTurn();
227
438
  return {
228
- message: { customType: PLAN_CONTEXT_TYPE, content: PLAN_CONTEXT_MESSAGE, display: false },
439
+ message: { customType: MODE_CONTEXT_TYPE, content: buildModeContextMessage(mode), display: false },
229
440
  };
230
441
  });
231
- // Once plan mode is off, strip the stale plan context so the model does not
232
- // keep believing writes are held.
442
+ // Strip stale mode-context messages from prior turns so the model does not
443
+ // see contradictory mode instructions.
233
444
  pi.on("context", async (event) => {
234
- if (mode === "plan")
235
- return;
236
- const filtered = filterStalePlanContext(event.messages);
445
+ const filtered = filterStaleModeContext(event.messages, mode);
237
446
  if (filtered === event.messages)
238
447
  return;
239
448
  return { messages: filtered };
@@ -95,6 +95,26 @@ The reasoning, grounded in what you actually read. Cite files and lines.
95
95
  The specific evidence that would flip this. If you were not able to verify something load-bearing, say so here rather than hedging the recommendation.
96
96
 
97
97
  Be terse and decisive. The caller is mid-task and paying peak rates for your turn.`;
98
+ const GUARDIAN_BODY = `You are a safety reviewer. You review shell commands before they run and decide whether to allow or deny them. You output only a JSON verdict — no tools, no exploration, no prose.
99
+
100
+ Risk taxonomy:
101
+ - Data exfiltration: sensitive data sent to untrusted destinations. High risk.
102
+ - Credential probing: extracting credentials from unintended sources (browser profiles, logs, env files). High risk.
103
+ - Persistent security weakening: broad or production-impact security changes. High risk.
104
+ - Destructive actions: irreversible data loss, production breaks, broad git cleanup. High risk.
105
+ - Low-risk actions: sandbox retries, benign local filesystem reads, user-requested operations on specific paths. Low risk.
106
+
107
+ Outcome rules:
108
+ - Deny any action that exposes sensitive data without explicit user authorization for that specific destination.
109
+ - Deny credential probing.
110
+ - Deny broad or persistent security weakening unless the user explicitly authorized the exact change.
111
+ - Deny broad destructive actions with significant risk of irreversible damage.
112
+ - Allow low-risk actions and actions the user clearly requested as part of their stated task.
113
+
114
+ Output ONLY a JSON object with this exact shape:
115
+ {"outcome":"allow"|"deny","riskLevel":"low"|"medium"|"high"|"critical","rationale":"one sentence explaining the verdict"}
116
+
117
+ Do not output anything else. No markdown, no prose, only the JSON object.`;
98
118
  /** Persona body keyed by the agent name referenced in `stages.ts`. */
99
119
  export const PERSONA_BODIES = {
100
120
  scout: SCOUT_BODY,
@@ -102,6 +122,7 @@ export const PERSONA_BODIES = {
102
122
  worker: WORKER_BODY,
103
123
  reviewer: REVIEWER_BODY,
104
124
  advisor: ADVISOR_BODY,
125
+ guardian: GUARDIAN_BODY,
105
126
  };
106
127
  /**
107
128
  * Grounding-FREE persona bodies for the M6 grounded-vs-blind eval ONLY. These are
@@ -192,6 +213,7 @@ export const BLIND_PERSONA_BODIES = {
192
213
  worker: WORKER_BLIND,
193
214
  reviewer: REVIEWER_BLIND,
194
215
  advisor: ADVISOR_BLIND,
216
+ guardian: GUARDIAN_BODY,
195
217
  };
196
218
  /** The lens-specific clause appended to the reviewer body, one per review angle. */
197
219
  const LENS_CLAUSES = {
@@ -36,5 +36,5 @@ export declare function promptEnrichmentDisabled(env: NodeJS.ProcessEnv): boolea
36
36
  * model, and the load-bearing instructions (ask_yagni contract, delegation)
37
37
  * live elsewhere in the prompt.
38
38
  */
39
- export declare const ENGINEERING_PRACTICE_SECTION = "Engineering practice:\n\nBias to action: when the user asks you to implement, fix, or change something, use your tools to make the actual edits and run the actual commands \u2014 do not answer with a description of what you would do, or with code for the user to apply themselves. When the user asks HOW to approach something, answer the question first; do not jump into making changes they have not asked for.\n\nConventions:\n- Never assume a library is available, however well known. Before using one, confirm the project already depends on it (its package manifest, or imports in neighboring files).\n- When editing, read the surrounding code and its imports first; match the file's existing style, naming, and patterns rather than introducing your own.\n- When creating a new file or component, study an existing sibling first and follow its structure.\n- Never write code that logs or exposes secrets, keys, or credentials.\n\nVerification:\n- Consider what the code you are changing is supposed to do (from its name, location, and callers) before you change it.\n- Verify changes with the project's own tests when possible. Never assume a test framework or command \u2014 check the README, package scripts, or neighboring tests for the real one.\n- After completing a task, run the project's lint and typecheck commands if you know them; if you cannot find them, ask the user and suggest recording them in AGENTS.md for next time.\n\nVersion control:\n- No unsolicited commits: commit only when the user asked for one or the task at hand clearly calls for it.\n\nCommunication:\n- Answer directly, without preamble or postamble (\"Here is what I will do next...\", \"Based on the information provided...\"). Match the length of your answer to the question.\n- After making edits, report the outcome briefly; do not restate the diff or explain the code you just wrote unless asked.\n- Do not add code comments that narrate what you changed or why the change is correct; comments are for future readers of the code.\n- Reference code as file_path:line_number so the user can jump to it.\n- Before running a non-trivial command that changes state, say in one line what it does and why.\n- Never guess or fabricate URLs. Only use URLs the user provided or that appear in local files.\n- No emojis unless the user asks for them.";
39
+ export declare const ENGINEERING_PRACTICE_SECTION = "Engineering practice:\n\nBias to action: when the user asks you to implement, fix, or change something, use your tools to make the actual edits and run the actual commands \u2014 do not answer with a description of what you would do, or with code for the user to apply themselves. When the user asks HOW to approach something, answer the question first; do not jump into making changes they have not asked for.\n\nConventions:\n- Never assume a library is available, however well known. Before using one, confirm the project already depends on it (its package manifest, or imports in neighboring files).\n- When editing, read the surrounding code and its imports first; match the file's existing style, naming, and patterns rather than introducing your own.\n- When creating a new file or component, study an existing sibling first and follow its structure.\n- Never write code that logs or exposes secrets, keys, or credentials.\n\nVerification:\n- Consider what the code you are changing is supposed to do (from its name, location, and callers) before you change it.\n- Verify changes with the project's own tests when possible. Never assume a test framework or command \u2014 check the README, package scripts, or neighboring tests for the real one.\n- After completing a task, run the project's lint and typecheck commands if you know them; if you cannot find them, ask the user and suggest recording them in AGENTS.md for next time.\n\nVersion control:\n- No unsolicited commits: commit only when the user asked for one or the task at hand clearly calls for it.\n\nGit safety:\n- You may be in a dirty git worktree. Never revert existing changes you did not make unless explicitly asked \u2014 these were made by the user.\n- If there are unrelated changes in files you are touching, read and work with them rather than reverting.\n- If changes appear in unrelated files, ignore them and do not revert.\n- Do not amend a commit unless explicitly asked.\n- If you notice unexpected changes you did not make while working, stop immediately and ask the user.\n- Never use destructive git commands (git reset --hard, git checkout --) unless the user explicitly requests or approves them.\n\nTodo discipline:\n- Track multi-step work with todo_write: keep exactly one item in_progress at a time, mark items completed the moment they are done, and add newly discovered steps as pending.\n- Do not batch-complete items or create single-step plans. Skip planning for trivially small work (~25% of tasks).\n\nMode awareness:\n- In auto mode, proactively run tests, lint, and typecheck after your changes.\n- In review mode, propose verification steps but wait for approval before running them.\n- In plan mode, explore and design only \u2014 the gate holds all writes.\n\nCommunication:\n- Answer directly, without preamble or postamble (\"Here is what I will do next...\", \"Based on the information provided...\"). Match the length of your answer to the question.\n- After making edits, report the outcome briefly; do not restate the diff or explain the code you just wrote unless asked.\n- Do not add code comments that narrate what you changed or why the change is correct; comments are for future readers of the code.\n- Reference code as file_path:line_number so the user can jump to it.\n- Before running a non-trivial command that changes state, say in one line what it does and why.\n- Never guess or fabricate URLs. Only use URLs the user provided or that appear in local files.\n- No emojis unless the user asks for them.";
40
40
  //# sourceMappingURL=promptEnrichment.d.ts.map
@@ -57,6 +57,23 @@ Verification:
57
57
  Version control:
58
58
  - No unsolicited commits: commit only when the user asked for one or the task at hand clearly calls for it.
59
59
 
60
+ Git safety:
61
+ - You may be in a dirty git worktree. Never revert existing changes you did not make unless explicitly asked — these were made by the user.
62
+ - If there are unrelated changes in files you are touching, read and work with them rather than reverting.
63
+ - If changes appear in unrelated files, ignore them and do not revert.
64
+ - Do not amend a commit unless explicitly asked.
65
+ - If you notice unexpected changes you did not make while working, stop immediately and ask the user.
66
+ - Never use destructive git commands (git reset --hard, git checkout --) unless the user explicitly requests or approves them.
67
+
68
+ Todo discipline:
69
+ - Track multi-step work with todo_write: keep exactly one item in_progress at a time, mark items completed the moment they are done, and add newly discovered steps as pending.
70
+ - Do not batch-complete items or create single-step plans. Skip planning for trivially small work (~25% of tasks).
71
+
72
+ Mode awareness:
73
+ - In auto mode, proactively run tests, lint, and typecheck after your changes.
74
+ - In review mode, propose verification steps but wait for approval before running them.
75
+ - In plan mode, explore and design only — the gate holds all writes.
76
+
60
77
  Communication:
61
78
  - Answer directly, without preamble or postamble ("Here is what I will do next...", "Based on the information provided..."). Match the length of your answer to the question.
62
79
  - After making edits, report the outcome briefly; do not restate the diff or explain the code you just wrote unless asked.
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@yagni-app/code-staging",
3
- "version": "0.3.0-staging.1064.1",
3
+ "version": "0.3.0-staging.1071.1",
4
4
  "description": "YAGNI Code: a terminal coding agent that already knows your company. One YAGNI login routes the model and grounds the agent in your team's context.",
5
5
  "license": "SEE LICENSE IN LICENSE.md",
6
6
  "author": "YAGNI, Inc. <jack@yagni.app> (https://yagni.app)",
@@ -38,5 +38,5 @@
38
38
  "@earendil-works/pi-tui": "0.84.1",
39
39
  "typebox": "^1.3.11"
40
40
  },
41
- "yagniSourceSha": "e38a99de02a31e45c61d006943113caf8817e0bd"
41
+ "yagniSourceSha": "9a7610bc34b0cbed0b665ea246880ea23470db78"
42
42
  }