flavor-code 1.4.3-beta.2 → 1.4.4-beta.2

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 (34) hide show
  1. package/README.md +27 -2
  2. package/README.zh-CN.md +27 -2
  3. package/dist/agent/loop.d.ts +1 -0
  4. package/dist/{app-G35YBHPR.js → app-C5I2TH7I.js} +399 -109
  5. package/dist/{chunk-FAJ6YDZK.js → chunk-36IDJRLQ.js} +1 -1
  6. package/dist/{chunk-HGTCIHWA.js → chunk-5BDKRA6U.js} +30 -1
  7. package/dist/{chunk-T6RE76D2.js → chunk-IKGT4MDT.js} +35 -23
  8. package/dist/{chunk-K7UGGWJV.js → chunk-JQM2L2YM.js} +1 -1
  9. package/dist/{chunk-6X62CGMA.js → chunk-ONRY3C2L.js} +3 -0
  10. package/dist/{chunk-IDJAHEG6.js → chunk-U4XTQ5PU.js} +392 -188
  11. package/dist/{chunk-SJUIIHIA.js → chunk-X6U6BO5D.js} +1 -1
  12. package/dist/{claude-ink-BE56DB6N.js → claude-ink-F554I3GJ.js} +1 -1
  13. package/dist/{cli-3MF6EL2H.js → cli-V3BLZ5UW.js} +7 -7
  14. package/dist/cli-main.js +16 -16
  15. package/dist/config/protected-file.d.ts +2 -0
  16. package/dist/config/schema.d.ts +3 -0
  17. package/dist/context/global-instructions.d.ts +12 -0
  18. package/dist/context/manager.d.ts +2 -0
  19. package/dist/desktop/main.js +660 -419
  20. package/dist/desktop-renderer/assets/{index-DCqIrF1S.js → index-CjC0Ibiq.js} +2 -2
  21. package/dist/desktop-renderer/assets/{interactive-terminal-B6GbpzIn.js → interactive-terminal-IjIT3Xoa.js} +1 -1
  22. package/dist/desktop-renderer/index.html +1 -1
  23. package/dist/{doctor-OHCGK52S.js → doctor-SEL32R7M.js} +3 -3
  24. package/dist/harness/local.d.ts +1 -0
  25. package/dist/{load-QK6CMLDF.js → load-RHWXNUCS.js} +2 -2
  26. package/dist/{manager-5SI4MPES.js → manager-IYBNNKUP.js} +2 -2
  27. package/dist/{production-6DIZ6YLQ.js → production-TD7UWWXW.js} +6 -6
  28. package/dist/production.d.ts +2 -0
  29. package/dist/sdk/index.js +6 -6
  30. package/dist/{store-ZIHN6363.js → store-MZZERW7K.js} +2 -2
  31. package/dist/ui/commands.d.ts +11 -3
  32. package/dist/ui/session.d.ts +2 -1
  33. package/package.json +1 -1
  34. package//346/212/200/346/234/257/346/226/271/346/241/210/346/212/245/345/221/212.md +649 -1
@@ -17,7 +17,7 @@ import {
17
17
  rgPath,
18
18
  runDoctor,
19
19
  terminateProcessTree
20
- } from "./chunk-K7UGGWJV.js";
20
+ } from "./chunk-JQM2L2YM.js";
21
21
  import {
22
22
  execFileNoThrow
23
23
  } from "./chunk-IU3UBETC.js";
@@ -49,7 +49,7 @@ import {
49
49
  normalizeMemoryContent,
50
50
  registerCrashCleanup,
51
51
  renderMemoryDocument
52
- } from "./chunk-FAJ6YDZK.js";
52
+ } from "./chunk-36IDJRLQ.js";
53
53
  import {
54
54
  redactErrorText,
55
55
  redactSecrets
@@ -94,8 +94,9 @@ import {
94
94
  readRecoverableFile,
95
95
  redactConfig,
96
96
  setProjectMcpServerDisabled,
97
+ updatePlainTextFile,
97
98
  updateProtectedFile
98
- } from "./chunk-HGTCIHWA.js";
99
+ } from "./chunk-5BDKRA6U.js";
99
100
  import {
100
101
  SESSION_VERSION,
101
102
  SessionStore,
@@ -120,14 +121,14 @@ import {
120
121
  transcriptReducer,
121
122
  updatePlanTask,
122
123
  withTimeout
123
- } from "./chunk-SJUIIHIA.js";
124
+ } from "./chunk-X6U6BO5D.js";
124
125
  import {
125
126
  message
126
127
  } from "./chunk-BDX777JR.js";
127
128
  import {
128
129
  PERMISSION_MODES,
129
130
  normalizePermissionMode
130
- } from "./chunk-6X62CGMA.js";
131
+ } from "./chunk-ONRY3C2L.js";
131
132
  import {
132
133
  NEVER,
133
134
  ZodIssueCode,
@@ -8288,11 +8289,11 @@ var require_ignore = __commonJS({
8288
8289
  });
8289
8290
 
8290
8291
  // src/production.ts
8291
- import { readFile as readFile18 } from "fs/promises";
8292
+ import { readFile as readFile19 } from "fs/promises";
8292
8293
  import { mkdirSync as mkdirSync3, readFileSync as readFileSync2, renameSync as renameSync2, unlinkSync as unlinkSync3, writeFileSync as writeFileSync2 } from "fs";
8293
8294
  import { createHash as createHash16, randomUUID as randomUUID16 } from "crypto";
8294
8295
  import { homedir, release as osRelease, tmpdir as tmpdir3, version as osVersion } from "os";
8295
- import { basename as basename4, dirname as dirname13, join as join21, relative as relative16, resolve as resolve25, sep as sep16 } from "path";
8296
+ import { basename as basename4, dirname as dirname13, join as join22, relative as relative16, resolve as resolve25, sep as sep16 } from "path";
8296
8297
  import { performance } from "perf_hooks";
8297
8298
 
8298
8299
  // src/agent/task-tools.ts
@@ -8527,6 +8528,7 @@ var ContextManager = class _ContextManager {
8527
8528
  #workspaceInstructions;
8528
8529
  #memory;
8529
8530
  #userMemory;
8531
+ #globalInstructions;
8530
8532
  #compactAtChars;
8531
8533
  #toolOutputChars;
8532
8534
  #recentTurns;
@@ -8571,6 +8573,7 @@ var ContextManager = class _ContextManager {
8571
8573
  this.#workspaceInstructions = options.workspaceInstructions;
8572
8574
  this.#memory = options.memory;
8573
8575
  this.#userMemory = options.userMemory;
8576
+ this.#globalInstructions = options.globalInstructions;
8574
8577
  this.#taskState = options.taskState;
8575
8578
  this.#compactAtChars = options.compactAtChars ?? Number.POSITIVE_INFINITY;
8576
8579
  this.#toolOutputChars = options.toolOutputChars;
@@ -8610,6 +8613,7 @@ var ContextManager = class _ContextManager {
8610
8613
  ...this.#memory === void 0 ? {} : { memory: this.#memory },
8611
8614
  ...this.#taskState === void 0 ? {} : { taskState: this.#taskState },
8612
8615
  ...userMemory === void 0 ? {} : { userMemory },
8616
+ ...this.#globalInstructions === void 0 ? {} : { globalInstructions: this.#globalInstructions },
8613
8617
  compactAtChars: this.#compactAtChars,
8614
8618
  toolOutputChars: this.#toolOutputChars,
8615
8619
  compaction: this.#compaction,
@@ -8691,12 +8695,14 @@ var ContextManager = class _ContextManager {
8691
8695
  for (const key of DYNAMIC_SOURCE_ORDER) {
8692
8696
  const value = next[key];
8693
8697
  if (value === void 0 || value === this.#epoch.sources[key]) continue;
8694
- updates.push(contextUpdateMessage(key, deltaSourceValue(this.#epoch.sources[key], displaySourceValue(key, value))));
8698
+ const display = key === "global-instructions" ? this.#epoch.sources[key] === void 0 ? value : `These replace all earlier global instructions.
8699
+ ${value}` : deltaSourceValue(this.#epoch.sources[key], displaySourceValue(key, value));
8700
+ updates.push(contextUpdateMessage(key, display));
8695
8701
  this.#epoch.sources[key] = value;
8696
8702
  }
8697
8703
  for (const key of DYNAMIC_SOURCE_ORDER) {
8698
8704
  if (key in next || !(key in this.#epoch.sources)) continue;
8699
- updates.push(contextUpdateMessage(key, "(removed)"));
8705
+ updates.push(contextUpdateMessage(key, key === "global-instructions" ? "Global instructions were removed; disregard all earlier global instructions." : "(removed)"));
8700
8706
  delete this.#epoch.sources[key];
8701
8707
  }
8702
8708
  if (updates.length === 0) return false;
@@ -8716,9 +8722,15 @@ var ContextManager = class _ContextManager {
8716
8722
  const legacySummary = snapshot3.summary?.role === "system" && snapshot3.summary.content.startsWith("Conversation summary\n") ? snapshot3.summary.content.slice("Conversation summary\n".length) : void 0;
8717
8723
  this.#compact = snapshot3.compact === void 0 ? legacySummary === void 0 ? void 0 : { summary: legacySummary, compactedAt: (/* @__PURE__ */ new Date(0)).toISOString() } : { ...snapshot3.compact };
8718
8724
  if (snapshot3.epoch !== void 0) this.#epoch = cloneEpoch(snapshot3.epoch);
8725
+ const globalInstructions = tryResolveOptionalSystemSections(this.#globalInstructions);
8726
+ const globalInstructionsAbsent = globalInstructions.available && (globalInstructions.sections?.length ?? 0) === 0;
8727
+ if (globalInstructionsAbsent) {
8728
+ delete this.#epoch.sources["global-instructions"];
8729
+ if (this.#epoch.pinnedSources !== void 0) delete this.#epoch.pinnedSources["global-instructions"];
8730
+ }
8719
8731
  this.#visibilityLog = boundVisibilityLog(snapshot3.visibilityLog);
8720
8732
  this.#activeTransientSystem.clear();
8721
- this.#messages = messages.map((message2) => message2.role === "tool" ? { ...message2, content: truncateToolOutput(message2.content, this.#toolOutputChars) } : cloneMessage2(message2));
8733
+ this.#messages = (globalInstructionsAbsent ? messages.filter((message2) => message2.role !== "system" || !modelContentText(message2.content).startsWith("Context update [global-instructions]\n")) : messages).map((message2) => message2.role === "tool" ? { ...message2, content: truncateToolOutput(message2.content, this.#toolOutputChars) } : cloneMessage2(message2));
8722
8734
  this.#lastRecordedInputTokens = void 0;
8723
8735
  this.#estimatedTokensAtLastRecordedUsage = void 0;
8724
8736
  this.#consecutiveAutoCompactFailures = 0;
@@ -8934,6 +8946,12 @@ ${userMemory}`,
8934
8946
  }
8935
8947
  #resolvedDynamicSources(previous = {}) {
8936
8948
  const result = {};
8949
+ const globalInstructions = tryResolveOptionalSystemSections(this.#globalInstructions);
8950
+ if (!globalInstructions.available && previous["global-instructions"] !== void 0) {
8951
+ result["global-instructions"] = previous["global-instructions"];
8952
+ } else if (globalInstructions.sections !== void 0 && globalInstructions.sections.length > 0) {
8953
+ result["global-instructions"] = globalInstructions.sections.join("\n\n");
8954
+ }
8937
8955
  if (this.#memory !== void 0) result["long-term-memory"] = this.#memory;
8938
8956
  if (this.#taskState !== void 0) result["task-state"] = this.#taskState;
8939
8957
  const volatile = tryResolveOptionalSystemSections(this.#volatileSystem);
@@ -8942,7 +8960,7 @@ ${userMemory}`,
8942
8960
  return result;
8943
8961
  }
8944
8962
  };
8945
- var DYNAMIC_SOURCE_ORDER = ["long-term-memory", "task-state", "runtime"];
8963
+ var DYNAMIC_SOURCE_ORDER = ["global-instructions", "long-term-memory", "task-state", "runtime"];
8946
8964
  function sourceMessages(sources) {
8947
8965
  return DYNAMIC_SOURCE_ORDER.flatMap((key) => {
8948
8966
  const content = sources[key];
@@ -8956,7 +8974,7 @@ function sourceMessages(sources) {
8956
8974
  } catch {
8957
8975
  }
8958
8976
  }
8959
- const label = key === "long-term-memory" ? "Long-term memory" : key === "task-state" ? "Task state" : "Runtime";
8977
+ const label = key === "global-instructions" ? "Global instructions" : key === "long-term-memory" ? "Long-term memory" : key === "task-state" ? "Task state" : "Runtime";
8960
8978
  return [{
8961
8979
  role: "system",
8962
8980
  content: `${label}
@@ -9112,9 +9130,122 @@ function providerValidMessages(input) {
9112
9130
  return output;
9113
9131
  }
9114
9132
 
9133
+ // src/context/global-instructions.ts
9134
+ import { lstat, readFile } from "fs/promises";
9135
+ import { join } from "path";
9136
+ var GlobalInstructions = class _GlobalInstructions {
9137
+ static MAX_BYTES = 64 * 1024;
9138
+ static MAX_RULE_BYTES = 4 * 1024;
9139
+ path;
9140
+ #content;
9141
+ constructor(home) {
9142
+ this.path = join(home, ".flavor-code", "GLOBAL.md");
9143
+ }
9144
+ get content() {
9145
+ return this.#content;
9146
+ }
9147
+ async refresh() {
9148
+ let info;
9149
+ try {
9150
+ info = await lstat(this.path);
9151
+ } catch (error) {
9152
+ if (isMissing(error)) {
9153
+ this.#content = void 0;
9154
+ return;
9155
+ }
9156
+ throw error;
9157
+ }
9158
+ if (!info.isFile()) throw new Error(`Global instructions must be a regular file: ${this.path}`);
9159
+ if (info.size > _GlobalInstructions.MAX_BYTES) {
9160
+ throw new Error(`Global instructions exceed ${_GlobalInstructions.MAX_BYTES} bytes: ${this.path}`);
9161
+ }
9162
+ const bytes = await readFile(this.path);
9163
+ if (bytes.length > _GlobalInstructions.MAX_BYTES) {
9164
+ throw new Error(`Global instructions exceed ${_GlobalInstructions.MAX_BYTES} bytes: ${this.path}`);
9165
+ }
9166
+ const content = new TextDecoder("utf-8", { fatal: true }).decode(bytes).trim();
9167
+ this.#content = content || void 0;
9168
+ }
9169
+ async remember(input) {
9170
+ const rule = input.trim();
9171
+ if (!rule || /[\r\n]/u.test(rule)) throw new Error("Global rules must be one non-empty line.");
9172
+ if (Buffer.byteLength(rule, "utf8") > _GlobalInstructions.MAX_RULE_BYTES) {
9173
+ throw new Error(`Global rule exceeds ${_GlobalInstructions.MAX_RULE_BYTES} bytes.`);
9174
+ }
9175
+ let added = false;
9176
+ const next = await updatePlainTextFile(this.path, async (current) => {
9177
+ const exists2 = await regularFileExists(this.path);
9178
+ const base = exists2 ? current ?? "" : "";
9179
+ validateDocument(base);
9180
+ if (ruleLines(base).some((item) => item.text.toLocaleLowerCase() === rule.toLocaleLowerCase())) return base;
9181
+ const eol = base.includes("\r\n") ? "\r\n" : "\n";
9182
+ const nextContent = base.length === 0 ? `# Global instructions${eol}${eol}- ${rule}${eol}` : `${base}${base.endsWith(eol) ? "" : eol}${eol}- ${rule}${eol}`;
9183
+ validateDocument(nextContent);
9184
+ added = true;
9185
+ return nextContent;
9186
+ }, _GlobalInstructions.MAX_BYTES);
9187
+ this.#content = next?.trim() || void 0;
9188
+ return added ? "added" : "already-present";
9189
+ }
9190
+ async forget(input) {
9191
+ const query = input.trim().toLocaleLowerCase();
9192
+ if (!query || /[\r\n]/u.test(query)) throw new Error("Specify one global rule to forget.");
9193
+ if (!await regularFileExists(this.path)) return "not-found";
9194
+ let result = "not-found";
9195
+ const next = await updatePlainTextFile(this.path, async (current) => {
9196
+ if (!await regularFileExists(this.path)) return void 0;
9197
+ const base = current ?? "";
9198
+ validateDocument(base);
9199
+ const entries = ruleLines(base);
9200
+ const exact = entries.filter((entry) => entry.text.toLocaleLowerCase() === query);
9201
+ const matches = exact.length > 0 ? exact : entries.filter((entry) => entry.text.toLocaleLowerCase().includes(query));
9202
+ if (matches.length === 0) return base;
9203
+ if (matches.length > 1) {
9204
+ result = "ambiguous";
9205
+ return base;
9206
+ }
9207
+ const match = matches[0];
9208
+ result = "removed";
9209
+ const remaining = `${base.slice(0, match.start)}${base.slice(match.end)}`;
9210
+ return remaining.trim() === "# Global instructions" ? "" : remaining;
9211
+ }, _GlobalInstructions.MAX_BYTES);
9212
+ this.#content = next?.trim() || void 0;
9213
+ return result;
9214
+ }
9215
+ };
9216
+ function ruleLines(content) {
9217
+ return [...content.matchAll(/^[ \t]*[-*+][ \t]+([^\r\n]+)(?:\r?\n|$)/gmu)].map((match) => ({
9218
+ text: match[1].trim(),
9219
+ start: match.index,
9220
+ end: match.index + match[0].length
9221
+ }));
9222
+ }
9223
+ function validateDocument(content) {
9224
+ if (Buffer.byteLength(content, "utf8") > GlobalInstructions.MAX_BYTES) {
9225
+ throw new Error(`Global instructions exceed ${GlobalInstructions.MAX_BYTES} bytes.`);
9226
+ }
9227
+ return content;
9228
+ }
9229
+ async function regularFileExists(path2) {
9230
+ try {
9231
+ const info = await lstat(path2);
9232
+ if (!info.isFile()) throw new Error(`Global instructions must be a regular file: ${path2}`);
9233
+ if (info.size > GlobalInstructions.MAX_BYTES) {
9234
+ throw new Error(`Global instructions exceed ${GlobalInstructions.MAX_BYTES} bytes: ${path2}`);
9235
+ }
9236
+ return true;
9237
+ } catch (error) {
9238
+ if (isMissing(error)) return false;
9239
+ throw error;
9240
+ }
9241
+ }
9242
+ function isMissing(error) {
9243
+ return error instanceof Error && "code" in error && error.code === "ENOENT";
9244
+ }
9245
+
9115
9246
  // src/context/workspace-instructions.ts
9116
9247
  import { createHash as createHash2 } from "crypto";
9117
- import { lstat, readFile, realpath } from "fs/promises";
9248
+ import { lstat as lstat2, readFile as readFile2, realpath } from "fs/promises";
9118
9249
  import { dirname, isAbsolute, relative, resolve, sep } from "path";
9119
9250
  var NAMES = ["AGENTS.md", "CLAUDE.md", "AGENTS.local.md", "CLAUDE.local.md"];
9120
9251
  var ROOT_NAMES = ["AGENTS.md", "CLAUDE.md", "AGENTS.local.md", "CLAUDE.local.md"];
@@ -9174,11 +9305,11 @@ var WorkspaceInstructions = class {
9174
9305
  for (const name of names) {
9175
9306
  const path2 = resolve(directory.path, name);
9176
9307
  try {
9177
- const info = await lstat(path2);
9308
+ const info = await lstat2(path2);
9178
9309
  if (!info.isFile() && !info.isSymbolicLink()) continue;
9179
9310
  const physical = await realpath(path2);
9180
9311
  if (!within(physicalRoot, physical)) continue;
9181
- const bytes = await readFile(path2);
9312
+ const bytes = await readFile2(path2);
9182
9313
  const clipped = bytes.subarray(0, this.#maxFileBytes);
9183
9314
  const suffix = bytes.length > clipped.length ? `
9184
9315
 
@@ -9217,7 +9348,7 @@ ${document.content}
9217
9348
  };
9218
9349
  async function directoryForPath(path2) {
9219
9350
  try {
9220
- return (await lstat(path2)).isDirectory() ? path2 : dirname(path2);
9351
+ return (await lstat2(path2)).isDirectory() ? path2 : dirname(path2);
9221
9352
  } catch {
9222
9353
  return dirname(path2);
9223
9354
  }
@@ -10013,6 +10144,12 @@ var AgentLoop = class {
10013
10144
  }
10014
10145
  }
10015
10146
  async *#runInternal(request) {
10147
+ try {
10148
+ await this.#options.beforeModelCall?.(request.signal);
10149
+ } catch (error) {
10150
+ yield { type: "error", error: normalizeProviderError(error) };
10151
+ return;
10152
+ }
10016
10153
  this.#options.context.refreshContextSources();
10017
10154
  this.#options.context.append(request.initialUserMessage ?? { role: "user", content: request.prompt });
10018
10155
  let totalInputTokens = 0;
@@ -10073,6 +10210,7 @@ var AgentLoop = class {
10073
10210
  return;
10074
10211
  }
10075
10212
  try {
10213
+ await this.#options.beforeModelCall?.(request.signal);
10076
10214
  if (await this.#options.context.prepareForModelCall(request.signal)) yield { type: "compacted" };
10077
10215
  } catch (error) {
10078
10216
  yield { type: "error", error: normalizeProviderError(error) };
@@ -11077,7 +11215,7 @@ function redactClassifierText(value) {
11077
11215
  // src/tools/runtime.ts
11078
11216
  import { randomUUID as randomUUID2 } from "crypto";
11079
11217
  import { mkdir, writeFile } from "fs/promises";
11080
- import { basename, join, resolve as resolve3 } from "path";
11218
+ import { basename, join as join2, resolve as resolve3 } from "path";
11081
11219
 
11082
11220
  // src/tools/types.ts
11083
11221
  var TOOL_PRESENTATION = /* @__PURE__ */ Symbol("flavor.tool-presentation");
@@ -11410,9 +11548,9 @@ var ToolRuntime = class {
11410
11548
  const reason = perToolExceeded && turnExceeded ? "per_tool_and_turn_limit" : perToolExceeded ? "per_tool_limit" : "turn_limit";
11411
11549
  const preview = headTailPreview(serialized, previewChars);
11412
11550
  this.#turnOutputChars += preview.length;
11413
- const directory = join(this.#workspace, ".flavor", "tool-results");
11551
+ const directory = join2(this.#workspace, ".flavor", "tool-results");
11414
11552
  const filename2 = `${(/* @__PURE__ */ new Date()).toISOString().replaceAll(":", "-")}-${randomUUID2()}-${safeFilename(tool)}.txt`;
11415
- const savedTo = join(directory, filename2);
11553
+ const savedTo = join2(directory, filename2);
11416
11554
  let storage;
11417
11555
  try {
11418
11556
  await mkdir(directory, { recursive: true });
@@ -11631,6 +11769,7 @@ var LocalHarness = class {
11631
11769
  ...this.#options.softLimitFactor === void 0 ? {} : { softLimitFactor: this.#options.softLimitFactor },
11632
11770
  ...this.#options.extendIterations === void 0 ? {} : { extendIterations: this.#options.extendIterations },
11633
11771
  ...isMain && this.#options.hasActiveProgress !== void 0 ? { hasActiveProgress: this.#options.hasActiveProgress } : {},
11772
+ ...this.#options.beforeModelCall === void 0 ? {} : { beforeModelCall: this.#options.beforeModelCall },
11634
11773
  ...isMain && this.#options.hallucinationGuard !== void 0 ? { hallucinationGuard: this.#options.hallucinationGuard } : {},
11635
11774
  ...this.#options.modelJournal === void 0 ? {} : { modelJournal: this.#options.modelJournal }
11636
11775
  });
@@ -11660,7 +11799,7 @@ function toModelTool(tool) {
11660
11799
  // src/harness/journal.ts
11661
11800
  import { createHash as createHash3, randomUUID as randomUUID3 } from "crypto";
11662
11801
  import { closeSync, constants, existsSync as existsSync2, fsyncSync, mkdirSync, openSync, readFileSync, realpathSync as realpathSync2, renameSync, statSync, truncateSync, unlinkSync, writeSync } from "fs";
11663
- import { join as join2, relative as relative3, resolve as resolve4, sep as sep3 } from "path";
11802
+ import { join as join3, relative as relative3, resolve as resolve4, sep as sep3 } from "path";
11664
11803
 
11665
11804
  // src/harness/invariants.ts
11666
11805
  function harnessInvariantViolations(records) {
@@ -11749,14 +11888,14 @@ var HarnessJournal = class {
11749
11888
  #records = [];
11750
11889
  constructor(options) {
11751
11890
  const workspace = resolve4(options.workspace);
11752
- const directory = join2(workspace, ".flavor", "sessions");
11891
+ const directory = join3(workspace, ".flavor", "sessions");
11753
11892
  assertSessionId(options.sessionId);
11754
11893
  mkdirSync(directory, { recursive: true, mode: 448 });
11755
11894
  assertWithin(workspace, directory);
11756
11895
  const canonicalWorkspace = realpathSync2.native(workspace);
11757
11896
  const canonicalDirectory = realpathSync2.native(directory);
11758
11897
  if (!isWithin2(canonicalWorkspace, canonicalDirectory)) throw new Error("Harness journal directory escapes the workspace");
11759
- this.#path = join2(canonicalDirectory, `${options.sessionId}.events.jsonl`);
11898
+ this.#path = join3(canonicalDirectory, `${options.sessionId}.events.jsonl`);
11760
11899
  this.#now = options.now ?? (() => (/* @__PURE__ */ new Date()).toISOString());
11761
11900
  this.#maxBytes = options.maxBytes ?? MAX_JOURNAL_BYTES;
11762
11901
  if (!Number.isSafeInteger(this.#maxBytes) || this.#maxBytes < 1024) throw new Error("maxBytes must be an integer of at least 1024");
@@ -12398,7 +12537,7 @@ function createIncidentReporter(options) {
12398
12537
  import { createHash as createHash4, randomUUID as randomUUID5 } from "crypto";
12399
12538
  import { chmod, unlink } from "fs/promises";
12400
12539
  import { tmpdir } from "os";
12401
- import { join as join3 } from "path";
12540
+ import { join as join4 } from "path";
12402
12541
  import { createServer } from "net";
12403
12542
  var MAX_REQUEST_BYTES = 64 * 1024;
12404
12543
  var SOCKET_TIMEOUT_MS = 1e4;
@@ -12406,7 +12545,7 @@ function islandControlEndpoint(sessionId, platform = process.platform) {
12406
12545
  const digest = createHash4("sha256").update(`${process.pid}\0${sessionId}`).digest("hex").slice(0, 20);
12407
12546
  if (platform === "win32") return `\\\\.\\pipe\\flavor-island-control-${digest}`;
12408
12547
  const uid = typeof process.getuid === "function" ? process.getuid() : 0;
12409
- return join3(tmpdir(), `flavor-island-control-${uid}-${digest}.sock`);
12548
+ return join4(tmpdir(), `flavor-island-control-${uid}-${digest}.sock`);
12410
12549
  }
12411
12550
  async function createIslandControlServer(options) {
12412
12551
  const platform = options.platform ?? process.platform;
@@ -12533,17 +12672,17 @@ function closeServer(server) {
12533
12672
  // src/evolve/service.ts
12534
12673
  import { spawn as spawn2 } from "child_process";
12535
12674
  import { writeFile as writeFile5 } from "fs/promises";
12536
- import { join as join6 } from "path";
12675
+ import { join as join7 } from "path";
12537
12676
 
12538
12677
  // src/evolve/loader.ts
12539
12678
  import { cp, mkdir as mkdir2, mkdtemp, readdir as readdir2, rm, stat as stat2, writeFile as writeFile3 } from "fs/promises";
12540
12679
  import { tmpdir as tmpdir2 } from "os";
12541
- import { join as join4, resolve as resolve6 } from "path";
12680
+ import { join as join5, resolve as resolve6 } from "path";
12542
12681
 
12543
12682
  // src/plugins/host.ts
12544
12683
  import { createHash as createHash5 } from "crypto";
12545
12684
  import { lstatSync, realpathSync as realpathSync3 } from "fs";
12546
- import { lstat as lstat2, open, readFile as readFile2, readdir, realpath as realpath3, stat, writeFile as writeFile2 } from "fs/promises";
12685
+ import { lstat as lstat3, open, readFile as readFile3, readdir, realpath as realpath3, stat, writeFile as writeFile2 } from "fs/promises";
12547
12686
  import { isAbsolute as isAbsolute3, relative as relative4, resolve as resolve5, sep as sep4 } from "path";
12548
12687
  import { pathToFileURL } from "url";
12549
12688
 
@@ -12857,7 +12996,7 @@ var PluginHost = class {
12857
12996
  async #readCandidate(inputRoot, source, label = inputRoot) {
12858
12997
  let diagnosticLabel = label;
12859
12998
  try {
12860
- const rootInfo = await lstat2(inputRoot);
12999
+ const rootInfo = await lstat3(inputRoot);
12861
13000
  if (!rootInfo.isDirectory() || rootInfo.isSymbolicLink()) throw new Error("Plugin root must be a static directory");
12862
13001
  const root = await realpath3(inputRoot);
12863
13002
  const rawManifest = await readManifest(resolve5(root, MANIFEST));
@@ -12867,7 +13006,7 @@ var PluginHost = class {
12867
13006
  const manifest = PluginManifestSchema.parse(rawManifest);
12868
13007
  const entry = resolveContained(root, manifest.main, "Plugin entry");
12869
13008
  const entrySnapshot = await snapshotEntry(root, entry);
12870
- const fingerprint2 = createHash5("sha256").update(JSON.stringify(manifest)).update(await readFile2(entrySnapshot.physicalPath)).digest("hex");
13009
+ const fingerprint2 = createHash5("sha256").update(JSON.stringify(manifest)).update(await readFile3(entrySnapshot.physicalPath)).digest("hex");
12871
13010
  return { manifest, root, source, entry, entrySnapshot, fingerprint: fingerprint2 };
12872
13011
  } catch (error) {
12873
13012
  this.#diagnose(diagnosticLabel, error, inputRoot);
@@ -13114,7 +13253,7 @@ var PluginHost = class {
13114
13253
  }
13115
13254
  };
13116
13255
  async function readManifest(path2) {
13117
- const lexical = await lstat2(path2, { bigint: true });
13256
+ const lexical = await lstat3(path2, { bigint: true });
13118
13257
  if (!lexical.isFile() || lexical.isSymbolicLink()) throw new Error("Plugin manifest must be a static regular file");
13119
13258
  const handle = await open(path2, "r");
13120
13259
  try {
@@ -13131,7 +13270,7 @@ async function readManifest(path2) {
13131
13270
  if (bytesRead > MAX_MANIFEST_BYTES) throw new Error(`Plugin manifest exceeds ${MAX_MANIFEST_BYTES} bytes`);
13132
13271
  const after = fromStats(await handle.stat({ bigint: true }));
13133
13272
  assertSnapshot(before, after, "Plugin manifest");
13134
- const current = fromStats(await lstat2(path2, { bigint: true }));
13273
+ const current = fromStats(await lstat3(path2, { bigint: true }));
13135
13274
  assertSnapshot(before, current, "Plugin manifest path");
13136
13275
  return JSON.parse(new TextDecoder("utf-8", { fatal: true }).decode(buffer.subarray(0, bytesRead)));
13137
13276
  } finally {
@@ -13139,7 +13278,7 @@ async function readManifest(path2) {
13139
13278
  }
13140
13279
  }
13141
13280
  async function snapshotEntry(root, entry) {
13142
- const lexicalStats = await lstat2(entry, { bigint: true });
13281
+ const lexicalStats = await lstat3(entry, { bigint: true });
13143
13282
  if (!lexicalStats.isFile() && !lexicalStats.isSymbolicLink()) throw new Error("Plugin entry must be a file");
13144
13283
  const physicalPath = await realpath3(entry);
13145
13284
  assertContained(root, physicalPath, "Plugin entry symlink");
@@ -13227,8 +13366,8 @@ function compare(left, right) {
13227
13366
  }
13228
13367
 
13229
13368
  // src/evolve/loader.ts
13230
- var PLUGIN_DIR = join4(".flavor", "plugins");
13231
- var VERSIONS_DIR = join4(".flavor", "plugins", ".versions");
13369
+ var PLUGIN_DIR = join5(".flavor", "plugins");
13370
+ var VERSIONS_DIR = join5(".flavor", "plugins", ".versions");
13232
13371
  function sanitizePluginName(tool) {
13233
13372
  const base = String(tool ?? "fix").replace(/[^a-z0-9-]/gi, "-").toLowerCase();
13234
13373
  return base.startsWith("fix-") ? base : `fix-${base}`;
@@ -13272,8 +13411,8 @@ export function activate(ctx) {
13272
13411
  async function scaffoldFixPlugin(workspace, name) {
13273
13412
  const dir = fixPluginDir(workspace, name);
13274
13413
  await mkdir2(dir, { recursive: true });
13275
- const manifestPath = join4(dir, "flavor-plugin.json");
13276
- const entryPath = join4(dir, "index.js");
13414
+ const manifestPath = join5(dir, "flavor-plugin.json");
13415
+ const entryPath = join5(dir, "index.js");
13277
13416
  try {
13278
13417
  await writeFile3(manifestPath, SCAFFOLD_MANIFEST(name), { encoding: "utf8", flag: "wx" });
13279
13418
  await writeFile3(entryPath, SCAFFOLD_ENTRY(name), { encoding: "utf8", flag: "wx" });
@@ -13293,9 +13432,9 @@ async function verifyFixPlugin(workspace, name) {
13293
13432
  return { ok: false, provided: [], tools: [], commands: [], error: `Plugin "${name}" not found.` };
13294
13433
  }
13295
13434
  const collected = { tools: [], commands: [] };
13296
- const sandbox = await mkdtemp(join4(tmpdir2(), "flavor-evolve-verify-"));
13435
+ const sandbox = await mkdtemp(join5(tmpdir2(), "flavor-evolve-verify-"));
13297
13436
  try {
13298
- await cp(pluginRoot, join4(sandbox, name), { recursive: true });
13437
+ await cp(pluginRoot, join5(sandbox, name), { recursive: true });
13299
13438
  const host = new PluginHost({
13300
13439
  projectPluginDirs: [sandbox],
13301
13440
  sandbox: true,
@@ -13346,7 +13485,7 @@ async function verifyFixPlugin(workspace, name) {
13346
13485
  }
13347
13486
  async function snapshotFixPlugin(workspace, name) {
13348
13487
  const pluginRoot = fixPluginDir(workspace, name);
13349
- const snapshotRoot = join4(versionsDirFor(workspace, name), timestamp());
13488
+ const snapshotRoot = join5(versionsDirFor(workspace, name), timestamp());
13350
13489
  await mkdir2(snapshotRoot, { recursive: true });
13351
13490
  await cp(pluginRoot, snapshotRoot, { recursive: true });
13352
13491
  return snapshotRoot;
@@ -13367,7 +13506,7 @@ async function revertFixPlugin(workspace, name) {
13367
13506
  }
13368
13507
  const snapshots = entries.filter((entry) => !entry.startsWith(".")).sort();
13369
13508
  if (snapshots.length === 0) throw new Error(`Plugin "${name}" has no snapshot to restore.`);
13370
- const latest = join4(versionsRoot, snapshots[snapshots.length - 1]);
13509
+ const latest = join5(versionsRoot, snapshots[snapshots.length - 1]);
13371
13510
  await rm(pluginRoot, { recursive: true, force: true });
13372
13511
  await mkdir2(pluginRoot, { recursive: true });
13373
13512
  await cp(latest, pluginRoot, { recursive: true });
@@ -13376,8 +13515,8 @@ async function revertFixPlugin(workspace, name) {
13376
13515
 
13377
13516
  // src/evolve/store.ts
13378
13517
  import { createHash as createHash6 } from "crypto";
13379
- import { appendFile, mkdir as mkdir3, readFile as readFile3, rm as rm2, writeFile as writeFile4 } from "fs/promises";
13380
- import { dirname as dirname4, join as join5 } from "path";
13518
+ import { appendFile, mkdir as mkdir3, readFile as readFile4, rm as rm2, writeFile as writeFile4 } from "fs/promises";
13519
+ import { dirname as dirname4, join as join6 } from "path";
13381
13520
  function normalizeError(message2) {
13382
13521
  return String(message2 ?? "").replace(/\s+/g, " ").replace(/"[^"]*"/g, '"\u2026"').replace(/`[^`]*`/g, '"\u2026"').trim().slice(0, 160);
13383
13522
  }
@@ -13390,7 +13529,7 @@ function argKeys(args) {
13390
13529
  }
13391
13530
  async function readJsonLines(file) {
13392
13531
  try {
13393
- const text = await readFile3(file, "utf8");
13532
+ const text = await readFile4(file, "utf8");
13394
13533
  const records = [];
13395
13534
  for (const line of text.split("\n")) {
13396
13535
  const trimmed = line.trim();
@@ -13413,7 +13552,7 @@ async function writeJsonLines(file, records) {
13413
13552
  }
13414
13553
  async function readJsonArray(file) {
13415
13554
  try {
13416
- const parsed = JSON.parse(await readFile3(file, "utf8"));
13555
+ const parsed = JSON.parse(await readFile4(file, "utf8"));
13417
13556
  return Array.isArray(parsed) ? parsed.filter((item) => typeof item === "string") : [];
13418
13557
  } catch {
13419
13558
  return [];
@@ -13421,7 +13560,7 @@ async function readJsonArray(file) {
13421
13560
  }
13422
13561
  async function readRules(file) {
13423
13562
  try {
13424
- const parsed = JSON.parse(await readFile3(file, "utf8"));
13563
+ const parsed = JSON.parse(await readFile4(file, "utf8"));
13425
13564
  if (!Array.isArray(parsed)) return [];
13426
13565
  return parsed.filter((item) => item !== null && typeof item === "object").filter((item) => typeof item.id === "string" && typeof item.text === "string").map((item) => ({
13427
13566
  id: item.id,
@@ -13450,12 +13589,12 @@ var EvolveStore = class {
13450
13589
  #queue = Promise.resolve();
13451
13590
  constructor(options) {
13452
13591
  const { workspace, maxSignals = 400, maxRules = 20 } = options;
13453
- this.dir = join5(workspace, ".flavor", "evolve");
13454
- this.signalsFile = join5(this.dir, "signals.jsonl");
13455
- this.reflectionsFile = join5(this.dir, "reflections.jsonl");
13456
- this.doneFile = join5(this.dir, "done.json");
13457
- this.verifiedFile = join5(this.dir, "verified.json");
13458
- this.rulesFile = join5(this.dir, "rules.json");
13592
+ this.dir = join6(workspace, ".flavor", "evolve");
13593
+ this.signalsFile = join6(this.dir, "signals.jsonl");
13594
+ this.reflectionsFile = join6(this.dir, "reflections.jsonl");
13595
+ this.doneFile = join6(this.dir, "done.json");
13596
+ this.verifiedFile = join6(this.dir, "verified.json");
13597
+ this.rulesFile = join6(this.dir, "rules.json");
13459
13598
  this.maxSignals = maxSignals;
13460
13599
  this.maxRules = maxRules;
13461
13600
  }
@@ -13998,7 +14137,7 @@ function createEvolveService(options) {
13998
14137
  if (suggestion === void 0) return `No open suggestion with id "${suggestionId}". Use /evolve suggest to list them.`;
13999
14138
  const name = sanitizePluginName(suggestion.tool);
14000
14139
  const dir = await scaffoldFixPlugin(workspace, name);
14001
- await writeFile5(join6(dir, "PLAN.md"), buildPlan(suggestion, name, "See the repeated failure below; describe your fix in PLAN.md."), "utf8");
14140
+ await writeFile5(join7(dir, "PLAN.md"), buildPlan(suggestion, name, "See the repeated failure below; describe your fix in PLAN.md."), "utf8");
14002
14141
  await snapshotFixPlugin(workspace, name);
14003
14142
  return [
14004
14143
  `suggestion ${suggestion.id}: ${suggestion.tool} x${suggestion.count} \u2014 ${suggestion.error}`,
@@ -14071,7 +14210,7 @@ ${result.stderr.slice(-4e3)}` : ""}`;
14071
14210
  name: "evolve_improve",
14072
14211
  description: "Implement a fix for one repeated tool failure. Default kind=plugin scaffolds the fix-<tool>/ plugin dir and writes PLAN.md with instructions for implementing, verifying, reloading, and testing it. kind=prompt_rule instead stores a concise guardrail rule (from `implementation`) that is injected into future system prompts and closes the suggestion. Use when the model proposes a concrete fix for a suggestion in the system prompt.",
14073
14212
  inputSchema,
14074
- paths: (input) => [join6(workspace, ".flavor", "evolve"), ...input.kind === "prompt_rule" ? [] : [join6(workspace, ".flavor", "plugins")]],
14213
+ paths: (input) => [join7(workspace, ".flavor", "evolve"), ...input.kind === "prompt_rule" ? [] : [join7(workspace, ".flavor", "plugins")]],
14075
14214
  execute: async (input, signal) => {
14076
14215
  signal.throwIfAborted();
14077
14216
  const { suggestionId, implementation, kind } = input;
@@ -14091,7 +14230,7 @@ ${result.stderr.slice(-4e3)}` : ""}`;
14091
14230
  }
14092
14231
  const name = sanitizePluginName(suggestion.tool);
14093
14232
  const dir = await scaffoldFixPlugin(workspace, name);
14094
- await writeFile5(join6(dir, "PLAN.md"), buildPlan(suggestion, name, implementation), "utf8");
14233
+ await writeFile5(join7(dir, "PLAN.md"), buildPlan(suggestion, name, implementation), "utf8");
14095
14234
  await snapshotFixPlugin(workspace, name);
14096
14235
  return [
14097
14236
  `Scaffolded fix plugin at ${dir} for suggestion [${suggestion.id}] (${suggestion.tool} x${suggestion.count}).`,
@@ -14589,7 +14728,7 @@ ${cycle.verification.summary}` !== signature) break;
14589
14728
  // src/goal/orchestrator.ts
14590
14729
  import { createHash as createHash8 } from "crypto";
14591
14730
  import { writeFile as writeFile6, mkdir as mkdir4 } from "fs/promises";
14592
- import { join as join8 } from "path";
14731
+ import { join as join9 } from "path";
14593
14732
 
14594
14733
  // src/goal/types.ts
14595
14734
  var GoalPhaseSchema = external_exports.enum([
@@ -14759,8 +14898,8 @@ function extractJson(text) {
14759
14898
 
14760
14899
  // src/goal/classifier.ts
14761
14900
  import { createHash as createHash7 } from "crypto";
14762
- import { readFile as readFile4 } from "fs/promises";
14763
- import { join as join7 } from "path";
14901
+ import { readFile as readFile5 } from "fs/promises";
14902
+ import { join as join8 } from "path";
14764
14903
  function buildSkepticPrompt(evidence, plan, index) {
14765
14904
  const criteriaList = plan.criteria.map((c) => ` ${c.id}. [${c.type}] ${c.description}`).join("\n");
14766
14905
  return [
@@ -14939,10 +15078,10 @@ function computeFingerprint(gaps) {
14939
15078
  }
14940
15079
  async function collectEvidence(workspace, objective, finalResponse, priorGaps, contractHash2 = createHash7("sha256").update(objective).digest("hex"), hostVerification) {
14941
15080
  const workspaceState = await collectWorkspaceState(workspace);
14942
- const planPath = join7(workspace, ".flavor", "goal-plan.md");
15081
+ const planPath = join8(workspace, ".flavor", "goal-plan.md");
14943
15082
  let planFile = null;
14944
15083
  try {
14945
- planFile = await readFile4(planPath, "utf8");
15084
+ planFile = await readFile5(planPath, "utf8");
14946
15085
  } catch {
14947
15086
  }
14948
15087
  return {
@@ -15311,8 +15450,8 @@ function waitForStageRetry(delayMs, signal) {
15311
15450
  });
15312
15451
  }
15313
15452
  async function writePlanFile(workspace, plan) {
15314
- const dir = join8(workspace, ".flavor");
15315
- const planPath = join8(dir, "goal-plan.md");
15453
+ const dir = join9(workspace, ".flavor");
15454
+ const planPath = join9(dir, "goal-plan.md");
15316
15455
  await mkdir4(dir, { recursive: true });
15317
15456
  const criteriaText = plan.criteria.map((c) => `- [${c.type}] AC-${c.id}: ${c.description}`).join("\n");
15318
15457
  const content = [
@@ -15389,8 +15528,8 @@ function formatPriorGaps(gaps) {
15389
15528
  // src/goal/store.ts
15390
15529
  import { constants as constants2 } from "fs";
15391
15530
  import { randomUUID as randomUUID6 } from "crypto";
15392
- import { lstat as lstat3, mkdir as mkdir5, open as open2, readFile as readFile5, rename, rm as rm3, stat as stat3 } from "fs/promises";
15393
- import { dirname as dirname5, join as join9, relative as relative5, resolve as resolve7, sep as sep5 } from "path";
15531
+ import { lstat as lstat4, mkdir as mkdir5, open as open2, readFile as readFile6, rename, rm as rm3, stat as stat3 } from "fs/promises";
15532
+ import { dirname as dirname5, join as join10, relative as relative5, resolve as resolve7, sep as sep5 } from "path";
15394
15533
  var DEFAULT_MAX_BYTES = 1024 * 1024;
15395
15534
  var GoalStore = class {
15396
15535
  #workspace;
@@ -15398,7 +15537,7 @@ var GoalStore = class {
15398
15537
  #maxBytes;
15399
15538
  constructor(options) {
15400
15539
  this.#workspace = resolve7(options.workspace);
15401
- this.#root = join9(this.#workspace, ".flavor", "goals");
15540
+ this.#root = join10(this.#workspace, ".flavor", "goals");
15402
15541
  this.#maxBytes = options.maxBytes ?? DEFAULT_MAX_BYTES;
15403
15542
  }
15404
15543
  async save(input) {
@@ -15406,13 +15545,13 @@ var GoalStore = class {
15406
15545
  await assertNoSymlink(this.#workspace, dirname5(this.#root));
15407
15546
  await mkdir5(this.#root, { recursive: true, mode: 448 });
15408
15547
  await assertNoSymlink(this.#workspace, this.#root);
15409
- const target = join9(this.#root, `${state.id}.json`);
15548
+ const target = join10(this.#root, `${state.id}.json`);
15410
15549
  const body = `${JSON.stringify(state, null, 2)}
15411
15550
  `;
15412
15551
  if (Buffer.byteLength(body) > this.#maxBytes) {
15413
15552
  throw new Error(`Goal state exceeds maximum size of ${this.#maxBytes} bytes`);
15414
15553
  }
15415
- const temporary = join9(this.#root, `.${state.id}.${process.pid}.${randomUUID6()}.tmp`);
15554
+ const temporary = join10(this.#root, `.${state.id}.${process.pid}.${randomUUID6()}.tmp`);
15416
15555
  let handle;
15417
15556
  try {
15418
15557
  handle = await open2(temporary, constants2.O_CREAT | constants2.O_EXCL | constants2.O_WRONLY, 384);
@@ -15433,13 +15572,13 @@ var GoalStore = class {
15433
15572
  }
15434
15573
  await assertNoSymlink(this.#workspace, dirname5(this.#root));
15435
15574
  await assertNoSymlink(this.#workspace, this.#root);
15436
- const target = join9(this.#root, `${goalId}.json`);
15575
+ const target = join10(this.#root, `${goalId}.json`);
15437
15576
  try {
15438
15577
  const metadata = await stat3(target);
15439
15578
  if (!metadata.isFile() || metadata.size > this.#maxBytes) {
15440
15579
  throw new Error("Goal state is not a bounded regular file");
15441
15580
  }
15442
- const raw = await readFile5(target, "utf8");
15581
+ const raw = await readFile6(target, "utf8");
15443
15582
  return GoalStateSchema.parse(JSON.parse(raw));
15444
15583
  } catch (error) {
15445
15584
  if (isCode(error, "ENOENT")) throw new Error(`Goal "${goalId}" was not found`);
@@ -15463,7 +15602,7 @@ async function assertNoSymlink(root, target) {
15463
15602
  }
15464
15603
  for (const path2 of chain.reverse()) {
15465
15604
  try {
15466
- if ((await lstat3(path2)).isSymbolicLink()) throw new Error(`Goal path contains a symbolic link: ${path2}`);
15605
+ if ((await lstat4(path2)).isSymbolicLink()) throw new Error(`Goal path contains a symbolic link: ${path2}`);
15467
15606
  } catch (error) {
15468
15607
  if (!isCode(error, "ENOENT")) throw error;
15469
15608
  }
@@ -15474,8 +15613,8 @@ function isCode(error, code) {
15474
15613
  }
15475
15614
 
15476
15615
  // src/loop/isolation.ts
15477
- import { appendFile as appendFile2, mkdir as mkdir6, readFile as readFile6 } from "fs/promises";
15478
- import { join as join10, resolve as resolve8 } from "path";
15616
+ import { appendFile as appendFile2, mkdir as mkdir6, readFile as readFile7 } from "fs/promises";
15617
+ import { join as join11, resolve as resolve8 } from "path";
15479
15618
  var CODE_CHANGE = /\b(?:fix|implement|add|update|change|modify|refactor|build|create|remove|delete|write|migrate)\b|修复|实现|添加|更新|修改|重构|创建|删除|迁移/i;
15480
15619
  var READ_ONLY = /\b(?:review|analy[sz]e|inspect|explain|research|investigate|audit|summari[sz]e|report)\b|调研|分析|审查|检查|解释|研究|总结|报告/i;
15481
15620
  function inferGoalIntent(goal) {
@@ -15498,8 +15637,8 @@ async function prepareLoopWorkspace(options) {
15498
15637
  return { kind: "needs_human", reason: "The workspace has uncommitted changes that a new worktree would omit." };
15499
15638
  }
15500
15639
  options.signal?.throwIfAborted();
15501
- const worktrees = join10(root, ".worktrees");
15502
- const path2 = join10(worktrees, `loop-${options.loopId}`);
15640
+ const worktrees = join11(root, ".worktrees");
15641
+ const path2 = join11(worktrees, `loop-${options.loopId}`);
15503
15642
  const branch = `loop/${options.loopId}`;
15504
15643
  try {
15505
15644
  await ensureWorktreeDirectoryIgnored(root);
@@ -15529,12 +15668,12 @@ async function ensureWorktreeDirectoryIgnored(root) {
15529
15668
  const commonDirectory = await git(root, ["rev-parse", "--git-common-dir"]);
15530
15669
  if (commonDirectory.code !== 0) throw new Error(commonDirectory.stderr.trim() || "Git common directory is unavailable");
15531
15670
  const gitDirectory = resolve8(root, commonDirectory.stdout.trim());
15532
- const infoDirectory = join10(gitDirectory, "info");
15533
- const excludePath = join10(infoDirectory, "exclude");
15671
+ const infoDirectory = join11(gitDirectory, "info");
15672
+ const excludePath = join11(infoDirectory, "exclude");
15534
15673
  await mkdir6(infoDirectory, { recursive: true });
15535
15674
  let existing = "";
15536
15675
  try {
15537
- existing = await readFile6(excludePath, "utf8");
15676
+ existing = await readFile7(excludePath, "utf8");
15538
15677
  } catch (error) {
15539
15678
  if (!(typeof error === "object" && error !== null && "code" in error && error.code === "ENOENT")) throw error;
15540
15679
  }
@@ -15545,8 +15684,8 @@ async function ensureWorktreeDirectoryIgnored(root) {
15545
15684
 
15546
15685
  // src/loop/store.ts
15547
15686
  import { constants as constants3 } from "fs";
15548
- import { lstat as lstat4, mkdir as mkdir7, open as open3, readFile as readFile7, realpath as realpath4, rename as rename2, rm as rm4, stat as stat4 } from "fs/promises";
15549
- import { dirname as dirname6, join as join11, relative as relative6, resolve as resolve9, sep as sep6 } from "path";
15687
+ import { lstat as lstat5, mkdir as mkdir7, open as open3, readFile as readFile8, realpath as realpath4, rename as rename2, rm as rm4, stat as stat4 } from "fs/promises";
15688
+ import { dirname as dirname6, join as join12, relative as relative6, resolve as resolve9, sep as sep6 } from "path";
15550
15689
  import { randomUUID as randomUUID7 } from "crypto";
15551
15690
  var LOOP_ID = /^[A-Za-z0-9][A-Za-z0-9._-]{0,127}$/;
15552
15691
  var DEFAULT_MAX_BYTES2 = 5 * 1024 * 1024;
@@ -15557,7 +15696,7 @@ var LoopStore = class {
15557
15696
  #appendTails = /* @__PURE__ */ new Map();
15558
15697
  constructor(options) {
15559
15698
  this.#workspace = resolve9(options.workspace);
15560
- this.#root = join11(this.#workspace, ".flavor", "loops");
15699
+ this.#root = join12(this.#workspace, ".flavor", "loops");
15561
15700
  this.#maxBytes = options.maxBytes ?? DEFAULT_MAX_BYTES2;
15562
15701
  }
15563
15702
  async create(state) {
@@ -15574,11 +15713,11 @@ var LoopStore = class {
15574
15713
  const state = LoopStateSchema.parse(input);
15575
15714
  await this.#assertWorkspace(state.workspace);
15576
15715
  const directory = await this.#prepare(state.loopId);
15577
- const target = join11(directory, "state.json");
15716
+ const target = join12(directory, "state.json");
15578
15717
  const body = `${JSON.stringify(state, null, 2)}
15579
15718
  `;
15580
15719
  if (Buffer.byteLength(body) > this.#maxBytes) throw new Error(`Loop state exceeds maximum size of ${this.#maxBytes} bytes`);
15581
- const temporary = join11(directory, `.state.${process.pid}.${randomUUID7()}.tmp`);
15720
+ const temporary = join12(directory, `.state.${process.pid}.${randomUUID7()}.tmp`);
15582
15721
  let handle;
15583
15722
  try {
15584
15723
  handle = await open3(temporary, constants3.O_CREAT | constants3.O_EXCL | constants3.O_WRONLY, 384);
@@ -15594,11 +15733,11 @@ var LoopStore = class {
15594
15733
  }
15595
15734
  async load(loopId) {
15596
15735
  const directory = await this.#safeExisting(loopId);
15597
- const target = join11(directory, "state.json");
15736
+ const target = join12(directory, "state.json");
15598
15737
  try {
15599
15738
  const metadata = await stat4(target);
15600
15739
  if (!metadata.isFile() || metadata.size > this.#maxBytes) throw new Error("Loop state is not a bounded regular file");
15601
- const raw = await readFile7(target, "utf8");
15740
+ const raw = await readFile8(target, "utf8");
15602
15741
  const state = LoopStateSchema.parse(JSON.parse(raw));
15603
15742
  await this.#assertWorkspace(state.workspace);
15604
15743
  return state;
@@ -15614,7 +15753,7 @@ var LoopStore = class {
15614
15753
  const previous = this.#appendTails.get(event.loopId) ?? Promise.resolve();
15615
15754
  const next = previous.catch(() => void 0).then(async () => {
15616
15755
  const directory = await this.#prepare(event.loopId);
15617
- const path2 = join11(directory, "events.jsonl");
15756
+ const path2 = join12(directory, "events.jsonl");
15618
15757
  const handle = await open3(path2, constants3.O_CREAT | constants3.O_APPEND | constants3.O_WRONLY, 384);
15619
15758
  try {
15620
15759
  await handle.writeFile(`${JSON.stringify(event)}
@@ -15631,7 +15770,7 @@ var LoopStore = class {
15631
15770
  await assertNoSymlink2(this.#workspace, dirname6(this.#root));
15632
15771
  await mkdir7(this.#root, { recursive: true, mode: 448 });
15633
15772
  await assertNoSymlink2(this.#workspace, this.#root);
15634
- const directory = join11(this.#root, loopId);
15773
+ const directory = join12(this.#root, loopId);
15635
15774
  await mkdir7(directory, { recursive: true, mode: 448 });
15636
15775
  await assertNoSymlink2(this.#workspace, directory);
15637
15776
  return directory;
@@ -15639,7 +15778,7 @@ var LoopStore = class {
15639
15778
  async #safeExisting(loopId) {
15640
15779
  validateId(loopId);
15641
15780
  await assertNoSymlink2(this.#workspace, this.#root);
15642
- const directory = join11(this.#root, loopId);
15781
+ const directory = join12(this.#root, loopId);
15643
15782
  await assertNoSymlink2(this.#workspace, directory);
15644
15783
  return directory;
15645
15784
  }
@@ -15665,7 +15804,7 @@ async function assertNoSymlink2(root, target) {
15665
15804
  }
15666
15805
  for (const path2 of chain.reverse()) {
15667
15806
  try {
15668
- if ((await lstat4(path2)).isSymbolicLink()) throw new Error(`Loop path contains a symbolic link: ${path2}`);
15807
+ if ((await lstat5(path2)).isSymbolicLink()) throw new Error(`Loop path contains a symbolic link: ${path2}`);
15669
15808
  } catch (error) {
15670
15809
  if (!isCode2(error, "ENOENT")) throw error;
15671
15810
  }
@@ -15687,20 +15826,20 @@ function isCode2(error, code) {
15687
15826
  }
15688
15827
 
15689
15828
  // src/loop/verifier.ts
15690
- import { readFile as readFile8 } from "fs/promises";
15691
- import { join as join12 } from "path";
15829
+ import { readFile as readFile9 } from "fs/promises";
15830
+ import { join as join13 } from "path";
15692
15831
  var SCRIPT_PRIORITY = ["test", "typecheck", "lint", "build", "smoke:install"];
15693
15832
  var MAX_PERSISTED_VERIFICATION_STREAM_BYTES = 1024;
15694
15833
  async function inferVerificationPlan(workspace) {
15695
15834
  const commands = [];
15696
15835
  const seen = /* @__PURE__ */ new Set();
15697
- const packageJson = await optionalJson(join12(workspace, "package.json"));
15836
+ const packageJson = await optionalJson(join13(workspace, "package.json"));
15698
15837
  const scripts = isRecord(packageJson?.scripts) ? packageJson.scripts : {};
15699
15838
  for (const name of SCRIPT_PRIORITY) {
15700
15839
  if (typeof scripts[name] !== "string" || isUnconditionalPassThrough(scripts[name])) continue;
15701
15840
  add(commands, seen, npmScript(name));
15702
15841
  }
15703
- const flavor = await optionalText(join12(workspace, "FLAVOR.md"));
15842
+ const flavor = await optionalText(join13(workspace, "FLAVOR.md"));
15704
15843
  if (flavor !== void 0) {
15705
15844
  for (const match of flavor.matchAll(/^- `([^`]+)`\s*$/gm)) {
15706
15845
  const parsed = parseTrustedCommand(match[1] ?? "");
@@ -15775,7 +15914,7 @@ function isUnconditionalPassThrough(script) {
15775
15914
  }
15776
15915
  async function optionalText(path2) {
15777
15916
  try {
15778
- return await readFile8(path2, "utf8");
15917
+ return await readFile9(path2, "utf8");
15779
15918
  } catch (error) {
15780
15919
  if (isCode3(error, "ENOENT")) return void 0;
15781
15920
  throw error;
@@ -15795,7 +15934,7 @@ function isCode3(error, code) {
15795
15934
  }
15796
15935
 
15797
15936
  // src/models/anthropic.ts
15798
- import { readFile as readFile9 } from "fs/promises";
15937
+ import { readFile as readFile10 } from "fs/promises";
15799
15938
 
15800
15939
  // src/utils/json.ts
15801
15940
  function safeParseJson(input) {
@@ -16069,7 +16208,7 @@ ${modelContentText(message2.content)}
16069
16208
  source: {
16070
16209
  type: "base64",
16071
16210
  media_type: block.mediaType,
16072
- data: (await readFile9(block.source.path)).toString("base64")
16211
+ data: (await readFile10(block.source.path)).toString("base64")
16073
16212
  }
16074
16213
  }));
16075
16214
  if (message2.cacheBreakpoint && Array.isArray(content) && content.length > 0) {
@@ -26604,7 +26743,7 @@ function isUndiciDispatcherVersionMismatchError(error) {
26604
26743
 
26605
26744
  // src/models/openai.ts
26606
26745
  import { createHash as createHash9 } from "crypto";
26607
- import { readFile as readFile10 } from "fs/promises";
26746
+ import { readFile as readFile11 } from "fs/promises";
26608
26747
  var DEFAULT_THINKING_EFFORT = "high";
26609
26748
  var EXPLICIT_CACHE_BREAKPOINT = { mode: "explicit" };
26610
26749
  var MAX_EXPLICIT_CACHE_BREAKPOINTS = 3;
@@ -26627,7 +26766,7 @@ async function toInput(message2, cacheBreakpoint = false) {
26627
26766
  prompt_cache_breakpoint: EXPLICIT_CACHE_BREAKPOINT
26628
26767
  }] : message2.content : await Promise.all(message2.content.map(async (block) => block.type === "text" ? { type: "input_text", text: block.text } : {
26629
26768
  type: "input_image",
26630
- image_url: `data:${block.mediaType};base64,${(await readFile10(block.source.path)).toString("base64")}`,
26769
+ image_url: `data:${block.mediaType};base64,${(await readFile11(block.source.path)).toString("base64")}`,
26631
26770
  detail: "auto"
26632
26771
  }));
26633
26772
  if (cacheBreakpoint && Array.isArray(content) && content.length > 0) {
@@ -26871,16 +27010,28 @@ function selectExplicitBreakpointIndexes(messages) {
26871
27010
  const indexes = messages.map((message2, index) => message2.cacheBreakpoint ? index : -1).filter((index) => index >= 0);
26872
27011
  const tail = messages.length - 1;
26873
27012
  if (tail >= 0 && indexes.at(-1) !== tail) indexes.push(tail);
26874
- return new Set(indexes.slice(-MAX_EXPLICIT_CACHE_BREAKPOINTS));
27013
+ const firstNonSystem = messages.findIndex((message2) => message2.role !== "system");
27014
+ const leadingSystemEnd = firstNonSystem < 0 ? tail : firstNonSystem - 1;
27015
+ const stable = indexes.filter((index) => index <= leadingSystemEnd).at(-1);
27016
+ if (stable === void 0) return new Set(indexes.slice(-MAX_EXPLICIT_CACHE_BREAKPOINTS));
27017
+ return /* @__PURE__ */ new Set([
27018
+ stable,
27019
+ ...indexes.filter((index) => index !== stable).slice(-(MAX_EXPLICIT_CACHE_BREAKPOINTS - 1))
27020
+ ]);
26875
27021
  }
26876
27022
  function promptCacheKey(request, sortedTools) {
26877
- let stableEnd = -1;
26878
- request.messages.forEach((message2, index) => {
26879
- if (message2.cacheBreakpoint) stableEnd = index;
26880
- });
26881
27023
  const firstNonSystem = request.messages.findIndex((message2) => message2.role !== "system");
26882
27024
  const leadingSystemEnd = firstNonSystem < 0 ? request.messages.length - 1 : firstNonSystem - 1;
26883
- stableEnd = Math.max(stableEnd, leadingSystemEnd);
27025
+ let stableEnd = -1;
27026
+ for (let index = 0; index <= leadingSystemEnd; index += 1) {
27027
+ if (request.messages[index]?.cacheBreakpoint) stableEnd = index;
27028
+ }
27029
+ if (stableEnd < 0) {
27030
+ request.messages.forEach((message2, index) => {
27031
+ if (message2.cacheBreakpoint) stableEnd = index;
27032
+ });
27033
+ stableEnd = Math.max(stableEnd, leadingSystemEnd);
27034
+ }
26884
27035
  const stableMessages = stableEnd < 0 ? [] : request.messages.slice(0, stableEnd + 1);
26885
27036
  const digest = createHash9("sha256").update(JSON.stringify({ model: request.model, messages: stableMessages, tools: sortedTools })).digest("hex").slice(0, 56);
26886
27037
  return `flavor-${digest}`;
@@ -32531,8 +32682,8 @@ function isStoredToken(value) {
32531
32682
  }
32532
32683
 
32533
32684
  // src/permissions/policy.ts
32534
- import { readFile as readFile11 } from "fs/promises";
32535
- import { join as join13, resolve as resolve11 } from "path";
32685
+ import { readFile as readFile12 } from "fs/promises";
32686
+ import { join as join14, resolve as resolve11 } from "path";
32536
32687
  var DecisionSchema2 = external_exports.enum(["allow", "ask", "deny"]);
32537
32688
  var PrefixSchema = external_exports.array(external_exports.string().min(1).max(1024)).min(1).max(64);
32538
32689
  var RuleSchema = external_exports.object({
@@ -32568,15 +32719,15 @@ var CompiledPermissionPolicy = class {
32568
32719
  async function loadPermissionPolicy(options) {
32569
32720
  const sources = [
32570
32721
  ...options.managedPath === void 0 ? [] : [{ tier: "managed", path: resolve11(options.managedPath) }],
32571
- { tier: "user", path: join13(resolve11(options.home), ".flavor-code", "permissions.json") },
32572
- { tier: "project", path: join13(resolve11(options.workspace), ".flavor", "permissions.json") },
32573
- { tier: "local", path: join13(resolve11(options.workspace), ".flavor", "permissions.local.json") }
32722
+ { tier: "user", path: join14(resolve11(options.home), ".flavor-code", "permissions.json") },
32723
+ { tier: "project", path: join14(resolve11(options.workspace), ".flavor", "permissions.json") },
32724
+ { tier: "local", path: join14(resolve11(options.workspace), ".flavor", "permissions.local.json") }
32574
32725
  ];
32575
32726
  const rules = [];
32576
32727
  for (const source of sources) {
32577
32728
  let raw;
32578
32729
  try {
32579
- raw = await readFile11(source.path, "utf8");
32730
+ raw = await readFile12(source.path, "utf8");
32580
32731
  } catch (error) {
32581
32732
  if (isCode4(error, "ENOENT")) continue;
32582
32733
  throw error;
@@ -32907,14 +33058,14 @@ function createSkillResourceTool(registry) {
32907
33058
 
32908
33059
  // src/session/tree.ts
32909
33060
  import { randomUUID as randomUUID9 } from "crypto";
32910
- import { mkdir as mkdir9, readFile as readFile13, rename as rename4, writeFile as writeFile8 } from "fs/promises";
32911
- import { dirname as dirname9, join as join15, resolve as resolve13 } from "path";
33061
+ import { mkdir as mkdir9, readFile as readFile14, rename as rename4, writeFile as writeFile8 } from "fs/promises";
33062
+ import { dirname as dirname9, join as join16, resolve as resolve13 } from "path";
32912
33063
 
32913
33064
  // src/session/checkpoint.ts
32914
33065
  import { createHash as createHash13, randomUUID as randomUUID8 } from "crypto";
32915
33066
  import { execFile } from "child_process";
32916
- import { lstat as lstat5, mkdir as mkdir8, open as open4, readFile as readFile12, readdir as readdir3, rename as rename3, rm as rm5, writeFile as writeFile7 } from "fs/promises";
32917
- import { dirname as dirname8, join as join14, relative as relative7, resolve as resolve12, sep as sep7 } from "path";
33067
+ import { lstat as lstat6, mkdir as mkdir8, open as open4, readFile as readFile13, readdir as readdir3, rename as rename3, rm as rm5, writeFile as writeFile7 } from "fs/promises";
33068
+ import { dirname as dirname8, join as join15, relative as relative7, resolve as resolve12, sep as sep7 } from "path";
32918
33069
  var EXCLUDED = /* @__PURE__ */ new Set([".git", ".flavor", "node_modules", "dist", "build", "release", ".worktrees"]);
32919
33070
  var MAX_FILE_BYTES = 16 * 1024 * 1024;
32920
33071
  var MAX_TOTAL_BYTES = 128 * 1024 * 1024;
@@ -32927,9 +33078,9 @@ var WorkspaceCheckpointStore = class {
32927
33078
  #maxTotalBytes;
32928
33079
  constructor(options) {
32929
33080
  this.#workspace = resolve12(options.workspace);
32930
- this.#root = join14(this.#workspace, ".flavor", "checkpoints");
32931
- this.#objects = join14(this.#root, "objects");
32932
- this.#manifests = join14(this.#root, "manifests");
33081
+ this.#root = join15(this.#workspace, ".flavor", "checkpoints");
33082
+ this.#objects = join15(this.#root, "objects");
33083
+ this.#manifests = join15(this.#root, "manifests");
32933
33084
  this.#maxFileBytes = options.maxFileBytes ?? MAX_FILE_BYTES;
32934
33085
  this.#maxTotalBytes = options.maxTotalBytes ?? MAX_TOTAL_BYTES;
32935
33086
  }
@@ -32941,15 +33092,15 @@ var WorkspaceCheckpointStore = class {
32941
33092
  await mkdir8(this.#manifests, { recursive: true });
32942
33093
  for (const path2 of paths) {
32943
33094
  const absolute = contained(this.#workspace, path2);
32944
- const info = await lstat5(absolute);
33095
+ const info = await lstat6(absolute);
32945
33096
  if (info.isSymbolicLink()) throw new Error(`Checkpoint refuses symbolic link: ${path2}`);
32946
33097
  if (!info.isFile()) continue;
32947
33098
  if (info.size > this.#maxFileBytes) throw new Error(`Checkpoint file exceeds size limit: ${path2}`);
32948
33099
  total += info.size;
32949
33100
  if (total > this.#maxTotalBytes) throw new Error("Checkpoint exceeds total size limit");
32950
- const content = await readFile12(absolute);
33101
+ const content = await readFile13(absolute);
32951
33102
  const digest = createHash13("sha256").update(content).digest("hex");
32952
- const objectPath = join14(this.#objects, digest);
33103
+ const objectPath = join15(this.#objects, digest);
32953
33104
  try {
32954
33105
  const handle = await open4(objectPath, "wx");
32955
33106
  try {
@@ -32969,12 +33120,12 @@ var WorkspaceCheckpointStore = class {
32969
33120
  ...label === void 0 || label.trim().length === 0 ? {} : { label: label.trim() },
32970
33121
  files
32971
33122
  };
32972
- await atomicJson(join14(this.#manifests, `${checkpoint.id}.json`), checkpoint);
33123
+ await atomicJson(join15(this.#manifests, `${checkpoint.id}.json`), checkpoint);
32973
33124
  return checkpoint;
32974
33125
  }
32975
33126
  async load(id) {
32976
33127
  if (!/^checkpoint-[A-Za-z0-9-]+$/.test(id)) throw new Error("Invalid checkpoint id");
32977
- const value = JSON.parse(await readFile12(join14(this.#manifests, `${id}.json`), "utf8"));
33128
+ const value = JSON.parse(await readFile13(join15(this.#manifests, `${id}.json`), "utf8"));
32978
33129
  if (value.version !== 1 || value.id !== id || !Array.isArray(value.files)) throw new Error("Invalid checkpoint manifest");
32979
33130
  for (const file of value.files) {
32980
33131
  contained(this.#workspace, file.path);
@@ -32986,7 +33137,7 @@ var WorkspaceCheckpointStore = class {
32986
33137
  const checkpoint = await this.load(id);
32987
33138
  const objects = /* @__PURE__ */ new Map();
32988
33139
  for (const file of checkpoint.files) {
32989
- const object3 = await readFile12(join14(this.#objects, file.digest));
33140
+ const object3 = await readFile13(join15(this.#objects, file.digest));
32990
33141
  if (object3.length !== file.size || createHash13("sha256").update(object3).digest("hex") !== file.digest) {
32991
33142
  throw new Error(`Checkpoint object is corrupt: ${file.digest}`);
32992
33143
  }
@@ -33009,7 +33160,7 @@ var WorkspaceCheckpointStore = class {
33009
33160
  }
33010
33161
  };
33011
33162
  async function discoverFiles(workspace) {
33012
- if (await exists(join14(workspace, ".git"))) {
33163
+ if (await exists(join15(workspace, ".git"))) {
33013
33164
  const listed = await gitFiles(workspace);
33014
33165
  return listed.filter((path2) => !EXCLUDED.has(path2.split("/")[0] ?? "")).sort();
33015
33166
  }
@@ -33018,7 +33169,7 @@ async function discoverFiles(workspace) {
33018
33169
  const entries = await readdir3(directory, { withFileTypes: true });
33019
33170
  for (const entry of entries) {
33020
33171
  if (EXCLUDED.has(entry.name)) continue;
33021
- const absolute = join14(directory, entry.name);
33172
+ const absolute = join15(directory, entry.name);
33022
33173
  const path2 = normalizeRelative(relative7(workspace, absolute));
33023
33174
  if (entry.isSymbolicLink()) throw new Error(`Checkpoint refuses symbolic link: ${path2}`);
33024
33175
  if (entry.isDirectory()) await walk(absolute);
@@ -33052,7 +33203,7 @@ async function gitFiles(workspace) {
33052
33203
  }
33053
33204
  async function exists(path2) {
33054
33205
  try {
33055
- await lstat5(path2);
33206
+ await lstat6(path2);
33056
33207
  return true;
33057
33208
  } catch (error) {
33058
33209
  if (isCode5(error, "ENOENT")) return false;
@@ -33093,17 +33244,17 @@ var SessionHistory = class _SessionHistory {
33093
33244
  #leafId;
33094
33245
  #unrevert;
33095
33246
  constructor(options, document) {
33096
- this.#path = join15(resolve13(options.workspace), ".flavor", "session-trees", options.sessionId, "tree.json");
33247
+ this.#path = join16(resolve13(options.workspace), ".flavor", "session-trees", options.sessionId, "tree.json");
33097
33248
  this.#checkpoints = new WorkspaceCheckpointStore({ workspace: options.workspace });
33098
33249
  this.#restoreContext = options.restoreContext;
33099
33250
  this.#nodes = boundedNodes(document.nodes);
33100
33251
  this.#leafId = document.leafId !== null && this.#nodes.some((node) => node.id === document.leafId) ? document.leafId : this.#nodes.at(-1)?.id ?? null;
33101
33252
  }
33102
33253
  static async open(options) {
33103
- const path2 = join15(resolve13(options.workspace), ".flavor", "session-trees", options.sessionId, "tree.json");
33254
+ const path2 = join16(resolve13(options.workspace), ".flavor", "session-trees", options.sessionId, "tree.json");
33104
33255
  let document = { version: 1, sessionId: options.sessionId, leafId: null, nodes: [] };
33105
33256
  try {
33106
- const parsed = JSON.parse(await readFile13(path2, "utf8"));
33257
+ const parsed = JSON.parse(await readFile14(path2, "utf8"));
33107
33258
  validate(parsed, options.sessionId);
33108
33259
  document = parsed;
33109
33260
  } catch (error) {
@@ -33236,7 +33387,7 @@ function isCode6(error, code) {
33236
33387
 
33237
33388
  // src/utils/rotation-census.ts
33238
33389
  import { mkdirSync as mkdirSync2, readdirSync, unlinkSync as unlinkSync2, writeFileSync } from "fs";
33239
- import { join as join16 } from "path";
33390
+ import { join as join17 } from "path";
33240
33391
  import { writeHeapSnapshot } from "v8";
33241
33392
  var CENSUS_PREFIX = "rotation-census-";
33242
33393
  var SNAPSHOT_PREFIX = "heap-";
@@ -33260,9 +33411,9 @@ function censusHeapBlock(reading) {
33260
33411
  }
33261
33412
  function writeRotationCensus(workspace, census) {
33262
33413
  try {
33263
- const directory = join16(workspace, ".flavor", "tmp");
33414
+ const directory = join17(workspace, ".flavor", "tmp");
33264
33415
  mkdirSync2(directory, { recursive: true });
33265
- const path2 = join16(directory, `${CENSUS_PREFIX}${Date.now()}.json`);
33416
+ const path2 = join17(directory, `${CENSUS_PREFIX}${Date.now()}.json`);
33266
33417
  writeFileSync(path2, `${JSON.stringify(census, null, 2)}
33267
33418
  `, { encoding: "utf8", mode: 384 });
33268
33419
  prune(directory, CENSUS_PREFIX, ".json", KEEP_CENSUS_FILES);
@@ -33273,9 +33424,9 @@ function writeRotationCensus(workspace, census) {
33273
33424
  }
33274
33425
  function writeRotationHeapSnapshot(workspace) {
33275
33426
  try {
33276
- const directory = join16(workspace, ".flavor", "tmp");
33427
+ const directory = join17(workspace, ".flavor", "tmp");
33277
33428
  mkdirSync2(directory, { recursive: true });
33278
- const path2 = join16(directory, `${SNAPSHOT_PREFIX}${Date.now()}.heapsnapshot`);
33429
+ const path2 = join17(directory, `${SNAPSHOT_PREFIX}${Date.now()}.heapsnapshot`);
33279
33430
  writeHeapSnapshot(path2);
33280
33431
  prune(directory, SNAPSHOT_PREFIX, ".heapsnapshot", KEEP_SNAPSHOT_FILES);
33281
33432
  return path2;
@@ -33287,7 +33438,7 @@ function prune(directory, prefix, suffix, keep) {
33287
33438
  const stale = readdirSync(directory).filter((name) => name.startsWith(prefix) && name.endsWith(suffix)).sort().slice(0, -keep);
33288
33439
  for (const name of stale) {
33289
33440
  try {
33290
- unlinkSync2(join16(directory, name));
33441
+ unlinkSync2(join17(directory, name));
33291
33442
  } catch {
33292
33443
  }
33293
33444
  }
@@ -33296,8 +33447,8 @@ function prune(directory, prefix, suffix, keep) {
33296
33447
  // src/sleep/organizer.ts
33297
33448
  import { constants as constants4 } from "fs";
33298
33449
  import { randomUUID as randomUUID10 } from "crypto";
33299
- import { lstat as lstat6, mkdir as mkdir10, open as open5, readdir as readdir4, realpath as realpath5, rename as rename5, rm as rm6 } from "fs/promises";
33300
- import { isAbsolute as isAbsolute4, join as join17, relative as relative8, resolve as resolve14, sep as sep8 } from "path";
33450
+ import { lstat as lstat7, mkdir as mkdir10, open as open5, readdir as readdir4, realpath as realpath5, rename as rename5, rm as rm6 } from "fs/promises";
33451
+ import { isAbsolute as isAbsolute4, join as join18, relative as relative8, resolve as resolve14, sep as sep8 } from "path";
33301
33452
  var DateKeySchema = external_exports.string().regex(/^\d{4}-\d{2}-\d{2}$/);
33302
33453
  var ReviewSchema = external_exports.object({
33303
33454
  title: external_exports.string().min(1).max(120),
@@ -33351,7 +33502,7 @@ var ProjectSleepOrganizer = class {
33351
33502
  #generate;
33352
33503
  constructor(options) {
33353
33504
  this.#workspace = resolve14(options.workspace);
33354
- this.#sleepDirectory = join17(this.#workspace, ".flavor", "sleep");
33505
+ this.#sleepDirectory = join18(this.#workspace, ".flavor", "sleep");
33355
33506
  this.#sessions = options.sessions ?? new SessionStore({ workspace: this.#workspace });
33356
33507
  this.#generate = options.generate;
33357
33508
  }
@@ -33360,7 +33511,7 @@ var ProjectSleepOrganizer = class {
33360
33511
  signal.throwIfAborted();
33361
33512
  await this.#prepareDirectory();
33362
33513
  if (await this.#hasReport(date)) return { status: "exists", date };
33363
- const lockPath = join17(this.#sleepDirectory, `.${date}.lock`);
33514
+ const lockPath = join18(this.#sleepDirectory, `.${date}.lock`);
33364
33515
  let lock;
33365
33516
  try {
33366
33517
  lock = await open5(lockPath, constants4.O_CREAT | constants4.O_EXCL | constants4.O_WRONLY, 384);
@@ -33395,7 +33546,7 @@ Previous output was invalid. Repair it and return strict JSON only.`;
33395
33546
  }
33396
33547
  if (review === void 0) throw lastError;
33397
33548
  const filename2 = `${date}-${filenameSummary(review.title)}.md`;
33398
- const path2 = join17(this.#sleepDirectory, filename2);
33549
+ const path2 = join18(this.#sleepDirectory, filename2);
33399
33550
  const markdown = renderSleepReport(date, review, documents);
33400
33551
  await this.#atomicWrite(path2, markdown);
33401
33552
  return { status: "written", date, path: path2, sessionCount: documents.length };
@@ -33437,7 +33588,7 @@ Previous output was invalid. Repair it and return strict JSON only.`;
33437
33588
  });
33438
33589
  }
33439
33590
  async #prepareDirectory() {
33440
- const flavorDirectory = join17(this.#workspace, ".flavor");
33591
+ const flavorDirectory = join18(this.#workspace, ".flavor");
33441
33592
  await assertNotSymlink(flavorDirectory);
33442
33593
  await mkdir10(flavorDirectory, { recursive: true, mode: 448 });
33443
33594
  await assertNotSymlink(this.#sleepDirectory);
@@ -33451,7 +33602,7 @@ Previous output was invalid. Repair it and return strict JSON only.`;
33451
33602
  return (await readdir4(this.#sleepDirectory, { withFileTypes: true })).some((entry) => entry.isFile() && entry.name.startsWith(prefix) && entry.name.endsWith(".md"));
33452
33603
  }
33453
33604
  async #atomicWrite(path2, content) {
33454
- const temporary = join17(this.#sleepDirectory, `.${randomUUID10()}.tmp`);
33605
+ const temporary = join18(this.#sleepDirectory, `.${randomUUID10()}.tmp`);
33455
33606
  let handle;
33456
33607
  try {
33457
33608
  handle = await open5(temporary, constants4.O_CREAT | constants4.O_EXCL | constants4.O_WRONLY, 384);
@@ -33767,7 +33918,7 @@ function assertDateKey(value) {
33767
33918
  }
33768
33919
  async function assertNotSymlink(path2) {
33769
33920
  try {
33770
- if ((await lstat6(path2)).isSymbolicLink()) throw new Error(`Refusing to use symbolic link: ${path2}`);
33921
+ if ((await lstat7(path2)).isSymbolicLink()) throw new Error(`Refusing to use symbolic link: ${path2}`);
33771
33922
  } catch (error) {
33772
33923
  if (!hasCode(error, "ENOENT")) throw error;
33773
33924
  }
@@ -33825,7 +33976,7 @@ function computeSessionStats(documents) {
33825
33976
 
33826
33977
  // src/tools/files.ts
33827
33978
  import { constants as constants5 } from "fs";
33828
- import { mkdir as mkdir11, open as open6, readFile as readFile14, realpath as realpath6, rename as rename6, rm as rm7, stat as stat5 } from "fs/promises";
33979
+ import { mkdir as mkdir11, open as open6, readFile as readFile15, realpath as realpath6, rename as rename6, rm as rm7, stat as stat5 } from "fs/promises";
33829
33980
  import { dirname as dirname10, isAbsolute as isAbsolute5, relative as relative9, resolve as resolve15, sep as sep9 } from "path";
33830
33981
  import { randomUUID as randomUUID11 } from "crypto";
33831
33982
 
@@ -34170,7 +34321,7 @@ function createPathGuard(workspace) {
34170
34321
  if (!within2(physicalRoot, physical)) throw new Error("Path escapes the workspace through a symlink");
34171
34322
  break;
34172
34323
  } catch (error) {
34173
- if (!isMissing(error)) throw error;
34324
+ if (!isMissing2(error)) throw error;
34174
34325
  const parent = dirname10(ancestor);
34175
34326
  if (parent === ancestor) throw error;
34176
34327
  ancestor = parent;
@@ -34188,7 +34339,7 @@ async function atomicWrite(path2, content, signal, expectedVersion) {
34188
34339
  try {
34189
34340
  mode = (await stat5(path2)).mode & 511;
34190
34341
  } catch (error) {
34191
- if (!isMissing(error)) throw error;
34342
+ if (!isMissing2(error)) throw error;
34192
34343
  }
34193
34344
  try {
34194
34345
  const handle = await open6(temporary, constants5.O_CREAT | constants5.O_EXCL | constants5.O_WRONLY, 384);
@@ -34209,7 +34360,7 @@ async function atomicWrite(path2, content, signal, expectedVersion) {
34209
34360
  }
34210
34361
  }
34211
34362
  async function readText(path2) {
34212
- const contents = await readFile14(path2);
34363
+ const contents = await readFile15(path2);
34213
34364
  if (isBinary(contents)) throw new Error("Cannot edit binary file as text");
34214
34365
  return decodeFileText(contents);
34215
34366
  }
@@ -34237,7 +34388,7 @@ async function fileVersion(path2) {
34237
34388
  try {
34238
34389
  return versionOfStat(await stat5(path2));
34239
34390
  } catch (error) {
34240
- if (isMissing(error)) return ABSENT_VERSION;
34391
+ if (isMissing2(error)) return ABSENT_VERSION;
34241
34392
  throw error;
34242
34393
  }
34243
34394
  }
@@ -34246,10 +34397,10 @@ function versionOfStat(info) {
34246
34397
  }
34247
34398
  async function readOptionalPresentationText(path2) {
34248
34399
  try {
34249
- const contents = await readFile14(path2);
34400
+ const contents = await readFile15(path2);
34250
34401
  return isBinary(contents) ? { exists: true } : { exists: true, text: contents.toString("utf8") };
34251
34402
  } catch (error) {
34252
- return isMissing(error) ? { exists: false } : { exists: true };
34403
+ return isMissing2(error) ? { exists: false } : { exists: true };
34253
34404
  }
34254
34405
  }
34255
34406
  function isBinary(contents) {
@@ -34358,7 +34509,7 @@ function mergeRange(ranges, start, end) {
34358
34509
  function abortIfNeeded(signal) {
34359
34510
  if (signal.aborted) throw signal.reason;
34360
34511
  }
34361
- function isMissing(error) {
34512
+ function isMissing2(error) {
34362
34513
  return typeof error === "object" && error !== null && "code" in error && error.code === "ENOENT";
34363
34514
  }
34364
34515
  var PATCH_SEARCH_RADIUS = 100;
@@ -34443,7 +34594,7 @@ async function requireAbsent(guard, path2) {
34443
34594
  try {
34444
34595
  await guard.existing(path2);
34445
34596
  } catch (error) {
34446
- if (isMissing(error)) return "";
34597
+ if (isMissing2(error)) return "";
34447
34598
  throw error;
34448
34599
  }
34449
34600
  throw new Error("Patch creation destination already exists");
@@ -35277,7 +35428,7 @@ async function readOptionalIgnoreFile(path2, maxBytes, signal) {
35277
35428
  try {
35278
35429
  handle = await open7(path2, "r");
35279
35430
  } catch (error) {
35280
- if (isMissing2(error)) return void 0;
35431
+ if (isMissing3(error)) return void 0;
35281
35432
  throw error;
35282
35433
  }
35283
35434
  const buffer = Buffer.allocUnsafe(maxBytes + 1);
@@ -35295,7 +35446,7 @@ async function readOptionalIgnoreFile(path2, maxBytes, signal) {
35295
35446
  if (offset > maxBytes) throw new Error(`Ignore file byte limit of ${maxBytes} exceeded: ${path2}`);
35296
35447
  return buffer.subarray(0, offset);
35297
35448
  }
35298
- function isMissing2(error) {
35449
+ function isMissing3(error) {
35299
35450
  return typeof error === "object" && error !== null && "code" in error && error.code === "ENOENT";
35300
35451
  }
35301
35452
 
@@ -35628,7 +35779,7 @@ function charset(contentType) {
35628
35779
  // src/tools/lsp.ts
35629
35780
  import { spawn as spawn5 } from "child_process";
35630
35781
  import { existsSync as existsSync3 } from "fs";
35631
- import { access, constants as constants6, readFile as readFile15 } from "fs/promises";
35782
+ import { access, constants as constants6, readFile as readFile16 } from "fs/promises";
35632
35783
  import { basename as basename2, dirname as dirname11, extname, isAbsolute as isAbsolute7, relative as relative11, resolve as resolve17, sep as sep11 } from "path";
35633
35784
  import { fileURLToPath, pathToFileURL as pathToFileURL2 } from "url";
35634
35785
  var LSP_REQUEST_TIMEOUT_MS = 15e3;
@@ -35823,7 +35974,7 @@ var LspConnection = class {
35823
35974
  const url2 = new URL(uri);
35824
35975
  if (url2.protocol !== "file:") return;
35825
35976
  const filePath = fileURLToPath(url2);
35826
- const text = await readFile15(filePath, "utf8");
35977
+ const text = await readFile16(filePath, "utf8");
35827
35978
  const current = this.#documents.get(uri);
35828
35979
  if (current === void 0) {
35829
35980
  this.#documents.set(uri, { text, version: 1 });
@@ -36556,12 +36707,12 @@ ${explanation}` : "";
36556
36707
  // src/desktop/astgraph-service.ts
36557
36708
  import { stat as stat7 } from "fs/promises";
36558
36709
  import { createRequire as nodeCreateRequire } from "module";
36559
- import { join as join18, resolve as resolve18 } from "path";
36710
+ import { join as join19, resolve as resolve18 } from "path";
36560
36711
  var FIELDS = "id, kind, name, qualified_name, file_path, language, start_line, end_line, signature";
36561
36712
  var DesktopAstGraphService = class {
36562
36713
  #path;
36563
36714
  constructor(workspace) {
36564
- this.#path = join18(resolve18(workspace), ".flavor", "astgraph", "index.db");
36715
+ this.#path = join19(resolve18(workspace), ".flavor", "astgraph", "index.db");
36565
36716
  }
36566
36717
  async status() {
36567
36718
  if ((await stat7(this.#path).catch(() => void 0))?.isFile() !== true) {
@@ -36841,8 +36992,8 @@ function createTodoWriteTool() {
36841
36992
 
36842
36993
  // src/tools/managed.ts
36843
36994
  import { randomUUID as randomUUID12 } from "crypto";
36844
- import { link, lstat as lstat7, mkdir as mkdir12, readFile as readFile16, readdir as readdir5, unlink as unlink2, writeFile as writeFile9 } from "fs/promises";
36845
- import { dirname as dirname12, join as join19, resolve as resolve20 } from "path";
36995
+ import { link, lstat as lstat8, mkdir as mkdir12, readFile as readFile17, readdir as readdir5, unlink as unlink2, writeFile as writeFile9 } from "fs/promises";
36996
+ import { dirname as dirname12, join as join20, resolve as resolve20 } from "path";
36846
36997
  var MANAGED_TOOL_VERSION = 1;
36847
36998
  var MAX_RECORD_BYTES = 256 * 1024;
36848
36999
  var TOOL_NAME = /^[A-Za-z][A-Za-z0-9_]{0,63}$/;
@@ -36893,8 +37044,8 @@ var ManagedToolStore = class {
36893
37044
  #diagnostics = [];
36894
37045
  constructor(options) {
36895
37046
  this.#workspace = resolve20(options.workspace);
36896
- this.#projectRoot = join19(this.#workspace, ".flavor", "tools");
36897
- this.#globalRoot = join19(resolve20(options.home), ".flavor-code", "tools");
37047
+ this.#projectRoot = join20(this.#workspace, ".flavor", "tools");
37048
+ this.#globalRoot = join20(resolve20(options.home), ".flavor-code", "tools");
36898
37049
  this.#now = options.now ?? (() => (/* @__PURE__ */ new Date()).toISOString());
36899
37050
  }
36900
37051
  get diagnostics() {
@@ -36941,7 +37092,7 @@ var ManagedToolStore = class {
36941
37092
  version: MANAGED_TOOL_VERSION,
36942
37093
  createdAt: this.#now()
36943
37094
  });
36944
- const path2 = join19(this.directory(input.scope), filename(input.name));
37095
+ const path2 = join20(this.directory(input.scope), filename(input.name));
36945
37096
  const definition = compileDefinition(record2, input.scope, this.#workspace);
36946
37097
  await writeExclusive(path2, `${JSON.stringify(record2, void 0, 2)}
36947
37098
  `);
@@ -36955,7 +37106,7 @@ var ManagedToolStore = class {
36955
37106
  if (matches.length === 0) throw new Error(`Managed tool "${input.name}" was not found`);
36956
37107
  if (matches.length > 1) throw new Error(`Managed tool "${input.name}" exists in multiple scopes; specify scope`);
36957
37108
  const entry = matches[0];
36958
- const info = await lstat7(entry.path);
37109
+ const info = await lstat8(entry.path);
36959
37110
  if (!info.isFile() || info.isSymbolicLink()) throw new Error(`Managed tool path is no longer a regular file: ${entry.path}`);
36960
37111
  await unlink2(entry.path);
36961
37112
  this.#entries.delete(scopedKey(entry.scope, entry.record.name));
@@ -36966,7 +37117,7 @@ var ManagedToolStore = class {
36966
37117
  if (!parsed.success) return [];
36967
37118
  const matches = this.#matching(parsed.data.name).filter((entry) => parsed.data.scope === void 0 || entry.scope === parsed.data.scope);
36968
37119
  if (matches.length > 0) return matches.map((entry) => entry.path);
36969
- return [join19(this.directory(parsed.data.scope ?? "project"), filename(parsed.data.name))];
37120
+ return [join20(this.directory(parsed.data.scope ?? "project"), filename(parsed.data.name))];
36970
37121
  }
36971
37122
  async #loadDirectory(scope2) {
36972
37123
  const root = this.directory(scope2);
@@ -36974,17 +37125,17 @@ var ManagedToolStore = class {
36974
37125
  try {
36975
37126
  names = (await readdir5(root, { withFileTypes: true })).filter((entry) => entry.name.endsWith(".json")).sort((left, right) => compare2(left.name, right.name)).map((entry) => entry.name);
36976
37127
  } catch (error) {
36977
- if (isMissing3(error)) return;
37128
+ if (isMissing4(error)) return;
36978
37129
  this.#diagnostics.push(`${root}: ${message(error)}`);
36979
37130
  return;
36980
37131
  }
36981
37132
  for (const name of names) {
36982
- const path2 = join19(root, name);
37133
+ const path2 = join20(root, name);
36983
37134
  try {
36984
- const info = await lstat7(path2);
37135
+ const info = await lstat8(path2);
36985
37136
  if (!info.isFile() || info.isSymbolicLink()) throw new Error("Managed tool record must be a regular file");
36986
37137
  if (info.size > MAX_RECORD_BYTES) throw new Error(`Managed tool record exceeds ${MAX_RECORD_BYTES} bytes`);
36987
- const record2 = ManagedToolRecordSchema.parse(JSON.parse(await readFile16(path2, "utf8")));
37138
+ const record2 = ManagedToolRecordSchema.parse(JSON.parse(await readFile17(path2, "utf8")));
36988
37139
  if (record2.scope !== scope2) throw new Error(`Managed tool record scope must match its ${scope2} directory`);
36989
37140
  const key = scopedKey(scope2, record2.name);
36990
37141
  if (this.#entries.has(key)) throw new Error(`Duplicate managed tool name in ${scope2} scope: ${record2.name}`);
@@ -37177,7 +37328,7 @@ function compare2(left, right) {
37177
37328
  function compareScope(left, right) {
37178
37329
  return left === right ? 0 : left === "global" ? -1 : 1;
37179
37330
  }
37180
- function isMissing3(error) {
37331
+ function isMissing4(error) {
37181
37332
  return typeof error === "object" && error !== null && "code" in error && error.code === "ENOENT";
37182
37333
  }
37183
37334
 
@@ -37214,6 +37365,7 @@ var MVP_COMMANDS = [
37214
37365
  "remember",
37215
37366
  "forget",
37216
37367
  "forget-cold",
37368
+ "global",
37217
37369
  "checkpoint",
37218
37370
  "tree",
37219
37371
  "rewind",
@@ -37256,6 +37408,7 @@ var COMMAND_DESCRIPTIONS = {
37256
37408
  remember: "Add a long-term memory",
37257
37409
  forget: "Remove matching long-term memories",
37258
37410
  "forget-cold": "Remove all cold long-term memories and their files",
37411
+ global: "Show or edit global coding instructions",
37259
37412
  checkpoint: "Create a workspace and context checkpoint",
37260
37413
  tree: "Show the session history tree",
37261
37414
  rewind: "Restore a prior session node",
@@ -37354,6 +37507,13 @@ function parseSlashCommand(input, dynamicCommands = [], skillCommands = [], mana
37354
37507
  const query = args.join(" ").trim();
37355
37508
  return query.length === 0 ? { name: "invalid", command: name, message: "Use /forget <text-or-id>." } : { name, query };
37356
37509
  }
37510
+ if (name === "global") {
37511
+ const [action, ...parts] = args;
37512
+ if (action === void 0) return { name, action: "show" };
37513
+ const text = parts.join(" ").trim();
37514
+ if ((action === "remember" || action === "forget") && text.length > 0) return { name, action, text };
37515
+ return { name: "invalid", command: name, message: "Use /global [remember <rule>|forget <rule>]." };
37516
+ }
37357
37517
  if (name === "checkpoint") {
37358
37518
  const label = args.join(" ").trim();
37359
37519
  return label.length === 0 ? { name } : { name, label };
@@ -37518,6 +37678,7 @@ var HELP = [
37518
37678
  "/logout clear stored OAuth credentials",
37519
37679
  "/init /config /skills /plugins /hooks /tasks /doctor",
37520
37680
  "/memory /remember [type] <text> /forget <text-or-id> /forget-cold /finish",
37681
+ "/global /global remember <rule> /global forget <rule>",
37521
37682
  "/checkpoint [label] /tree /rewind <node> /unrevert /fork <node>",
37522
37683
  "/compact /clear /paste-image /help /exit",
37523
37684
  "/loop <goal> run a verified autonomous loop",
@@ -37988,6 +38149,8 @@ Address this now (for example run the required verification or fix), then finish
37988
38149
  this.#notice(await required(this.#services.ide, "ide")());
37989
38150
  } else if (command.name === "memory") {
37990
38151
  this.#notice(await this.#services.memory());
38152
+ } else if (command.name === "global") {
38153
+ this.#notice(await required(this.#services.global, "global")(command));
37991
38154
  } else if (command.name === "remember") {
37992
38155
  this.#notice(await this.#services.remember(command.type, command.text));
37993
38156
  } else if (command.name === "forget") {
@@ -39333,8 +39496,8 @@ function createExecutionEnvironment(workspace, config) {
39333
39496
  }
39334
39497
 
39335
39498
  // src/ide/client.ts
39336
- import { readFile as readFile17, readdir as readdir6, stat as stat8 } from "fs/promises";
39337
- import { basename as basename3, join as join20, relative as relative14, resolve as resolve22, sep as sep14 } from "path";
39499
+ import { readFile as readFile18, readdir as readdir6, stat as stat8 } from "fs/promises";
39500
+ import { basename as basename3, join as join21, relative as relative14, resolve as resolve22, sep as sep14 } from "path";
39338
39501
  var FlavorIdeClient = class {
39339
39502
  #workspace;
39340
39503
  #home;
@@ -39430,7 +39593,7 @@ var FlavorIdeClient = class {
39430
39593
  };
39431
39594
  if (await this.#healthy(configured)) return configured;
39432
39595
  }
39433
- const directory = join20(this.#home, ".flavor-code", "ide");
39596
+ const directory = join21(this.#home, ".flavor-code", "ide");
39434
39597
  let entries;
39435
39598
  try {
39436
39599
  entries = (await readdir6(directory)).filter((name) => name.endsWith(".lock"));
@@ -39438,9 +39601,9 @@ var FlavorIdeClient = class {
39438
39601
  return void 0;
39439
39602
  }
39440
39603
  const candidates = (await Promise.all(entries.map(async (name) => {
39441
- const path2 = join20(directory, name);
39604
+ const path2 = join21(directory, name);
39442
39605
  try {
39443
- const [contents, info] = await Promise.all([readFile17(path2, "utf8"), stat8(path2)]);
39606
+ const [contents, info] = await Promise.all([readFile18(path2, "utf8"), stat8(path2)]);
39444
39607
  const lock = parseLockfile(contents);
39445
39608
  return lock === void 0 ? void 0 : { lock, modified: info.mtimeMs };
39446
39609
  } catch {
@@ -40633,6 +40796,8 @@ async function createProductionRuntime(options) {
40633
40796
  const gitRepository = detectGitRepository(workspace);
40634
40797
  const loaded = await loadConfig({ cwd: workspace, home, environment });
40635
40798
  const config = loaded.config;
40799
+ const globalInstructions = config.globalInstructions.enabled ? new GlobalInstructions(home) : void 0;
40800
+ await globalInstructions?.refresh();
40636
40801
  const mcpReady = connectMcpServers({
40637
40802
  servers: config.mcpServers,
40638
40803
  workspace,
@@ -40749,6 +40914,7 @@ async function createProductionRuntime(options) {
40749
40914
  protocolVersion: 2,
40750
40915
  workspace,
40751
40916
  ...hookSessionId === void 0 ? {} : { sessionId: hookSessionId },
40917
+ ...hookSessionId === void 0 || options.islandSessionTitle === void 0 ? {} : { sessionTitle: options.islandSessionTitle(hookSessionId) },
40752
40918
  ...islandControlMetadata === void 0 ? {} : {
40753
40919
  islandControlEndpoint: islandControlMetadata.endpoint,
40754
40920
  islandControlToken: islandControlMetadata.token,
@@ -40767,6 +40933,18 @@ async function createProductionRuntime(options) {
40767
40933
  );
40768
40934
  const registry = new ModelRegistry();
40769
40935
  const diagnostics = [];
40936
+ let lastGlobalInstructionError;
40937
+ const refreshGlobalInstructions = async () => {
40938
+ if (globalInstructions === void 0) return;
40939
+ try {
40940
+ await globalInstructions.refresh();
40941
+ lastGlobalInstructionError = void 0;
40942
+ } catch (error) {
40943
+ const diagnostic = `Global instructions could not be refreshed: ${message(error)}`;
40944
+ if (diagnostic !== lastGlobalInstructionError) diagnostics.push(diagnostic);
40945
+ lastGlobalInstructionError = diagnostic;
40946
+ }
40947
+ };
40770
40948
  const permissionPolicy = await loadPermissionPolicy({
40771
40949
  workspace,
40772
40950
  home,
@@ -40870,8 +41048,8 @@ async function createProductionRuntime(options) {
40870
41048
  const registeredProviders = registration.registered;
40871
41049
  let effectiveLlm = registration.effectiveLlm;
40872
41050
  const pluginHost = new PluginHost({
40873
- globalPluginDirs: [join21(home, ".flavor-code", "plugins")],
40874
- projectPluginDirs: [join21(workspace, ".flavor", "plugins")],
41051
+ globalPluginDirs: [join22(home, ".flavor-code", "plugins")],
41052
+ projectPluginDirs: [join22(workspace, ".flavor", "plugins")],
40875
41053
  config,
40876
41054
  // Existing project plugins (including astgraph and superharness) use Node.js
40877
41055
  // built-ins. The Worker/vm sandbox deliberately blocks those imports, so it
@@ -40980,7 +41158,7 @@ async function createProductionRuntime(options) {
40980
41158
  const address = palSocketAddress({
40981
41159
  platform: process.platform,
40982
41160
  userScope: home,
40983
- runtimeDir: join21(tmpdir3(), `flavor-code-pals-${createHash16("sha256").update(home).digest("hex").slice(0, 16)}`)
41161
+ runtimeDir: join22(tmpdir3(), `flavor-code-pals-${createHash16("sha256").update(home).digest("hex").slice(0, 16)}`)
40984
41162
  });
40985
41163
  const baseCollaborationClient = collaboration.client ?? collaboration.createClient?.({
40986
41164
  instanceId: collaboration.instanceId,
@@ -41091,8 +41269,8 @@ async function createProductionRuntime(options) {
41091
41269
  diagnostics.push(`MCP servers could not start: ${message(error)}`);
41092
41270
  });
41093
41271
  const skills = new SkillRegistry({
41094
- globalRoots: [join21(home, ".flavor-code", "skills")],
41095
- projectRoots: [join21(workspace, ".flavor", "skills"), ...pluginSkillRoots],
41272
+ globalRoots: [join22(home, ".flavor-code", "skills")],
41273
+ projectRoots: [join22(workspace, ".flavor", "skills"), ...pluginSkillRoots],
41096
41274
  authorizeResource: async () => true,
41097
41275
  disabledNames: config.skills.disabled
41098
41276
  });
@@ -41100,7 +41278,7 @@ async function createProductionRuntime(options) {
41100
41278
  skillsReady.catch(() => void 0);
41101
41279
  tools.push(createSkillTool(skills), createSkillResourceTool(skills));
41102
41280
  if (options.extraTools !== void 0) tools.push(...options.extraTools);
41103
- const flavor = await optionalText2(join21(workspace, "FLAVOR.md"));
41281
+ const flavor = await optionalText2(join22(workspace, "FLAVOR.md"));
41104
41282
  const instructionBaseline = await workspaceInstructions.baseline();
41105
41283
  let memoryContext;
41106
41284
  if (memoryStore !== void 0) {
@@ -41340,6 +41518,7 @@ async function createProductionRuntime(options) {
41340
41518
  ...memoryContext === void 0 ? {} : { memory: memoryContext },
41341
41519
  ...taskState === void 0 ? {} : { taskState },
41342
41520
  userMemory: () => userMemoryContext ?? "",
41521
+ ...globalInstructions === void 0 ? {} : { globalInstructions: () => globalInstructions.content ?? "" },
41343
41522
  ...compactAtChars === void 0 ? {} : { compactAtChars },
41344
41523
  toolOutputChars,
41345
41524
  compaction,
@@ -41377,6 +41556,7 @@ async function createProductionRuntime(options) {
41377
41556
  softLimitFactor: config.maxIterations.softLimitFactor,
41378
41557
  extendIterations: config.maxIterations.extendBy,
41379
41558
  hasActiveProgress,
41559
+ ...globalInstructions === void 0 ? {} : { beforeModelCall: refreshGlobalInstructions },
41380
41560
  afterToolSuccess: async (_tool, paths, _input, _output, context) => workspaceInstructions.discover(paths, context.ownerId ?? context.agent),
41381
41561
  toolJournal: {
41382
41562
  start: (tool, input, retrySafe) => harnessJournal.startTool(tool, input, retrySafe),
@@ -41697,7 +41877,7 @@ async function createProductionRuntime(options) {
41697
41877
  evolveService.toolDefinition(),
41698
41878
  ...mcpTools
41699
41879
  ];
41700
- const loopFlavor = await optionalText2(join21(input.workspace, "FLAVOR.md"));
41880
+ const loopFlavor = await optionalText2(join22(input.workspace, "FLAVOR.md"));
41701
41881
  const loopInstructionBaseline = await loopInstructions.baseline();
41702
41882
  const loopEnvironment = createPromptEnvironment({
41703
41883
  now: /* @__PURE__ */ new Date(),
@@ -41737,6 +41917,7 @@ async function createProductionRuntime(options) {
41737
41917
  ...loopInstructionBaseline === "" ? {} : { workspaceInstructions: loopInstructionBaseline },
41738
41918
  ...memoryContext === void 0 ? {} : { memory: memoryContext },
41739
41919
  userMemory: () => userMemoryContext ?? "",
41920
+ ...globalInstructions === void 0 ? {} : { globalInstructions: () => globalInstructions.content ?? "" },
41740
41921
  ...compactAtChars === void 0 ? {} : { compactAtChars },
41741
41922
  toolOutputChars,
41742
41923
  compaction,
@@ -41770,6 +41951,7 @@ async function createProductionRuntime(options) {
41770
41951
  softLimitFactor: config.maxIterations.softLimitFactor,
41771
41952
  extendIterations: config.maxIterations.extendBy,
41772
41953
  loopMode: true,
41954
+ ...globalInstructions === void 0 ? {} : { beforeModelCall: refreshGlobalInstructions },
41773
41955
  afterToolSuccess: async (_tool, paths, _input, _output, context) => loopInstructions.discover(paths, context.ownerId ?? context.agent),
41774
41956
  toolJournal: {
41775
41957
  start: (tool, input2, retrySafe) => harnessJournal.startTool(tool, input2, retrySafe),
@@ -42155,7 +42337,7 @@ async function createProductionRuntime(options) {
42155
42337
  const root = resolve25(workspace);
42156
42338
  const abs = resolve25(root, path2);
42157
42339
  if (abs !== root && !abs.startsWith(root + sep16)) throw new Error(`path outside workspace: ${path2}`);
42158
- return readFile18(abs, "utf8");
42340
+ return readFile19(abs, "utf8");
42159
42341
  },
42160
42342
  history: async (path2, limit2) => fileHistory(workspace, path2, limit2),
42161
42343
  questions,
@@ -42167,7 +42349,7 @@ async function createProductionRuntime(options) {
42167
42349
  }, query, focus, signal),
42168
42350
  audit: async (toolFilter) => {
42169
42351
  try {
42170
- const raw = await readFile18(auditLogger.path, "utf8");
42352
+ const raw = await readFile19(auditLogger.path, "utf8");
42171
42353
  const lines = raw.trim().split("\n").filter((line) => line.length > 0);
42172
42354
  const entries = lines.map((line) => {
42173
42355
  try {
@@ -42207,7 +42389,7 @@ ${summary2}`;
42207
42389
  doctor: async () => formatDoctorReport(await runDoctor({ workspace, home, environment })),
42208
42390
  usage: async () => {
42209
42391
  try {
42210
- const raw = await readFile18(usageLogPath(), "utf8");
42392
+ const raw = await readFile19(usageLogPath(), "utf8");
42211
42393
  return formatUsageSummary(summarizeUsage(parseUsageEntries(raw)));
42212
42394
  } catch (error) {
42213
42395
  if (typeof error === "object" && error !== null && "code" in error && error.code === "ENOENT") {
@@ -42286,6 +42468,28 @@ ${summary2}`;
42286
42468
  Path: ${memoryStore.path}` : `Path: ${memoryStore.path}
42287
42469
 
42288
42470
  ${renderMemoryDocument(entries)}`;
42471
+ },
42472
+ global: async (command) => {
42473
+ const path2 = join22(home, ".flavor-code", "GLOBAL.md");
42474
+ if (globalInstructions === void 0) return `Global instructions are disabled.
42475
+ Path: ${path2}`;
42476
+ if (command.action === "show") {
42477
+ await globalInstructions.refresh();
42478
+ return globalInstructions.content === void 0 ? `No global instructions stored.
42479
+ Path: ${path2}` : `Global instructions (enabled)
42480
+ Path: ${path2}
42481
+
42482
+ ${globalInstructions.content}`;
42483
+ }
42484
+ if (command.action === "remember") {
42485
+ const result2 = await globalInstructions.remember(command.text);
42486
+ return result2 === "added" ? `Remembered global rule.
42487
+ Path: ${path2}` : `Global rule already exists.
42488
+ Path: ${path2}`;
42489
+ }
42490
+ const result = await globalInstructions.forget(command.text);
42491
+ return result === "removed" ? `Forgot global rule.
42492
+ Path: ${path2}` : result === "ambiguous" ? "Multiple global rules match. Use the full text of one rule." : "No matching global rule found.";
42289
42493
  },
42290
42494
  refreshMemory: refreshMemoryState,
42291
42495
  remember: async (type, text) => {
@@ -42360,7 +42564,7 @@ ${renderMemoryDocument(entries)}`;
42360
42564
  return `Provider "${providerName}" is missing authorizationUrl, tokenUrl, or clientId.`;
42361
42565
  }
42362
42566
  try {
42363
- const tokenStore = createFileTokenStore(join21(home, ".flavor-code", "auth.json"));
42567
+ const tokenStore = createFileTokenStore(join22(home, ".flavor-code", "auth.json"));
42364
42568
  const credentialId = oauthCredentialId(oauthConfig.tokenUrl, oauthConfig.clientId);
42365
42569
  await migrateLegacyOAuthToken(tokenStore, providerName, credentialId);
42366
42570
  const oauth = new OAuthCallbackAuthProvider({
@@ -42408,7 +42612,7 @@ ${renderMemoryDocument(entries)}`;
42408
42612
  }
42409
42613
  },
42410
42614
  async logout() {
42411
- const tokenStore = createFileTokenStore(join21(home, ".flavor-code", "auth.json"));
42615
+ const tokenStore = createFileTokenStore(join22(home, ".flavor-code", "auth.json"));
42412
42616
  const tokens = await tokenStore.load();
42413
42617
  const hadCredentials = Object.keys(tokens).length > 0 || effectiveLlm !== void 0;
42414
42618
  if (!hadCredentials) {
@@ -42719,7 +42923,7 @@ async function workspaceFingerprint(workspace) {
42719
42923
  hash.update(name);
42720
42924
  if (remaining <= 0) continue;
42721
42925
  try {
42722
- const content = await readFile18(path2);
42926
+ const content = await readFile19(path2);
42723
42927
  const slice = content.subarray(0, remaining);
42724
42928
  hash.update(slice);
42725
42929
  remaining -= slice.length;
@@ -42990,7 +43194,7 @@ async function registerConfiguredAdapters(providers, registry, environment, diag
42990
43194
  const configured = { ...providers };
42991
43195
  if (configured.openai === void 0 && environment.OPENAI_API_KEY) configured.openai = { type: "openai", apiKey: environment.OPENAI_API_KEY };
42992
43196
  if (configured.anthropic === void 0 && environment.ANTHROPIC_API_KEY) configured.anthropic = { type: "anthropic", apiKey: environment.ANTHROPIC_API_KEY };
42993
- const oauthTokenStore = createFileTokenStore(join21(home, ".flavor-code", "auth.json"));
43197
+ const oauthTokenStore = createFileTokenStore(join22(home, ".flavor-code", "auth.json"));
42994
43198
  const registered = [];
42995
43199
  let effectiveLlm;
42996
43200
  for (const [name, provider] of Object.entries(configured)) {
@@ -43299,7 +43503,7 @@ async function generateSleepReview(registry, modelId, prompt, signal) {
43299
43503
  }
43300
43504
  async function optionalText2(path2) {
43301
43505
  try {
43302
- return await readFile18(path2, "utf8");
43506
+ return await readFile19(path2, "utf8");
43303
43507
  } catch (error) {
43304
43508
  if (typeof error === "object" && error !== null && "code" in error && error.code === "ENOENT") return void 0;
43305
43509
  throw error;