@fusengine/harness 0.1.32 → 0.1.34

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.
@@ -1,22 +1,21 @@
1
1
  import { r as resolveMaxLines } from "./limits-CHn8AIL1.mjs";
2
2
  import { r as projectLayout } from "./layout-C0jaaCQC.mjs";
3
3
  import { i as walkUpFor, n as projectRoot, r as projectRootOrNull, t as isCodeFile } from "./project-root-3kk7gCOp.mjs";
4
- import { A as evaluateApex, E as detectCreationIntent, L as requiredArchSkill, P as detectModularArchitecture, S as frameworkSolidGate, T as capVerbosity, _ as buildClaudeMdContext, b as skillTriggerGate, c as parseBodyDesc, d as PROJECT_INDICATORS, l as parseField, n as missingSeoElements, o as parseEnrichment, p as buildApexTaskInjection, r as descFromText, s as parseEntry, t as isHtmlLike, u as EXCLUDE_DIRS$1 } from "./validate-CccewDwk.mjs";
5
- import { j as detectFramework, k as countLines, n as FAIL_CLOSED, t as evaluate } from "./evaluate-9ch1K2kt.mjs";
4
+ import { A as evaluateApex, E as detectCreationIntent, L as requiredArchSkill, P as detectModularArchitecture, S as frameworkSolidGate, T as capVerbosity, _ as buildClaudeMdContext, b as skillTriggerGate, c as parseBodyDesc, d as PROJECT_INDICATORS, l as parseField, n as missingSeoElements, o as parseEnrichment, p as buildApexTaskInjection, r as descFromText, s as parseEntry, t as isHtmlLike, u as EXCLUDE_DIRS$1 } from "./validate-DLrWtaDR.mjs";
5
+ import { j as detectFramework, k as countLines, n as FAIL_CLOSED, t as evaluate } from "./evaluate-zyxeVZPB.mjs";
6
6
  import { t as formatPrompt } from "./types-ernB1Dy3.mjs";
7
7
  import { a as nowStamp, l as throttleMs, n as readRoots, o as readState, s as setStateField, t as addRoot } from "./registry-BkoEbdec.mjs";
8
8
  import { a as extractText, o as loadIndex, r as cacheStore, t as cacheLookup } from "./store-PrNPm6So.mjs";
9
- import { i as writeJsonFile, r as readJsonFile, t as atomicWrite } from "./json-io-CAn72gI4.mjs";
9
+ import { a as writeJsonFile, i as readJsonFile, r as hashText, t as atomicWrite } from "./json-io-CvSumjtz.mjs";
10
10
  import { t as loadRefs } from "./loader-CyAoJv2W.mjs";
11
- import { a as recordAgent, c as recordRefRead, l as recordTrivialEdit, n as saveTrack, o as recordBrainstormRequired, r as agentsFresh, s as recordDoc, t as loadTrack, u as trivialCount } from "./store-D-ge2ZPI.mjs";
12
- import { n as denyResponse, t as contextResponse } from "./claude-BWZcrZbS.mjs";
11
+ import { a as recordAgent, c as recordRefRead, l as recordTrivialEdit, n as saveTrack, o as recordBrainstormRequired, r as agentsFresh, s as recordDoc, t as loadTrack, u as trivialCount } from "./store-7j02oGjt.mjs";
12
+ import { c as pathExists, d as spawnCapture, f as writeText, l as readText, n as denyResponse, s as collectFiles, t as contextResponse, u as sleep } from "./claude-3PqBGt_7.mjs";
13
13
  import { basename, dirname, extname, join, relative, resolve, sep } from "node:path";
14
14
  import { appendFileSync, copyFileSync, existsSync, mkdirSync, readFileSync, readdirSync, renameSync, rmSync, rmdirSync, statSync, unlinkSync, writeFileSync } from "node:fs";
15
- import { homedir, tmpdir } from "node:os";
15
+ import { homedir } from "node:os";
16
16
  import { createHash } from "node:crypto";
17
17
  import { mkdir, rmdir } from "node:fs/promises";
18
18
  import { execFileSync } from "node:child_process";
19
- import { Glob } from "bun";
20
19
  //#region src/runtime/activity.ts
21
20
  /** Min response length (chars) for a lead agent call to count as `sufficient`. */
22
21
  const AGENT_QUALITY_MIN = 500;
@@ -170,9 +169,54 @@ function normalizeEvent(id, payload) {
170
169
  }
171
170
  //#endregion
172
171
  //#region src/runtime/paths.ts
173
- /** Path to a session's track file (under a per-tool base dir). */
174
- function trackFile(sessionId, baseDir = join(tmpdir(), "fuse-harness")) {
175
- return join(baseDir, `track-${sessionId.replace(/[^A-Za-z0-9_-]/g, "_") || "default"}.json`);
172
+ /**
173
+ * @module paths
174
+ * Runtime path helpers — per-project, out-of-tree harness state.
175
+ *
176
+ * Default base: ~/.claude/fuse-harness/state/<projectHash>/
177
+ * where projectHash = 8-char MD5 of CLAUDE_PROJECT_DIR (or cwd).
178
+ * Persistent and not world-writable (unlike /tmp), and OUTSIDE the repo so the
179
+ * agent has no "legitimate" reason to write it (the protected-path guard denies
180
+ * it, and the gate verifies freshness from the transcript, not this file).
181
+ *
182
+ * @packageDocumentation
183
+ */
184
+ /** Resolve the project root from the environment or fall back to cwd. */
185
+ function resolveProjectDir() {
186
+ return process.env["CLAUDE_PROJECT_DIR"] ?? process.cwd();
187
+ }
188
+ /**
189
+ * Compute a stable 8-char hex hash for a project directory path.
190
+ * Delegates to `hashText` (MD5, non-cryptographic — used as a stable dir key only).
191
+ *
192
+ * @param projectDir - Absolute path to the project root; defaults to CLAUDE_PROJECT_DIR/cwd.
193
+ * @returns 8-char lowercase hex string.
194
+ */
195
+ function projectHash$1(projectDir) {
196
+ return hashText(projectDir ?? resolveProjectDir());
197
+ }
198
+ /**
199
+ * Canonical base directory for per-project harness state.
200
+ * Resolves to: ~/.claude/fuse-harness/state/<projectHash>/
201
+ *
202
+ * @param projectDir - Optional override for hashing; defaults to CLAUDE_PROJECT_DIR/cwd.
203
+ * @returns Absolute directory path (not yet created on disk).
204
+ */
205
+ function defaultStateDir(projectDir) {
206
+ return join(homedir(), ".claude", "fuse-harness", "state", projectHash$1(projectDir));
207
+ }
208
+ /**
209
+ * Absolute path to a session's track JSON file.
210
+ *
211
+ * The session identifier is sanitised to `[A-Za-z0-9_-]` before use in the filename.
212
+ *
213
+ * @param sessionId - Claude session identifier (raw value accepted; sanitised internally).
214
+ * @param baseDir - Override the base directory. Omit in production; pass an explicit
215
+ * temp path in unit tests to avoid touching $HOME.
216
+ * @returns Absolute path, e.g. ~/.claude/fuse-harness/state/a1b2c3d4/track-abc123.json
217
+ */
218
+ function trackFile(sessionId, baseDir) {
219
+ return join(baseDir ?? defaultStateDir(), `track-${sessionId.replace(/[^A-Za-z0-9_-]/g, "_") || "default"}.json`);
176
220
  }
177
221
  //#endregion
178
222
  //#region src/runtime/record.ts
@@ -1910,8 +1954,8 @@ function cartographerContext() {
1910
1954
  async function injectApexSubagentContext(cwd, home = homedir()) {
1911
1955
  const apexDir = join(process.env.CLAUDE_PROJECT_DIR ?? cwd, ".claude", "apex");
1912
1956
  if (!existsSync(apexDir)) return "";
1913
- const agentsFile = Bun.file(join(apexDir, "AGENTS.md"));
1914
- const agents = await agentsFile.exists() ? (await agentsFile.text()).slice(0, 4e3) : "";
1957
+ const agentsPath = join(apexDir, "AGENTS.md");
1958
+ const agents = existsSync(agentsPath) ? readText(agentsPath).slice(0, 4e3) : "";
1915
1959
  const taskData = await readJsonFile(join(apexDir, "task.json"));
1916
1960
  return contextResponse("SubagentStart", `## APEX Sub-Agent Instructions
1917
1961
 
@@ -1967,7 +2011,7 @@ function cacheAge(ts, now = Date.now()) {
1967
2011
  /** Full SHA-256 hex checksum of a file's text; "" when unreadable. */
1968
2012
  async function fileChecksum(path) {
1969
2013
  try {
1970
- return createHash("sha256").update(await Bun.file(path).text()).digest("hex");
2014
+ return createHash("sha256").update(readText(path)).digest("hex");
1971
2015
  } catch {
1972
2016
  return "";
1973
2017
  }
@@ -2036,9 +2080,8 @@ function parseEntries(raw) {
2036
2080
  async function cacheAnalyticsSave(home = homedir(), now = Date.now()) {
2037
2081
  const dir = join(cacheBaseDir(home), "analytics");
2038
2082
  const sessionsFile = join(dir, "sessions.jsonl");
2039
- const file = Bun.file(sessionsFile);
2040
- if (!await file.exists()) return;
2041
- const raw = await file.text();
2083
+ if (!pathExists(sessionsFile)) return;
2084
+ const raw = readText(sessionsFile);
2042
2085
  if (!raw.trim()) return;
2043
2086
  const entries = parseEntries(raw);
2044
2087
  if (entries.length === 0) return;
@@ -2066,8 +2109,7 @@ async function cacheAnalyticsSave(home = homedir(), now = Date.now()) {
2066
2109
  }
2067
2110
  await writeJsonFile(join(dir, "summary.json"), merged, true);
2068
2111
  const cutoff = (/* @__PURE__ */ new Date(now - 30 * 864e5)).toISOString();
2069
- const kept = entries.filter((e) => e.ts >= cutoff);
2070
- await Bun.write(sessionsFile, kept.map((e) => JSON.stringify(e)).join("\n") + "\n");
2112
+ writeText(sessionsFile, entries.filter((e) => e.ts >= cutoff).map((e) => JSON.stringify(e)).join("\n") + "\n");
2071
2113
  }
2072
2114
  //#endregion
2073
2115
  //#region src/runtime/lifecycle/aipilot/inject-explore.ts
@@ -2091,17 +2133,11 @@ const CONFIG_FILES = [
2091
2133
  /** Compute a config hash from git-tracked config files; "noconfig" on failure. */
2092
2134
  async function configHash(cwd) {
2093
2135
  try {
2094
- const proc = Bun.spawn([
2095
- "git",
2136
+ const output = spawnCapture("git", [
2096
2137
  "ls-tree",
2097
2138
  "HEAD",
2098
2139
  ...CONFIG_FILES
2099
- ], {
2100
- cwd,
2101
- stdout: "pipe",
2102
- stderr: "ignore"
2103
- });
2104
- const output = await new Response(proc.stdout).text();
2140
+ ], cwd);
2105
2141
  return output.trim() ? hashText16(output) : "noconfig";
2106
2142
  } catch {
2107
2143
  return "noconfig";
@@ -2127,8 +2163,7 @@ async function injectExploreCache(cwd, home = homedir(), now = Date.now()) {
2127
2163
  const cfgHash = await configHash(projPath);
2128
2164
  let context = "";
2129
2165
  const meta = await readJsonFile(metaFile);
2130
- const snapBunFile = Bun.file(snapFile);
2131
- const snapshot = await snapBunFile.exists() ? await snapBunFile.text() : "";
2166
+ const snapshot = pathExists(snapFile) ? readText(snapFile) : "";
2132
2167
  if (meta?.timestamp && snapshot) {
2133
2168
  const age = cacheAge(meta.timestamp, now);
2134
2169
  if (age < TTL_SECONDS$2 && meta.config_hash === cfgHash) {
@@ -2164,9 +2199,9 @@ async function buildDocsContext(entries, docsDir, now) {
2164
2199
  if (age > maxAge) maxAge = age;
2165
2200
  if (!entry.hash || seen.has(entry.hash)) continue;
2166
2201
  seen.add(entry.hash);
2167
- const file = Bun.file(join(docsDir, `${entry.hash}.md`));
2168
- if (!await file.exists()) continue;
2169
- const content = await file.text();
2202
+ const docPath = join(docsDir, `${entry.hash}.md`);
2203
+ if (!pathExists(docPath)) continue;
2204
+ const content = readText(docPath);
2170
2205
  if (!content) continue;
2171
2206
  ctx += `\n${content}\n`;
2172
2207
  count++;
@@ -2202,32 +2237,36 @@ async function injectDocCache(cwd, home = homedir(), now = Date.now()) {
2202
2237
  * Ported from the ai-pilot plugin's `cache/source-collector.ts` +
2203
2238
  * the stack detection in `cache/lesson-helpers.ts` (now removed).
2204
2239
  */
2205
- /** Source file glob patterns (monorepo-aware; separate to avoid brace-wildcards). */
2206
- const SRC_PATTERNS = [
2207
- "src/**/*.{ts,tsx,js,jsx}",
2208
- "app/**/*.{ts,tsx,js,jsx}",
2209
- "apps/*/src/**/*.{ts,tsx,js,jsx}",
2210
- "packages/*/src/**/*.{ts,tsx,js,jsx}"
2211
- ];
2240
+ /** Source extensions to collect (monorepo-aware, dot-prefixed for matching). */
2241
+ const SRC_EXTS = /* @__PURE__ */ new Set([
2242
+ ".ts",
2243
+ ".tsx",
2244
+ ".js",
2245
+ ".jsx"
2246
+ ]);
2247
+ /** Roots walked: `src`, `app`, plus each child `src` under `apps/` and `packages/`. */
2248
+ const TOP_DIRS = ["src", "app"];
2249
+ const NESTED_PARENTS = ["apps", "packages"];
2250
+ /** Collect the existing monorepo `src` roots nested under `apps/` and `packages/`. */
2251
+ function nestedRoots(projectPath) {
2252
+ const roots = [];
2253
+ for (const parent of NESTED_PARENTS) try {
2254
+ for (const e of readdirSync(join(projectPath, parent), { withFileTypes: true })) if (e.isDirectory()) roots.push(join(projectPath, parent, e.name, "src"));
2255
+ } catch {}
2256
+ return roots;
2257
+ }
2212
2258
  /**
2213
2259
  * Scan source files in `projectPath` (monorepo-aware), capped at `maxFiles`.
2260
+ * Node+Bun portable: walks `node:fs` recursively (replaces the Bun `Glob`).
2214
2261
  * @param projectPath - Absolute project root.
2215
2262
  * @param maxFiles - Max files to collect (default 200).
2216
- * @returns Absolute paths matching the source patterns.
2263
+ * @returns Absolute paths matching the source extensions.
2217
2264
  */
2218
2265
  async function scanSourceFiles(projectPath, maxFiles = 200) {
2219
2266
  const files = [];
2220
- for (const pattern of SRC_PATTERNS) {
2221
- try {
2222
- for await (const p of new Glob(pattern).scan({
2223
- cwd: projectPath,
2224
- absolute: true
2225
- })) {
2226
- if (p.includes("node_modules")) continue;
2227
- files.push(p);
2228
- if (files.length >= maxFiles) break;
2229
- }
2230
- } catch {}
2267
+ const roots = [...TOP_DIRS.map((d) => join(projectPath, d)), ...nestedRoots(projectPath)];
2268
+ for (const root of roots) {
2269
+ collectFiles(root, SRC_EXTS, files, maxFiles);
2231
2270
  if (files.length >= maxFiles) break;
2232
2271
  }
2233
2272
  return files;
@@ -2455,7 +2494,7 @@ function projectRootFromPaths(filePaths) {
2455
2494
  }
2456
2495
  /** Extract all absolute file paths from tool_use entries in a JSONL transcript. */
2457
2496
  async function transcriptFilePaths(transcriptPath) {
2458
- const text = await Bun.file(transcriptPath).text();
2497
+ const text = readText(transcriptPath);
2459
2498
  const paths = /* @__PURE__ */ new Set();
2460
2499
  for (const line of text.split("\n").filter(Boolean)) try {
2461
2500
  const content = JSON.parse(line)?.message?.content;
@@ -2470,7 +2509,7 @@ async function transcriptFilePaths(transcriptPath) {
2470
2509
  }
2471
2510
  /** Extract deduplicated Edit tool_use entries (keyed by basename) from a transcript. */
2472
2511
  async function transcriptEdits(transcriptPath) {
2473
- const text = await Bun.file(transcriptPath).text();
2512
+ const text = readText(transcriptPath);
2474
2513
  const edits = [];
2475
2514
  for (const line of text.split("\n").filter(Boolean)) try {
2476
2515
  const content = JSON.parse(line)?.message?.content;
@@ -2487,7 +2526,7 @@ async function transcriptEdits(transcriptPath) {
2487
2526
  }
2488
2527
  /** Extract the last assistant text report (first 500 lines) from a transcript. */
2489
2528
  async function transcriptReport(transcriptPath) {
2490
- const text = await Bun.file(transcriptPath).text();
2529
+ const text = readText(transcriptPath);
2491
2530
  let lastReport = "";
2492
2531
  for (const line of text.split("\n").filter(Boolean)) try {
2493
2532
  const entry = JSON.parse(line);
@@ -2515,7 +2554,7 @@ const RETRY_DELAYS = [
2515
2554
  ];
2516
2555
  /** Extract the longest assistant synthesis + queried library ids from a transcript. */
2517
2556
  async function extractSynthesis(path) {
2518
- const lines = (await Bun.file(path).text()).split("\n").filter(Boolean);
2557
+ const lines = readText(path).split("\n").filter(Boolean);
2519
2558
  const libraries = [];
2520
2559
  let synthesis = "";
2521
2560
  for (const line of lines) try {
@@ -2543,14 +2582,14 @@ async function extractSynthesis(path) {
2543
2582
  * @param home - Home dir (defaults to `~`).
2544
2583
  */
2545
2584
  async function cacheDocFromTranscript(transcript, cwd, home = homedir()) {
2546
- if (!transcript || !await Bun.file(transcript).exists()) return;
2585
+ if (!transcript || !pathExists(transcript)) return;
2547
2586
  const projPath = projectRootFromPaths(await transcriptFilePaths(transcript)) ?? process.env.CLAUDE_PROJECT_DIR ?? cwd;
2548
2587
  const cacheDir = cacheDirFor("doc", projPath, home);
2549
2588
  const docsDir = join(cacheDir, "docs");
2550
2589
  let result = await extractSynthesis(transcript);
2551
2590
  for (const delay of RETRY_DELAYS) {
2552
2591
  if (result.text.length >= MIN_TEXT_SIZE && result.libraries.length > 0) break;
2553
- await Bun.sleep(delay);
2592
+ await sleep(delay);
2554
2593
  result = await extractSynthesis(transcript);
2555
2594
  }
2556
2595
  const { text, libraries } = result;
@@ -2564,7 +2603,7 @@ async function cacheDocFromTranscript(transcript, cwd, home = homedir()) {
2564
2603
  const content = text.slice(0, MAX_DOC_SIZE);
2565
2604
  const topic = libraries.join(", ");
2566
2605
  const docHash = hashText16(topic);
2567
- await Bun.write(join(docsDir, `${docHash}.md`), content);
2606
+ writeText(join(docsDir, `${docHash}.md`), content);
2568
2607
  const sizeKb = Math.floor(content.length / 1024);
2569
2608
  for (const lib of libraries) {
2570
2609
  index.docs = index.docs.filter((d) => d.library !== lib);
@@ -2594,7 +2633,7 @@ async function cacheDocFromTranscript(transcript, cwd, home = homedir()) {
2594
2633
  * @param home - Home dir (defaults to `~`).
2595
2634
  */
2596
2635
  async function cacheSniperLessons(transcript, cwd, home = homedir()) {
2597
- if (!transcript || !await Bun.file(transcript).exists()) return;
2636
+ if (!transcript || !pathExists(transcript)) return;
2598
2637
  const edits = await transcriptEdits(transcript);
2599
2638
  if (edits.length === 0) return;
2600
2639
  const projectPath = projectRootFromPaths(edits.map((e) => e.file)) ?? process.env.CLAUDE_PROJECT_DIR ?? cwd;
@@ -2633,7 +2672,7 @@ async function cacheSniperLessons(transcript, cwd, home = homedir()) {
2633
2672
  */
2634
2673
  /** Extract linter-related command/output text from a JSONL transcript. */
2635
2674
  async function extractLinterOutput(path) {
2636
- const text = await Bun.file(path).text();
2675
+ const text = readText(path);
2637
2676
  const outputs = [];
2638
2677
  for (const line of text.split("\n").filter(Boolean)) try {
2639
2678
  const content = JSON.parse(line)?.message?.content;
@@ -2655,7 +2694,7 @@ async function extractLinterOutput(path) {
2655
2694
  * @param home - Home dir (defaults to `~`).
2656
2695
  */
2657
2696
  async function cacheTestResults(transcript, cwd, home = homedir()) {
2658
- if (!transcript || !await Bun.file(transcript).exists()) return;
2697
+ if (!transcript || !pathExists(transcript)) return;
2659
2698
  const projectPath = projectRootFromPaths(await transcriptFilePaths(transcript)) ?? process.env.CLAUDE_PROJECT_DIR ?? cwd;
2660
2699
  const pHash = projectHash(projectPath);
2661
2700
  const cacheDir = cacheDirFor("tests", projectPath, home);
@@ -2715,7 +2754,7 @@ async function acquireLock(lockDir, timeoutMs = 5e3) {
2715
2754
  } catch {}
2716
2755
  };
2717
2756
  } catch {
2718
- await Bun.sleep(100);
2757
+ await sleep(100);
2719
2758
  }
2720
2759
  return null;
2721
2760
  }
@@ -2781,16 +2820,7 @@ async function taskComplete(file, id) {
2781
2820
  /** True when the project has uncommitted git changes. */
2782
2821
  async function hasGitChanges(cwd) {
2783
2822
  try {
2784
- const proc = Bun.spawn([
2785
- "git",
2786
- "status",
2787
- "--porcelain"
2788
- ], {
2789
- cwd,
2790
- stdout: "pipe",
2791
- stderr: "ignore"
2792
- });
2793
- return (await new Response(proc.stdout).text()).trim().length > 0;
2823
+ return spawnCapture("git", ["status", "--porcelain"], cwd).trim().length > 0;
2794
2824
  } catch {
2795
2825
  return false;
2796
2826
  }
@@ -3250,6 +3280,90 @@ function postEditContext(scope, event, now) {
3250
3280
  return trackSessionChanges(event.sessionId, event.filePath, void 0, now) || postEditTypescript(event.filePath);
3251
3281
  }
3252
3282
  //#endregion
3283
+ //#region src/runtime/gate-helpers.ts
3284
+ /**
3285
+ * Code-only line count of the existing on-disk file (undefined if
3286
+ * absent/unreadable). Uses {@link countLines} (skips blank/comment lines) so a
3287
+ * partial Edit judges the full file by the SAME metric as the incoming snippet —
3288
+ * a raw `split("\n").length` would over-count JSDoc/blank lines (and add a
3289
+ * trailing-newline off-by-one), falsely blocking well-documented files.
3290
+ * @param path - Absolute path of the file being edited (or undefined).
3291
+ * @returns Code-only line count, or undefined when the file is absent/unreadable.
3292
+ */
3293
+ function existingLineCount(path) {
3294
+ if (!path) return void 0;
3295
+ try {
3296
+ return existsSync(path) ? countLines(readFileSync(path, "utf8")) : void 0;
3297
+ } catch {
3298
+ return;
3299
+ }
3300
+ }
3301
+ //#endregion
3302
+ //#region src/freshness/agent-evidence.ts
3303
+ /**
3304
+ * Platform-authored transcript evidence for APEX agent freshness.
3305
+ * Parses the Claude Code session JSONL transcript to find genuine Task
3306
+ * tool_use entries — forging this requires writing into the transcript file
3307
+ * which the Claude Code platform controls, unlike the self-recorded track.
3308
+ */
3309
+ /** Parse a raw `timestamp` field to epoch ms; `undefined` when absent or invalid. */
3310
+ function parseTs(raw) {
3311
+ if (raw === void 0) return void 0;
3312
+ if (typeof raw === "number") return raw;
3313
+ const ms = Date.parse(raw);
3314
+ return Number.isFinite(ms) ? ms : void 0;
3315
+ }
3316
+ /**
3317
+ * Return `true` ONLY when, for EVERY name in `names`, the Claude Code
3318
+ * transcript at `transcriptPath` contains a genuine `tool_use` of the `Task`
3319
+ * tool whose `subagent_type` (or `name`) input field matches, with the entry
3320
+ * timestamp within `windowMs` of `now`.
3321
+ *
3322
+ * **Timestamp note:** when a transcript entry carries no `timestamp` field it
3323
+ * is counted as within-window (we cannot prove staleness). This is
3324
+ * intentionally lenient to stay robust across transcript-format evolution; the
3325
+ * tamper-resistance guarantee derives from the platform authoring the file —
3326
+ * not from the timestamp alone.
3327
+ *
3328
+ * @param transcriptPath - Absolute path to the session `.jsonl` transcript
3329
+ * (hook payload field: `transcript_path`). Returns `false` when `undefined`.
3330
+ * @param names - Required agent `subagent_type` values — ALL must appear.
3331
+ * @param windowMs - Freshness window in milliseconds.
3332
+ * @param now - Current epoch ms (pass `Date.now()` at the call-site).
3333
+ * @returns `true` when ALL agents have real, within-window transcript evidence.
3334
+ */
3335
+ function agentsRanFromTranscript(transcriptPath, names, windowMs, now) {
3336
+ if (!transcriptPath || names.length === 0) return false;
3337
+ let text;
3338
+ try {
3339
+ text = readText(transcriptPath);
3340
+ } catch {
3341
+ return false;
3342
+ }
3343
+ const cutoff = now - windowMs;
3344
+ const found = /* @__PURE__ */ new Set();
3345
+ for (const line of text.split("\n")) {
3346
+ if (!line.trim()) continue;
3347
+ let entry;
3348
+ try {
3349
+ entry = JSON.parse(line);
3350
+ } catch {
3351
+ continue;
3352
+ }
3353
+ const ts = parseTs(entry.timestamp);
3354
+ if (ts !== void 0 && ts <= cutoff) continue;
3355
+ const content = entry.message?.content;
3356
+ if (!Array.isArray(content)) continue;
3357
+ for (const block of content) {
3358
+ if (block?.type !== "tool_use" || block.name !== "Task") continue;
3359
+ const agent = block.input?.subagent_type ?? block.input?.name;
3360
+ if (typeof agent === "string" && names.includes(agent)) found.add(agent);
3361
+ }
3362
+ if (found.size === names.length) return true;
3363
+ }
3364
+ return names.every((n) => found.has(n));
3365
+ }
3366
+ //#endregion
3253
3367
  //#region src/runtime/dry-patterns.ts
3254
3368
  /** Short identifiers never worth a duplication check (control flow, tiny names). */
3255
3369
  const DRY_KEYWORDS = /* @__PURE__ */ new Set([
@@ -3585,22 +3699,6 @@ const DEFAULT_WINDOW_MS = 12e4;
3585
3699
  /** Trivial edits allowed within the window before the full APEX gates apply. */
3586
3700
  const TRIVIAL_BUDGET = 4;
3587
3701
  /**
3588
- * Code-only line count of the existing on-disk file (undefined if
3589
- * absent/unreadable). Uses {@link countLines} (skips blank/comment lines) to
3590
- * mirror the Python `count_code_lines(get_full_file_content(...))`, so a partial
3591
- * Edit judges the full file by the SAME metric as the incoming snippet — a raw
3592
- * `split("\n").length` would over-count JSDoc/blank lines (and add a
3593
- * trailing-newline off-by-one), falsely blocking well-documented files.
3594
- */
3595
- function existingLineCount(path) {
3596
- if (!path) return void 0;
3597
- try {
3598
- return existsSync(path) ? countLines(readFileSync(path, "utf8")) : void 0;
3599
- } catch {
3600
- return;
3601
- }
3602
- }
3603
- /**
3604
3702
  * Full gate: the stateless guards (file-size, git, security...) first, then a
3605
3703
  * trivial-edit fast path, then the stateful APEX gates fed from the session
3606
3704
  * track. Returns the first blocking prompt, or null to allow.
@@ -3635,6 +3733,7 @@ async function gate(input) {
3635
3733
  await saveTrack(input.trackFile, recordTrivialEdit(track, input.now, window, input.now));
3636
3734
  return null;
3637
3735
  }
3736
+ const freshnessFor = (names) => input.transcriptPath ? agentsRanFromTranscript(input.transcriptPath, names, window, input.now) : agentsFresh(track, names, window, input.now);
3638
3737
  const ctx = {
3639
3738
  sessionId: input.sessionId,
3640
3739
  framework: input.framework,
@@ -3643,9 +3742,9 @@ async function gate(input) {
3643
3742
  authorizations: track.authorizations,
3644
3743
  refs: input.refs,
3645
3744
  refsRead: track.refsRead,
3646
- agentsFresh: agentsFresh(track, [...REQUIRED_AGENTS], window, input.now),
3745
+ agentsFresh: freshnessFor([...REQUIRED_AGENTS]),
3647
3746
  brainstormRequired: track.brainstormRequired,
3648
- brainstormFresh: agentsFresh(track, ["brainstorming"], window, input.now)
3747
+ brainstormFresh: freshnessFor(["brainstorming"])
3649
3748
  };
3650
3749
  try {
3651
3750
  const apex = evaluateApex(ctx);
@@ -3708,7 +3807,8 @@ async function handlePre(ctx) {
3708
3807
  agentType: event.agentType,
3709
3808
  windowMs: opts.windowMs,
3710
3809
  now: opts.now,
3711
- trackFile: file
3810
+ trackFile: file,
3811
+ transcriptPath: typeof payload.transcript_path === "string" ? payload.transcript_path : void 0
3712
3812
  });
3713
3813
  return prompt ? {
3714
3814
  stdout: respond(id, prompt),
@@ -3733,7 +3833,7 @@ function rawEventName(payload) {
3733
3833
  async function handleHook(id, payload, opts) {
3734
3834
  const event = normalizeEvent(id, payload);
3735
3835
  const layout = projectLayout(opts.cwd);
3736
- const file = trackFile(event.sessionId, layout.trackDir);
3836
+ const file = trackFile(event.sessionId, defaultStateDir(opts.cwd));
3737
3837
  const mcpDir = layout.cacheDir;
3738
3838
  const framework = detectFramework(event.filePath ?? "", event.content ?? "");
3739
3839
  if (designLifecycle(payload, mcpDir, opts.cwd, String(opts.now), opts.now)) return {
@@ -3803,4 +3903,4 @@ async function handleHook(id, payload, opts) {
3803
3903
  });
3804
3904
  }
3805
3905
  //#endregion
3806
- export { detectSolidProfile as $, dispatchLessons as A, activityFor as At, mergeLines as B, securityStateDir as C, trackFile as Ct, dispatchLifecycle as D, mcpPostStore as Dt, trackEnrichment as E, isMcpTool as Et, writePluginMap as F, trackSessionChanges as G, getFileDesc as H, generateProjectMap as I, saveApexState as J, validateRulesLoaded as K, isProject as L, lessonsStateFileFor as M, cartoSessionStart as N, aipilotPostToolUse as O, mcpPreIntercept as Ot, generateEcosystemMap as P, subagentCacheContext as Q, writeTree as R, saveSecurityState as S, recordActivity as St, todayUtc as T, MCP_TTL_MS as Tt, listChildren as U, countFiles as V, postEditTypescript as W, validateTeammateOutput as X, logToolFailure as Y, trackAgentMemory as Z, trackWatchResearch as _, sessionStatePath as _t, TRIVIAL_BUDGET as a, pruneEmptyDirs as at, isoUtc as b, taskContext as bt, detectDuplication as c, trimLogFile as ct, lifecycleStdout as d, projectContext as dt, solidDetectStart as et, postEditContext as f, claudeHome as ft, postTrackingSideEffects as g, saveSessionState as gt, securityAdvisory as h, sanitizeSessionId as ht, REQUIRED_AGENTS as i, sessionStartCore as it, lessonsFileFor as j, dispatchAipilot as k, queryOf as kt, dryGate as l, devContext as lt, seoPostToolUseResponse as m, loadSessionState as mt, handlePre as n, readRules as nt, gate as o, purgeTtlTree as ot, seoPostToolUse as p, fusengineCache as pt, cleanupSession as q, DEFAULT_WINDOW_MS as r, runSessionStartCleanups as rt, preCommitGate as s, removeOldFiles as st, handleHook as t, injectRules as tt, extractSymbols as u, gitContext as ut, trackMcpResearch as v, sessionsDir as vt, securityStatePath as w, normalizeEvent as wt, loadSecurityState as x, respond as xt, trackSkillRead as y, promptSubmitContext as yt, loadEnriched as z };
3906
+ export { detectSolidProfile as $, dispatchLessons as A, mcpPreIntercept as At, mergeLines as B, securityStateDir as C, defaultStateDir as Ct, dispatchLifecycle as D, MCP_TTL_MS as Dt, trackEnrichment as E, normalizeEvent as Et, writePluginMap as F, trackSessionChanges as G, getFileDesc as H, generateProjectMap as I, saveApexState as J, validateRulesLoaded as K, isProject as L, lessonsStateFileFor as M, activityFor as Mt, cartoSessionStart as N, aipilotPostToolUse as O, isMcpTool as Ot, generateEcosystemMap as P, subagentCacheContext as Q, writeTree as R, saveSecurityState as S, recordActivity as St, todayUtc as T, trackFile as Tt, listChildren as U, countFiles as V, postEditTypescript as W, validateTeammateOutput as X, logToolFailure as Y, trackAgentMemory as Z, trackWatchResearch as _, sessionStatePath as _t, TRIVIAL_BUDGET as a, pruneEmptyDirs as at, isoUtc as b, taskContext as bt, detectDuplication as c, trimLogFile as ct, lifecycleStdout as d, projectContext as dt, solidDetectStart as et, postEditContext as f, claudeHome as ft, postTrackingSideEffects as g, saveSessionState as gt, securityAdvisory as h, sanitizeSessionId as ht, REQUIRED_AGENTS as i, sessionStartCore as it, lessonsFileFor as j, queryOf as jt, dispatchAipilot as k, mcpPostStore as kt, dryGate as l, devContext as lt, seoPostToolUseResponse as m, loadSessionState as mt, handlePre as n, readRules as nt, gate as o, purgeTtlTree as ot, seoPostToolUse as p, fusengineCache as pt, cleanupSession as q, DEFAULT_WINDOW_MS as r, runSessionStartCleanups as rt, preCommitGate as s, removeOldFiles as st, handleHook as t, injectRules as tt, extractSymbols as u, gitContext as ut, trackMcpResearch as v, sessionsDir as vt, securityStatePath as w, projectHash$1 as wt, loadSecurityState as x, respond as xt, trackSkillRead as y, promptSubmitContext as yt, loadEnriched as z };
@@ -170,9 +170,19 @@ declare const ASK_PATTERNS: RegExp[];
170
170
  declare function securityGuard(ctx: GuardContext): Prompt | null;
171
171
  //#endregion
172
172
  //#region src/policy/guards/protected-path.d.ts
173
- /** Path fragments that mark a location as internal/generated state (off-limits to Write/Edit). */
173
+ /** Path fragments that mark a location as internal/generated state. */
174
174
  declare const PROTECTED_FRAGMENTS: readonly string[];
175
- /** Blocks direct edits to internal/generated state directories. */
175
+ /**
176
+ * Blocks direct edits to internal/generated state directories.
177
+ *
178
+ * Covers:
179
+ * - Write / Edit tool calls whose `filePath` targets a protected fragment.
180
+ * - Bash commands that both reference a protected fragment *and* contain a
181
+ * recognisable shell write operation (best-effort; see `bashHasWriteOp`).
182
+ *
183
+ * @param ctx - The guard context (tool, filePath, command).
184
+ * @returns A blocking {@link Prompt}, or null to allow.
185
+ */
176
186
  declare function protectedPathGuard(ctx: GuardContext): Prompt | null;
177
187
  //#endregion
178
188
  //#region src/policy/guards/bash-write.d.ts
package/dist/index.d.mts CHANGED
@@ -5,7 +5,7 @@ import { a as detectHarness, i as HarnessVia, n as HarnessInfo, o as detectMode,
5
5
  import { a as isDocConsulted, i as formatDocSatisfactionStatus, n as DocSatisfactionStatus, o as resolveSessions, r as formatDocDeny, t as AuthEntry } from "./doc-helpers-CG1nuf-c.mjs";
6
6
  import { t as incrementTrivialEditCounter } from "./index-BOBXQ91y.mjs";
7
7
  import { a as compactJson, i as walkUpFor, n as projectRoot, r as projectRootOrNull, t as isCodeFile } from "./index-BEMumjOw.mjs";
8
- import { $ as APEX_GATES, A as GUARDS, B as TS_DECL_RE, C as SKILL_TRIGGERS, Ct as detectProjectType, D as capVerbosity, E as MAX_TOKENS, F as GO_DECL_RE, G as bashWriteGuard, H as ASK_WRITERS, I as JAVA_DECL_RE, J as ASK_PATTERNS, K as PROTECTED_FRAGMENTS, L as PHP_DECL_RE, M as registerGuard, N as runGuards, O as detectCreationIntent, P as installGuard, Q as GuardContext, R as PY_MODEL_RE, S as skillTriggerGate, St as detectModularArchitecture, T as MAX_EXA_RESULTS, Tt as requiredArchSkill, U as CODE_MUTATORS, V as interfaceSeparationGuard, W as CODE_REDIRECT, X as securityGuard, Y as CRITICAL_PATTERNS, Z as Guard, _ as DEV_VERBS, _t as evaluateFileSize, a as firstHeading, at as freshnessGate, b as detectClaudeMdProjectType, bt as ModularArchitecture, c as parseEntry, ct as PolicyResult, d as EXCLUDE_DIRS, dt as GIT_BLOCKED, et as ApexContext, f as PROJECT_INDICATORS, ft as PROJECT_INSTALL, g as loadApexTaskState, gt as countLines, h as buildApexTaskInjection, ht as FileSizeVerdict, i as firstComment, it as evaluateApex, j as clearUserGuards, k as FAIL_CLOSED, l as parseBodyDesc, lt as evaluate, m as buildApexTaskContext, mt as matchPatterns, n as missingSeoElements, nt as brainstormGate, o as TreeEntry, ot as solidReadGate, p as ApexTaskState, pt as SYSTEM_INSTALL, q as protectedPathGuard, r as descFromText, rt as docConsultedGate, s as parseEnrichment, st as PolicyContext, t as isHtmlLike, tt as ApexGate, u as parseField, ut as GIT_ASK, v as buildApexInstruction, vt as detectFramework, w as frameworkSolidGate, wt as isApexCommand, x as detectRequiredSkills, xt as ProjectType, y as buildClaudeMdContext, yt as DEV_KEYWORDS, z as SWIFT_PROTO_RE } from "./index-DmbOUJK8.mjs";
8
+ import { $ as APEX_GATES, A as GUARDS, B as TS_DECL_RE, C as SKILL_TRIGGERS, Ct as detectProjectType, D as capVerbosity, E as MAX_TOKENS, F as GO_DECL_RE, G as bashWriteGuard, H as ASK_WRITERS, I as JAVA_DECL_RE, J as ASK_PATTERNS, K as PROTECTED_FRAGMENTS, L as PHP_DECL_RE, M as registerGuard, N as runGuards, O as detectCreationIntent, P as installGuard, Q as GuardContext, R as PY_MODEL_RE, S as skillTriggerGate, St as detectModularArchitecture, T as MAX_EXA_RESULTS, Tt as requiredArchSkill, U as CODE_MUTATORS, V as interfaceSeparationGuard, W as CODE_REDIRECT, X as securityGuard, Y as CRITICAL_PATTERNS, Z as Guard, _ as DEV_VERBS, _t as evaluateFileSize, a as firstHeading, at as freshnessGate, b as detectClaudeMdProjectType, bt as ModularArchitecture, c as parseEntry, ct as PolicyResult, d as EXCLUDE_DIRS, dt as GIT_BLOCKED, et as ApexContext, f as PROJECT_INDICATORS, ft as PROJECT_INSTALL, g as loadApexTaskState, gt as countLines, h as buildApexTaskInjection, ht as FileSizeVerdict, i as firstComment, it as evaluateApex, j as clearUserGuards, k as FAIL_CLOSED, l as parseBodyDesc, lt as evaluate, m as buildApexTaskContext, mt as matchPatterns, n as missingSeoElements, nt as brainstormGate, o as TreeEntry, ot as solidReadGate, p as ApexTaskState, pt as SYSTEM_INSTALL, q as protectedPathGuard, r as descFromText, rt as docConsultedGate, s as parseEnrichment, st as PolicyContext, t as isHtmlLike, tt as ApexGate, u as parseField, ut as GIT_ASK, v as buildApexInstruction, vt as detectFramework, w as frameworkSolidGate, wt as isApexCommand, x as detectRequiredSkills, xt as ProjectType, y as buildClaudeMdContext, yt as DEV_KEYWORDS, z as SWIFT_PROTO_RE } from "./index-QzK2dv0V.mjs";
9
9
  import { n as RouteResult, r as ScoredRef, t as RefMeta } from "./types-CY5qT2X1.mjs";
10
10
  import { a as ReminderState, c as readState, d as throttleMs, i as registryFile, l as setStateField, n as addRoot, o as lessonsFileFor, r as readRoots, s as nowStamp, t as ensureMemoryGitignore, u as stateFileFor } from "./index-DLYhervv.mjs";
11
11
  import { a as globToRe, i as scoreReferences, n as toRefMeta, o as parseFrontmatter, r as routeReferences, t as loadRefs } from "./index-DL8MxjuP.mjs";
package/dist/index.mjs CHANGED
@@ -4,16 +4,16 @@ import { n as STATE_ROOT, r as projectLayout, t as STATE_GITIGNORE } from "./lay
4
4
  import { t as compactJson } from "./compact-json-DK2nX-MK.mjs";
5
5
  import { i as walkUpFor, n as projectRoot, r as projectRootOrNull, t as isCodeFile } from "./project-root-3kk7gCOp.mjs";
6
6
  import { n as detectMode, r as modeFor, t as detectHarness } from "./harness-C8Nxxyn_.mjs";
7
- import { A as evaluateApex, C as MAX_EXA_RESULTS, D as APEX_GATES, E as detectCreationIntent, F as detectProjectType, I as isApexCommand, L as requiredArchSkill, M as solidReadGate, N as DEV_KEYWORDS, O as brainstormGate, P as detectModularArchitecture, S as frameworkSolidGate, T as capVerbosity, _ as buildClaudeMdContext, a as firstHeading, b as skillTriggerGate, c as parseBodyDesc, d as PROJECT_INDICATORS, f as buildApexTaskContext, g as buildApexInstruction, h as DEV_VERBS, i as firstComment, j as freshnessGate, k as docConsultedGate, l as parseField, m as loadApexTaskState, n as missingSeoElements, o as parseEnrichment, p as buildApexTaskInjection, r as descFromText, s as parseEntry, t as isHtmlLike, u as EXCLUDE_DIRS, v as detectClaudeMdProjectType, w as MAX_TOKENS, x as SKILL_TRIGGERS, y as detectRequiredSkills } from "./validate-CccewDwk.mjs";
8
- import { A as evaluateFileSize, C as securityGuard, D as SYSTEM_INSTALL, E as PROJECT_INSTALL, O as matchPatterns, S as CRITICAL_PATTERNS, T as GIT_BLOCKED, _ as CODE_REDIRECT, a as registerGuard, b as protectedPathGuard, c as GO_DECL_RE, d as PY_MODEL_RE, f as SWIFT_PROTO_RE, g as CODE_MUTATORS, h as ASK_WRITERS, i as clearUserGuards, j as detectFramework, k as countLines, l as JAVA_DECL_RE, m as interfaceSeparationGuard, n as FAIL_CLOSED, o as runGuards, p as TS_DECL_RE, r as GUARDS, s as installGuard, t as evaluate, u as PHP_DECL_RE, v as bashWriteGuard, w as GIT_ASK, x as ASK_PATTERNS, y as PROTECTED_FRAGMENTS } from "./evaluate-9ch1K2kt.mjs";
7
+ import { A as evaluateApex, C as MAX_EXA_RESULTS, D as APEX_GATES, E as detectCreationIntent, F as detectProjectType, I as isApexCommand, L as requiredArchSkill, M as solidReadGate, N as DEV_KEYWORDS, O as brainstormGate, P as detectModularArchitecture, S as frameworkSolidGate, T as capVerbosity, _ as buildClaudeMdContext, a as firstHeading, b as skillTriggerGate, c as parseBodyDesc, d as PROJECT_INDICATORS, f as buildApexTaskContext, g as buildApexInstruction, h as DEV_VERBS, i as firstComment, j as freshnessGate, k as docConsultedGate, l as parseField, m as loadApexTaskState, n as missingSeoElements, o as parseEnrichment, p as buildApexTaskInjection, r as descFromText, s as parseEntry, t as isHtmlLike, u as EXCLUDE_DIRS, v as detectClaudeMdProjectType, w as MAX_TOKENS, x as SKILL_TRIGGERS, y as detectRequiredSkills } from "./validate-DLrWtaDR.mjs";
8
+ import { A as evaluateFileSize, C as securityGuard, D as SYSTEM_INSTALL, E as PROJECT_INSTALL, O as matchPatterns, S as CRITICAL_PATTERNS, T as GIT_BLOCKED, _ as CODE_REDIRECT, a as registerGuard, b as protectedPathGuard, c as GO_DECL_RE, d as PY_MODEL_RE, f as SWIFT_PROTO_RE, g as CODE_MUTATORS, h as ASK_WRITERS, i as clearUserGuards, j as detectFramework, k as countLines, l as JAVA_DECL_RE, m as interfaceSeparationGuard, n as FAIL_CLOSED, o as runGuards, p as TS_DECL_RE, r as GUARDS, s as installGuard, t as evaluate, u as PHP_DECL_RE, v as bashWriteGuard, w as GIT_ASK, x as ASK_PATTERNS, y as PROTECTED_FRAGMENTS } from "./evaluate-zyxeVZPB.mjs";
9
9
  import { i as resolveSessions, n as formatDocSatisfactionStatus, r as isDocConsulted, t as formatDocDeny } from "./doc-helpers-Dd_x1-tZ.mjs";
10
10
  import { i as parseFrontmatter, n as scoreReferences, r as globToRe, t as routeReferences } from "./router-D8cVrI-s.mjs";
11
11
  import { t as formatPrompt } from "./types-ernB1Dy3.mjs";
12
12
  import { a as nowStamp, c as stateFileFor, i as lessonsFileFor, l as throttleMs, n as readRoots, o as readState, r as registryFile, s as setStateField, t as addRoot, u as ensureMemoryGitignore } from "./registry-BkoEbdec.mjs";
13
13
  import { n as jaccardSimilar, r as queryHash, t as compactMarkdown } from "./cache-C9z9LclL.mjs";
14
14
  import { a as extractText, i as mcpCacheKey, n as cachePath, o as loadIndex, r as cacheStore, s as summarizeIndex, t as cacheLookup } from "./store-PrNPm6So.mjs";
15
- import { t as incrementTrivialEditCounter } from "./freshness-otdUpuvP.mjs";
15
+ import { t as incrementTrivialEditCounter } from "./freshness-43gxYpiX.mjs";
16
16
  import { n as toRefMeta, t as loadRefs } from "./loader-CyAoJv2W.mjs";
17
- import { a as ensureStateDir, c as stateFilePath, i as apexStateDir, l as acquireLock, n as taskCreate, o as loadState, r as taskStart, s as saveState, t as taskComplete } from "./state-ByhLeKyD.mjs";
17
+ import { a as ensureStateDir, c as stateFilePath, i as apexStateDir, l as acquireLock, n as taskCreate, o as loadState, r as taskStart, s as saveState, t as taskComplete } from "./state-BthKK4Jj.mjs";
18
18
  import { a as formatPath, c as colors, d as GRADIENT_BLOCKS, f as PROGRESS_BAR_DEFAULTS, i as formatCost, l as progressiveColor, m as TIME_INTERVALS, n as generateProgressBar, o as formatTimeLeft, p as PROGRESS_CHARS, r as formatBasename, s as formatTokens, t as generateGradientBar, u as COLOR_THRESHOLDS } from "./statusline-D87eUNXl.mjs";
19
19
  export { APEX_GATES, ASK_PATTERNS, ASK_WRITERS, CODE_MUTATORS, CODE_REDIRECT, COLOR_THRESHOLDS, CRITICAL_PATTERNS, DEFAULT_MAX_LINES, DEFAULT_TTL_SEC, DEV_KEYWORDS, DEV_VERBS, EXCLUDE_DIRS, FAIL_CLOSED, GIT_ASK, GIT_BLOCKED, GO_DECL_RE, GRADIENT_BLOCKS, GUARDS, JAVA_DECL_RE, MAX_EXA_RESULTS, MAX_LINES_ENV_KEY, MAX_TOKENS, PHP_DECL_RE, PROGRESS_BAR_DEFAULTS, PROGRESS_CHARS, PROJECT_INDICATORS, PROJECT_INSTALL, PROTECTED_FRAGMENTS, PY_MODEL_RE, SKILL_TRIGGERS, STATE_GITIGNORE, STATE_ROOT, SWIFT_PROTO_RE, SYSTEM_INSTALL, TIME_INTERVALS, TS_DECL_RE, TTL_ENV_KEY, acquireLock, addRoot, apexStateDir, bashWriteGuard, brainstormGate, buildApexInstruction, buildApexTaskContext, buildApexTaskInjection, buildClaudeMdContext, cacheLookup, cachePath, cacheStore, capVerbosity, clearUserGuards, colors, compactJson, compactMarkdown, countLines, descFromText, detectClaudeMdProjectType, detectCreationIntent, detectFramework, detectHarness, detectMode, detectModularArchitecture, detectProjectType, detectRequiredSkills, docConsultedGate, ensureMemoryGitignore, ensureStateDir, evaluate, evaluateApex, evaluateFileSize, extractText, firstComment, firstHeading, formatBasename, formatCost, formatDocDeny, formatDocSatisfactionStatus, formatPath, formatPrompt, formatTimeLeft, formatTokens, frameworkSolidGate, freshnessGate, generateGradientBar, generateProgressBar, globToRe, incrementTrivialEditCounter, installGuard, interfaceSeparationGuard, isApexCommand, isCodeFile, isDocConsulted, isHtmlLike, jaccardSimilar, lessonsFileFor, loadApexTaskState, loadIndex, loadRefs, loadState, matchPatterns, mcpCacheKey, missingSeoElements, modeFor, nowStamp, parseBodyDesc, parseEnrichment, parseEntry, parseEnvInt, parseField, parseFrontmatter, progressiveColor, projectLayout, projectRoot, projectRootOrNull, protectedPathGuard, queryHash, readRoots, readState, registerGuard, registryFile, requiredArchSkill, resolveMaxLines, resolveSessions, resolveTtlSec, routeReferences, runGuards, saveState, scoreReferences, securityGuard, setStateField, skillTriggerGate, solidReadGate, splitTarget, stateFileFor, stateFilePath, summarizeIndex, taskComplete, taskCreate, taskStart, throttleMs, toRefMeta, ttlLabel, walkUpFor };
@@ -1,2 +1,2 @@
1
- import { a as codexInit, i as clineInit, n as writeInitFile, o as cursorInit, r as claudeInit, s as geminiInit, t as initFor } from "../run-D91N4ul1.mjs";
1
+ import { a as codexInit, i as clineInit, n as writeInitFile, o as cursorInit, r as claudeInit, s as geminiInit, t as initFor } from "../run-Do2JltgU.mjs";
2
2
  export { claudeInit, clineInit, codexInit, cursorInit, geminiInit, initFor, writeInitFile };
@@ -1,7 +1,7 @@
1
1
  import { t as compactJson } from "./compact-json-DK2nX-MK.mjs";
2
2
  import { dirname } from "node:path";
3
3
  import { chmodSync, existsSync, mkdirSync, readFileSync, renameSync, unlinkSync, writeFileSync } from "node:fs";
4
- import { randomUUID } from "node:crypto";
4
+ import { createHash, randomUUID } from "node:crypto";
5
5
  import { mkdir } from "node:fs/promises";
6
6
  //#region src/util/json-io.ts
7
7
  /** Atomically write `data` to `path` (temp + rename, 0o600). Cross-FS safe on macOS/Linux. */
@@ -36,5 +36,9 @@ async function readJsonFile(path) {
36
36
  async function writeJsonFile(path, data, compact = false) {
37
37
  atomicWrite(path, compact ? compactJson(data) : JSON.stringify(data, null, 2));
38
38
  }
39
+ /** 8-char MD5 of text (cache key; non-cryptographic). Portable Node+Bun. */
40
+ function hashText(text) {
41
+ return createHash("md5").update(text).digest("hex").slice(0, 8);
42
+ }
39
43
  //#endregion
40
- export { writeJsonFile as i, ensureDir as n, readJsonFile as r, atomicWrite as t };
44
+ export { writeJsonFile as a, readJsonFile as i, ensureDir as n, hashText as r, atomicWrite as t };
@@ -1,2 +1,2 @@
1
- import { $ as APEX_GATES, A as GUARDS, B as TS_DECL_RE, C as SKILL_TRIGGERS, Ct as detectProjectType, D as capVerbosity, E as MAX_TOKENS, F as GO_DECL_RE, G as bashWriteGuard, H as ASK_WRITERS, I as JAVA_DECL_RE, J as ASK_PATTERNS, K as PROTECTED_FRAGMENTS, L as PHP_DECL_RE, M as registerGuard, N as runGuards, O as detectCreationIntent, P as installGuard, Q as GuardContext, R as PY_MODEL_RE, S as skillTriggerGate, St as detectModularArchitecture, T as MAX_EXA_RESULTS, Tt as requiredArchSkill, U as CODE_MUTATORS, V as interfaceSeparationGuard, W as CODE_REDIRECT, X as securityGuard, Y as CRITICAL_PATTERNS, Z as Guard, _ as DEV_VERBS, _t as evaluateFileSize, a as firstHeading, at as freshnessGate, b as detectClaudeMdProjectType, bt as ModularArchitecture, c as parseEntry, ct as PolicyResult, d as EXCLUDE_DIRS, dt as GIT_BLOCKED, et as ApexContext, f as PROJECT_INDICATORS, ft as PROJECT_INSTALL, g as loadApexTaskState, gt as countLines, h as buildApexTaskInjection, ht as FileSizeVerdict, i as firstComment, it as evaluateApex, j as clearUserGuards, k as FAIL_CLOSED, l as parseBodyDesc, lt as evaluate, m as buildApexTaskContext, mt as matchPatterns, n as missingSeoElements, nt as brainstormGate, o as TreeEntry, ot as solidReadGate, p as ApexTaskState, pt as SYSTEM_INSTALL, q as protectedPathGuard, r as descFromText, rt as docConsultedGate, s as parseEnrichment, st as PolicyContext, t as isHtmlLike, tt as ApexGate, u as parseField, ut as GIT_ASK, v as buildApexInstruction, vt as detectFramework, w as frameworkSolidGate, wt as isApexCommand, x as detectRequiredSkills, xt as ProjectType, y as buildClaudeMdContext, yt as DEV_KEYWORDS, z as SWIFT_PROTO_RE } from "../index-DmbOUJK8.mjs";
1
+ import { $ as APEX_GATES, A as GUARDS, B as TS_DECL_RE, C as SKILL_TRIGGERS, Ct as detectProjectType, D as capVerbosity, E as MAX_TOKENS, F as GO_DECL_RE, G as bashWriteGuard, H as ASK_WRITERS, I as JAVA_DECL_RE, J as ASK_PATTERNS, K as PROTECTED_FRAGMENTS, L as PHP_DECL_RE, M as registerGuard, N as runGuards, O as detectCreationIntent, P as installGuard, Q as GuardContext, R as PY_MODEL_RE, S as skillTriggerGate, St as detectModularArchitecture, T as MAX_EXA_RESULTS, Tt as requiredArchSkill, U as CODE_MUTATORS, V as interfaceSeparationGuard, W as CODE_REDIRECT, X as securityGuard, Y as CRITICAL_PATTERNS, Z as Guard, _ as DEV_VERBS, _t as evaluateFileSize, a as firstHeading, at as freshnessGate, b as detectClaudeMdProjectType, bt as ModularArchitecture, c as parseEntry, ct as PolicyResult, d as EXCLUDE_DIRS, dt as GIT_BLOCKED, et as ApexContext, f as PROJECT_INDICATORS, ft as PROJECT_INSTALL, g as loadApexTaskState, gt as countLines, h as buildApexTaskInjection, ht as FileSizeVerdict, i as firstComment, it as evaluateApex, j as clearUserGuards, k as FAIL_CLOSED, l as parseBodyDesc, lt as evaluate, m as buildApexTaskContext, mt as matchPatterns, n as missingSeoElements, nt as brainstormGate, o as TreeEntry, ot as solidReadGate, p as ApexTaskState, pt as SYSTEM_INSTALL, q as protectedPathGuard, r as descFromText, rt as docConsultedGate, s as parseEnrichment, st as PolicyContext, t as isHtmlLike, tt as ApexGate, u as parseField, ut as GIT_ASK, v as buildApexInstruction, vt as detectFramework, w as frameworkSolidGate, wt as isApexCommand, x as detectRequiredSkills, xt as ProjectType, y as buildClaudeMdContext, yt as DEV_KEYWORDS, z as SWIFT_PROTO_RE } from "../index-QzK2dv0V.mjs";
2
2
  export { APEX_GATES, ASK_PATTERNS, ASK_WRITERS, ApexContext, ApexGate, ApexTaskState, CODE_MUTATORS, CODE_REDIRECT, CRITICAL_PATTERNS, DEV_KEYWORDS, DEV_VERBS, EXCLUDE_DIRS, FAIL_CLOSED, FileSizeVerdict, GIT_ASK, GIT_BLOCKED, GO_DECL_RE, GUARDS, Guard, GuardContext, JAVA_DECL_RE, MAX_EXA_RESULTS, MAX_TOKENS, ModularArchitecture, PHP_DECL_RE, PROJECT_INDICATORS, PROJECT_INSTALL, PROTECTED_FRAGMENTS, PY_MODEL_RE, PolicyContext, PolicyResult, ProjectType, SKILL_TRIGGERS, SWIFT_PROTO_RE, SYSTEM_INSTALL, TS_DECL_RE, TreeEntry, bashWriteGuard, brainstormGate, buildApexInstruction, buildApexTaskContext, buildApexTaskInjection, buildClaudeMdContext, capVerbosity, clearUserGuards, countLines, descFromText, detectClaudeMdProjectType, detectCreationIntent, detectFramework, detectModularArchitecture, detectProjectType, detectRequiredSkills, docConsultedGate, evaluate, evaluateApex, evaluateFileSize, firstComment, firstHeading, frameworkSolidGate, freshnessGate, installGuard, interfaceSeparationGuard, isApexCommand, isHtmlLike, loadApexTaskState, matchPatterns, missingSeoElements, parseBodyDesc, parseEnrichment, parseEntry, parseField, protectedPathGuard, registerGuard, requiredArchSkill, runGuards, securityGuard, skillTriggerGate, solidReadGate };
@@ -1,4 +1,4 @@
1
- import { A as evaluateApex, C as MAX_EXA_RESULTS, D as APEX_GATES, E as detectCreationIntent, F as detectProjectType, I as isApexCommand, L as requiredArchSkill, M as solidReadGate, N as DEV_KEYWORDS, O as brainstormGate, P as detectModularArchitecture, S as frameworkSolidGate, T as capVerbosity, _ as buildClaudeMdContext, a as firstHeading, b as skillTriggerGate, c as parseBodyDesc, d as PROJECT_INDICATORS, f as buildApexTaskContext, g as buildApexInstruction, h as DEV_VERBS, i as firstComment, j as freshnessGate, k as docConsultedGate, l as parseField, m as loadApexTaskState, n as missingSeoElements, o as parseEnrichment, p as buildApexTaskInjection, r as descFromText, s as parseEntry, t as isHtmlLike, u as EXCLUDE_DIRS, v as detectClaudeMdProjectType, w as MAX_TOKENS, x as SKILL_TRIGGERS, y as detectRequiredSkills } from "../validate-CccewDwk.mjs";
2
- import { A as evaluateFileSize, C as securityGuard, D as SYSTEM_INSTALL, E as PROJECT_INSTALL, O as matchPatterns, S as CRITICAL_PATTERNS, T as GIT_BLOCKED, _ as CODE_REDIRECT, a as registerGuard, b as protectedPathGuard, c as GO_DECL_RE, d as PY_MODEL_RE, f as SWIFT_PROTO_RE, g as CODE_MUTATORS, h as ASK_WRITERS, i as clearUserGuards, j as detectFramework, k as countLines, l as JAVA_DECL_RE, m as interfaceSeparationGuard, n as FAIL_CLOSED, o as runGuards, p as TS_DECL_RE, r as GUARDS, s as installGuard, t as evaluate, u as PHP_DECL_RE, v as bashWriteGuard, w as GIT_ASK, x as ASK_PATTERNS, y as PROTECTED_FRAGMENTS } from "../evaluate-9ch1K2kt.mjs";
1
+ import { A as evaluateApex, C as MAX_EXA_RESULTS, D as APEX_GATES, E as detectCreationIntent, F as detectProjectType, I as isApexCommand, L as requiredArchSkill, M as solidReadGate, N as DEV_KEYWORDS, O as brainstormGate, P as detectModularArchitecture, S as frameworkSolidGate, T as capVerbosity, _ as buildClaudeMdContext, a as firstHeading, b as skillTriggerGate, c as parseBodyDesc, d as PROJECT_INDICATORS, f as buildApexTaskContext, g as buildApexInstruction, h as DEV_VERBS, i as firstComment, j as freshnessGate, k as docConsultedGate, l as parseField, m as loadApexTaskState, n as missingSeoElements, o as parseEnrichment, p as buildApexTaskInjection, r as descFromText, s as parseEntry, t as isHtmlLike, u as EXCLUDE_DIRS, v as detectClaudeMdProjectType, w as MAX_TOKENS, x as SKILL_TRIGGERS, y as detectRequiredSkills } from "../validate-DLrWtaDR.mjs";
2
+ import { A as evaluateFileSize, C as securityGuard, D as SYSTEM_INSTALL, E as PROJECT_INSTALL, O as matchPatterns, S as CRITICAL_PATTERNS, T as GIT_BLOCKED, _ as CODE_REDIRECT, a as registerGuard, b as protectedPathGuard, c as GO_DECL_RE, d as PY_MODEL_RE, f as SWIFT_PROTO_RE, g as CODE_MUTATORS, h as ASK_WRITERS, i as clearUserGuards, j as detectFramework, k as countLines, l as JAVA_DECL_RE, m as interfaceSeparationGuard, n as FAIL_CLOSED, o as runGuards, p as TS_DECL_RE, r as GUARDS, s as installGuard, t as evaluate, u as PHP_DECL_RE, v as bashWriteGuard, w as GIT_ASK, x as ASK_PATTERNS, y as PROTECTED_FRAGMENTS } from "../evaluate-zyxeVZPB.mjs";
3
3
  import "../policy-la_KkjCS.mjs";
4
4
  export { APEX_GATES, ASK_PATTERNS, ASK_WRITERS, CODE_MUTATORS, CODE_REDIRECT, CRITICAL_PATTERNS, DEV_KEYWORDS, DEV_VERBS, EXCLUDE_DIRS, FAIL_CLOSED, GIT_ASK, GIT_BLOCKED, GO_DECL_RE, GUARDS, JAVA_DECL_RE, MAX_EXA_RESULTS, MAX_TOKENS, PHP_DECL_RE, PROJECT_INDICATORS, PROJECT_INSTALL, PROTECTED_FRAGMENTS, PY_MODEL_RE, SKILL_TRIGGERS, SWIFT_PROTO_RE, SYSTEM_INSTALL, TS_DECL_RE, bashWriteGuard, brainstormGate, buildApexInstruction, buildApexTaskContext, buildApexTaskInjection, buildClaudeMdContext, capVerbosity, clearUserGuards, countLines, descFromText, detectClaudeMdProjectType, detectCreationIntent, detectFramework, detectModularArchitecture, detectProjectType, detectRequiredSkills, docConsultedGate, evaluate, evaluateApex, evaluateFileSize, firstComment, firstHeading, frameworkSolidGate, freshnessGate, installGuard, interfaceSeparationGuard, isApexCommand, isHtmlLike, loadApexTaskState, matchPatterns, missingSeoElements, parseBodyDesc, parseEnrichment, parseEntry, parseField, protectedPathGuard, registerGuard, requiredArchSkill, runGuards, securityGuard, skillTriggerGate, solidReadGate };
@@ -1,5 +1,5 @@
1
1
  import { t as isCodeFile } from "./project-root-3kk7gCOp.mjs";
2
- import { t as evaluate } from "./evaluate-9ch1K2kt.mjs";
2
+ import { t as evaluate } from "./evaluate-zyxeVZPB.mjs";
3
3
  import { t as formatPrompt } from "./types-ernB1Dy3.mjs";
4
4
  import { execSync } from "node:child_process";
5
5
  //#region src/cli/run.ts