compact-agent 1.29.0 → 1.29.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 (2) hide show
  1. package/bin/ecc-hooks.cjs +62 -23
  2. package/package.json +1 -1
package/bin/ecc-hooks.cjs CHANGED
@@ -178,15 +178,35 @@ const checks = {
178
178
  },
179
179
 
180
180
  /**
181
- * Simplified GateGuard — force investigation before first Edit/Write per
182
- * file in this session. Upstream's full implementation (878 LOC) tracks
183
- * elaborate session state, destructive-bash detection, and quote-aware
184
- * SQL pattern matching. We port the highest-leverage mechanism: a
185
- * per-session per-file flag that demands investigation on first touch.
181
+ * GateGuard — surface "first Edit/Write to this file" as a hint or a
182
+ * block, depending on COMPACT_AGENT_GATEGUARD_MODE.
186
183
  *
187
- * State lives at ~/.compact-agent/state/gateguard/<sessionId>.json — a
188
- * JSON array of paths already touched. After the first touch of a file,
189
- * subsequent Edit/Write calls pass through normally.
184
+ * Modes:
185
+ * warn (default) print the investigation hint, allow the edit.
186
+ * The agent reads the hint as conversational
187
+ * context but isn't forced to retry. Zero
188
+ * round-trip cost on the happy path.
189
+ * block — the original behavior: block once per file,
190
+ * allow on retry. Use this when you actively
191
+ * want to force the agent to re-investigate
192
+ * every existing file before its first edit.
193
+ * off — silent no-op.
194
+ *
195
+ * Why warn is the new default (changed in v1.29.1):
196
+ * - The block-first-allow-on-retry pattern wasted a round-trip on
197
+ * every first edit to every existing file.
198
+ * - The investigation hint is just as useful when delivered as a
199
+ * warning the agent observes — it doesn't need a forced retry
200
+ * to be acted on.
201
+ * - User reports across three sessions showed agents always
202
+ * succeeded on retry, so the block was friction without
203
+ * proportional safety benefit.
204
+ * - Strict users who actually want block-first can opt back in
205
+ * via COMPACT_AGENT_GATEGUARD_MODE=block.
206
+ *
207
+ * State lives at ~/.compact-agent/state/gateguard/<sessionId>.json
208
+ * even in warn mode — tracking per-file means we only emit the hint
209
+ * once per file per session (don't nag).
190
210
  *
191
211
  * Auto-cleanup: state files older than 24h are deleted on next run.
192
212
  *
@@ -194,10 +214,9 @@ const checks = {
194
214
  */
195
215
  'gateguard': () => {
196
216
  // ── Disable knob ─────────────────────────────────────
197
- // Documented in the block message below. Accepts the new
217
+ // Documented in the hint/block message below. Accepts the
198
218
  // COMPACT_AGENT_GATEGUARD env var primarily, with the legacy
199
- // CROWCODER_GATEGUARD kept as an alias so the previous docs +
200
- // muscle memory still work.
219
+ // CROWCODER_GATEGUARD as alias.
201
220
  const disableEnv = (
202
221
  process.env.COMPACT_AGENT_GATEGUARD ||
203
222
  process.env.CROWCODER_GATEGUARD ||
@@ -205,6 +224,16 @@ const checks = {
205
224
  ).trim();
206
225
  if (/^(off|false|0|no|disabled?)$/i.test(disableEnv)) return ok();
207
226
 
227
+ // ── Mode selection ───────────────────────────────────
228
+ // Pick warn (new default) vs block (legacy strict) vs off.
229
+ const modeEnv = (
230
+ process.env.COMPACT_AGENT_GATEGUARD_MODE ||
231
+ process.env.CROWCODER_GATEGUARD_MODE ||
232
+ 'warn'
233
+ ).toLowerCase().trim();
234
+ if (modeEnv === 'off') return ok();
235
+ const strict = modeEnv === 'block' || modeEnv === 'strict';
236
+
208
237
  // ── yolo bypass ──────────────────────────────────────
209
238
  // Permission mode 'yolo' is the user's explicit "trust the agent,
210
239
  // skip the speed bumps" contract. GateGuard's investigate-first
@@ -273,24 +302,34 @@ const checks = {
273
302
  }
274
303
  } catch { /* corrupt state: treat as empty */ }
275
304
 
276
- if (touched.has(targetPath)) return ok(); // already investigated this session
305
+ if (touched.has(targetPath)) return ok(); // already seen this file this session
277
306
 
278
- // First touch — record + block with investigation instruction.
307
+ // First touch — record. In strict (block) mode we block here; in
308
+ // warn (default) mode we print the hint as a one-line warning and
309
+ // exit ok() so the edit proceeds. Persist either way so the next
310
+ // edit to the same file doesn't re-emit the hint.
279
311
  touched.add(targetPath);
280
312
  try {
281
313
  fs.mkdirSync(stateDir, { recursive: true });
282
314
  fs.writeFileSync(stateFile, JSON.stringify([...touched]), 'utf-8');
283
- } catch { /* if we can't persist, still block this one time */ }
284
-
285
- return block(
286
- `First Edit/Write to ${targetPath} this session. Before proceeding, ` +
287
- `investigate: (1) Read the file to understand its current contents. ` +
288
- `(2) Grep for importers / callers / refs so the change doesn't break ` +
289
- `consumers. (3) If it's a schema/type, check existing data usage. ` +
290
- `After investigating, retry the edit GateGuard tracks per-file and ` +
291
- `will let the retry through. Set COMPACT_AGENT_GATEGUARD=off to disable, ` +
292
- `or use /perm yolo for a session-wide bypass.`,
315
+ } catch { /* if we can't persist, still emit the hint this one time */ }
316
+
317
+ if (strict) {
318
+ return block(
319
+ `First Edit/Write to ${targetPath} this session. Before proceeding, ` +
320
+ `investigate: (1) Read the file. (2) Grep for importers. ` +
321
+ `(3) If schema/type, check existing data. After investigating, ` +
322
+ `retry the edit. Set COMPACT_AGENT_GATEGUARD_MODE=warn to switch ` +
323
+ `to non-blocking hints, or =off to disable. /perm yolo also bypasses.`,
324
+ );
325
+ }
326
+ // Warn mode (default): one-line hint, exit 0 so the edit proceeds.
327
+ process.stderr.write(
328
+ `[ECC hint] first edit to ${targetPath} this session — make sure ` +
329
+ `you've read it + checked for callers. (set ` +
330
+ `COMPACT_AGENT_GATEGUARD_MODE=block to enforce, =off to silence)\n`,
293
331
  );
332
+ return ok();
294
333
  },
295
334
 
296
335
  /**
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "compact-agent",
3
- "version": "1.29.0",
3
+ "version": "1.29.1",
4
4
  "description": "Terminal AI coding CLI. Speaks any OpenAI-compatible API (OpenRouter, OpenAI, NVIDIA, Ollama, LM Studio, DeepSeek). Modes, slash commands, multi-agent swarming, key-rotation pool, optional voice + screen-reader, sandbox + permission gates, persistent input box, bundled everything-claude-code skills.",
5
5
  "type": "module",
6
6
  "license": "MIT",