flavor-code 1.3.22 → 1.4.0-beta.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (134) hide show
  1. package/README.md +437 -434
  2. package/README.zh-CN.md +434 -431
  3. package/dist/{app-MWGRT3BJ.js → app-C5ZQUOKJ.js} +37 -63
  4. package/dist/astgraph/grammars.mjs +68 -68
  5. package/dist/astgraph/vendor/tree-sitter.js +3980 -3980
  6. package/dist/astgraph/vendor/zod/LICENSE +21 -21
  7. package/dist/astgraph/vendor/zod/index.js +4 -4
  8. package/dist/astgraph/vendor/zod/locales/index.js +1 -1
  9. package/dist/astgraph/vendor/zod/locales/package.json +7 -7
  10. package/dist/astgraph/vendor/zod/package.json +135 -135
  11. package/dist/astgraph/vendor/zod/v4/classic/checks.js +1 -1
  12. package/dist/astgraph/vendor/zod/v4/classic/coerce.js +17 -17
  13. package/dist/astgraph/vendor/zod/v4/classic/compat.js +31 -31
  14. package/dist/astgraph/vendor/zod/v4/classic/errors.js +48 -48
  15. package/dist/astgraph/vendor/zod/v4/classic/external.js +20 -20
  16. package/dist/astgraph/vendor/zod/v4/classic/from-json-schema.js +599 -599
  17. package/dist/astgraph/vendor/zod/v4/classic/index.js +4 -4
  18. package/dist/astgraph/vendor/zod/v4/classic/iso.js +30 -30
  19. package/dist/astgraph/vendor/zod/v4/classic/package.json +7 -7
  20. package/dist/astgraph/vendor/zod/v4/classic/parse.js +15 -15
  21. package/dist/astgraph/vendor/zod/v4/classic/schemas.js +1395 -1395
  22. package/dist/astgraph/vendor/zod/v4/core/api.js +1087 -1087
  23. package/dist/astgraph/vendor/zod/v4/core/checks.js +575 -575
  24. package/dist/astgraph/vendor/zod/v4/core/core.js +78 -78
  25. package/dist/astgraph/vendor/zod/v4/core/doc.js +35 -35
  26. package/dist/astgraph/vendor/zod/v4/core/errors.js +185 -185
  27. package/dist/astgraph/vendor/zod/v4/core/index.js +16 -16
  28. package/dist/astgraph/vendor/zod/v4/core/json-schema-generator.js +95 -95
  29. package/dist/astgraph/vendor/zod/v4/core/json-schema-processors.js +601 -601
  30. package/dist/astgraph/vendor/zod/v4/core/json-schema.js +1 -1
  31. package/dist/astgraph/vendor/zod/v4/core/package.json +7 -7
  32. package/dist/astgraph/vendor/zod/v4/core/parse.js +93 -93
  33. package/dist/astgraph/vendor/zod/v4/core/regexes.js +139 -139
  34. package/dist/astgraph/vendor/zod/v4/core/registries.js +51 -51
  35. package/dist/astgraph/vendor/zod/v4/core/schemas.js +2239 -2239
  36. package/dist/astgraph/vendor/zod/v4/core/standard-schema.js +1 -1
  37. package/dist/astgraph/vendor/zod/v4/core/to-json-schema.js +448 -448
  38. package/dist/astgraph/vendor/zod/v4/core/util.js +674 -674
  39. package/dist/astgraph/vendor/zod/v4/core/versions.js +5 -5
  40. package/dist/astgraph/vendor/zod/v4/index.js +3 -3
  41. package/dist/astgraph/vendor/zod/v4/locales/ar.js +106 -106
  42. package/dist/astgraph/vendor/zod/v4/locales/az.js +105 -105
  43. package/dist/astgraph/vendor/zod/v4/locales/be.js +156 -156
  44. package/dist/astgraph/vendor/zod/v4/locales/bg.js +120 -120
  45. package/dist/astgraph/vendor/zod/v4/locales/ca.js +107 -107
  46. package/dist/astgraph/vendor/zod/v4/locales/cs.js +111 -111
  47. package/dist/astgraph/vendor/zod/v4/locales/da.js +115 -115
  48. package/dist/astgraph/vendor/zod/v4/locales/de.js +108 -108
  49. package/dist/astgraph/vendor/zod/v4/locales/el.js +109 -109
  50. package/dist/astgraph/vendor/zod/v4/locales/en.js +113 -113
  51. package/dist/astgraph/vendor/zod/v4/locales/eo.js +109 -109
  52. package/dist/astgraph/vendor/zod/v4/locales/es.js +132 -132
  53. package/dist/astgraph/vendor/zod/v4/locales/fa.js +114 -114
  54. package/dist/astgraph/vendor/zod/v4/locales/fi.js +112 -112
  55. package/dist/astgraph/vendor/zod/v4/locales/fr-CA.js +107 -107
  56. package/dist/astgraph/vendor/zod/v4/locales/fr.js +125 -125
  57. package/dist/astgraph/vendor/zod/v4/locales/he.js +214 -214
  58. package/dist/astgraph/vendor/zod/v4/locales/hr.js +122 -122
  59. package/dist/astgraph/vendor/zod/v4/locales/hu.js +108 -108
  60. package/dist/astgraph/vendor/zod/v4/locales/hy.js +147 -147
  61. package/dist/astgraph/vendor/zod/v4/locales/id.js +106 -106
  62. package/dist/astgraph/vendor/zod/v4/locales/index.js +52 -52
  63. package/dist/astgraph/vendor/zod/v4/locales/is.js +109 -109
  64. package/dist/astgraph/vendor/zod/v4/locales/it.js +108 -108
  65. package/dist/astgraph/vendor/zod/v4/locales/ja.js +107 -107
  66. package/dist/astgraph/vendor/zod/v4/locales/ka.js +112 -112
  67. package/dist/astgraph/vendor/zod/v4/locales/kh.js +5 -5
  68. package/dist/astgraph/vendor/zod/v4/locales/km.js +110 -110
  69. package/dist/astgraph/vendor/zod/v4/locales/ko.js +111 -111
  70. package/dist/astgraph/vendor/zod/v4/locales/lt.js +203 -203
  71. package/dist/astgraph/vendor/zod/v4/locales/mk.js +109 -109
  72. package/dist/astgraph/vendor/zod/v4/locales/ms.js +107 -107
  73. package/dist/astgraph/vendor/zod/v4/locales/nl.js +110 -110
  74. package/dist/astgraph/vendor/zod/v4/locales/no.js +108 -108
  75. package/dist/astgraph/vendor/zod/v4/locales/ota.js +109 -109
  76. package/dist/astgraph/vendor/zod/v4/locales/package.json +7 -7
  77. package/dist/astgraph/vendor/zod/v4/locales/pl.js +109 -109
  78. package/dist/astgraph/vendor/zod/v4/locales/ps.js +114 -114
  79. package/dist/astgraph/vendor/zod/v4/locales/pt.js +108 -108
  80. package/dist/astgraph/vendor/zod/v4/locales/ro.js +119 -119
  81. package/dist/astgraph/vendor/zod/v4/locales/ru.js +156 -156
  82. package/dist/astgraph/vendor/zod/v4/locales/sl.js +109 -109
  83. package/dist/astgraph/vendor/zod/v4/locales/sv.js +110 -110
  84. package/dist/astgraph/vendor/zod/v4/locales/ta.js +110 -110
  85. package/dist/astgraph/vendor/zod/v4/locales/th.js +110 -110
  86. package/dist/astgraph/vendor/zod/v4/locales/tr.js +105 -105
  87. package/dist/astgraph/vendor/zod/v4/locales/ua.js +5 -5
  88. package/dist/astgraph/vendor/zod/v4/locales/uk.js +108 -108
  89. package/dist/astgraph/vendor/zod/v4/locales/ur.js +110 -110
  90. package/dist/astgraph/vendor/zod/v4/locales/uz.js +110 -110
  91. package/dist/astgraph/vendor/zod/v4/locales/vi.js +108 -108
  92. package/dist/astgraph/vendor/zod/v4/locales/yo.js +107 -107
  93. package/dist/astgraph/vendor/zod/v4/locales/zh-CN.js +109 -109
  94. package/dist/astgraph/vendor/zod/v4/locales/zh-TW.js +107 -107
  95. package/dist/astgraph/vendor/zod/v4/mini/checks.js +1 -1
  96. package/dist/astgraph/vendor/zod/v4/mini/coerce.js +22 -22
  97. package/dist/astgraph/vendor/zod/v4/mini/external.js +14 -14
  98. package/dist/astgraph/vendor/zod/v4/mini/index.js +3 -3
  99. package/dist/astgraph/vendor/zod/v4/mini/iso.js +34 -34
  100. package/dist/astgraph/vendor/zod/v4/mini/package.json +7 -7
  101. package/dist/astgraph/vendor/zod/v4/mini/parse.js +1 -1
  102. package/dist/astgraph/vendor/zod/v4/mini/schemas.js +961 -961
  103. package/dist/astgraph/vendor/zod/v4/package.json +7 -7
  104. package/dist/{chunk-7OICVL2M.js → chunk-2BF7CVWZ.js} +0 -42
  105. package/dist/{chunk-RQTYHUMK.js → chunk-3WCDZ6ZL.js} +15 -0
  106. package/dist/{chunk-Z62P4LL7.js → chunk-6MNOZ2TX.js} +8962 -8422
  107. package/dist/{chunk-VEUSROUQ.js → chunk-FOTMP4ZS.js} +1 -1
  108. package/dist/{chunk-SUSLIHXO.js → chunk-ODRWR4KK.js} +1 -1
  109. package/dist/{chunk-RPFWB45Y.js → chunk-URUVFECH.js} +1 -1
  110. package/dist/{claude-ink-6CEG2XZZ.js → claude-ink-LP32NSX5.js} +2 -3
  111. package/dist/cli-main.js +27 -11
  112. package/dist/config/load.d.ts +2 -0
  113. package/dist/desktop/main.js +1092 -541
  114. package/dist/desktop-renderer/assets/index-Bk6FYhyh.css +1 -0
  115. package/dist/desktop-renderer/assets/{index-D75ozXnZ.js → index-CqRIr-hz.js} +3 -3
  116. package/dist/desktop-renderer/assets/{interactive-terminal-CTFo4fK9.js → interactive-terminal-C4o7MNPO.js} +1 -1
  117. package/dist/desktop-renderer/index.html +2 -2
  118. package/dist/doctor.d.ts +38 -0
  119. package/dist/execution/docker.d.ts +5 -1
  120. package/dist/execution/types.d.ts +11 -0
  121. package/dist/jobs/registry.d.ts +1 -0
  122. package/dist/{load-4FWNXE6F.js → load-V2UIYYQI.js} +1 -1
  123. package/dist/sdk/index.js +4 -4
  124. package/dist/tools/shell.d.ts +17 -12
  125. package/dist/tools/types.d.ts +1 -0
  126. package/dist/ui/commands.d.ts +1 -1
  127. package/dist/ui/session.d.ts +1 -0
  128. package/dist/update/check.d.ts +20 -0
  129. package/dist/utils/semver.d.ts +3 -0
  130. package/dist/utils/spawn-executable.d.ts +2 -0
  131. package/package.json +1 -1
  132. package//346/212/200/346/234/257/346/226/271/346/241/210/346/212/245/345/221/212.md +5848 -5848
  133. package/dist/chunk-OTYFL4AG.js +0 -16
  134. package/dist/desktop-renderer/assets/index-ZZsUx916.css +0 -1
@@ -1,8 +1,8 @@
1
1
  // src/desktop/main.ts
2
2
  import { appendFileSync } from "fs";
3
3
  import { createHash as createHash28, randomUUID as randomUUID29 } from "crypto";
4
- import { readFile as readFile39, stat as stat18, writeFile as writeFile19 } from "fs/promises";
5
- import { basename as basename11, dirname as dirname25, join as join48, resolve as resolve41 } from "path";
4
+ import { readFile as readFile40, stat as stat19, writeFile as writeFile19 } from "fs/promises";
5
+ import { basename as basename12, dirname as dirname25, join as join50, resolve as resolve42 } from "path";
6
6
  import { fileURLToPath as fileURLToPath4 } from "url";
7
7
  import { app, BrowserWindow as BrowserWindow2, clipboard, dialog, ipcMain, Menu, nativeImage, Notification, shell } from "electron";
8
8
 
@@ -1990,7 +1990,7 @@ function createEmbeddedD2cAutomation(getHost, options = {}) {
1990
1990
  const ready = await candidate.executeJavaScript("document.readyState", false).catch(() => void 0);
1991
1991
  if (ready === "complete" || ready === "interactive") return candidate;
1992
1992
  }
1993
- await new Promise((resolve42) => setTimeout(resolve42, pollIntervalMs));
1993
+ await new Promise((resolve43) => setTimeout(resolve43, pollIntervalMs));
1994
1994
  }
1995
1995
  throw new Error(`D2C embedded preview did not navigate within ${navigationTimeoutMs} ms: ${url}${observedUrl === void 0 ? "" : `; last observed ${observedUrl}`}${observedFrames.length === 0 ? "; no live frames" : `; live frames ${observedFrames.join(", ")}`}`);
1996
1996
  };
@@ -2005,13 +2005,13 @@ function createEmbeddedD2cAutomation(getHost, options = {}) {
2005
2005
  while (Date.now() <= deadline) {
2006
2006
  last = await frame(baseUrl).executeJavaScript(assertionScript(step), true);
2007
2007
  if (last.passed) return last;
2008
- await new Promise((resolve42) => setTimeout(resolve42, 50));
2008
+ await new Promise((resolve43) => setTimeout(resolve43, 50));
2009
2009
  }
2010
2010
  return last;
2011
2011
  };
2012
2012
  const settleAfterAction = async (previousUrl) => {
2013
2013
  const origin = new URL(previousUrl).origin;
2014
- await new Promise((resolve42) => setTimeout(resolve42, Math.min(180, Math.max(20, visualDelayMs * 0.25))));
2014
+ await new Promise((resolve43) => setTimeout(resolve43, Math.min(180, Math.max(20, visualDelayMs * 0.25))));
2015
2015
  const deadline = Date.now() + Math.min(2e3, navigationTimeoutMs);
2016
2016
  while (Date.now() <= deadline) {
2017
2017
  const candidate = host().mainFrame.framesInSubtree.find((item) => !item.isDestroyed() && sameOrigin(item.url, origin));
@@ -2019,7 +2019,7 @@ function createEmbeddedD2cAutomation(getHost, options = {}) {
2019
2019
  const ready = await candidate.executeJavaScript("document.readyState", false).catch(() => void 0);
2020
2020
  if (ready === "complete" || ready === "interactive") return candidate;
2021
2021
  }
2022
- await new Promise((resolve42) => setTimeout(resolve42, pollIntervalMs));
2022
+ await new Promise((resolve43) => setTimeout(resolve43, pollIntervalMs));
2023
2023
  }
2024
2024
  throw new Error("D2C preview frame was unavailable after an interaction action");
2025
2025
  };
@@ -2106,7 +2106,7 @@ function createEmbeddedD2cAutomation(getHost, options = {}) {
2106
2106
  return result;
2107
2107
  },
2108
2108
  settle: async () => {
2109
- await new Promise((resolve42) => setTimeout(resolve42, 150));
2109
+ await new Promise((resolve43) => setTimeout(resolve43, 150));
2110
2110
  requests = await frame(activeUrl).executeJavaScript(`performance.getEntriesByType("resource").filter((entry) => {
2111
2111
  try { return ["fetch", "xmlhttprequest"].includes(entry.initiatorType) && new URL(entry.name).origin === ${JSON.stringify(mockOrigin)}; } catch { return false; }
2112
2112
  }).length`, false).catch(() => 0);
@@ -2192,7 +2192,7 @@ function responseText(protocol, value) {
2192
2192
  }
2193
2193
  throw new Error("D2C Judge response did not contain text output");
2194
2194
  }
2195
- function createD2cJudgeClient(fetcher = globalThis.fetch, retryDelay = (ms) => new Promise((resolve42) => setTimeout(resolve42, ms))) {
2195
+ function createD2cJudgeClient(fetcher = globalThis.fetch, retryDelay = (ms) => new Promise((resolve43) => setTimeout(resolve43, ms))) {
2196
2196
  const request = async (config, prompt, images, maxTokens) => {
2197
2197
  const openAi = config.protocol === "openai-compatible";
2198
2198
  const imageContent = openAi ? images.map((image) => ({ type: "image_url", image_url: { url: dataUrl(image), detail: "high" } })) : images.map((image) => ({ type: "image", source: { type: "base64", media_type: "image/png", data: image.toString("base64") } }));
@@ -2551,7 +2551,7 @@ import { z as z7 } from "zod";
2551
2551
  import { Worker } from "worker_threads";
2552
2552
  function comparePngsInWorker(left, right, signal) {
2553
2553
  signal?.throwIfAborted();
2554
- return new Promise((resolve42, reject) => {
2554
+ return new Promise((resolve43, reject) => {
2555
2555
  const worker = new Worker(new URL("./pixel-worker.js", import.meta.url));
2556
2556
  let settled = false;
2557
2557
  const finish = (callback) => {
@@ -2569,7 +2569,7 @@ function comparePngsInWorker(left, right, signal) {
2569
2569
  finish(() => reject(new Error(value.error ?? "D2C pixel worker failed")));
2570
2570
  return;
2571
2571
  }
2572
- finish(() => resolve42({ ...value.result, heatmapPng: Buffer.from(value.result.heatmapPng) }));
2572
+ finish(() => resolve43({ ...value.result, heatmapPng: Buffer.from(value.result.heatmapPng) }));
2573
2573
  });
2574
2574
  worker.once("error", (error) => finish(() => reject(error)));
2575
2575
  worker.once("exit", (code) => {
@@ -4225,11 +4225,11 @@ ${summarizeReport(report2)}`).join("\n\n")
4225
4225
  }
4226
4226
 
4227
4227
  // src/production.ts
4228
- import { readFile as readFile27 } from "fs/promises";
4228
+ import { readFile as readFile28 } from "fs/promises";
4229
4229
  import { mkdirSync as mkdirSync4, readFileSync as readFileSync3, writeFileSync as writeFileSync2 } from "fs";
4230
4230
  import { createHash as createHash21, randomUUID as randomUUID25 } from "crypto";
4231
- import { homedir as homedir2, release as osRelease, tmpdir as tmpdir4, version as osVersion } from "os";
4232
- import { basename as basename8, dirname as dirname24, join as join35, relative as relative23, resolve as resolve34, sep as sep23 } from "path";
4231
+ import { homedir as homedir3, release as osRelease, tmpdir as tmpdir4, version as osVersion } from "os";
4232
+ import { basename as basename9, dirname as dirname24, join as join37, relative as relative24, resolve as resolve35, sep as sep24 } from "path";
4233
4233
 
4234
4234
  // src/agent/subagents.ts
4235
4235
  import { z as z10 } from "zod";
@@ -4442,12 +4442,12 @@ function hasCycle(nodes) {
4442
4442
 
4443
4443
  // src/utils/async.ts
4444
4444
  function withTimeout(promise, ms) {
4445
- return new Promise((resolve42, reject) => {
4445
+ return new Promise((resolve43, reject) => {
4446
4446
  let settled = false;
4447
4447
  const timer = setTimeout(() => {
4448
4448
  if (settled) return;
4449
4449
  settled = true;
4450
- resolve42({ timedOut: true, value: void 0 });
4450
+ resolve43({ timedOut: true, value: void 0 });
4451
4451
  }, ms);
4452
4452
  timer.unref?.();
4453
4453
  promise.then(
@@ -4455,7 +4455,7 @@ function withTimeout(promise, ms) {
4455
4455
  if (!settled) {
4456
4456
  settled = true;
4457
4457
  clearTimeout(timer);
4458
- resolve42({ timedOut: false, value });
4458
+ resolve43({ timedOut: false, value });
4459
4459
  }
4460
4460
  },
4461
4461
  (error) => {
@@ -4470,13 +4470,13 @@ function withTimeout(promise, ms) {
4470
4470
  }
4471
4471
  function awaitWithSignal3(promise, signal) {
4472
4472
  signal.throwIfAborted();
4473
- return new Promise((resolve42, reject) => {
4473
+ return new Promise((resolve43, reject) => {
4474
4474
  const onAbort = () => reject(signal.reason);
4475
4475
  signal.addEventListener("abort", onAbort, { once: true });
4476
4476
  promise.then(
4477
4477
  (value) => {
4478
4478
  signal.removeEventListener("abort", onAbort);
4479
- resolve42(value);
4479
+ resolve43(value);
4480
4480
  },
4481
4481
  (error) => {
4482
4482
  signal.removeEventListener("abort", onAbort);
@@ -5000,7 +5000,7 @@ async function loadConfig(options) {
5000
5000
  process.env[key] = value;
5001
5001
  }
5002
5002
  }
5003
- await seedGlobalEnv(globalEnvPath);
5003
+ if (options.seedGlobalEnv !== false) await seedGlobalEnv(globalEnvPath);
5004
5004
  const projectRead = await readJsonIfPresent(projectPath);
5005
5005
  const projectConfig = projectRead?.value;
5006
5006
  if (projectRead) sources.push(projectRead.source);
@@ -6525,10 +6525,10 @@ function sanitize(message2, secrets) {
6525
6525
  }
6526
6526
  function waitForRetry(delayMs, signal) {
6527
6527
  if (signal?.aborted) return Promise.reject(signal.reason);
6528
- return new Promise((resolve42, reject) => {
6528
+ return new Promise((resolve43, reject) => {
6529
6529
  const timer = setTimeout(() => {
6530
6530
  signal?.removeEventListener("abort", onAbort);
6531
- resolve42();
6531
+ resolve43();
6532
6532
  }, delayMs);
6533
6533
  const onAbort = () => {
6534
6534
  clearTimeout(timer);
@@ -7166,10 +7166,10 @@ function serializeToolInput(input) {
7166
7166
  }
7167
7167
  function waitForRetry2(delayMs, signal) {
7168
7168
  if (signal?.aborted) return Promise.reject(signal.reason);
7169
- return new Promise((resolve42, reject) => {
7169
+ return new Promise((resolve43, reject) => {
7170
7170
  const timer = setTimeout(() => {
7171
7171
  signal?.removeEventListener("abort", onAbort);
7172
- resolve42();
7172
+ resolve43();
7173
7173
  }, delayMs);
7174
7174
  const onAbort = () => {
7175
7175
  clearTimeout(timer);
@@ -8026,8 +8026,8 @@ var ToolRuntime = class {
8026
8026
  const limited = await this.#limitOutput(tool, content);
8027
8027
  return typeof limited === "string" ? limited : serializeToolOutput(limited);
8028
8028
  }
8029
- async #fail(toolCallId, tool, input, agent, code, errorMessage3) {
8030
- const result = { ok: false, error: { code, message: errorMessage3 } };
8029
+ async #fail(toolCallId, tool, input, agent, code, errorMessage4) {
8030
+ const result = { ok: false, error: { code, message: errorMessage4 } };
8031
8031
  try {
8032
8032
  await this.#hooks.emit({
8033
8033
  version: 1,
@@ -8715,16 +8715,16 @@ var HookDecisionSchema = z16.object({
8715
8715
  });
8716
8716
 
8717
8717
  // src/utils/spawn-executable.ts
8718
- import { existsSync as existsSync3, statSync as statSync2 } from "fs";
8719
- import { extname as extname2, isAbsolute as isAbsolute6, resolve as resolve9 } from "path";
8720
- var WINDOWS_EXECUTABLE = /\.(?:com|exe)$/iu;
8718
+ import { accessSync, constants as constants3, existsSync as existsSync3, statSync as statSync2 } from "fs";
8719
+ import { delimiter, extname as extname2, isAbsolute as isAbsolute6, resolve as resolve9 } from "path";
8720
+ var WINDOWS_BATCH = /\.(?:bat|cmd)$/iu;
8721
8721
  var WINDOWS_CMD_SHIM = /node_modules[\\/].bin[\\/][^\\/]+\.cmd$/iu;
8722
8722
  var CMD_META = /([()\][%!^"`<>&|;, *?])/gu;
8723
8723
  function prepareSpawnInvocation(command, args, options = {}) {
8724
8724
  if ((options.platform ?? process.platform) !== "win32") return { command, args: [...args] };
8725
8725
  const env = options.env ?? process.env;
8726
- const commandFile = resolveWindowsCommand(command, options.cwd, env);
8727
- if (commandFile === void 0 || WINDOWS_EXECUTABLE.test(commandFile)) {
8726
+ const commandFile = resolveExecutablePath(command, options);
8727
+ if (commandFile === void 0 || !WINDOWS_BATCH.test(commandFile)) {
8728
8728
  return { command: commandFile ?? command, args: [...args] };
8729
8729
  }
8730
8730
  const doubleEscape = WINDOWS_CMD_SHIM.test(commandFile);
@@ -8735,11 +8735,22 @@ function prepareSpawnInvocation(command, args, options = {}) {
8735
8735
  windowsVerbatimArguments: true
8736
8736
  };
8737
8737
  }
8738
+ function resolveExecutablePath(command, options = {}) {
8739
+ const platform = options.platform ?? process.platform;
8740
+ const env = options.env ?? process.env;
8741
+ if (platform === "win32") return resolveWindowsCommand(command, options.cwd, env);
8742
+ const hasPath = isAbsolute6(command) || command.includes("/");
8743
+ const bases = hasPath ? [isAbsolute6(command) ? command : resolve9(options.cwd ?? process.cwd(), command)] : (env.PATH ?? "").split(delimiter).filter(Boolean).map((entry2) => resolve9(entry2, command));
8744
+ return bases.find(isExecutableFile);
8745
+ }
8738
8746
  function resolveWindowsCommand(command, cwd, env) {
8739
8747
  const pathValue = environmentValue(env, "PATH") ?? "";
8740
8748
  const pathExt = (environmentValue(env, "PATHEXT") ?? ".COM;.EXE;.BAT;.CMD").split(";").filter(Boolean);
8741
8749
  const hasPath = isAbsolute6(command) || /[\\/]/u.test(command);
8742
- const bases = hasPath ? [isAbsolute6(command) ? command : resolve9(cwd ?? process.cwd(), command)] : pathValue.split(";").filter(Boolean).map((entry2) => resolve9(unquote(entry2), command));
8750
+ const bases = hasPath ? [isAbsolute6(command) ? command : resolve9(cwd ?? process.cwd(), command)] : [
8751
+ resolve9(cwd ?? process.cwd(), command),
8752
+ ...pathValue.split(";").filter(Boolean).map((entry2) => resolve9(unquote(entry2), command))
8753
+ ];
8743
8754
  for (const base of bases) {
8744
8755
  const candidates = extname2(base).length > 0 ? [base] : [base, ...pathExt.map((extension2) => `${base}${extension2}`)];
8745
8756
  for (const candidate of candidates) {
@@ -8763,6 +8774,15 @@ function isRegularFile(path) {
8763
8774
  return false;
8764
8775
  }
8765
8776
  }
8777
+ function isExecutableFile(path) {
8778
+ try {
8779
+ if (!existsSync3(path) || !statSync2(path).isFile()) return false;
8780
+ accessSync(path, constants3.X_OK);
8781
+ return true;
8782
+ } catch {
8783
+ return false;
8784
+ }
8785
+ }
8766
8786
  function escapeCmdCommand(value) {
8767
8787
  return value.replace(CMD_META, "^$1");
8768
8788
  }
@@ -8878,7 +8898,7 @@ function decoratePayload(payload, envelope) {
8878
8898
  return envelope === void 0 ? payload : { ...payload, ...envelope };
8879
8899
  }
8880
8900
  function runShellHandler(descriptor, event, signal) {
8881
- return new Promise((resolve42, reject) => {
8901
+ return new Promise((resolve43, reject) => {
8882
8902
  const env = { ...process.env, ...descriptor.env };
8883
8903
  const invocation = prepareSpawnInvocation(descriptor.command, descriptor.args ?? [], { env });
8884
8904
  const child = spawn(invocation.command, invocation.args, {
@@ -8924,7 +8944,7 @@ function runShellHandler(descriptor, event, signal) {
8924
8944
  return;
8925
8945
  }
8926
8946
  try {
8927
- resolve42(HookDecisionSchema.parse(JSON.parse(stdout)));
8947
+ resolve43(HookDecisionSchema.parse(JSON.parse(stdout)));
8928
8948
  } catch (error) {
8929
8949
  reject(error);
8930
8950
  }
@@ -8963,7 +8983,7 @@ function zodRecord(payload) {
8963
8983
  // src/utils/execFileNoThrow.ts
8964
8984
  import { execFile } from "child_process";
8965
8985
  function execFileNoThrow(file, args, options = {}) {
8966
- return new Promise((resolve42) => {
8986
+ return new Promise((resolve43) => {
8967
8987
  const child = execFile(file, args, {
8968
8988
  timeout: options.timeout,
8969
8989
  cwd: options.useCwd === false ? void 0 : process.cwd(),
@@ -8971,7 +8991,7 @@ function execFileNoThrow(file, args, options = {}) {
8971
8991
  encoding: "utf8",
8972
8992
  windowsHide: true
8973
8993
  }, (error, stdout, stderr) => {
8974
- resolve42({
8994
+ resolve43({
8975
8995
  stdout,
8976
8996
  stderr,
8977
8997
  code: typeof error?.code === "number" ? error.code : error ? 1 : 0,
@@ -9143,14 +9163,14 @@ async function createIslandControlServer(options) {
9143
9163
  void dispatch(line, socket, options, token);
9144
9164
  });
9145
9165
  });
9146
- await new Promise((resolve42, reject) => {
9166
+ await new Promise((resolve43, reject) => {
9147
9167
  const onError = (error) => {
9148
9168
  server.removeListener("listening", onListening);
9149
9169
  reject(error);
9150
9170
  };
9151
9171
  const onListening = () => {
9152
9172
  server.removeListener("error", onError);
9153
- resolve42();
9173
+ resolve43();
9154
9174
  };
9155
9175
  server.once("error", onError);
9156
9176
  server.once("listening", onListening);
@@ -9221,11 +9241,11 @@ function write(socket, payload) {
9221
9241
  }
9222
9242
  }
9223
9243
  function closeServer(server) {
9224
- return new Promise((resolve42) => {
9244
+ return new Promise((resolve43) => {
9225
9245
  try {
9226
- server.close(() => resolve42());
9246
+ server.close(() => resolve43());
9227
9247
  } catch {
9228
- resolve42();
9248
+ resolve43();
9229
9249
  }
9230
9250
  });
9231
9251
  }
@@ -10117,8 +10137,8 @@ import { dirname as dirname8, join as join11 } from "path";
10117
10137
  function normalizeError(message2) {
10118
10138
  return String(message2 ?? "").replace(/\s+/g, " ").replace(/"[^"]*"/g, '"\u2026"').replace(/`[^`]*`/g, '"\u2026"').trim().slice(0, 160);
10119
10139
  }
10120
- function fingerprint(tool, errorCode, error) {
10121
- return createHash9("sha1").update(`${tool}::${errorCode ?? ""}::${normalizeError(error)}`).digest("hex").slice(0, 12);
10140
+ function fingerprint(tool, errorCode3, error) {
10141
+ return createHash9("sha1").update(`${tool}::${errorCode3 ?? ""}::${normalizeError(error)}`).digest("hex").slice(0, 12);
10122
10142
  }
10123
10143
  function argKeys(args) {
10124
10144
  if (args === null || typeof args !== "object") return [];
@@ -10410,7 +10430,7 @@ var USAGE = [
10410
10430
  " clear reset signals, done markers, and verified markers"
10411
10431
  ].join("\n");
10412
10432
  function runCommand(command, cwd, timeoutMs) {
10413
- return new Promise((resolve42) => {
10433
+ return new Promise((resolve43) => {
10414
10434
  const isWindows = process.platform === "win32";
10415
10435
  const child = spawn2(isWindows ? "cmd.exe" : "/bin/sh", isWindows ? ["/d", "/s", "/c", command] : ["-c", command], {
10416
10436
  cwd,
@@ -10420,7 +10440,7 @@ function runCommand(command, cwd, timeoutMs) {
10420
10440
  let stderr = "";
10421
10441
  const timer = setTimeout(() => {
10422
10442
  child.kill();
10423
- resolve42({ ok: false, stdout, stderr: `${stderr}
10443
+ resolve43({ ok: false, stdout, stderr: `${stderr}
10424
10444
  [killed: exceeded ${timeoutMs}ms]`, code: "timeout" });
10425
10445
  }, timeoutMs);
10426
10446
  child.stdout.on("data", (chunk) => {
@@ -10431,12 +10451,12 @@ function runCommand(command, cwd, timeoutMs) {
10431
10451
  });
10432
10452
  child.on("error", (error) => {
10433
10453
  clearTimeout(timer);
10434
- resolve42({ ok: false, stdout, stderr: `${stderr}
10454
+ resolve43({ ok: false, stdout, stderr: `${stderr}
10435
10455
  ${error.message}`, code: "spawn" });
10436
10456
  });
10437
10457
  child.on("close", (code) => {
10438
10458
  clearTimeout(timer);
10439
- resolve42({ ok: code === 0, stdout, stderr, code: code ?? "unknown" });
10459
+ resolve43({ ok: code === 0, stdout, stderr, code: code ?? "unknown" });
10440
10460
  });
10441
10461
  });
10442
10462
  }
@@ -10513,12 +10533,12 @@ function createEvolveService(options) {
10513
10533
  logger.warn(`prompt section failed \u2014 ${error instanceof Error ? error.message : String(error)}`);
10514
10534
  }
10515
10535
  }
10516
- async function captureFailure(tool, errorCode, message2, args) {
10536
+ async function captureFailure(tool, errorCode3, message2, args) {
10517
10537
  toolErrors += 1;
10518
10538
  runToolErrors[tool] = (runToolErrors[tool] ?? 0) + 1;
10519
10539
  const { record: record3 } = await store.recordSignal({
10520
10540
  tool,
10521
- ...errorCode === void 0 ? {} : { errorCode },
10541
+ ...errorCode3 === void 0 ? {} : { errorCode: errorCode3 },
10522
10542
  error: message2,
10523
10543
  args
10524
10544
  });
@@ -10559,8 +10579,12 @@ function createEvolveService(options) {
10559
10579
  try {
10560
10580
  const stderr = typeof shell2.stderr === "string" ? shell2.stderr : "";
10561
10581
  const stdout = typeof shell2.stdout === "string" ? shell2.stdout : "";
10562
- const message2 = stderr.trim().slice(0, 300) || stdout.trim().slice(0, 300) || (exitCode === void 0 ? "shell timed out" : `exit code ${exitCode}`);
10563
- await captureFailure(tool, exitCode === void 0 ? "shell_exit_timeout" : `shell_exit_${exitCode}`, message2, payload.input);
10582
+ const diagnostic = shell2.diagnostic !== null && typeof shell2.diagnostic === "object" ? shell2.diagnostic : void 0;
10583
+ const diagnosticMessage = typeof diagnostic?.message === "string" ? diagnostic.message : "";
10584
+ const diagnosticKind = typeof diagnostic?.kind === "string" ? diagnostic.kind : void 0;
10585
+ const message2 = diagnosticMessage.trim().slice(0, 300) || stderr.trim().slice(0, 300) || stdout.trim().slice(0, 300) || (exitCode === void 0 ? "shell timed out" : `exit code ${exitCode}`);
10586
+ const errorCode3 = diagnosticKind === void 0 ? exitCode === void 0 ? "shell_exit_timeout" : `shell_exit_${exitCode}` : `shell_${diagnosticKind}`;
10587
+ await captureFailure(tool, errorCode3, message2, payload.input);
10564
10588
  } catch (error) {
10565
10589
  logger.warn(`shell capture failed \u2014 ${error instanceof Error ? error.message : String(error)}`);
10566
10590
  }
@@ -12412,7 +12436,7 @@ function formatPriorGaps(gaps) {
12412
12436
  }
12413
12437
 
12414
12438
  // src/goal/store.ts
12415
- import { constants as constants3 } from "fs";
12439
+ import { constants as constants4 } from "fs";
12416
12440
  import { randomUUID as randomUUID11 } from "crypto";
12417
12441
  import { lstat as lstat5, mkdir as mkdir11, open as open5, rename as rename4, rm as rm6 } from "fs/promises";
12418
12442
  import { dirname as dirname10, join as join16, relative as relative9, resolve as resolve12, sep as sep9 } from "path";
@@ -12440,7 +12464,7 @@ var GoalStore = class {
12440
12464
  const temporary = join16(this.#root, `.${state.id}.${process.pid}.${randomUUID11()}.tmp`);
12441
12465
  let handle;
12442
12466
  try {
12443
- handle = await open5(temporary, constants3.O_CREAT | constants3.O_EXCL | constants3.O_WRONLY, 384);
12467
+ handle = await open5(temporary, constants4.O_CREAT | constants4.O_EXCL | constants4.O_WRONLY, 384);
12444
12468
  await handle.writeFile(body, "utf8");
12445
12469
  await handle.sync();
12446
12470
  await handle.close();
@@ -12547,7 +12571,7 @@ async function ensureWorktreeDirectoryIgnored(root) {
12547
12571
  }
12548
12572
 
12549
12573
  // src/loop/store.ts
12550
- import { constants as constants4 } from "fs";
12574
+ import { constants as constants5 } from "fs";
12551
12575
  import { lstat as lstat6, mkdir as mkdir13, open as open6, readFile as readFile12, realpath as realpath8, rename as rename5, rm as rm7, stat as stat6 } from "fs/promises";
12552
12576
  import { dirname as dirname11, join as join18, relative as relative10, resolve as resolve14, sep as sep10 } from "path";
12553
12577
  import { randomUUID as randomUUID12 } from "crypto";
@@ -12584,7 +12608,7 @@ var LoopStore = class {
12584
12608
  const temporary = join18(directory, `.state.${process.pid}.${randomUUID12()}.tmp`);
12585
12609
  let handle;
12586
12610
  try {
12587
- handle = await open6(temporary, constants4.O_CREAT | constants4.O_EXCL | constants4.O_WRONLY, 384);
12611
+ handle = await open6(temporary, constants5.O_CREAT | constants5.O_EXCL | constants5.O_WRONLY, 384);
12588
12612
  await handle.writeFile(body, "utf8");
12589
12613
  await handle.sync();
12590
12614
  await handle.close();
@@ -12618,7 +12642,7 @@ var LoopStore = class {
12618
12642
  const next = previous.catch(() => void 0).then(async () => {
12619
12643
  const directory = await this.#prepare(event.loopId);
12620
12644
  const path = join18(directory, "events.jsonl");
12621
- const handle = await open6(path, constants4.O_CREAT | constants4.O_APPEND | constants4.O_WRONLY, 384);
12645
+ const handle = await open6(path, constants5.O_CREAT | constants5.O_APPEND | constants5.O_WRONLY, 384);
12622
12646
  try {
12623
12647
  await handle.writeFile(`${JSON.stringify(event)}
12624
12648
  `, "utf8");
@@ -12691,11 +12715,12 @@ function isCode5(error, code) {
12691
12715
 
12692
12716
  // src/loop/verifier.ts
12693
12717
  import { readFile as readFile13 } from "fs/promises";
12694
- import { join as join19 } from "path";
12718
+ import { join as join20 } from "path";
12695
12719
 
12696
12720
  // src/tools/shell.ts
12697
- import { spawn as spawn3, spawnSync } from "child_process";
12698
- import { isAbsolute as isAbsolute9, relative as relative11, resolve as resolve15, sep as sep11 } from "path";
12721
+ import { spawn as spawn3 } from "child_process";
12722
+ import { existsSync as existsSync4 } from "fs";
12723
+ import { basename as basename5, delimiter as delimiter2, isAbsolute as isAbsolute9, join as join19, relative as relative11, resolve as resolve15, sep as sep11 } from "path";
12699
12724
  import { z as z23 } from "zod";
12700
12725
 
12701
12726
  // src/tools/jobs.ts
@@ -12784,8 +12809,8 @@ var TERMINATION_GRACE_MS = 250;
12784
12809
  var TERMINATION_FAILURE_MS = 5e3;
12785
12810
  var STREAM_CLOSE_GRACE_MS = 500;
12786
12811
  var ShellInput = z23.object({
12787
- command: z23.string().min(1),
12788
- args: z23.array(z23.string()),
12812
+ command: z23.string().trim().min(1).refine((value) => !value.includes("\0"), "command cannot contain a null byte").describe("Executable name or path. Put each argument in args; use a shell expression here only for pipes, redirects, or other shell syntax."),
12813
+ args: z23.array(z23.string().refine((value) => !value.includes("\0"), "argument cannot contain a null byte").describe("One logical argument without surrounding shell quotes.")),
12789
12814
  cwd: z23.string().min(1).nullable().optional(),
12790
12815
  timeoutMs: z23.coerce.number().int().positive().max(864e5).nullable().optional(),
12791
12816
  // Accepts booleans and their string forms (weak-typed models emit "true");
@@ -12793,23 +12818,39 @@ var ShellInput = z23.object({
12793
12818
  background: z23.union([z23.boolean(), z23.string().refine((value) => value === "true" || value === "false")]).optional()
12794
12819
  });
12795
12820
  function normalizeShellCommand(input) {
12796
- const { command, args } = input;
12797
- if (!/\s/.test(command)) return { command, args: [...args] };
12821
+ const command = unquoteToken(input.command.trim());
12822
+ const args = [...input.args];
12823
+ const executable = basename5(command).toLowerCase();
12824
+ if ((executable === "cmd" || executable === "cmd.exe") && /^\/[ck]$/iu.test(args[0] ?? "")) {
12825
+ return { command, args: [args[0], ...args.slice(1).map(unquoteToken)] };
12826
+ }
12827
+ if ((executable === "pwsh" || executable === "pwsh.exe" || executable === "powershell" || executable === "powershell.exe") && /^(?:-c|-command)$/iu.test(args[0] ?? "") && args.length > 1) {
12828
+ return { command, args: [args[0], ...args.slice(1).map(unquoteToken)] };
12829
+ }
12830
+ if (/^(?:sh|bash|zsh|fish)$/iu.test(executable) && args[0] === "-c" && args[1] !== void 0) {
12831
+ return { command, args: [args[0], unquoteToken(args[1]), ...args.slice(2)] };
12832
+ }
12833
+ if (!/\s/u.test(command) || containsShellSyntax(command)) return { command, args };
12798
12834
  const tokens2 = tokenizeCommandLine(command);
12799
- if (tokens2.length <= 1) return { command, args: [...args] };
12835
+ if (tokens2.length <= 1) return { command, args };
12800
12836
  const head = unquoteToken(tokens2[0]);
12801
- if (head.includes("/") || head.includes("\\")) return { command, args: [...args] };
12837
+ if (head.includes("/") || head.includes("\\")) return { command, args };
12802
12838
  return { command: head, args: [...tokens2.slice(1).map(unquoteToken), ...args] };
12803
12839
  }
12804
12840
  function tokenizeCommandLine(command) {
12805
12841
  const tokens2 = [];
12806
12842
  let current = "";
12807
- let inQuote = false;
12808
- for (const char of command) {
12809
- if (char === '"') {
12810
- inQuote = !inQuote;
12843
+ let quote;
12844
+ for (let index = 0; index < command.length; index += 1) {
12845
+ const char = command[index];
12846
+ if (char === "\\" && quote === '"' && command[index + 1] !== void 0) {
12847
+ current += `${char}${command[index + 1]}`;
12848
+ index += 1;
12849
+ } else if (char === '"' || char === "'") {
12850
+ if (quote === void 0) quote = char;
12851
+ else if (quote === char) quote = void 0;
12811
12852
  current += char;
12812
- } else if (char === " " && !inQuote) {
12853
+ } else if (/\s/u.test(char) && quote === void 0) {
12813
12854
  if (current !== "") {
12814
12855
  tokens2.push(current);
12815
12856
  current = "";
@@ -12822,33 +12863,190 @@ function tokenizeCommandLine(command) {
12822
12863
  return tokens2;
12823
12864
  }
12824
12865
  function unquoteToken(token) {
12825
- return token.length >= 2 && token.startsWith('"') && token.endsWith('"') ? token.slice(1, -1) : token;
12866
+ const trimmed = token.trim();
12867
+ if (trimmed.length < 2) return trimmed;
12868
+ const first = trimmed[0];
12869
+ return (first === '"' || first === "'") && trimmed.endsWith(first) ? trimmed.slice(1, -1) : trimmed;
12870
+ }
12871
+ function serializeCmdCommand(args) {
12872
+ return args.map((arg) => {
12873
+ const value = unquoteToken(arg);
12874
+ return /[\s&|<>^()]/u.test(value) ? `"${value.replace(/"/gu, '""')}"` : value;
12875
+ }).join(" ");
12876
+ }
12877
+ function serializeCmdPayload(args) {
12878
+ if (args.length === 1) return unquoteToken(args[0]);
12879
+ return args.map((arg) => /^(?:&&|\|\||[|&<>])$/u.test(arg) ? arg : serializeCmdCommand([arg])).join(" ");
12880
+ }
12881
+ function containsShellSyntax(command, platform = process.platform) {
12882
+ let quote;
12883
+ for (let index = 0; index < command.length; index += 1) {
12884
+ const char = command[index];
12885
+ if (quote !== void 0) {
12886
+ if (char === quote) quote = void 0;
12887
+ else if (char === "\\" && quote === '"') index += 1;
12888
+ else if (char === "$" && quote === '"') return true;
12889
+ continue;
12890
+ }
12891
+ if (char === "\\" && platform !== "win32") {
12892
+ index += 1;
12893
+ continue;
12894
+ }
12895
+ if (char === '"' || char === "'") {
12896
+ quote = char;
12897
+ continue;
12898
+ }
12899
+ if (/[|&;<>`$*?()\n\r]/u.test(char)) return true;
12900
+ if (char === "[" && command.indexOf("]", index + 1) > index) return true;
12901
+ if (char === "~" && (index === 0 || /\s/u.test(command[index - 1])) && /[\\/]/u.test(command[index + 1] ?? "")) return true;
12902
+ if (platform === "win32" && char === "%" && /^%[A-Za-z_][A-Za-z0-9_]*%/u.test(command.slice(index))) return true;
12903
+ }
12904
+ return quote !== void 0;
12826
12905
  }
12827
- var cachedWindowsShell;
12828
- function hasExecutable(name) {
12829
- try {
12830
- return spawnSync("where", [name], { stdio: "ignore", windowsHide: true }).status === 0;
12831
- } catch {
12832
- return false;
12906
+ function hasUnmatchedShellQuote(command) {
12907
+ let quote;
12908
+ for (let index = 0; index < command.length; index += 1) {
12909
+ const char = command[index];
12910
+ if (char === "\\" && quote !== "'") {
12911
+ index += 1;
12912
+ continue;
12913
+ }
12914
+ if (char !== '"' && char !== "'") continue;
12915
+ if (quote === void 0) quote = char;
12916
+ else if (quote === char) quote = void 0;
12833
12917
  }
12918
+ return quote !== void 0;
12834
12919
  }
12835
- function detectWindowsShell() {
12836
- if (cachedWindowsShell !== void 0) return cachedWindowsShell;
12837
- cachedWindowsShell = hasExecutable("pwsh") ? "pwsh" : hasExecutable("powershell") ? "powershell" : "cmd";
12838
- return cachedWindowsShell;
12920
+ function resolveRuntimeShell(platform = process.platform, environment = process.env) {
12921
+ if (platform === "win32") {
12922
+ for (const candidate of ["pwsh", "powershell"]) {
12923
+ const command2 = resolveExecutablePath(candidate, { platform: "win32", env: environment });
12924
+ if (command2 !== void 0) return { kind: candidate, command: command2 };
12925
+ }
12926
+ const configured = environment.ComSpec ?? environment.COMSPEC ?? "cmd.exe";
12927
+ const command = resolveExecutablePath(configured, { platform: "win32", env: environment });
12928
+ return command === void 0 ? void 0 : { kind: "cmd", command };
12929
+ }
12930
+ for (const candidate of [environment.SHELL, "/bin/sh"]) {
12931
+ if (!candidate) continue;
12932
+ const command = resolveExecutablePath(candidate, { platform, env: environment });
12933
+ if (command !== void 0) return { kind: "posix", command };
12934
+ }
12935
+ return void 0;
12839
12936
  }
12840
12937
  function psQuote(token) {
12841
12938
  return `'${token.replace(/'/g, "''")}'`;
12842
12939
  }
12843
- function buildWindowsCommandLine(kind, commandLine, command, args) {
12844
- if (kind === "cmd") return `chcp 65001>nul & ${commandLine}`;
12845
- const executable = kind === "pwsh" ? "pwsh" : "powershell";
12846
- const script = `& ${psQuote(command)}${args.length > 0 ? ` ${args.map(psQuote).join(" ")}` : ""}; exit $LASTEXITCODE`;
12940
+ function shellInvocation(input, cwd, environment) {
12941
+ const resolved = resolveExecutablePath(input.command, { cwd, env: environment });
12942
+ if (resolved !== void 0) {
12943
+ const executable = basename5(resolved).toLowerCase();
12944
+ if (process.platform === "win32" && (executable === "cmd" || executable === "cmd.exe")) {
12945
+ const commandFlag = input.args.findIndex((arg) => /^\/[ck]$/iu.test(arg));
12946
+ if (commandFlag >= 0 && input.args[commandFlag + 1] !== void 0) {
12947
+ const payload = serializeCmdPayload(input.args.slice(commandFlag + 1));
12948
+ return {
12949
+ command: resolved,
12950
+ args: [...input.args.slice(0, commandFlag + 1), `chcp 65001>nul & ${payload}`]
12951
+ };
12952
+ }
12953
+ }
12954
+ return prepareSpawnInvocation(resolved, input.args, { cwd, env: environment });
12955
+ }
12956
+ const isScript = containsShellSyntax(input.command);
12957
+ if (!isScript && (isAbsolute9(input.command) || /[\\/]/u.test(input.command))) {
12958
+ return { command: input.command, args: [...input.args] };
12959
+ }
12960
+ const shell2 = resolveRuntimeShell(process.platform, environment);
12961
+ if (shell2 === void 0) return { command: input.command, args: [...input.args] };
12962
+ const script = isScript ? appendShellArguments(shell2, input.command, input.args) : structuredShellScript(shell2, input.command, input.args);
12963
+ if (shell2.kind === "posix") return { command: shell2.command, args: ["-c", script] };
12964
+ if (shell2.kind === "cmd") return {
12965
+ command: shell2.command,
12966
+ args: ["/d", "/s", "/c", `chcp 65001>nul & ${script}`]
12967
+ };
12847
12968
  const encoded = Buffer.from(script, "utf16le").toString("base64");
12848
- return `chcp 65001>nul & ${executable} -NoLogo -NoProfile -NonInteractive -ExecutionPolicy Bypass -EncodedCommand ${encoded}`;
12969
+ return {
12970
+ command: shell2.command,
12971
+ args: ["-NoLogo", "-NoProfile", "-NonInteractive", "-ExecutionPolicy", "Bypass", "-EncodedCommand", encoded]
12972
+ };
12973
+ }
12974
+ function appendShellArguments(shell2, script, args) {
12975
+ if (args.length === 0) return script;
12976
+ if (shell2.kind === "posix") return `${script} ${args.map(posixQuote).join(" ")}`;
12977
+ if (shell2.kind === "cmd") return `${script} ${serializeCmdCommand(args)}`;
12978
+ return `${script} ${args.map(psQuote).join(" ")}`;
12979
+ }
12980
+ var POSIX_SHELL_BUILTINS = /* @__PURE__ */ new Set([".", ":", "alias", "cd", "command", "eval", "exec", "export", "read", "set", "source", "trap", "type", "ulimit", "umask", "unalias", "unset", "wait"]);
12981
+ function executionEnvironmentCommand(environment, command, args) {
12982
+ if (environment.kind !== "docker") return { command, args: [...args] };
12983
+ const script = containsShellSyntax(command, "linux") ? appendShellArguments({ kind: "posix", command: "/bin/sh" }, command, args) : POSIX_SHELL_BUILTINS.has(command) ? structuredShellScript({ kind: "posix", command: "/bin/sh" }, command, args) : void 0;
12984
+ return script === void 0 ? { command, args: [...args] } : { command: "/bin/sh", args: ["-c", script] };
12985
+ }
12986
+ function structuredShellScript(shell2, command, args) {
12987
+ if (shell2.kind === "posix") return [command, ...args].map(posixQuote).join(" ");
12988
+ if (shell2.kind === "cmd") return serializeCmdCommand([command, ...args]);
12989
+ const invocation = `& ${psQuote(command)}${args.length > 0 ? ` ${args.map(psQuote).join(" ")}` : ""}`;
12990
+ return `$ErrorActionPreference='Stop'; try { ${invocation}; if ($null -ne $LASTEXITCODE) { exit $LASTEXITCODE } else { exit 0 } } catch { [Console]::Error.WriteLine($_.Exception.Message); exit 127 }`;
12991
+ }
12992
+ function posixQuote(value) {
12993
+ return `'${value.replace(/'/gu, `'"'"'`)}'`;
12994
+ }
12995
+ function withShellDiagnostic(result, command, spawnError) {
12996
+ const diagnostic = classifyShellFailure(result, command, spawnError);
12997
+ if (diagnostic === void 0) return result;
12998
+ return { ...result, diagnostic };
12999
+ }
13000
+ function classifyShellFailure(result, command, spawnError) {
13001
+ if (result.terminationReason === "timeout") return {
13002
+ kind: "timeout",
13003
+ message: `Command exceeded its timeout and was terminated.`,
13004
+ hint: "Increase timeoutMs only if this command is expected to take longer."
13005
+ };
13006
+ if (result.terminationReason === "cancelled") return { kind: "cancelled", message: "Command was cancelled." };
13007
+ if (result.exitCode === 0 && result.signal === null && spawnError === void 0) return void 0;
13008
+ if (hasUnmatchedShellQuote(command)) return {
13009
+ kind: "shell-syntax",
13010
+ message: "The command contains an unmatched shell quote.",
13011
+ hint: "Close the quoted value, or pass it as one unquoted args item when shell expansion is not required."
13012
+ };
13013
+ const detail = `${spawnError?.message ?? ""}
13014
+ ${result.stderr}`;
13015
+ const code = errorCode(spawnError);
13016
+ if (code === "ENOENT" && (isAbsolute9(command) || /[\\/]/u.test(command))) {
13017
+ return { kind: "path-not-found", message: `Executable path "${command}" does not exist.` };
13018
+ }
13019
+ if (code === "ENOENT" || /(?:not recognized as (?:an internal|(?:a|the) name)|command not found|not found\s*$|无法将.+识别为|不是内部或外部命令)/imu.test(detail)) {
13020
+ return {
13021
+ kind: "command-not-found",
13022
+ message: `Executable "${command}" was not found by the selected runtime shell.`,
13023
+ hint: "Use a command available on PATH, or pass an existing executable path without surrounding quotes."
13024
+ };
13025
+ }
13026
+ if (code === "EACCES" || /(?:permission denied|access is denied|拒绝访问)/iu.test(detail)) {
13027
+ return { kind: "permission-denied", message: `Command "${command}" could not be executed because access was denied.` };
13028
+ }
13029
+ if (/(?:no such file or directory|cannot find (?:the )?path|system cannot find|系统找不到指定的路径)/iu.test(detail)) {
13030
+ return { kind: "path-not-found", message: "The command referenced a path that does not exist." };
13031
+ }
13032
+ if (/(?:syntax error|unexpected token|parsererror|语法错误)/iu.test(detail)) {
13033
+ return {
13034
+ kind: "shell-syntax",
13035
+ message: "The selected runtime shell rejected the command syntax.",
13036
+ hint: "Pass normal arguments through args; reserve shell syntax for pipes, redirects, and operators."
13037
+ };
13038
+ }
13039
+ return {
13040
+ kind: "non-zero-exit",
13041
+ message: result.signal === null ? `Command exited with code ${result.exitCode ?? "unknown"}.` : `Command was terminated by signal ${result.signal}.`
13042
+ };
13043
+ }
13044
+ function errorCode(error) {
13045
+ return error instanceof Error && "code" in error ? String(error.code) : void 0;
12849
13046
  }
12850
13047
  function createShellTool(workspace, options = {}) {
12851
13048
  const root = resolve15(workspace);
13049
+ const environment = shellEnvironment(root, options.environment ?? process.env);
12852
13050
  const maxBytes = options.maxOutputBytes ?? DEFAULT_MAX_OUTPUT_BYTES2;
12853
13051
  const defaultTimeoutMs = options.defaultTimeoutMs ?? DEFAULT_SHELL_TIMEOUT_MS;
12854
13052
  if (!Number.isSafeInteger(maxBytes) || maxBytes <= 0) throw new Error("maxOutputBytes must be a positive integer");
@@ -12857,7 +13055,7 @@ function createShellTool(workspace, options = {}) {
12857
13055
  }
12858
13056
  return {
12859
13057
  name: "Shell",
12860
- description: "Run a bounded command with an argument array inside the workspace; background=true returns a managed job id",
13058
+ description: "Run a bounded command reliably inside the workspace. Pass a bare executable in command and one unquoted logical value per args entry; shell syntax is supported only when command contains pipes, redirects, or operators. stdout/stderr are returned verbatim and failures include a structured diagnostic. background=true returns a managed job id.",
12861
13059
  inputSchema: ShellInput,
12862
13060
  paths: (input) => [workingDirectory(root, input.cwd ?? void 0)],
12863
13061
  summarize: (input) => [input.command, ...input.args].join(" "),
@@ -12878,7 +13076,7 @@ function createShellTool(workspace, options = {}) {
12878
13076
  jobKind: "shell",
12879
13077
  label: [input.command, ...input.args].join(" "),
12880
13078
  state: "running"
12881
- } : { kind: "terminal", variant: "command", title: [input.command, ...input.args].join(" "), command: [input.command, ...input.args].join(" "), stdout: output.stdout, stderr: output.stderr, exitCode: output.exitCode, state: output.terminationReason === "cancelled" ? "cancelled" : output.exitCode === 0 ? "completed" : "failed", truncated: output.truncated },
13079
+ } : { kind: "terminal", variant: "command", title: [input.command, ...input.args].join(" "), command: [input.command, ...input.args].join(" "), stdout: output.stdout, stderr: output.stderr, ...output.diagnostic === void 0 ? {} : { diagnostic: output.diagnostic.message }, exitCode: output.exitCode, state: output.terminationReason === "cancelled" ? "cancelled" : output.exitCode === 0 ? "completed" : "failed", truncated: output.truncated },
12882
13080
  execute: async (input, signal, context) => {
12883
13081
  const normalized = normalizeShellCommand(input);
12884
13082
  const effective = { ...input, command: normalized.command, args: normalized.args };
@@ -12896,19 +13094,23 @@ function createShellTool(workspace, options = {}) {
12896
13094
  try {
12897
13095
  let result2;
12898
13096
  if (options.executionEnvironment === void 0) {
12899
- result2 = await executeShell(root, { ...effective, timeoutMs }, cancellation.signal, maxBytes, (stream, text) => job.append(stream === "stderr" ? `[stderr] ${text}` : text));
13097
+ result2 = await executeShell(root, { ...effective, timeoutMs }, cancellation.signal, maxBytes, environment, (stream, text) => job.append(stream === "stderr" ? `[stderr] ${text}` : text));
12900
13098
  } else {
13099
+ const invocation2 = executionEnvironmentCommand(options.executionEnvironment, effective.command, effective.args);
12901
13100
  const raw = await options.executionEnvironment.exec({
12902
- command: effective.command,
12903
- args: effective.args,
13101
+ ...invocation2,
12904
13102
  cwd: workingDirectory(root, effective.cwd ?? void 0),
12905
- timeoutMs
13103
+ timeoutMs,
13104
+ maxOutputBytes: maxBytes
12906
13105
  }, cancellation.signal);
12907
- job.append(raw.stdout);
12908
- if (raw.stderr !== "") job.append(`[stderr] ${raw.stderr}`);
12909
- result2 = { ...raw, truncated: false, truncation: { stdout: { truncated: false, originalBytes: Buffer.byteLength(raw.stdout), limitBytes: maxBytes }, stderr: { truncated: false, originalBytes: Buffer.byteLength(raw.stderr), limitBytes: maxBytes } } };
13106
+ result2 = boundedExecutionResult(raw, maxBytes, effective.command);
13107
+ job.append(result2.stdout);
13108
+ if (result2.stderr !== "") job.append(`[stderr] ${result2.stderr}`);
12910
13109
  }
12911
- job.complete({ exitCode: result2.exitCode });
13110
+ job.complete({
13111
+ exitCode: result2.exitCode,
13112
+ ...result2.diagnostic === void 0 ? {} : { error: result2.diagnostic.message }
13113
+ });
12912
13114
  } catch (error) {
12913
13115
  job.fail(error);
12914
13116
  }
@@ -12930,32 +13132,20 @@ function createShellTool(workspace, options = {}) {
12930
13132
  };
12931
13133
  }
12932
13134
  if (options.executionEnvironment === void 0) {
12933
- return executeShell(root, { ...effective, timeoutMs }, signal, maxBytes);
13135
+ return executeShell(root, { ...effective, timeoutMs }, signal, maxBytes, environment);
12934
13136
  }
13137
+ const invocation = executionEnvironmentCommand(options.executionEnvironment, effective.command, effective.args);
12935
13138
  const result = await options.executionEnvironment.exec({
12936
- command: effective.command,
12937
- args: effective.args,
13139
+ ...invocation,
12938
13140
  cwd: workingDirectory(root, effective.cwd ?? void 0),
12939
- timeoutMs
13141
+ timeoutMs,
13142
+ maxOutputBytes: maxBytes
12940
13143
  }, signal);
12941
- const stdout = new BoundedOutput(maxBytes);
12942
- const stderr = new BoundedOutput(maxBytes);
12943
- stdout.add(Buffer.from(result.stdout));
12944
- stderr.add(Buffer.from(result.stderr));
12945
- return {
12946
- ...result,
12947
- stdout: stdout.text(),
12948
- stderr: stderr.text(),
12949
- truncated: stdout.truncated || stderr.truncated,
12950
- truncation: {
12951
- stdout: stdout.metadata(),
12952
- stderr: stderr.metadata()
12953
- }
12954
- };
13144
+ return boundedExecutionResult(result, maxBytes, effective.command);
12955
13145
  }
12956
13146
  };
12957
13147
  }
12958
- async function executeShell(root, input, cancellation, maxBytes, onOutput) {
13148
+ async function executeShell(root, input, cancellation, maxBytes, environment = process.env, onOutput) {
12959
13149
  const cwd = workingDirectory(root, input.cwd ?? void 0);
12960
13150
  const stdout = new BoundedOutput(maxBytes);
12961
13151
  const stderr = new BoundedOutput(maxBytes);
@@ -12964,15 +13154,14 @@ async function executeShell(root, input, cancellation, maxBytes, onOutput) {
12964
13154
  reject(cancellation.reason);
12965
13155
  return;
12966
13156
  }
12967
- const quoteArg = (s) => s.includes(" ") ? `"${s.replace(/"/g, '""')}"` : s;
12968
- const quoted = input.args.map(quoteArg).join(" ");
12969
- const commandLine = quoted.length > 0 ? `${quoteArg(input.command)} ${quoted}` : quoteArg(input.command);
12970
- const shellCommandLine = process.platform === "win32" ? buildWindowsCommandLine(detectWindowsShell(), commandLine, input.command, input.args) : commandLine;
12971
- const child = spawn3(shellCommandLine, {
13157
+ const invocation = shellInvocation(input, cwd, environment);
13158
+ const child = spawn3(invocation.command, invocation.args, {
12972
13159
  cwd,
12973
- shell: true,
13160
+ shell: false,
12974
13161
  windowsHide: true,
12975
- detached: process.platform !== "win32"
13162
+ detached: process.platform !== "win32",
13163
+ env: environment,
13164
+ ...invocation.windowsVerbatimArguments === void 0 ? {} : { windowsVerbatimArguments: invocation.windowsVerbatimArguments }
12976
13165
  });
12977
13166
  let exitObserved = false;
12978
13167
  let observedExitCode = null;
@@ -13033,12 +13222,12 @@ async function executeShell(root, input, cancellation, maxBytes, onOutput) {
13033
13222
  streamCloseTimer.unref();
13034
13223
  });
13035
13224
  child.once("error", (error) => {
13036
- finishReject(error);
13225
+ void finishResolve(errorCode(error) === "ENOENT" ? 127 : 1, null, error);
13037
13226
  });
13038
13227
  child.once("close", (exitCode, signal) => {
13039
13228
  void finishResolve(exitCode, signal);
13040
13229
  });
13041
- async function finishResolve(exitCode, exitSignal) {
13230
+ async function finishResolve(exitCode, exitSignal, spawnError) {
13042
13231
  if (settled) return;
13043
13232
  settled = true;
13044
13233
  cleanup();
@@ -13047,7 +13236,7 @@ async function executeShell(root, input, cancellation, maxBytes, onOutput) {
13047
13236
  const stderrTail = stderrDecoder?.end() ?? "";
13048
13237
  if (stdoutTail !== "") onOutput?.("stdout", stdoutTail);
13049
13238
  if (stderrTail !== "") onOutput?.("stderr", stderrTail);
13050
- resolvePromise({
13239
+ resolvePromise(withShellDiagnostic({
13051
13240
  exitCode,
13052
13241
  signal: exitSignal,
13053
13242
  stdout: stdout.text(),
@@ -13055,7 +13244,7 @@ async function executeShell(root, input, cancellation, maxBytes, onOutput) {
13055
13244
  truncated: stdout.truncated || stderr.truncated,
13056
13245
  truncation: { stdout: stdout.metadata(), stderr: stderr.metadata() },
13057
13246
  terminationReason
13058
- });
13247
+ }, input.command, spawnError));
13059
13248
  }
13060
13249
  function finishReject(error) {
13061
13250
  if (settled) return;
@@ -13113,7 +13302,7 @@ var BoundedOutput = class {
13113
13302
  text() {
13114
13303
  if (!this.truncated) return decodeOutput(this.#complete);
13115
13304
  const tail = Buffer.concat(this.#tailChunks).subarray(-this.#tailLimit);
13116
- const encoding = classifyUtf8(this.#complete) === "invalid" && process.platform === "win32" ? "gb18030" : "utf-8";
13305
+ const encoding = outputEncoding(this.#complete);
13117
13306
  return `${decodePrefix(this.#head, encoding)}${ELLIPSIS}${decodeSuffix(tail, encoding)}`;
13118
13307
  }
13119
13308
  };
@@ -13123,14 +13312,15 @@ var AdaptiveOutputDecoder = class {
13123
13312
  write(chunk) {
13124
13313
  if (this.#decoder !== void 0) return this.#decoder.decode(chunk, { stream: true });
13125
13314
  this.#pending = Buffer.concat([this.#pending, chunk]);
13126
- if (this.#pending.every((byte) => byte < 128)) {
13315
+ if (this.#pending.length === 1 && (this.#pending[0] === 255 || this.#pending[0] === 254)) return "";
13316
+ if (!this.#pending.includes(0) && this.#pending.every((byte) => byte < 128)) {
13127
13317
  const ascii = this.#pending.toString("ascii");
13128
13318
  this.#pending = Buffer.alloc(0);
13129
13319
  return ascii;
13130
13320
  }
13131
13321
  const classification = classifyUtf8(this.#pending);
13132
13322
  if (classification === "incomplete") return "";
13133
- const encoding = classification === "valid" || process.platform !== "win32" ? "utf-8" : "gb18030";
13323
+ const encoding = outputEncoding(this.#pending, classification);
13134
13324
  this.#decoder = new TextDecoder(encoding);
13135
13325
  const text = this.#decoder.decode(this.#pending, { stream: true });
13136
13326
  this.#pending = Buffer.alloc(0);
@@ -13199,9 +13389,56 @@ function delay2(milliseconds) {
13199
13389
  return new Promise((resolvePromise) => setTimeout(resolvePromise, milliseconds));
13200
13390
  }
13201
13391
  function decodeOutput(buffer) {
13202
- const encoding = classifyUtf8(buffer) === "invalid" && process.platform === "win32" ? "gb18030" : "utf-8";
13392
+ const encoding = outputEncoding(buffer);
13203
13393
  return new TextDecoder(encoding).decode(buffer);
13204
13394
  }
13395
+ function shellEnvironment(root, environment) {
13396
+ const localBin = join19(root, "node_modules", ".bin");
13397
+ if (!existsSync4(localBin)) return environment;
13398
+ const pathKey = Object.keys(environment).find((key) => key.toLowerCase() === "path") ?? "PATH";
13399
+ const current = environment[pathKey] ?? "";
13400
+ const entries = current.split(delimiter2).filter(Boolean);
13401
+ if (entries.some((entry2) => resolve15(entry2) === resolve15(localBin))) return environment;
13402
+ return { ...environment, [pathKey]: [localBin, ...entries].join(delimiter2) };
13403
+ }
13404
+ function boundedExecutionResult(result, maxBytes, command) {
13405
+ if (result.truncation !== void 0) {
13406
+ return withShellDiagnostic({
13407
+ ...result,
13408
+ truncated: result.truncated ?? (result.truncation.stdout.truncated || result.truncation.stderr.truncated),
13409
+ truncation: result.truncation
13410
+ }, command);
13411
+ }
13412
+ const stdout = new BoundedOutput(maxBytes);
13413
+ const stderr = new BoundedOutput(maxBytes);
13414
+ stdout.add(Buffer.from(result.stdout));
13415
+ stderr.add(Buffer.from(result.stderr));
13416
+ return withShellDiagnostic({
13417
+ ...result,
13418
+ stdout: stdout.text(),
13419
+ stderr: stderr.text(),
13420
+ truncated: stdout.truncated || stderr.truncated,
13421
+ truncation: { stdout: stdout.metadata(), stderr: stderr.metadata() }
13422
+ }, command);
13423
+ }
13424
+ function outputEncoding(buffer, utf8 = classifyUtf8(buffer)) {
13425
+ if (buffer.length >= 2) {
13426
+ if (buffer[0] === 255 && buffer[1] === 254) return "utf-16le";
13427
+ if (buffer[0] === 254 && buffer[1] === 255) return "utf-16be";
13428
+ }
13429
+ const sampleLength = Math.min(buffer.length, 256);
13430
+ let evenZeros = 0;
13431
+ let oddZeros = 0;
13432
+ for (let index = 0; index < sampleLength; index += 1) {
13433
+ if (buffer[index] !== 0) continue;
13434
+ if (index % 2 === 0) evenZeros += 1;
13435
+ else oddZeros += 1;
13436
+ }
13437
+ const threshold = Math.max(2, Math.floor(sampleLength / 8));
13438
+ if (oddZeros >= threshold && oddZeros > evenZeros * 2) return "utf-16le";
13439
+ if (evenZeros >= threshold && evenZeros > oddZeros * 2) return "utf-16be";
13440
+ return utf8 === "valid" || process.platform !== "win32" ? "utf-8" : "gb18030";
13441
+ }
13205
13442
  function decodePrefix(buffer, encoding) {
13206
13443
  for (let end = buffer.length; end >= Math.max(0, buffer.length - 3); end -= 1) {
13207
13444
  const candidate = buffer.subarray(0, end);
@@ -13259,13 +13496,13 @@ var SCRIPT_PRIORITY = ["test", "typecheck", "lint", "build", "smoke:install"];
13259
13496
  async function inferVerificationPlan(workspace) {
13260
13497
  const commands = [];
13261
13498
  const seen = /* @__PURE__ */ new Set();
13262
- const packageJson = await optionalJson(join19(workspace, "package.json"));
13499
+ const packageJson = await optionalJson(join20(workspace, "package.json"));
13263
13500
  const scripts = isRecord3(packageJson?.scripts) ? packageJson.scripts : {};
13264
13501
  for (const name of SCRIPT_PRIORITY) {
13265
13502
  if (typeof scripts[name] !== "string" || isUnconditionalPassThrough(scripts[name])) continue;
13266
13503
  add(commands, seen, npmScript(name));
13267
13504
  }
13268
- const flavor = await optionalText(join19(workspace, "FLAVOR.md"));
13505
+ const flavor = await optionalText(join20(workspace, "FLAVOR.md"));
13269
13506
  if (flavor !== void 0) {
13270
13507
  for (const match of flavor.matchAll(/^- `([^`]+)`\s*$/gm)) {
13271
13508
  const parsed = parseTrustedCommand(match[1] ?? "");
@@ -13402,11 +13639,11 @@ function isEnvTruthy(value) {
13402
13639
 
13403
13640
  // src/utils/log.ts
13404
13641
  import { appendFile as appendFile3, mkdir as mkdir14 } from "fs/promises";
13405
- import { dirname as dirname12, join as join20, resolve as resolve16 } from "path";
13642
+ import { dirname as dirname12, join as join21, resolve as resolve16 } from "path";
13406
13643
  var pending = Promise.resolve();
13407
13644
  var usagePending = Promise.resolve();
13408
13645
  function usageLogPath() {
13409
- return process.env.FLAVOR_USAGE_FILE ?? join20(process.cwd(), ".flavor", "usage.jsonl");
13646
+ return process.env.FLAVOR_USAGE_FILE ?? join21(process.cwd(), ".flavor", "usage.jsonl");
13410
13647
  }
13411
13648
  var usageSessionId = "unknown";
13412
13649
  var truncateOnNextAppend = false;
@@ -13435,7 +13672,7 @@ var AuditLogger = class {
13435
13672
  #path;
13436
13673
  #closed = false;
13437
13674
  constructor(workspace) {
13438
- this.#path = join20(resolve16(workspace), ".flavor", "audit.jsonl");
13675
+ this.#path = join21(resolve16(workspace), ".flavor", "audit.jsonl");
13439
13676
  }
13440
13677
  get path() {
13441
13678
  return this.#path;
@@ -14309,7 +14546,7 @@ import {
14309
14546
 
14310
14547
  // src/utils/version.ts
14311
14548
  import { readFileSync as readFileSync2 } from "fs";
14312
- import { dirname as dirname13, join as join21 } from "path";
14549
+ import { dirname as dirname13, join as join22 } from "path";
14313
14550
  import { fileURLToPath as fileURLToPath2 } from "url";
14314
14551
  var cached;
14315
14552
  function packageVersion() {
@@ -14317,7 +14554,7 @@ function packageVersion() {
14317
14554
  let directory = dirname13(fileURLToPath2(import.meta.url));
14318
14555
  for (; ; ) {
14319
14556
  try {
14320
- const manifest = JSON.parse(readFileSync2(join21(directory, "package.json"), "utf8"));
14557
+ const manifest = JSON.parse(readFileSync2(join22(directory, "package.json"), "utf8"));
14321
14558
  if (typeof manifest.version === "string" && manifest.version.length > 0) {
14322
14559
  cached = manifest.version;
14323
14560
  return cached;
@@ -14550,8 +14787,8 @@ var OAuthCallbackAuthProvider = class {
14550
14787
  async #startCallbackServer(expectedState, signal) {
14551
14788
  let serverResolve;
14552
14789
  let serverReject;
14553
- const codePromise = new Promise((resolve42, reject) => {
14554
- serverResolve = resolve42;
14790
+ const codePromise = new Promise((resolve43, reject) => {
14791
+ serverResolve = resolve43;
14555
14792
  serverReject = reject;
14556
14793
  });
14557
14794
  const server = createServer3((req, res) => {
@@ -14585,7 +14822,7 @@ var OAuthCallbackAuthProvider = class {
14585
14822
  );
14586
14823
  serverResolve(code);
14587
14824
  });
14588
- return new Promise((resolve42, reject) => {
14825
+ return new Promise((resolve43, reject) => {
14589
14826
  server.once("error", (error) => {
14590
14827
  reject(new Error(`OAuth callback server failed: ${error.message}`));
14591
14828
  });
@@ -14602,7 +14839,7 @@ var OAuthCallbackAuthProvider = class {
14602
14839
  signal.addEventListener("abort", () => {
14603
14840
  serverReject(new Error("OAuth flow aborted"));
14604
14841
  }, { once: true });
14605
- resolve42({ redirectUri, promise: codePromise });
14842
+ resolve43({ redirectUri, promise: codePromise });
14606
14843
  });
14607
14844
  });
14608
14845
  }
@@ -14745,7 +14982,7 @@ function isStoredToken(value) {
14745
14982
 
14746
14983
  // src/permissions/policy.ts
14747
14984
  import { readFile as readFile16 } from "fs/promises";
14748
- import { join as join22, resolve as resolve18 } from "path";
14985
+ import { join as join23, resolve as resolve18 } from "path";
14749
14986
  import { z as z25 } from "zod";
14750
14987
  var DecisionSchema2 = z25.enum(["allow", "ask", "deny"]);
14751
14988
  var PrefixSchema = z25.array(z25.string().min(1).max(1024)).min(1).max(64);
@@ -14782,9 +15019,9 @@ var CompiledPermissionPolicy = class {
14782
15019
  async function loadPermissionPolicy(options) {
14783
15020
  const sources = [
14784
15021
  ...options.managedPath === void 0 ? [] : [{ tier: "managed", path: resolve18(options.managedPath) }],
14785
- { tier: "user", path: join22(resolve18(options.home), ".flavor-code", "permissions.json") },
14786
- { tier: "project", path: join22(resolve18(options.workspace), ".flavor", "permissions.json") },
14787
- { tier: "local", path: join22(resolve18(options.workspace), ".flavor", "permissions.local.json") }
15022
+ { tier: "user", path: join23(resolve18(options.home), ".flavor-code", "permissions.json") },
15023
+ { tier: "project", path: join23(resolve18(options.workspace), ".flavor", "permissions.json") },
15024
+ { tier: "local", path: join23(resolve18(options.workspace), ".flavor", "permissions.local.json") }
14788
15025
  ];
14789
15026
  const rules = [];
14790
15027
  for (const source of sources) {
@@ -14946,7 +15183,7 @@ function toolsSection(toolNames) {
14946
15183
  addToolRule(rules, toolNames, "ApplyPatch", "Use `ApplyPatch` only when an edit genuinely spans multiple regions of one file; hunks may relocate only when their exact context has one unique nearby match. Prefer `Edit` for a single small replacement \u2014 it is easier to get right.");
14947
15184
  addToolRule(rules, toolNames, "Glob", "Use `Glob` to find files by path pattern instead of shell directory crawling. Confirm the exact path before reading, searching, or editing; the search scope is limited to the workspace, so never pass absolute paths outside it. Set `includeIgnored: true` only when the user explicitly asks to inspect ignored files or an ignored path is required for the task.");
14948
15185
  addToolRule(rules, toolNames, "Grep", "Use `Grep` to search file contents instead of shell text-search commands. Set `includeIgnored: true` only when the user explicitly asks to inspect ignored files or an ignored path is required for the task.");
14949
- addToolRule(rules, toolNames, "Shell", "Use `Shell` for builds, tests, Git inspection, and commands without a dedicated tool. Keep commands scoped, non-interactive, and easy to audit.");
15186
+ addToolRule(rules, toolNames, "Shell", "Use `Shell` for builds, tests, Git inspection, and commands without a dedicated tool. Pass only the executable name or path in `command`, and put each logical argument in its own `args` item without shell quotes. Put pipes, redirects, and operators in `command` only when shell syntax is actually required. Prefer `Glob` and `Grep` over platform-specific directory and text-search commands. Keep commands scoped, non-interactive, and easy to audit.");
14950
15187
  addToolRule(rules, toolNames, "LspFindRefs", "Use `LspFindRefs` to find all references to a symbol using the Language Server Protocol. Prefer this over Grep when you need precise semantic results.");
14951
15188
  addToolRule(rules, toolNames, "LspHover", "Use `LspHover` to get type information and documentation for a symbol at a cursor position.");
14952
15189
  addToolRule(rules, toolNames, "LspDiagnostics", "Use `LspDiagnostics` to read compiler and linter errors for a file. Always run this before claiming a code change is correct.");
@@ -15004,7 +15241,7 @@ function data(value) {
15004
15241
  }
15005
15242
 
15006
15243
  // src/skills/registry.ts
15007
- import { constants as constants5 } from "fs";
15244
+ import { constants as constants6 } from "fs";
15008
15245
  import { lstat as lstat7, open as defaultOpen, readdir as readdir5, realpath as realpath9 } from "fs/promises";
15009
15246
  import { isAbsolute as isAbsolute10, relative as relative12, resolve as resolve19, sep as sep12 } from "path";
15010
15247
  import { marked } from "marked";
@@ -15329,8 +15566,8 @@ async function openVerifiedFile(path, root, label, opener, expected) {
15329
15566
  assertRegular(initial, label);
15330
15567
  const canonical3 = await realpath9(path);
15331
15568
  if (!isWithin4(root, canonical3)) throw new Error(`${label} escapes the skill root`);
15332
- const noFollow = typeof constants5.O_NOFOLLOW === "number" ? constants5.O_NOFOLLOW : 0;
15333
- const handle = await opener(canonical3, constants5.O_RDONLY | noFollow);
15569
+ const noFollow = typeof constants6.O_NOFOLLOW === "number" ? constants6.O_NOFOLLOW : 0;
15570
+ const handle = await opener(canonical3, constants6.O_RDONLY | noFollow);
15334
15571
  try {
15335
15572
  const opened = await handle.stat({ bigint: true });
15336
15573
  assertRegular(opened, label);
@@ -15551,9 +15788,9 @@ function createSkillResourceTool(registry) {
15551
15788
  }
15552
15789
 
15553
15790
  // src/session/store.ts
15554
- import { constants as constants6 } from "fs";
15791
+ import { constants as constants7 } from "fs";
15555
15792
  import { lstat as lstat8, mkdir as mkdir15, open as open7, readFile as readFile17, readdir as readdir6, realpath as realpath10, rename as rename6, rm as rm8, stat as stat7 } from "fs/promises";
15556
- import { basename as basename5, dirname as dirname15, join as join23, relative as relative13, resolve as resolve20, sep as sep13 } from "path";
15793
+ import { basename as basename6, dirname as dirname15, join as join24, relative as relative13, resolve as resolve20, sep as sep13 } from "path";
15557
15794
  import { randomUUID as randomUUID13 } from "crypto";
15558
15795
  import { z as z27 } from "zod";
15559
15796
 
@@ -16249,7 +16486,7 @@ var SessionStore = class {
16249
16486
  #maxSessions;
16250
16487
  constructor(options) {
16251
16488
  this.#workspace = resolve20(options.workspace);
16252
- this.#sessions = join23(this.#workspace, ".flavor", "sessions");
16489
+ this.#sessions = join24(this.#workspace, ".flavor", "sessions");
16253
16490
  this.#maxBytes = options.maxBytes ?? DEFAULT_MAX_SESSION_BYTES;
16254
16491
  if (!Number.isSafeInteger(this.#maxBytes) || this.#maxBytes < 256) throw new Error("maxBytes must be an integer of at least 256");
16255
16492
  this.#maxSessions = options.maxSessions ?? 50;
@@ -16281,10 +16518,10 @@ var SessionStore = class {
16281
16518
  const body = `${lines.join("\n")}
16282
16519
  `;
16283
16520
  if (Buffer.byteLength(body) > this.#maxBytes) throw new Error(`Session exceeds maximum size of ${this.#maxBytes} bytes`);
16284
- const temporary = join23(this.#sessions, `.${document.sessionId}.${process.pid}.${randomUUID13()}.tmp`);
16521
+ const temporary = join24(this.#sessions, `.${document.sessionId}.${process.pid}.${randomUUID13()}.tmp`);
16285
16522
  let handle;
16286
16523
  try {
16287
- handle = await open7(temporary, constants6.O_CREAT | constants6.O_EXCL | constants6.O_WRONLY, 384);
16524
+ handle = await open7(temporary, constants7.O_CREAT | constants7.O_EXCL | constants7.O_WRONLY, 384);
16288
16525
  await handle.writeFile(body, "utf8");
16289
16526
  await handle.sync();
16290
16527
  await handle.close();
@@ -16300,13 +16537,13 @@ var SessionStore = class {
16300
16537
  async acquireLease(sessionId) {
16301
16538
  SessionIdSchema.parse(sessionId);
16302
16539
  await this.#prepareDirectory();
16303
- const path = join23(this.#sessions, `.${sessionId}.lock`);
16540
+ const path = join24(this.#sessions, `.${sessionId}.lock`);
16304
16541
  const token = JSON.stringify({ pid: process.pid, nonce: randomUUID13(), createdAt: (/* @__PURE__ */ new Date()).toISOString() });
16305
16542
  for (let attempt = 0; attempt < 3; attempt += 1) {
16306
16543
  let handle;
16307
16544
  let created = false;
16308
16545
  try {
16309
- handle = await open7(path, constants6.O_CREAT | constants6.O_EXCL | constants6.O_WRONLY, 384);
16546
+ handle = await open7(path, constants7.O_CREAT | constants7.O_EXCL | constants7.O_WRONLY, 384);
16310
16547
  created = true;
16311
16548
  await handle.writeFile(token, "utf8");
16312
16549
  await handle.sync();
@@ -16358,7 +16595,7 @@ var SessionStore = class {
16358
16595
  parsed = this.#parseSession(raw);
16359
16596
  } catch (error) {
16360
16597
  const quarantine = await this.#quarantine(path);
16361
- throw new Error(`Session "${sessionId}" is corrupt or incompatible and was quarantined as ${basename5(quarantine)}: ${message(error)}`);
16598
+ throw new Error(`Session "${sessionId}" is corrupt or incompatible and was quarantined as ${basename6(quarantine)}: ${message(error)}`);
16362
16599
  }
16363
16600
  await this.#assertWorkspace(parsed.workspace.path);
16364
16601
  const storedVersion = storedSessionVersion(raw);
@@ -16434,14 +16671,14 @@ var SessionStore = class {
16434
16671
  if (isCode8(error, "ENOENT")) return;
16435
16672
  throw error;
16436
16673
  }
16437
- const owner2 = await reclaimStaleSessionLock(join23(this.#sessions, `.${sessionId}.lock`));
16674
+ const owner2 = await reclaimStaleSessionLock(join24(this.#sessions, `.${sessionId}.lock`));
16438
16675
  if (owner2 !== void 0) throw new Error(`Session "${sessionId}" is active in process ${owner2} and cannot be deleted`);
16439
16676
  const path = this.#path(sessionId);
16440
16677
  try {
16441
16678
  const metadata = await lstat8(path);
16442
16679
  if (!metadata.isFile()) throw new Error("Session path is not a regular file");
16443
16680
  await rm8(path);
16444
- await rm8(join23(this.#sessions, `${sessionId}.events.jsonl`), { force: true }).catch(() => void 0);
16681
+ await rm8(join24(this.#sessions, `${sessionId}.events.jsonl`), { force: true }).catch(() => void 0);
16445
16682
  } catch (error) {
16446
16683
  if (!isCode8(error, "ENOENT")) throw error;
16447
16684
  }
@@ -16457,14 +16694,14 @@ var SessionStore = class {
16457
16694
  if (entries.length <= this.#maxSessions) return;
16458
16695
  let retained = 0;
16459
16696
  for (const entry2 of entries) {
16460
- const owner2 = await reclaimStaleSessionLock(join23(this.#sessions, `.${entry2.sessionId}.lock`));
16697
+ const owner2 = await reclaimStaleSessionLock(join24(this.#sessions, `.${entry2.sessionId}.lock`));
16461
16698
  if (owner2 !== void 0) continue;
16462
16699
  if (retained < this.#maxSessions) {
16463
16700
  retained += 1;
16464
16701
  continue;
16465
16702
  }
16466
16703
  await rm8(this.#path(entry2.sessionId), { force: true }).catch(() => void 0);
16467
- await rm8(join23(this.#sessions, `${entry2.sessionId}.events.jsonl`), { force: true }).catch(() => void 0);
16704
+ await rm8(join24(this.#sessions, `${entry2.sessionId}.events.jsonl`), { force: true }).catch(() => void 0);
16468
16705
  }
16469
16706
  }
16470
16707
  async #assertWorkspace(stored) {
@@ -16494,7 +16731,7 @@ var SessionStore = class {
16494
16731
  const target = `${path}.v${version}.bak`;
16495
16732
  let handle;
16496
16733
  try {
16497
- handle = await open7(target, constants6.O_CREAT | constants6.O_EXCL | constants6.O_WRONLY, 384);
16734
+ handle = await open7(target, constants7.O_CREAT | constants7.O_EXCL | constants7.O_WRONLY, 384);
16498
16735
  await handle.writeFile(raw, "utf8");
16499
16736
  await handle.sync();
16500
16737
  } catch (error) {
@@ -16748,13 +16985,13 @@ function sanitize2(input, seen = /* @__PURE__ */ new WeakSet()) {
16748
16985
  // src/session/tree.ts
16749
16986
  import { randomUUID as randomUUID15 } from "crypto";
16750
16987
  import { mkdir as mkdir17, readFile as readFile19, rename as rename8, writeFile as writeFile11 } from "fs/promises";
16751
- import { dirname as dirname17, join as join25, resolve as resolve22 } from "path";
16988
+ import { dirname as dirname17, join as join26, resolve as resolve22 } from "path";
16752
16989
 
16753
16990
  // src/session/checkpoint.ts
16754
16991
  import { createHash as createHash15, randomUUID as randomUUID14 } from "crypto";
16755
16992
  import { execFile as execFile2 } from "child_process";
16756
16993
  import { lstat as lstat9, mkdir as mkdir16, open as open8, readFile as readFile18, readdir as readdir7, rename as rename7, rm as rm9, writeFile as writeFile10 } from "fs/promises";
16757
- import { dirname as dirname16, join as join24, relative as relative14, resolve as resolve21, sep as sep14 } from "path";
16994
+ import { dirname as dirname16, join as join25, relative as relative14, resolve as resolve21, sep as sep14 } from "path";
16758
16995
  var EXCLUDED = /* @__PURE__ */ new Set([".git", ".flavor", "node_modules", "dist", "build", "release", ".worktrees"]);
16759
16996
  var MAX_FILE_BYTES = 16 * 1024 * 1024;
16760
16997
  var MAX_TOTAL_BYTES = 128 * 1024 * 1024;
@@ -16767,9 +17004,9 @@ var WorkspaceCheckpointStore = class {
16767
17004
  #maxTotalBytes;
16768
17005
  constructor(options) {
16769
17006
  this.#workspace = resolve21(options.workspace);
16770
- this.#root = join24(this.#workspace, ".flavor", "checkpoints");
16771
- this.#objects = join24(this.#root, "objects");
16772
- this.#manifests = join24(this.#root, "manifests");
17007
+ this.#root = join25(this.#workspace, ".flavor", "checkpoints");
17008
+ this.#objects = join25(this.#root, "objects");
17009
+ this.#manifests = join25(this.#root, "manifests");
16773
17010
  this.#maxFileBytes = options.maxFileBytes ?? MAX_FILE_BYTES;
16774
17011
  this.#maxTotalBytes = options.maxTotalBytes ?? MAX_TOTAL_BYTES;
16775
17012
  }
@@ -16789,7 +17026,7 @@ var WorkspaceCheckpointStore = class {
16789
17026
  if (total > this.#maxTotalBytes) throw new Error("Checkpoint exceeds total size limit");
16790
17027
  const content = await readFile18(absolute);
16791
17028
  const digest = createHash15("sha256").update(content).digest("hex");
16792
- const objectPath = join24(this.#objects, digest);
17029
+ const objectPath = join25(this.#objects, digest);
16793
17030
  try {
16794
17031
  const handle = await open8(objectPath, "wx");
16795
17032
  try {
@@ -16809,12 +17046,12 @@ var WorkspaceCheckpointStore = class {
16809
17046
  ...label === void 0 || label.trim().length === 0 ? {} : { label: label.trim() },
16810
17047
  files
16811
17048
  };
16812
- await atomicJson(join24(this.#manifests, `${checkpoint.id}.json`), checkpoint);
17049
+ await atomicJson(join25(this.#manifests, `${checkpoint.id}.json`), checkpoint);
16813
17050
  return checkpoint;
16814
17051
  }
16815
17052
  async load(id) {
16816
17053
  if (!/^checkpoint-[A-Za-z0-9-]+$/.test(id)) throw new Error("Invalid checkpoint id");
16817
- const value = JSON.parse(await readFile18(join24(this.#manifests, `${id}.json`), "utf8"));
17054
+ const value = JSON.parse(await readFile18(join25(this.#manifests, `${id}.json`), "utf8"));
16818
17055
  if (value.version !== 1 || value.id !== id || !Array.isArray(value.files)) throw new Error("Invalid checkpoint manifest");
16819
17056
  for (const file of value.files) {
16820
17057
  contained(this.#workspace, file.path);
@@ -16826,7 +17063,7 @@ var WorkspaceCheckpointStore = class {
16826
17063
  const checkpoint = await this.load(id);
16827
17064
  const objects = /* @__PURE__ */ new Map();
16828
17065
  for (const file of checkpoint.files) {
16829
- const object = await readFile18(join24(this.#objects, file.digest));
17066
+ const object = await readFile18(join25(this.#objects, file.digest));
16830
17067
  if (object.length !== file.size || createHash15("sha256").update(object).digest("hex") !== file.digest) {
16831
17068
  throw new Error(`Checkpoint object is corrupt: ${file.digest}`);
16832
17069
  }
@@ -16849,7 +17086,7 @@ var WorkspaceCheckpointStore = class {
16849
17086
  }
16850
17087
  };
16851
17088
  async function discoverFiles(workspace) {
16852
- if (await exists2(join24(workspace, ".git"))) {
17089
+ if (await exists2(join25(workspace, ".git"))) {
16853
17090
  const listed = await gitFiles(workspace);
16854
17091
  return listed.filter((path) => !EXCLUDED.has(path.split("/")[0] ?? "")).sort();
16855
17092
  }
@@ -16858,7 +17095,7 @@ async function discoverFiles(workspace) {
16858
17095
  const entries = await readdir7(directory, { withFileTypes: true });
16859
17096
  for (const entry2 of entries) {
16860
17097
  if (EXCLUDED.has(entry2.name)) continue;
16861
- const absolute = join24(directory, entry2.name);
17098
+ const absolute = join25(directory, entry2.name);
16862
17099
  const path = normalizeRelative(relative14(workspace, absolute));
16863
17100
  if (entry2.isSymbolicLink()) throw new Error(`Checkpoint refuses symbolic link: ${path}`);
16864
17101
  if (entry2.isDirectory()) await walk(absolute);
@@ -16933,14 +17170,14 @@ var SessionHistory = class _SessionHistory {
16933
17170
  #leafId;
16934
17171
  #unrevert;
16935
17172
  constructor(options, document) {
16936
- this.#path = join25(resolve22(options.workspace), ".flavor", "session-trees", options.sessionId, "tree.json");
17173
+ this.#path = join26(resolve22(options.workspace), ".flavor", "session-trees", options.sessionId, "tree.json");
16937
17174
  this.#checkpoints = new WorkspaceCheckpointStore({ workspace: options.workspace });
16938
17175
  this.#restoreContext = options.restoreContext;
16939
17176
  this.#nodes = boundedNodes(document.nodes);
16940
17177
  this.#leafId = document.leafId !== null && this.#nodes.some((node) => node.id === document.leafId) ? document.leafId : this.#nodes.at(-1)?.id ?? null;
16941
17178
  }
16942
17179
  static async open(options) {
16943
- const path = join25(resolve22(options.workspace), ".flavor", "session-trees", options.sessionId, "tree.json");
17180
+ const path = join26(resolve22(options.workspace), ".flavor", "session-trees", options.sessionId, "tree.json");
16944
17181
  let document = { version: 1, sessionId: options.sessionId, leafId: null, nodes: [] };
16945
17182
  try {
16946
17183
  const parsed = JSON.parse(await readFile19(path, "utf8"));
@@ -17071,12 +17308,12 @@ function isCode10(error, code) {
17071
17308
  }
17072
17309
 
17073
17310
  // src/utils/memory-restart.ts
17074
- import { join as join26 } from "path";
17311
+ import { join as join27 } from "path";
17075
17312
  var MEMORY_RESTART_EXIT_CODE = 75;
17076
17313
  var MEMORY_RESTART_MARKER_FILE = "memory-restart.json";
17077
17314
  var MEMORY_RESTART_WINDOW_MS = 30 * 60 * 1e3;
17078
17315
  function memoryRestartMarkerPath(workspace) {
17079
- return join26(workspace, ".flavor", "tmp", MEMORY_RESTART_MARKER_FILE);
17316
+ return join27(workspace, ".flavor", "tmp", MEMORY_RESTART_MARKER_FILE);
17080
17317
  }
17081
17318
  function nextMemoryRestartMarker(previous, sessionId, now) {
17082
17319
  const reusable = previous !== void 0 && previous.sessionId === sessionId && now.getTime() - Date.parse(previous.requestedAt) < MEMORY_RESTART_WINDOW_MS && Number.isInteger(previous.attempts) && previous.attempts > 0;
@@ -17095,10 +17332,10 @@ function parseMemoryRestartMarker(json) {
17095
17332
  }
17096
17333
 
17097
17334
  // src/sleep/organizer.ts
17098
- import { constants as constants7 } from "fs";
17335
+ import { constants as constants8 } from "fs";
17099
17336
  import { randomUUID as randomUUID16 } from "crypto";
17100
17337
  import { lstat as lstat10, mkdir as mkdir18, open as open9, readdir as readdir8, realpath as realpath11, rename as rename9, rm as rm10 } from "fs/promises";
17101
- import { isAbsolute as isAbsolute11, join as join27, relative as relative15, resolve as resolve23, sep as sep15 } from "path";
17338
+ import { isAbsolute as isAbsolute11, join as join28, relative as relative15, resolve as resolve23, sep as sep15 } from "path";
17102
17339
  import { z as z28 } from "zod";
17103
17340
  var DateKeySchema = z28.string().regex(/^\d{4}-\d{2}-\d{2}$/);
17104
17341
  var ReviewSchema = z28.object({
@@ -17153,7 +17390,7 @@ var ProjectSleepOrganizer = class {
17153
17390
  #generate;
17154
17391
  constructor(options) {
17155
17392
  this.#workspace = resolve23(options.workspace);
17156
- this.#sleepDirectory = join27(this.#workspace, ".flavor", "sleep");
17393
+ this.#sleepDirectory = join28(this.#workspace, ".flavor", "sleep");
17157
17394
  this.#sessions = options.sessions ?? new SessionStore({ workspace: this.#workspace });
17158
17395
  this.#generate = options.generate;
17159
17396
  }
@@ -17162,10 +17399,10 @@ var ProjectSleepOrganizer = class {
17162
17399
  signal.throwIfAborted();
17163
17400
  await this.#prepareDirectory();
17164
17401
  if (await this.#hasReport(date)) return { status: "exists", date };
17165
- const lockPath = join27(this.#sleepDirectory, `.${date}.lock`);
17402
+ const lockPath = join28(this.#sleepDirectory, `.${date}.lock`);
17166
17403
  let lock;
17167
17404
  try {
17168
- lock = await open9(lockPath, constants7.O_CREAT | constants7.O_EXCL | constants7.O_WRONLY, 384);
17405
+ lock = await open9(lockPath, constants8.O_CREAT | constants8.O_EXCL | constants8.O_WRONLY, 384);
17169
17406
  } catch (error) {
17170
17407
  if (hasCode(error, "EEXIST")) return { status: "locked", date };
17171
17408
  throw error;
@@ -17197,7 +17434,7 @@ Previous output was invalid. Repair it and return strict JSON only.`;
17197
17434
  }
17198
17435
  if (review === void 0) throw lastError;
17199
17436
  const filename2 = `${date}-${filenameSummary(review.title)}.md`;
17200
- const path = join27(this.#sleepDirectory, filename2);
17437
+ const path = join28(this.#sleepDirectory, filename2);
17201
17438
  const markdown = renderSleepReport(date, review, documents);
17202
17439
  await this.#atomicWrite(path, markdown);
17203
17440
  return { status: "written", date, path, sessionCount: documents.length };
@@ -17239,7 +17476,7 @@ Previous output was invalid. Repair it and return strict JSON only.`;
17239
17476
  });
17240
17477
  }
17241
17478
  async #prepareDirectory() {
17242
- const flavorDirectory = join27(this.#workspace, ".flavor");
17479
+ const flavorDirectory = join28(this.#workspace, ".flavor");
17243
17480
  await assertNotSymlink(flavorDirectory);
17244
17481
  await mkdir18(flavorDirectory, { recursive: true, mode: 448 });
17245
17482
  await assertNotSymlink(this.#sleepDirectory);
@@ -17253,10 +17490,10 @@ Previous output was invalid. Repair it and return strict JSON only.`;
17253
17490
  return (await readdir8(this.#sleepDirectory, { withFileTypes: true })).some((entry2) => entry2.isFile() && entry2.name.startsWith(prefix) && entry2.name.endsWith(".md"));
17254
17491
  }
17255
17492
  async #atomicWrite(path, content) {
17256
- const temporary = join27(this.#sleepDirectory, `.${randomUUID16()}.tmp`);
17493
+ const temporary = join28(this.#sleepDirectory, `.${randomUUID16()}.tmp`);
17257
17494
  let handle;
17258
17495
  try {
17259
- handle = await open9(temporary, constants7.O_CREAT | constants7.O_EXCL | constants7.O_WRONLY, 384);
17496
+ handle = await open9(temporary, constants8.O_CREAT | constants8.O_EXCL | constants8.O_WRONLY, 384);
17260
17497
  await handle.writeFile(content, "utf8");
17261
17498
  await handle.sync();
17262
17499
  await handle.close();
@@ -17626,7 +17863,7 @@ function computeSessionStats(documents) {
17626
17863
  }
17627
17864
 
17628
17865
  // src/tools/files.ts
17629
- import { constants as constants8 } from "fs";
17866
+ import { constants as constants9 } from "fs";
17630
17867
  import { mkdir as mkdir19, open as open10, readFile as readFile20, realpath as realpath12, rename as rename10, rm as rm11, stat as stat8 } from "fs/promises";
17631
17868
  import { dirname as dirname18, isAbsolute as isAbsolute12, relative as relative16, resolve as resolve24, sep as sep16 } from "path";
17632
17869
  import { randomUUID as randomUUID17 } from "crypto";
@@ -17766,7 +18003,7 @@ var FileObservationStore = class {
17766
18003
  };
17767
18004
  function createReadTool(workspace, options = {}) {
17768
18005
  const guard = createPathGuard(workspace);
17769
- const openFile2 = options.openFile ?? ((path) => open10(path, constants8.O_RDONLY));
18006
+ const openFile2 = options.openFile ?? ((path) => open10(path, constants9.O_RDONLY));
17770
18007
  const readHistory = /* @__PURE__ */ new Map();
17771
18008
  return {
17772
18009
  name: "Read",
@@ -17984,7 +18221,7 @@ async function atomicWrite(path, content, signal, expectedVersion) {
17984
18221
  await mkdir19(dirname18(path), { recursive: true });
17985
18222
  const temporary = `${path}.${randomUUID17()}.tmp`;
17986
18223
  try {
17987
- const handle = await open10(temporary, constants8.O_CREAT | constants8.O_EXCL | constants8.O_WRONLY, 384);
18224
+ const handle = await open10(temporary, constants9.O_CREAT | constants9.O_EXCL | constants9.O_WRONLY, 384);
17988
18225
  try {
17989
18226
  await handle.writeFile(content, "utf8");
17990
18227
  await handle.sync();
@@ -18876,7 +19113,7 @@ var SearchSpawnError = class extends Error {
18876
19113
  }
18877
19114
  fallbackSafe;
18878
19115
  };
18879
- function delimitedParser(delimiter2, onRecord) {
19116
+ function delimitedParser(delimiter4, onRecord) {
18880
19117
  let pending2 = Buffer.alloc(0);
18881
19118
  let stopped = false;
18882
19119
  return {
@@ -18885,7 +19122,7 @@ function delimitedParser(delimiter2, onRecord) {
18885
19122
  pending2 = pending2.length === 0 ? chunk : Buffer.concat([pending2, chunk]);
18886
19123
  let offset = 0;
18887
19124
  while (true) {
18888
- const end = pending2.indexOf(delimiter2, offset);
19125
+ const end = pending2.indexOf(delimiter4, offset);
18889
19126
  if (end < 0) break;
18890
19127
  if (!onRecord(pending2.subarray(offset, end))) {
18891
19128
  stopped = true;
@@ -19337,9 +19574,9 @@ function charset(contentType) {
19337
19574
 
19338
19575
  // src/tools/lsp.ts
19339
19576
  import { spawn as spawn5 } from "child_process";
19340
- import { existsSync as existsSync4 } from "fs";
19341
- import { access as access2, constants as constants9, readFile as readFile21 } from "fs/promises";
19342
- import { basename as basename6, dirname as dirname20, extname as extname3, isAbsolute as isAbsolute14, relative as relative18, resolve as resolve26, sep as sep18 } from "path";
19577
+ import { existsSync as existsSync5 } from "fs";
19578
+ import { access as access2, constants as constants10, readFile as readFile21 } from "fs/promises";
19579
+ import { basename as basename7, dirname as dirname20, extname as extname3, isAbsolute as isAbsolute14, relative as relative18, resolve as resolve26, sep as sep18 } from "path";
19343
19580
  import { fileURLToPath as fileURLToPath3, pathToFileURL as pathToFileURL3 } from "url";
19344
19581
  import { z as z32 } from "zod";
19345
19582
  var LSP_REQUEST_TIMEOUT_MS = 15e3;
@@ -19440,8 +19677,8 @@ var LspConnection = class {
19440
19677
  const id = this.#nextId++;
19441
19678
  const request = { jsonrpc: "2.0", id, method, params };
19442
19679
  const timeout = AbortSignal.timeout(LSP_REQUEST_TIMEOUT_MS);
19443
- const promise = new Promise((resolve42, reject) => {
19444
- this.#pending.set(id, { resolve: resolve42, reject });
19680
+ const promise = new Promise((resolve43, reject) => {
19681
+ this.#pending.set(id, { resolve: resolve43, reject });
19445
19682
  timeout.addEventListener("abort", () => {
19446
19683
  if (this.#pending.has(id)) {
19447
19684
  this.#pending.delete(id);
@@ -19648,7 +19885,7 @@ var RealLspManager = class {
19648
19885
  if (pending2 !== void 0) return pending2;
19649
19886
  const config = this.#serverConfigs.find((c) => c.language === language);
19650
19887
  if (config === void 0) return void 0;
19651
- const hasRoot = config.rootFiles.some((file) => existsSync4(resolve26(this.#workspace, file)));
19888
+ const hasRoot = config.rootFiles.some((file) => existsSync5(resolve26(this.#workspace, file)));
19652
19889
  if (!hasRoot) return void 0;
19653
19890
  const startPromise = this.#startServer(config);
19654
19891
  this.#pendingStarts.set(language, startPromise);
@@ -19662,7 +19899,7 @@ var RealLspManager = class {
19662
19899
  }
19663
19900
  async #startServer(config) {
19664
19901
  try {
19665
- await access2(config.command, constants9.X_OK);
19902
+ await access2(config.command, constants10.X_OK);
19666
19903
  } catch {
19667
19904
  }
19668
19905
  const connection = new LspConnection(config.command, config.args, this.#workspace);
@@ -19710,7 +19947,7 @@ function createLspTools(workspace, options = {}) {
19710
19947
  description: "Find all references to a symbol at a file position using the Language Server Protocol",
19711
19948
  inputSchema: LspFindRefsInput,
19712
19949
  paths: (input) => [guard(input.file)],
19713
- summarize: (input) => `${basename6(input.file)}:${input.line}:${input.character}${langTag(input.file)}`,
19950
+ summarize: (input) => `${basename7(input.file)}:${input.line}:${input.character}${langTag(input.file)}`,
19714
19951
  execute: async (input, _signal) => {
19715
19952
  const path = guard(input.file);
19716
19953
  const uri = toUri(path);
@@ -19727,7 +19964,7 @@ function createLspTools(workspace, options = {}) {
19727
19964
  description: "Get type information and documentation for a symbol at a file position using LSP",
19728
19965
  inputSchema: LspHoverInput,
19729
19966
  paths: (input) => [guard(input.file)],
19730
- summarize: (input) => `${basename6(input.file)}:${input.line}:${input.character}${langTag(input.file)}`,
19967
+ summarize: (input) => `${basename7(input.file)}:${input.line}:${input.character}${langTag(input.file)}`,
19731
19968
  execute: async (input, _signal) => {
19732
19969
  const path = guard(input.file);
19733
19970
  const uri = toUri(path);
@@ -19742,7 +19979,7 @@ function createLspTools(workspace, options = {}) {
19742
19979
  description: "Read compiler and linter diagnostics for a file using LSP",
19743
19980
  inputSchema: LspDiagnosticsInput,
19744
19981
  paths: (input) => [guard(input.file)],
19745
- summarize: (input) => `${basename6(input.file)}${langTag(input.file)}`,
19982
+ summarize: (input) => `${basename7(input.file)}${langTag(input.file)}`,
19746
19983
  execute: async (input, _signal) => {
19747
19984
  const path = guard(input.file);
19748
19985
  const uri = toUri(path);
@@ -19845,19 +20082,19 @@ async function changeSummary(workspace) {
19845
20082
  };
19846
20083
  }
19847
20084
  async function stagedDiff(workspace, maxChars = 96e3) {
19848
- const stat19 = await git2(workspace, ["diff", "--staged", "--stat"]);
20085
+ const stat20 = await git2(workspace, ["diff", "--staged", "--stat"]);
19849
20086
  const diff = await git2(workspace, ["diff", "--staged", "--no-ext-diff", "--no-color"]);
19850
20087
  if (!diff.ok) throw gitFailure(diff, "diff --staged");
19851
20088
  const text = diff.stdout;
19852
20089
  return {
19853
20090
  diff: text.length > maxChars ? `${text.slice(0, maxChars)}
19854
20091
  \u2026[diff truncated]` : text,
19855
- stat: stat19.stdout.trim(),
20092
+ stat: stat20.stdout.trim(),
19856
20093
  truncated: text.length > maxChars
19857
20094
  };
19858
20095
  }
19859
20096
  async function uncommittedDiff(workspace, maxChars = 96e3) {
19860
- const stat19 = await git2(workspace, ["diff", "HEAD", "--stat"]);
20097
+ const stat20 = await git2(workspace, ["diff", "HEAD", "--stat"]);
19861
20098
  const diff = await git2(workspace, ["diff", "HEAD", "--no-ext-diff", "--no-color"]);
19862
20099
  if (!diff.ok && !diff.stderr.includes("unknown revision")) throw gitFailure(diff, "diff HEAD");
19863
20100
  const summary2 = await changeSummary(workspace);
@@ -19865,7 +20102,7 @@ async function uncommittedDiff(workspace, maxChars = 96e3) {
19865
20102
  return {
19866
20103
  diff: text.length > maxChars ? `${text.slice(0, maxChars)}
19867
20104
  \u2026[diff truncated]` : text,
19868
- stat: stat19.stdout.trim(),
20105
+ stat: stat20.stdout.trim(),
19869
20106
  untracked: summary2.untrackedFiles
19870
20107
  };
19871
20108
  }
@@ -20159,12 +20396,12 @@ ${explanation}` : "";
20159
20396
  // src/desktop/astgraph-service.ts
20160
20397
  import { stat as stat10 } from "fs/promises";
20161
20398
  import { createRequire as nodeCreateRequire } from "module";
20162
- import { join as join28, resolve as resolve27 } from "path";
20399
+ import { join as join29, resolve as resolve27 } from "path";
20163
20400
  var FIELDS = "id, kind, name, qualified_name, file_path, language, start_line, end_line, signature";
20164
20401
  var DesktopAstGraphService = class {
20165
20402
  #path;
20166
20403
  constructor(workspace) {
20167
- this.#path = join28(resolve27(workspace), ".flavor", "astgraph", "index.db");
20404
+ this.#path = join29(resolve27(workspace), ".flavor", "astgraph", "index.db");
20168
20405
  }
20169
20406
  async status() {
20170
20407
  if ((await stat10(this.#path).catch(() => void 0))?.isFile() !== true) {
@@ -20326,7 +20563,7 @@ var QuestionBridge = class {
20326
20563
  }
20327
20564
  }
20328
20565
  #askLocally(questions, signal) {
20329
- return new Promise((resolve42, reject) => {
20566
+ return new Promise((resolve43, reject) => {
20330
20567
  const onAbort = () => {
20331
20568
  this.#pending = void 0;
20332
20569
  this.#active = false;
@@ -20340,7 +20577,7 @@ var QuestionBridge = class {
20340
20577
  this.#pending = void 0;
20341
20578
  this.#active = false;
20342
20579
  this.#onChange?.();
20343
- resolve42(answers);
20580
+ resolve43(answers);
20344
20581
  },
20345
20582
  reject: (error) => {
20346
20583
  signal.removeEventListener("abort", onAbort);
@@ -20433,7 +20670,7 @@ function createTodoWriteTool() {
20433
20670
  // src/tools/managed.ts
20434
20671
  import { randomUUID as randomUUID18 } from "crypto";
20435
20672
  import { link, lstat as lstat11, mkdir as mkdir20, readFile as readFile22, readdir as readdir9, unlink as unlink3, writeFile as writeFile12 } from "fs/promises";
20436
- import { dirname as dirname21, join as join29, resolve as resolve28 } from "path";
20673
+ import { dirname as dirname21, join as join30, resolve as resolve28 } from "path";
20437
20674
  import { z as z37 } from "zod";
20438
20675
  var MANAGED_TOOL_VERSION = 1;
20439
20676
  var MAX_RECORD_BYTES = 256 * 1024;
@@ -20476,8 +20713,8 @@ var ManagedToolStore = class {
20476
20713
  #diagnostics = [];
20477
20714
  constructor(options) {
20478
20715
  this.#workspace = resolve28(options.workspace);
20479
- this.#projectRoot = join29(this.#workspace, ".flavor", "tools");
20480
- this.#globalRoot = join29(resolve28(options.home), ".flavor-code", "tools");
20716
+ this.#projectRoot = join30(this.#workspace, ".flavor", "tools");
20717
+ this.#globalRoot = join30(resolve28(options.home), ".flavor-code", "tools");
20481
20718
  this.#now = options.now ?? (() => (/* @__PURE__ */ new Date()).toISOString());
20482
20719
  }
20483
20720
  get diagnostics() {
@@ -20524,7 +20761,7 @@ var ManagedToolStore = class {
20524
20761
  version: MANAGED_TOOL_VERSION,
20525
20762
  createdAt: this.#now()
20526
20763
  });
20527
- const path = join29(this.directory(input.scope), filename(input.name));
20764
+ const path = join30(this.directory(input.scope), filename(input.name));
20528
20765
  const definition = compileDefinition(record3, input.scope, this.#workspace);
20529
20766
  await writeExclusive(path, `${JSON.stringify(record3, void 0, 2)}
20530
20767
  `);
@@ -20549,7 +20786,7 @@ var ManagedToolStore = class {
20549
20786
  if (!parsed.success) return [];
20550
20787
  const matches = this.#matching(parsed.data.name).filter((entry2) => parsed.data.scope === void 0 || entry2.scope === parsed.data.scope);
20551
20788
  if (matches.length > 0) return matches.map((entry2) => entry2.path);
20552
- return [join29(this.directory(parsed.data.scope ?? "project"), filename(parsed.data.name))];
20789
+ return [join30(this.directory(parsed.data.scope ?? "project"), filename(parsed.data.name))];
20553
20790
  }
20554
20791
  async #loadDirectory(scope2) {
20555
20792
  const root = this.directory(scope2);
@@ -20562,7 +20799,7 @@ var ManagedToolStore = class {
20562
20799
  return;
20563
20800
  }
20564
20801
  for (const name of names) {
20565
- const path = join29(root, name);
20802
+ const path = join30(root, name);
20566
20803
  try {
20567
20804
  const info = await lstat11(path);
20568
20805
  if (!info.isFile() || info.isSymbolicLink()) throw new Error("Managed tool record must be a regular file");
@@ -21145,6 +21382,7 @@ var MVP_COMMANDS = [
21145
21382
  "exit",
21146
21383
  "audit",
21147
21384
  "usage",
21385
+ "doctor",
21148
21386
  "loop",
21149
21387
  "goal",
21150
21388
  "evolve",
@@ -21435,7 +21673,7 @@ var HELP = [
21435
21673
  "/permissions <default|acceptEdits|plan|bypassPermissions|auto|bubble>",
21436
21674
  "/login authenticate via OAuth PKCE",
21437
21675
  "/logout clear stored OAuth credentials",
21438
- "/init /config /skills /plugins /hooks /tasks",
21676
+ "/init /config /skills /plugins /hooks /tasks /doctor",
21439
21677
  "/memory /remember [type] <text> /forget <text-or-id> /forget-cold /finish",
21440
21678
  "/checkpoint [label] /tree /rewind <node> /unrevert /fork <node>",
21441
21679
  "/compact /clear /help /exit",
@@ -21917,6 +22155,7 @@ Address this now (for example run the required verification or fix), then finish
21917
22155
  else if (command.name === "hooks") this.#notice(format(this.#services.hooksStatus()));
21918
22156
  else if (command.name === "tasks") this.#notice(format(this.#services.tasks()));
21919
22157
  else if (command.name === "audit") this.#notice(await this.#services.audit(command.toolFilter));
22158
+ else if (command.name === "doctor") this.#notice(await required(this.#services.doctor, "doctor")());
21920
22159
  else if (command.name === "evolve") this.#notice(await this.#services.evolve(command.args));
21921
22160
  else if (command.name === "usage") this.#notice(await this.#services.usage());
21922
22161
  else if (command.name === "clear") {
@@ -22029,9 +22268,9 @@ Address this now (for example run the required verification or fix), then finish
22029
22268
  }
22030
22269
  #waitForPlanningIdle(key) {
22031
22270
  if (!this.#activePlanning.has(key)) return Promise.resolve();
22032
- return new Promise((resolve42) => {
22271
+ return new Promise((resolve43) => {
22033
22272
  const check2 = () => {
22034
- if (!this.#activePlanning.has(key)) resolve42();
22273
+ if (!this.#activePlanning.has(key)) resolve43();
22035
22274
  else this.#planningIdleWaiters.add(check2);
22036
22275
  };
22037
22276
  this.#planningIdleWaiters.add(check2);
@@ -22138,6 +22377,225 @@ function requiredPals(service) {
22138
22377
  return service;
22139
22378
  }
22140
22379
 
22380
+ // src/doctor.ts
22381
+ import { constants as constants11 } from "fs";
22382
+ import { access as access3, lstat as lstat12, readFile as readFile23, readdir as readdir10, stat as stat11 } from "fs/promises";
22383
+ import { homedir } from "os";
22384
+ import { isAbsolute as isAbsolute15, join as join31, relative as relative19, resolve as resolve29, sep as sep19 } from "path";
22385
+ import { rgPath as bundledRgPath2 } from "@vscode/ripgrep";
22386
+
22387
+ // src/utils/semver.ts
22388
+ import { gt as semverGt, gte as semverGte, valid as semverValid } from "semver";
22389
+ function gte(a, b) {
22390
+ return semverGte(a, b, { loose: true });
22391
+ }
22392
+
22393
+ // src/update/check.ts
22394
+ var NPM_PACKAGE_NAME = "flavor-code";
22395
+ var REGISTRY_LATEST_URL = `https://registry.npmjs.org/${NPM_PACKAGE_NAME}/latest`;
22396
+ var DEFAULT_TIMEOUT_MS2 = 3e3;
22397
+ async function fetchLatestVersion(options = {}) {
22398
+ const timeoutMs = options.timeoutMs ?? DEFAULT_TIMEOUT_MS2;
22399
+ const fetchImpl = options.fetchImpl ?? ((url, init) => fetch(url, init));
22400
+ let timer;
22401
+ const timeout = new Promise((resolve43) => {
22402
+ timer = setTimeout(() => resolve43(void 0), timeoutMs);
22403
+ timer.unref?.();
22404
+ });
22405
+ try {
22406
+ const response2 = await Promise.race([
22407
+ fetchImpl(REGISTRY_LATEST_URL, { signal: AbortSignal.timeout(timeoutMs) }),
22408
+ timeout
22409
+ ]);
22410
+ if (response2 === void 0 || !response2.ok) return void 0;
22411
+ const body = await Promise.race([response2.json(), timeout]);
22412
+ return typeof body?.version === "string" ? body.version : void 0;
22413
+ } catch {
22414
+ return void 0;
22415
+ } finally {
22416
+ if (timer !== void 0) clearTimeout(timer);
22417
+ }
22418
+ }
22419
+
22420
+ // src/doctor.ts
22421
+ async function runDoctor(options = {}, dependencies2 = {}) {
22422
+ const workspace = resolve29(options.workspace ?? process.cwd());
22423
+ const home = resolve29(options.home ?? homedir());
22424
+ const environment = options.environment ?? process.env;
22425
+ const platform = options.platform ?? process.platform;
22426
+ const arch = options.arch ?? process.arch;
22427
+ const nodeVersion = options.nodeVersion ?? process.version;
22428
+ const checks = [];
22429
+ checks.push(runtimeCheck(nodeVersion, platform, arch));
22430
+ checks.push(await directoryCheck("workspace", workspace, false));
22431
+ checks.push(await directoryCheck("state directory", join31(home, ".flavor-code"), true, home));
22432
+ let loaded;
22433
+ try {
22434
+ loaded = await (dependencies2.loadConfig ?? loadConfig)({
22435
+ cwd: workspace,
22436
+ home,
22437
+ environment,
22438
+ seedGlobalEnv: false
22439
+ });
22440
+ checks.push({
22441
+ name: "configuration",
22442
+ status: "pass",
22443
+ message: loaded.sources.length === 0 ? "defaults are valid; no config files found" : `valid (${loaded.sources.length} source${loaded.sources.length === 1 ? "" : "s"})`
22444
+ });
22445
+ } catch (error) {
22446
+ checks.push({ name: "configuration", status: "fail", message: errorMessage3(error) });
22447
+ }
22448
+ checks.push(providerCheck(loaded?.config, environment));
22449
+ checks.push(await searchCheck(dependencies2.rgPath ?? bundledRgPath2));
22450
+ try {
22451
+ const shell2 = await (dependencies2.probeShell ?? probeShell)(platform, environment);
22452
+ checks.push(shell2 === void 0 ? { name: "shell", status: "fail", message: "no usable command shell found" } : { name: "shell", status: "pass", message: shell2 });
22453
+ } catch (error) {
22454
+ checks.push({ name: "shell", status: "fail", message: errorMessage3(error) });
22455
+ }
22456
+ checks.push(await pluginCheck([
22457
+ join31(home, ".flavor-code", "plugins"),
22458
+ join31(workspace, ".flavor", "plugins")
22459
+ ]));
22460
+ try {
22461
+ const latest = await (dependencies2.fetchLatestVersion ?? fetchLatestVersion)({
22462
+ timeoutMs: options.registryTimeoutMs ?? 3e3
22463
+ });
22464
+ checks.push(latest === void 0 ? { name: "npm registry", status: "warn", message: "unreachable or returned an invalid response" } : { name: "npm registry", status: "pass", message: `reachable; latest flavor-code is v${latest}` });
22465
+ } catch (error) {
22466
+ checks.push({ name: "npm registry", status: "warn", message: errorMessage3(error) });
22467
+ }
22468
+ const summary2 = {
22469
+ passed: checks.filter(({ status }) => status === "pass").length,
22470
+ warnings: checks.filter(({ status }) => status === "warn").length,
22471
+ failed: checks.filter(({ status }) => status === "fail").length
22472
+ };
22473
+ return {
22474
+ version: packageVersion(),
22475
+ platform,
22476
+ arch,
22477
+ workspace,
22478
+ checks,
22479
+ summary: summary2,
22480
+ ok: summary2.failed === 0
22481
+ };
22482
+ }
22483
+ function formatDoctorReport(report) {
22484
+ const labels = { pass: "ok", warn: "warn", fail: "fail" };
22485
+ const lines = [
22486
+ `Flavor Doctor v${report.version}`,
22487
+ `Platform: ${report.platform} ${report.arch}`,
22488
+ `Workspace: ${report.workspace}`,
22489
+ "",
22490
+ ...report.checks.map((check2) => `[${labels[check2.status]}] ${check2.name}: ${check2.message}`),
22491
+ "",
22492
+ `Summary: ${report.summary.passed} passed, ${report.summary.warnings} warning${report.summary.warnings === 1 ? "" : "s"}, ${report.summary.failed} failed`
22493
+ ];
22494
+ return `${lines.join("\n")}
22495
+ `;
22496
+ }
22497
+ function runtimeCheck(version, platform, arch) {
22498
+ const major = Number.parseInt(version.replace(/^v/u, "").split(".")[0] ?? "", 10);
22499
+ if (!Number.isInteger(major) || !gte(`${major}.0.0`, "20.0.0")) {
22500
+ return { name: "runtime", status: "fail", message: `Node ${version} is unsupported; Node 20+ is required` };
22501
+ }
22502
+ return { name: "runtime", status: "pass", message: `Node ${version} (${platform} ${arch})` };
22503
+ }
22504
+ async function directoryCheck(name, path, mayBeCreated, parent = path) {
22505
+ try {
22506
+ const info = await stat11(path);
22507
+ if (!info.isDirectory()) return { name, status: "fail", message: `${path} is not a directory` };
22508
+ await access3(path, constants11.R_OK | constants11.W_OK);
22509
+ return { name, status: "pass", message: `${path} is readable and writable` };
22510
+ } catch (error) {
22511
+ if (mayBeCreated && errorCode2(error) === "ENOENT") {
22512
+ try {
22513
+ await access3(parent, constants11.R_OK | constants11.W_OK);
22514
+ return { name, status: "pass", message: `${path} can be created` };
22515
+ } catch (parentError) {
22516
+ return { name, status: "fail", message: errorMessage3(parentError) };
22517
+ }
22518
+ }
22519
+ return { name, status: "fail", message: errorMessage3(error) };
22520
+ }
22521
+ }
22522
+ function providerCheck(config, environment) {
22523
+ if (config === void 0) return { name: "providers", status: "warn", message: "skipped because configuration is invalid" };
22524
+ const configured = Object.keys(config.providers);
22525
+ const environmentProviders = [
22526
+ environment.OPENAI_API_KEY ? "openai env" : void 0,
22527
+ environment.ANTHROPIC_API_KEY ? "anthropic env" : void 0
22528
+ ].filter((value) => value !== void 0);
22529
+ if (configured.length === 0 && environmentProviders.length === 0) {
22530
+ return { name: "providers", status: "warn", message: "no provider or API credential detected" };
22531
+ }
22532
+ const parts = [
22533
+ configured.length > 0 ? `${configured.length} configured` : void 0,
22534
+ environmentProviders.length > 0 ? environmentProviders.join(", ") : void 0
22535
+ ].filter((value) => value !== void 0);
22536
+ return { name: "providers", status: "pass", message: parts.join("; ") };
22537
+ }
22538
+ async function searchCheck(path) {
22539
+ try {
22540
+ await access3(path, constants11.R_OK);
22541
+ return { name: "search", status: "pass", message: `bundled ripgrep found at ${path}` };
22542
+ } catch (error) {
22543
+ return { name: "search", status: "fail", message: `bundled ripgrep is unavailable: ${errorMessage3(error)}` };
22544
+ }
22545
+ }
22546
+ async function probeShell(platform, environment) {
22547
+ const shell2 = resolveRuntimeShell(platform, environment);
22548
+ if (shell2 === void 0) return void 0;
22549
+ const sentinel = "flavor-shell-ok";
22550
+ const args = shell2.kind === "posix" ? ["-c", `printf '${sentinel}'`] : shell2.kind === "cmd" ? ["/d", "/s", "/c", `echo|set /p=${sentinel}`] : ["-NoLogo", "-NoProfile", "-NonInteractive", "-Command", `[Console]::Out.Write('${sentinel}')`];
22551
+ const result = await execFileNoThrow(shell2.command, args, { timeout: 2e3, useCwd: false, env: environment });
22552
+ if (result.code !== 0 || result.stdout !== sentinel) {
22553
+ const detail = result.stderr.trim() || result.error || `unexpected output ${JSON.stringify(result.stdout)}`;
22554
+ throw new Error(`${shell2.command} was found but failed its execution probe: ${detail}`);
22555
+ }
22556
+ return `${shell2.command} (${shell2.kind}; execution probe passed)`;
22557
+ }
22558
+ async function pluginCheck(directories) {
22559
+ let valid = 0;
22560
+ const diagnostics = [];
22561
+ for (const directory of directories) {
22562
+ let entries;
22563
+ try {
22564
+ entries = await readdir10(directory, { withFileTypes: true });
22565
+ } catch (error) {
22566
+ if (errorCode2(error) !== "ENOENT") diagnostics.push(`${directory}: ${errorMessage3(error)}`);
22567
+ continue;
22568
+ }
22569
+ for (const entry2 of entries) {
22570
+ if (!entry2.isDirectory() || entry2.name.startsWith(".")) continue;
22571
+ const root = resolve29(directory, entry2.name);
22572
+ try {
22573
+ const rootInfo = await lstat12(root);
22574
+ if (rootInfo.isSymbolicLink()) throw new Error("plugin directory must not be a symbolic link");
22575
+ const manifest = PluginManifestSchema.parse(JSON.parse(await readFile23(join31(root, "flavor-plugin.json"), "utf8")));
22576
+ const main = resolve29(root, manifest.main);
22577
+ const delta = relative19(root, main);
22578
+ if (delta === ".." || delta.startsWith(`..${sep19}`) || isAbsolute15(delta)) throw new Error("plugin entry escapes its root");
22579
+ const mainInfo = await lstat12(main);
22580
+ if (!mainInfo.isFile() || mainInfo.isSymbolicLink()) throw new Error("plugin entry must be a regular file");
22581
+ valid += 1;
22582
+ } catch (error) {
22583
+ diagnostics.push(`${entry2.name}: ${errorMessage3(error)}`);
22584
+ }
22585
+ }
22586
+ }
22587
+ if (diagnostics.length > 0) {
22588
+ return { name: "plugins", status: "warn", message: `${valid} valid; ${diagnostics.join("; ")}` };
22589
+ }
22590
+ return { name: "plugins", status: "pass", message: valid === 0 ? "no project or global plugins found" : `${valid} manifest${valid === 1 ? "" : "s"} valid` };
22591
+ }
22592
+ function errorCode2(error) {
22593
+ return error instanceof Error && "code" in error ? String(error.code) : void 0;
22594
+ }
22595
+ function errorMessage3(error) {
22596
+ return error instanceof Error ? error.message : String(error);
22597
+ }
22598
+
22141
22599
  // src/utils/intl.ts
22142
22600
  function detectSystemLocale() {
22143
22601
  try {
@@ -22182,7 +22640,7 @@ var LANG_LABELS = /* @__PURE__ */ new Map([
22182
22640
 
22183
22641
  // src/utils/crash-guard.ts
22184
22642
  import { mkdirSync as mkdirSync3, writeFileSync } from "fs";
22185
- import { join as join30 } from "path";
22643
+ import { join as join32 } from "path";
22186
22644
  var installed = false;
22187
22645
  var crashing = false;
22188
22646
  var crashCleanups = /* @__PURE__ */ new Set();
@@ -22213,7 +22671,7 @@ function installCrashGuard(options = {}) {
22213
22671
  installed = true;
22214
22672
  const crashLogDir = () => {
22215
22673
  try {
22216
- return join30((options.workspace ?? process.cwd)(), ".flavor");
22674
+ return join32((options.workspace ?? process.cwd)(), ".flavor");
22217
22675
  } catch {
22218
22676
  return process.cwd();
22219
22677
  }
@@ -22222,7 +22680,7 @@ function installCrashGuard(options = {}) {
22222
22680
  try {
22223
22681
  const dir = crashLogDir();
22224
22682
  mkdirSync3(dir, { recursive: true });
22225
- const path = join30(dir, `crash-${(/* @__PURE__ */ new Date()).toISOString().replace(/[^0-9]/g, "")}-${process.pid}.log`);
22683
+ const path = join32(dir, `crash-${(/* @__PURE__ */ new Date()).toISOString().replace(/[^0-9]/g, "")}-${process.pid}.log`);
22226
22684
  writeFileSync(path, [
22227
22685
  `kind: ${kind}`,
22228
22686
  `version: ${process.version}`,
@@ -22721,14 +23179,14 @@ var RetryMonitor = class {
22721
23179
  recordCall(toolName, params) {
22722
23180
  this.#slidingWindow.push(toolName, params);
22723
23181
  }
22724
- recordError(toolName, params, errorCode) {
23182
+ recordError(toolName, params, errorCode3) {
22725
23183
  void params;
22726
23184
  const existing = this.#errorStates.get(toolName);
22727
23185
  if (existing === void 0) {
22728
- this.#errorStates.set(toolName, { retryCount: 1, lastErrorCode: errorCode });
23186
+ this.#errorStates.set(toolName, { retryCount: 1, lastErrorCode: errorCode3 });
22729
23187
  } else {
22730
23188
  existing.retryCount += 1;
22731
- existing.lastErrorCode = errorCode;
23189
+ existing.lastErrorCode = errorCode3;
22732
23190
  }
22733
23191
  }
22734
23192
  recordSuccess(toolName) {
@@ -22993,8 +23451,8 @@ ${total}`;
22993
23451
 
22994
23452
  // src/memory/store.ts
22995
23453
  import { createHash as createHash17 } from "crypto";
22996
- import { readFile as readFile23, rm as rm12 } from "fs/promises";
22997
- import { resolve as resolve29, join as join31, relative as relative19, sep as sep19 } from "path";
23454
+ import { readFile as readFile24, rm as rm12 } from "fs/promises";
23455
+ import { resolve as resolve30, join as join33, relative as relative20, sep as sep20 } from "path";
22998
23456
 
22999
23457
  // src/memory/similarity.ts
23000
23458
  function normalizeForSimilarity(value) {
@@ -23096,10 +23554,10 @@ var MemoryStore = class {
23096
23554
  constructor(options) {
23097
23555
  if (!Number.isSafeInteger(options.maxEntries) || options.maxEntries < 1) throw new Error("maxEntries must be an integer of at least 1");
23098
23556
  if (!Number.isSafeInteger(options.maxEntryChars) || options.maxEntryChars < 1) throw new Error("maxEntryChars must be an integer of at least 1");
23099
- this.workspace = resolve29(options.workspace);
23100
- this.#memoryRoot = join31(this.workspace, ".flavor", "memory");
23101
- this.path = join31(this.#memoryRoot, "MEMORY.md");
23102
- this.#behaviorPath = join31(this.#memoryRoot, "behavior.json");
23557
+ this.workspace = resolve30(options.workspace);
23558
+ this.#memoryRoot = join33(this.workspace, ".flavor", "memory");
23559
+ this.path = join33(this.#memoryRoot, "MEMORY.md");
23560
+ this.#behaviorPath = join33(this.#memoryRoot, "behavior.json");
23103
23561
  this.#maxEntries = options.maxEntries;
23104
23562
  this.#maxEntryChars = options.maxEntryChars;
23105
23563
  }
@@ -23317,14 +23775,14 @@ var MemoryStore = class {
23317
23775
  });
23318
23776
  }
23319
23777
  async #readTaskItem(reference) {
23320
- const document = decodeTask(await readFile23(this.#resolveContentPath(reference.contentPath), "utf8"));
23778
+ const document = decodeTask(await readFile24(this.#resolveContentPath(reference.contentPath), "utf8"));
23321
23779
  return document.items.find((item) => item.id === reference.id)?.content ?? reference.summary;
23322
23780
  }
23323
23781
  #resolveContentPath(contentPath) {
23324
23782
  if (!/^tasks\/[A-Za-z0-9][A-Za-z0-9._-]{0,127}\.md$/.test(contentPath)) throw new Error("Invalid memory content path");
23325
- const path = resolve29(this.#memoryRoot, ...contentPath.split("/"));
23326
- const rel = relative19(this.#memoryRoot, path);
23327
- if (rel === ".." || rel.startsWith(`..${sep19}`)) throw new Error("Memory content path escapes the memory directory");
23783
+ const path = resolve30(this.#memoryRoot, ...contentPath.split("/"));
23784
+ const rel = relative20(this.#memoryRoot, path);
23785
+ if (rel === ".." || rel.startsWith(`..${sep20}`)) throw new Error("Memory content path escapes the memory directory");
23328
23786
  return path;
23329
23787
  }
23330
23788
  async #updateIndex(update) {
@@ -23782,13 +24240,13 @@ var MemoryReviewBridge = class {
23782
24240
 
23783
24241
  // src/execution/docker.ts
23784
24242
  import { spawn as spawn6 } from "child_process";
23785
- import { relative as relative20, resolve as resolve30, sep as sep20, win32 } from "path";
24243
+ import { relative as relative21, resolve as resolve31, sep as sep21, win32 } from "path";
23786
24244
  function buildDockerInvocation(options) {
23787
24245
  const workspace = options.workspace;
23788
24246
  const cwd = options.request.cwd;
23789
24247
  const windows = /^[A-Za-z]:[\\/]/.test(workspace);
23790
- const delta = windows ? win32.relative(workspace, cwd) : relative20(resolve30(workspace), resolve30(cwd));
23791
- if (delta === ".." || delta.startsWith(`..${windows ? "\\" : sep20}`)) throw new Error("Docker cwd escapes workspace");
24248
+ const delta = windows ? win32.relative(workspace, cwd) : relative21(resolve31(workspace), resolve31(cwd));
24249
+ if (delta === ".." || delta.startsWith(`..${windows ? "\\" : sep21}`)) throw new Error("Docker cwd escapes workspace");
23792
24250
  const containerCwd = delta.length === 0 ? "/workspace" : `/workspace/${delta.replaceAll("\\", "/")}`;
23793
24251
  const args = [
23794
24252
  "run",
@@ -23826,53 +24284,123 @@ var DockerExecutionEnvironment = class {
23826
24284
  }
23827
24285
  exec(request, signal) {
23828
24286
  const invocation = buildDockerInvocation({ ...this.#options, request });
23829
- return (this.#options.run ?? runInvocation)(invocation, signal, request.timeoutMs);
24287
+ return (this.#options.run ?? runDockerInvocation)(invocation, signal, request.timeoutMs, request.maxOutputBytes);
23830
24288
  }
23831
24289
  async dispose() {
23832
24290
  }
23833
24291
  };
23834
- function runInvocation(invocation, signal, timeoutMs) {
24292
+ var DEFAULT_DOCKER_MAX_OUTPUT_BYTES = 1048576;
24293
+ var DOCKER_TERMINATION_GRACE_MS = 250;
24294
+ var DOCKER_TERMINATION_FAILURE_MS = 5e3;
24295
+ function runDockerInvocation(invocation, signal, timeoutMs, maxOutputBytes = DEFAULT_DOCKER_MAX_OUTPUT_BYTES) {
23835
24296
  return new Promise((resolvePromise) => {
23836
24297
  if (signal?.aborted) {
23837
24298
  resolvePromise({ exitCode: null, signal: null, stdout: "", stderr: "", terminationReason: "cancelled" });
23838
24299
  return;
23839
24300
  }
23840
24301
  const child = spawn6(invocation.command, invocation.args, { windowsHide: true, shell: false });
23841
- const stdout = [];
23842
- const stderr = [];
24302
+ const stdout = new BoundedUtf8Output(maxOutputBytes);
24303
+ const stderr = new BoundedUtf8Output(maxOutputBytes);
23843
24304
  let reason = null;
23844
24305
  let settled = false;
24306
+ let forceTimer;
24307
+ let failureTimer;
23845
24308
  const finish = (result) => {
23846
24309
  if (settled) return;
23847
24310
  settled = true;
23848
24311
  if (timer !== void 0) clearTimeout(timer);
24312
+ if (forceTimer !== void 0) clearTimeout(forceTimer);
24313
+ if (failureTimer !== void 0) clearTimeout(failureTimer);
23849
24314
  signal?.removeEventListener("abort", abort2);
23850
24315
  resolvePromise(result);
23851
24316
  };
24317
+ const collected = (exitCode, closedSignal) => ({
24318
+ exitCode,
24319
+ signal: closedSignal,
24320
+ stdout: stdout.text(),
24321
+ stderr: stderr.text(),
24322
+ terminationReason: reason,
24323
+ truncated: stdout.truncated || stderr.truncated,
24324
+ truncation: { stdout: stdout.metadata(), stderr: stderr.metadata() }
24325
+ });
23852
24326
  const stop = (next) => {
24327
+ if (reason !== null || settled) return;
23853
24328
  reason = next;
23854
- child.kill();
24329
+ child.kill("SIGTERM");
24330
+ forceTimer = setTimeout(() => child.kill("SIGKILL"), DOCKER_TERMINATION_GRACE_MS);
24331
+ forceTimer.unref();
24332
+ failureTimer = setTimeout(() => finish(collected(null, null)), DOCKER_TERMINATION_FAILURE_MS);
24333
+ failureTimer.unref();
23855
24334
  };
23856
24335
  const timer = timeoutMs === void 0 ? void 0 : setTimeout(() => stop("timeout"), timeoutMs);
23857
24336
  timer?.unref();
23858
24337
  const abort2 = () => stop("cancelled");
23859
24338
  signal?.addEventListener("abort", abort2, { once: true });
23860
- child.stdout.on("data", (value) => stdout.push(value));
23861
- child.stderr.on("data", (value) => stderr.push(value));
24339
+ child.stdout.on("data", (value) => stdout.add(value));
24340
+ child.stderr.on("data", (value) => stderr.add(value));
23862
24341
  child.once("error", (error) => {
23863
- finish({ exitCode: 127, signal: null, stdout: "", stderr: error.message, terminationReason: reason });
24342
+ stderr.add(Buffer.from(error.message));
24343
+ finish(collected(127, null));
23864
24344
  });
23865
24345
  child.once("close", (code, closedSignal) => {
23866
- finish({
23867
- exitCode: code,
23868
- signal: closedSignal,
23869
- stdout: Buffer.concat(stdout).toString("utf8"),
23870
- stderr: Buffer.concat(stderr).toString("utf8"),
23871
- terminationReason: reason
23872
- });
24346
+ finish(collected(code, closedSignal));
23873
24347
  });
23874
24348
  });
23875
24349
  }
24350
+ var BoundedUtf8Output = class {
24351
+ #limit;
24352
+ #headLimit;
24353
+ #tailLimit;
24354
+ #head = Buffer.alloc(0);
24355
+ #tailChunks = [];
24356
+ #tailBytes = 0;
24357
+ #complete = Buffer.alloc(0);
24358
+ #bytes = 0;
24359
+ constructor(limit) {
24360
+ this.#limit = Number.isSafeInteger(limit) && limit > 0 ? limit : DEFAULT_DOCKER_MAX_OUTPUT_BYTES;
24361
+ this.#headLimit = Math.ceil(this.#limit / 2);
24362
+ this.#tailLimit = Math.floor(this.#limit / 2);
24363
+ }
24364
+ get truncated() {
24365
+ return this.#bytes > this.#limit;
24366
+ }
24367
+ add(chunk) {
24368
+ this.#bytes += chunk.length;
24369
+ if (this.#complete.length < this.#limit) {
24370
+ this.#complete = Buffer.concat([this.#complete, chunk]).subarray(0, this.#limit);
24371
+ }
24372
+ if (this.#head.length < this.#headLimit) {
24373
+ this.#head = Buffer.concat([this.#head, chunk.subarray(0, this.#headLimit - this.#head.length)]);
24374
+ }
24375
+ if (this.#tailLimit > 0) {
24376
+ this.#tailChunks.push(chunk);
24377
+ this.#tailBytes += chunk.length;
24378
+ if (this.#tailBytes >= this.#tailLimit * 2) {
24379
+ const merged = Buffer.concat(this.#tailChunks).subarray(-this.#tailLimit);
24380
+ this.#tailChunks = [merged];
24381
+ this.#tailBytes = merged.length;
24382
+ }
24383
+ }
24384
+ }
24385
+ metadata() {
24386
+ return { truncated: this.truncated, originalBytes: this.#bytes, limitBytes: this.#limit };
24387
+ }
24388
+ text() {
24389
+ if (!this.truncated) return this.#complete.toString("utf8");
24390
+ const tail = Buffer.concat(this.#tailChunks).subarray(-this.#tailLimit);
24391
+ return `${decodeUtf8Edge(this.#head, "prefix")}\u2026${decodeUtf8Edge(tail, "suffix")}`;
24392
+ }
24393
+ };
24394
+ function decodeUtf8Edge(buffer, edge) {
24395
+ for (let offset = 0; offset <= Math.min(3, buffer.length); offset += 1) {
24396
+ const candidate = edge === "prefix" ? buffer.subarray(0, buffer.length - offset) : buffer.subarray(offset);
24397
+ try {
24398
+ return new TextDecoder("utf-8", { fatal: true }).decode(candidate);
24399
+ } catch {
24400
+ }
24401
+ }
24402
+ return new TextDecoder("utf-8").decode(buffer);
24403
+ }
23876
24404
 
23877
24405
  // src/execution/factory.ts
23878
24406
  function createExecutionEnvironment(workspace, config) {
@@ -23887,8 +24415,8 @@ function createExecutionEnvironment(workspace, config) {
23887
24415
  }
23888
24416
 
23889
24417
  // src/ide/client.ts
23890
- import { readFile as readFile24, readdir as readdir10, stat as stat11 } from "fs/promises";
23891
- import { basename as basename7, join as join32, relative as relative21, resolve as resolve31, sep as sep21 } from "path";
24418
+ import { readFile as readFile25, readdir as readdir11, stat as stat12 } from "fs/promises";
24419
+ import { basename as basename8, join as join34, relative as relative22, resolve as resolve32, sep as sep22 } from "path";
23892
24420
  var FlavorIdeClient = class {
23893
24421
  #workspace;
23894
24422
  #home;
@@ -23899,8 +24427,8 @@ var FlavorIdeClient = class {
23899
24427
  #sessionId;
23900
24428
  #eventTail = Promise.resolve();
23901
24429
  constructor(options) {
23902
- this.#workspace = resolve31(options.workspace);
23903
- this.#home = resolve31(options.home);
24430
+ this.#workspace = resolve32(options.workspace);
24431
+ this.#home = resolve32(options.home);
23904
24432
  this.#environment = options.environment ?? process.env;
23905
24433
  this.#fetch = options.fetch ?? fetch;
23906
24434
  }
@@ -23984,17 +24512,17 @@ var FlavorIdeClient = class {
23984
24512
  };
23985
24513
  if (await this.#healthy(configured)) return configured;
23986
24514
  }
23987
- const directory = join32(this.#home, ".flavor-code", "ide");
24515
+ const directory = join34(this.#home, ".flavor-code", "ide");
23988
24516
  let entries;
23989
24517
  try {
23990
- entries = (await readdir10(directory)).filter((name) => name.endsWith(".lock"));
24518
+ entries = (await readdir11(directory)).filter((name) => name.endsWith(".lock"));
23991
24519
  } catch {
23992
24520
  return void 0;
23993
24521
  }
23994
24522
  const candidates = (await Promise.all(entries.map(async (name) => {
23995
- const path = join32(directory, name);
24523
+ const path = join34(directory, name);
23996
24524
  try {
23997
- const [contents, info] = await Promise.all([readFile24(path, "utf8"), stat11(path)]);
24525
+ const [contents, info] = await Promise.all([readFile25(path, "utf8"), stat12(path)]);
23998
24526
  const lock = parseLockfile(contents);
23999
24527
  return lock === void 0 ? void 0 : { lock, modified: info.mtimeMs };
24000
24528
  } catch {
@@ -24098,17 +24626,17 @@ function parsePort(value) {
24098
24626
  return Number.isInteger(port2) && port2 > 0 && port2 <= 65535 ? port2 : void 0;
24099
24627
  }
24100
24628
  function isWithin8(parent, child) {
24101
- const base = normalize(resolve31(parent));
24102
- const target = normalize(resolve31(child));
24103
- return target === base || target.startsWith(`${base}${sep21}`);
24629
+ const base = normalize(resolve32(parent));
24630
+ const target = normalize(resolve32(child));
24631
+ return target === base || target.startsWith(`${base}${sep22}`);
24104
24632
  }
24105
24633
  function normalize(path) {
24106
24634
  const normalized = path.normalize("NFC");
24107
24635
  return process.platform === "win32" ? normalized.toLowerCase() : normalized;
24108
24636
  }
24109
24637
  function relativeDisplay(workspace, filePath) {
24110
- const path = relative21(workspace, filePath).replaceAll("\\", "/");
24111
- return path && !path.startsWith("../") ? path : basename7(filePath);
24638
+ const path = relative22(workspace, filePath).replaceAll("\\", "/");
24639
+ return path && !path.startsWith("../") ? path : basename8(filePath);
24112
24640
  }
24113
24641
 
24114
24642
  // src/jobs/registry.ts
@@ -24129,8 +24657,8 @@ var JobRegistry = class {
24129
24657
  const id = `job-${randomUUID19().slice(0, 12)}`;
24130
24658
  const now = (/* @__PURE__ */ new Date()).toISOString();
24131
24659
  let settle;
24132
- const done = new Promise((resolve42) => {
24133
- settle = resolve42;
24660
+ const done = new Promise((resolve43) => {
24661
+ settle = resolve43;
24134
24662
  });
24135
24663
  const record3 = {
24136
24664
  id,
@@ -24254,7 +24782,7 @@ function snapshot2(job) {
24254
24782
  // src/terminal/service.ts
24255
24783
  import { randomUUID as randomUUID20 } from "crypto";
24256
24784
  import { createRequire as nodeCreateRequire2 } from "module";
24257
- import { isAbsolute as isAbsolute15, relative as relative22, resolve as resolve32, sep as sep22 } from "path";
24785
+ import { isAbsolute as isAbsolute16, relative as relative23, resolve as resolve33, sep as sep23 } from "path";
24258
24786
  var TerminalService = class {
24259
24787
  #root;
24260
24788
  #factory;
@@ -24262,7 +24790,7 @@ var TerminalService = class {
24262
24790
  #maxOutputChars;
24263
24791
  #sessions = /* @__PURE__ */ new Map();
24264
24792
  constructor(workspace, options = {}) {
24265
- this.#root = resolve32(workspace);
24793
+ this.#root = resolve33(workspace);
24266
24794
  this.#factory = options.factory ?? defaultFactory;
24267
24795
  this.#jobs = options.jobs;
24268
24796
  this.#maxOutputChars = options.maxOutputChars ?? 2e5;
@@ -24380,15 +24908,15 @@ function cleanEnvironment(environment) {
24380
24908
  return Object.fromEntries(Object.entries(environment).filter((entry2) => entry2[1] !== void 0));
24381
24909
  }
24382
24910
  function workspacePath(root, input) {
24383
- const path = resolve32(root, input);
24384
- const delta = relative22(root, path);
24385
- if (delta === ".." || delta.startsWith(`..${sep22}`) || isAbsolute15(delta)) throw new Error("Terminal cwd is outside the workspace");
24911
+ const path = resolve33(root, input);
24912
+ const delta = relative23(root, path);
24913
+ if (delta === ".." || delta.startsWith(`..${sep23}`) || isAbsolute16(delta)) throw new Error("Terminal cwd is outside the workspace");
24386
24914
  return path;
24387
24915
  }
24388
24916
 
24389
24917
  // src/tools/terminal.ts
24390
24918
  import { z as z40 } from "zod";
24391
- import { resolve as resolve33 } from "path";
24919
+ import { resolve as resolve34 } from "path";
24392
24920
  var Id = z40.string().regex(/^term-[a-f0-9-]+$/);
24393
24921
  var OpenInput = z40.object({ cwd: z40.string().optional(), shell: z40.string().optional(), args: z40.array(z40.string()).optional(), columns: z40.coerce.number().int().min(10).max(500).optional(), rows: z40.coerce.number().int().min(2).max(300).optional() });
24394
24922
  var OptionalBoolean = z40.union([z40.boolean(), z40.string().refine((value) => value === "true" || value === "false")]);
@@ -24402,8 +24930,8 @@ function createTerminalTools(terminals, workspace = process.cwd()) {
24402
24930
  name: "TerminalOpen",
24403
24931
  description: "Open a persistent interactive pseudo-terminal in the workspace",
24404
24932
  inputSchema: OpenInput,
24405
- paths: (input) => [resolve33(workspace, input.cwd ?? ".")],
24406
- permissions: (input) => ({ paths: [resolve33(workspace, input.cwd ?? ".")], command: input.shell ?? "system-shell", args: input.args ?? [], cwd: resolve33(workspace, input.cwd ?? ".") }),
24933
+ paths: (input) => [resolve34(workspace, input.cwd ?? ".")],
24934
+ permissions: (input) => ({ paths: [resolve34(workspace, input.cwd ?? ".")], command: input.shell ?? "system-shell", args: input.args ?? [], cwd: resolve34(workspace, input.cwd ?? ".") }),
24407
24935
  execute: async (input, _signal, context) => terminals.open({
24408
24936
  owner: owner(context),
24409
24937
  ...input.cwd === void 0 ? {} : { cwd: input.cwd },
@@ -24499,29 +25027,29 @@ function palSocketAddress(options) {
24499
25027
  import { createHash as createHash20 } from "crypto";
24500
25028
  import { Socket as Socket2 } from "net";
24501
25029
  import { tmpdir as tmpdir3 } from "os";
24502
- import { join as join34 } from "path";
25030
+ import { join as join36 } from "path";
24503
25031
  import { spawn as spawn7 } from "child_process";
24504
25032
 
24505
25033
  // src/pals/broker.ts
24506
25034
  import { createHash as createHash19, randomUUID as randomUUID23, timingSafeEqual } from "crypto";
24507
- import { chmod as chmod3, lstat as lstat12, mkdir as mkdir23, unlink as unlink6 } from "fs/promises";
25035
+ import { chmod as chmod3, lstat as lstat13, mkdir as mkdir23, unlink as unlink6 } from "fs/promises";
24508
25036
  import { createServer as createServer4, Socket } from "net";
24509
25037
  import { dirname as dirname23 } from "path";
24510
25038
 
24511
25039
  // src/pals/auth.ts
24512
25040
  import { randomBytes as randomBytes3, randomUUID as randomUUID21 } from "crypto";
24513
- import { chmod as chmod2, link as link2, mkdir as mkdir21, open as open12, readFile as readFile25, unlink as unlink4 } from "fs/promises";
24514
- import { homedir } from "os";
24515
- import { join as join33 } from "path";
25041
+ import { chmod as chmod2, link as link2, mkdir as mkdir21, open as open12, readFile as readFile26, unlink as unlink4 } from "fs/promises";
25042
+ import { homedir as homedir2 } from "os";
25043
+ import { join as join35 } from "path";
24516
25044
  var PAL_AUTH_TOKEN_BYTES = 32;
24517
25045
  var TOKEN_PATTERN = /^[a-f0-9]{64}$/;
24518
25046
  async function loadOrCreatePalAuthToken(options = {}) {
24519
- const directory = join33(options.home ?? homedir(), ".flavor-code", "pals");
24520
- const tokenPath = join33(directory, "auth-token");
25047
+ const directory = join35(options.home ?? homedir2(), ".flavor-code", "pals");
25048
+ const tokenPath = join35(directory, "auth-token");
24521
25049
  await mkdir21(directory, { recursive: true, mode: 448 });
24522
25050
  await chmod2(directory, 448);
24523
25051
  const token = (options.random ?? randomBytes3)(PAL_AUTH_TOKEN_BYTES).toString("hex");
24524
- const temporaryPath = join33(directory, `.auth-token-${process.pid}-${randomUUID21()}.tmp`);
25052
+ const temporaryPath = join35(directory, `.auth-token-${process.pid}-${randomUUID21()}.tmp`);
24525
25053
  let handle;
24526
25054
  try {
24527
25055
  handle = await open12(temporaryPath, "wx", 384);
@@ -24544,7 +25072,7 @@ async function loadOrCreatePalAuthToken(options = {}) {
24544
25072
  if (error.code !== "ENOENT") throw error;
24545
25073
  });
24546
25074
  }
24547
- const stored = (await readFile25(tokenPath, "utf8")).trim();
25075
+ const stored = (await readFile26(tokenPath, "utf8")).trim();
24548
25076
  if (!TOKEN_PATTERN.test(stored)) throw new Error("Pals authentication token store is invalid");
24549
25077
  await chmod2(tokenPath, 384);
24550
25078
  return stored;
@@ -24556,7 +25084,7 @@ function validatePalAuthToken(token) {
24556
25084
 
24557
25085
  // src/pals/lifecycle.ts
24558
25086
  import { randomUUID as randomUUID22 } from "crypto";
24559
- import { mkdir as mkdir22, open as open13, readFile as readFile26, rename as rename11, stat as stat12, unlink as unlink5 } from "fs/promises";
25087
+ import { mkdir as mkdir22, open as open13, readFile as readFile27, rename as rename11, stat as stat13, unlink as unlink5 } from "fs/promises";
24560
25088
  import { dirname as dirname22 } from "path";
24561
25089
  function defaultProcessAlive(pid) {
24562
25090
  if (!Number.isSafeInteger(pid) || pid <= 0) return true;
@@ -24588,7 +25116,7 @@ async function acquirePalFileLock(options) {
24588
25116
  handle = await open13(options.path, "wx", 384);
24589
25117
  } catch (error) {
24590
25118
  if (error.code !== "EEXIST") throw error;
24591
- const [contents, info] = await Promise.all([readFile26(options.path, "utf8").catch(() => ""), stat12(options.path).catch(() => void 0)]);
25119
+ const [contents, info] = await Promise.all([readFile27(options.path, "utf8").catch(() => ""), stat13(options.path).catch(() => void 0)]);
24592
25120
  if (await options.endpointLive()) return void 0;
24593
25121
  const existing = parseMetadata(contents);
24594
25122
  if (existing !== void 0 && (options.processAlive ?? defaultProcessAlive)(existing.pid)) return void 0;
@@ -24618,7 +25146,7 @@ async function acquirePalFileLock(options) {
24618
25146
  await handle.close();
24619
25147
  }
24620
25148
  const release = async () => {
24621
- const current = parseMetadata(await readFile26(options.path, "utf8").catch(() => ""));
25149
+ const current = parseMetadata(await readFile27(options.path, "utf8").catch(() => ""));
24622
25150
  if (current?.nonce !== nonce) return;
24623
25151
  await unlink5(options.path).catch((error) => {
24624
25152
  if (error.code !== "ENOENT") throw error;
@@ -24634,15 +25162,15 @@ async function acquirePalFileLock(options) {
24634
25162
  // src/pals/broker-cli.ts
24635
25163
  var startupAttempts = /* @__PURE__ */ new Map();
24636
25164
  function delay3(milliseconds) {
24637
- return new Promise((resolve42) => setTimeout(resolve42, milliseconds));
25165
+ return new Promise((resolve43) => setTimeout(resolve43, milliseconds));
24638
25166
  }
24639
25167
  function canConnect(address) {
24640
- return new Promise((resolve42) => {
25168
+ return new Promise((resolve43) => {
24641
25169
  const socket = new Socket2();
24642
25170
  const finish = (connected) => {
24643
25171
  socket.removeAllListeners();
24644
25172
  socket.destroy();
24645
- resolve42(connected);
25173
+ resolve43(connected);
24646
25174
  };
24647
25175
  socket.once("connect", () => finish(true));
24648
25176
  socket.once("error", () => finish(false));
@@ -24651,7 +25179,7 @@ function canConnect(address) {
24651
25179
  }
24652
25180
  function startupLockPath(address) {
24653
25181
  const digest = createHash20("sha256").update(address, "utf8").digest("hex").slice(0, 24);
24654
- return join34(tmpdir3(), `flavor-code-pals-${digest}.startup.lock`);
25182
+ return join36(tmpdir3(), `flavor-code-pals-${digest}.startup.lock`);
24655
25183
  }
24656
25184
  async function spawnProductionBroker(address) {
24657
25185
  const entry2 = process.argv[1];
@@ -24833,8 +25361,8 @@ var PalClient = class {
24833
25361
  let disconnectTimer;
24834
25362
  await Promise.race([
24835
25363
  this.request({ version: 1, type: "disconnect", requestId: randomUUID24() }).catch(() => void 0),
24836
- new Promise((resolve42) => {
24837
- disconnectTimer = setTimeout(resolve42, Math.min(this.requestTimeoutMs, 250));
25364
+ new Promise((resolve43) => {
25365
+ disconnectTimer = setTimeout(resolve43, Math.min(this.requestTimeoutMs, 250));
24838
25366
  })
24839
25367
  ]);
24840
25368
  if (disconnectTimer !== void 0) clearTimeout(disconnectTimer);
@@ -24880,7 +25408,7 @@ var PalClient = class {
24880
25408
  return presence;
24881
25409
  }
24882
25410
  openSocket() {
24883
- return new Promise((resolve42, reject) => {
25411
+ return new Promise((resolve43, reject) => {
24884
25412
  let settled = false;
24885
25413
  const rejectConnection = (error) => {
24886
25414
  if (settled) return;
@@ -24903,7 +25431,7 @@ var PalClient = class {
24903
25431
  settled = true;
24904
25432
  if (this.connectingReject === rejectConnection) this.connectingReject = void 0;
24905
25433
  socket.off("error", onInitialError);
24906
- resolve42();
25434
+ resolve43();
24907
25435
  };
24908
25436
  const onInitialError = (error) => {
24909
25437
  socket.off("connect", onConnect);
@@ -24934,12 +25462,12 @@ var PalClient = class {
24934
25462
  if (this.closed || socket === void 0 || socket.destroyed || !socket.writable) {
24935
25463
  return Promise.reject(new Error(this.closed ? "Pal client is closed" : "Pal client is not connected"));
24936
25464
  }
24937
- return new Promise((resolve42, reject) => {
25465
+ return new Promise((resolve43, reject) => {
24938
25466
  const timer = setTimeout(() => {
24939
25467
  this.pending.delete(request.requestId);
24940
25468
  reject(new Error(`Pal request '${request.type}' timed out`));
24941
25469
  }, this.requestTimeoutMs);
24942
- this.pending.set(request.requestId, { resolve: resolve42, reject, timer });
25470
+ this.pending.set(request.requestId, { resolve: resolve43, reject, timer });
24943
25471
  let frame;
24944
25472
  try {
24945
25473
  frame = encodeControlFrame(BrokerRequestSchema.parse(request));
@@ -25304,7 +25832,7 @@ function createPromptEnvironment(input = {}) {
25304
25832
  date: Number.isNaN(now.getTime()) ? "unknown" : localDateKey(now),
25305
25833
  platform: promptEnvironmentValue(input.platform ?? process.platform),
25306
25834
  osVersion: promptEnvironmentValue(input.osVersion ?? `${osVersion()} ${osRelease()}`),
25307
- shell: promptEnvironmentValue(input.shell ?? process.env.ComSpec ?? process.env.SHELL),
25835
+ shell: promptEnvironmentValue(input.shell ?? resolveRuntimeShell()?.command),
25308
25836
  isGitRepository: input.isGitRepository ?? "unknown"
25309
25837
  };
25310
25838
  }
@@ -25343,8 +25871,8 @@ var ApprovalBridge = class {
25343
25871
  }
25344
25872
  };
25345
25873
  async function createProductionRuntime(options) {
25346
- const workspace = resolve34(options.workspace ?? process.cwd());
25347
- const home = resolve34(options.home ?? homedir2());
25874
+ const workspace = resolve35(options.workspace ?? process.cwd());
25875
+ const home = resolve35(options.home ?? homedir3());
25348
25876
  const environment = options.environment ?? process.env;
25349
25877
  const ide = new FlavorIdeClient({ workspace, home, environment });
25350
25878
  const ideReady = ide.initialize();
@@ -25534,6 +26062,7 @@ async function createProductionRuntime(options) {
25534
26062
  createGitHistoryTool(workspace),
25535
26063
  createShellTool(workspace, {
25536
26064
  jobs,
26065
+ environment,
25537
26066
  ...executionEnvironment === void 0 ? {} : { executionEnvironment }
25538
26067
  }),
25539
26068
  createWebFetchTool(),
@@ -25587,8 +26116,8 @@ async function createProductionRuntime(options) {
25587
26116
  const registeredProviders = registration.registered;
25588
26117
  let effectiveLlm = registration.effectiveLlm;
25589
26118
  const pluginHost = new PluginHost({
25590
- globalPluginDirs: [join35(home, ".flavor-code", "plugins")],
25591
- projectPluginDirs: [join35(workspace, ".flavor", "plugins")],
26119
+ globalPluginDirs: [join37(home, ".flavor-code", "plugins")],
26120
+ projectPluginDirs: [join37(workspace, ".flavor", "plugins")],
25592
26121
  config,
25593
26122
  // Existing project plugins (including astgraph and superharness) use Node.js
25594
26123
  // built-ins. The Worker/vm sandbox deliberately blocks those imports, so it
@@ -25692,12 +26221,12 @@ async function createProductionRuntime(options) {
25692
26221
  try {
25693
26222
  if (options.collaboration !== void 0) {
25694
26223
  const collaboration = options.collaboration;
25695
- const defaultAlias = `${basename8(workspace) || "flavor"}-${collaboration.instanceId.slice(0, 8)}`.slice(0, 64);
26224
+ const defaultAlias = `${basename9(workspace) || "flavor"}-${collaboration.instanceId.slice(0, 8)}`.slice(0, 64);
25696
26225
  const alias = PalAliasSchema.parse(collaboration.alias?.trim() || defaultAlias);
25697
26226
  const address = palSocketAddress({
25698
26227
  platform: process.platform,
25699
26228
  userScope: home,
25700
- runtimeDir: join35(tmpdir4(), `flavor-code-pals-${createHash21("sha256").update(home).digest("hex").slice(0, 16)}`)
26229
+ runtimeDir: join37(tmpdir4(), `flavor-code-pals-${createHash21("sha256").update(home).digest("hex").slice(0, 16)}`)
25701
26230
  });
25702
26231
  const baseCollaborationClient = collaboration.client ?? collaboration.createClient?.({
25703
26232
  instanceId: collaboration.instanceId,
@@ -25808,8 +26337,8 @@ async function createProductionRuntime(options) {
25808
26337
  diagnostics.push(`MCP servers could not start: ${message(error)}`);
25809
26338
  });
25810
26339
  const skills = new SkillRegistry({
25811
- globalRoots: [join35(home, ".flavor-code", "skills")],
25812
- projectRoots: [join35(workspace, ".flavor", "skills"), ...pluginSkillRoots],
26340
+ globalRoots: [join37(home, ".flavor-code", "skills")],
26341
+ projectRoots: [join37(workspace, ".flavor", "skills"), ...pluginSkillRoots],
25813
26342
  authorizeResource: async () => true,
25814
26343
  disabledNames: config.skills.disabled
25815
26344
  });
@@ -25817,7 +26346,7 @@ async function createProductionRuntime(options) {
25817
26346
  skillsReady.catch(() => void 0);
25818
26347
  tools.push(createSkillTool(skills), createSkillResourceTool(skills));
25819
26348
  if (options.extraTools !== void 0) tools.push(...options.extraTools);
25820
- const flavor = await optionalText2(join35(workspace, "FLAVOR.md"));
26349
+ const flavor = await optionalText2(join37(workspace, "FLAVOR.md"));
25821
26350
  const instructionBaseline = await workspaceInstructions.baseline();
25822
26351
  let memoryContext;
25823
26352
  if (memoryStore !== void 0) {
@@ -25998,7 +26527,7 @@ async function createProductionRuntime(options) {
25998
26527
  now: /* @__PURE__ */ new Date(),
25999
26528
  platform: process.platform,
26000
26529
  osVersion: `${osVersion()} ${osRelease()}`,
26001
- shell: environment.ComSpec ?? environment.SHELL ?? "unknown",
26530
+ shell: resolveRuntimeShell(process.platform, environment)?.command ?? "unknown",
26002
26531
  isGitRepository: await gitRepository
26003
26532
  });
26004
26533
  const createContext = (agent, agentTools, contextModelId, parentContext) => {
@@ -26327,8 +26856,8 @@ async function createProductionRuntime(options) {
26327
26856
  modelId,
26328
26857
  agent,
26329
26858
  providerError,
26330
- errorCode,
26331
- errorMessage: errorMessage3,
26859
+ errorCode: errorCode3,
26860
+ errorMessage: errorMessage4,
26332
26861
  attempt,
26333
26862
  maxAttempts,
26334
26863
  purpose,
@@ -26343,8 +26872,8 @@ async function createProductionRuntime(options) {
26343
26872
  event: "ModelCallFailure",
26344
26873
  model: typeof modelId === "string" ? modelId : void 0,
26345
26874
  agent: typeof agent === "string" ? agent : void 0,
26346
- errorCode: typeof errorCode === "string" ? errorCode : void 0,
26347
- errorMessage: typeof errorMessage3 === "string" ? errorMessage3 : void 0,
26875
+ errorCode: typeof errorCode3 === "string" ? errorCode3 : void 0,
26876
+ errorMessage: typeof errorMessage4 === "string" ? errorMessage4 : void 0,
26348
26877
  attempt: typeof attempt === "number" ? attempt : void 0,
26349
26878
  maxAttempts: typeof maxAttempts === "number" ? maxAttempts : void 0,
26350
26879
  purpose: typeof purpose === "string" ? purpose : void 0,
@@ -26387,6 +26916,7 @@ async function createProductionRuntime(options) {
26387
26916
  createGrepTool(input.workspace),
26388
26917
  createShellTool(input.workspace, {
26389
26918
  jobs,
26919
+ environment,
26390
26920
  ...loopExecutionEnvironment === void 0 ? {} : { executionEnvironment: loopExecutionEnvironment }
26391
26921
  }),
26392
26922
  createWebFetchTool(),
@@ -26399,13 +26929,13 @@ async function createProductionRuntime(options) {
26399
26929
  evolveService.toolDefinition(),
26400
26930
  ...mcpTools
26401
26931
  ];
26402
- const loopFlavor = await optionalText2(join35(input.workspace, "FLAVOR.md"));
26932
+ const loopFlavor = await optionalText2(join37(input.workspace, "FLAVOR.md"));
26403
26933
  const loopInstructionBaseline = await loopInstructions.baseline();
26404
26934
  const loopEnvironment = createPromptEnvironment({
26405
26935
  now: /* @__PURE__ */ new Date(),
26406
26936
  platform: process.platform,
26407
26937
  osVersion: `${osVersion()} ${osRelease()}`,
26408
- shell: environment.ComSpec ?? environment.SHELL ?? "unknown",
26938
+ shell: resolveRuntimeShell(process.platform, environment)?.command ?? "unknown",
26409
26939
  isGitRepository: await detectGitRepository(input.workspace)
26410
26940
  });
26411
26941
  let compactionInputTokens = 0;
@@ -26758,10 +27288,10 @@ async function createProductionRuntime(options) {
26758
27288
  explain: (query, focus, signal, onText) => runExplain({
26759
27289
  graph: new DesktopAstGraphService(workspace),
26760
27290
  readFile: async (path) => {
26761
- const root = resolve34(workspace);
26762
- const abs = resolve34(root, path);
26763
- if (abs !== root && !abs.startsWith(root + sep23)) throw new Error(`path outside workspace: ${path}`);
26764
- return readFile27(abs, "utf8");
27291
+ const root = resolve35(workspace);
27292
+ const abs = resolve35(root, path);
27293
+ if (abs !== root && !abs.startsWith(root + sep24)) throw new Error(`path outside workspace: ${path}`);
27294
+ return readFile28(abs, "utf8");
26765
27295
  },
26766
27296
  history: async (path, limit) => fileHistory(workspace, path, limit),
26767
27297
  questions,
@@ -26773,7 +27303,7 @@ async function createProductionRuntime(options) {
26773
27303
  }, query, focus, signal),
26774
27304
  audit: async (toolFilter) => {
26775
27305
  try {
26776
- const raw = await readFile27(auditLogger.path, "utf8");
27306
+ const raw = await readFile28(auditLogger.path, "utf8");
26777
27307
  const lines = raw.trim().split("\n").filter((line) => line.length > 0);
26778
27308
  const entries = lines.map((line) => {
26779
27309
  try {
@@ -26810,9 +27340,10 @@ ${summary2}`;
26810
27340
  return `Failed to read audit log: ${message(error)}`;
26811
27341
  }
26812
27342
  },
27343
+ doctor: async () => formatDoctorReport(await runDoctor({ workspace, home, environment })),
26813
27344
  usage: async () => {
26814
27345
  try {
26815
- const raw = await readFile27(usageLogPath(), "utf8");
27346
+ const raw = await readFile28(usageLogPath(), "utf8");
26816
27347
  return formatUsageSummary(summarizeUsage(parseUsageEntries(raw)));
26817
27348
  } catch (error) {
26818
27349
  if (typeof error === "object" && error !== null && "code" in error && error.code === "ENOENT") {
@@ -26965,7 +27496,7 @@ ${renderMemoryDocument(entries)}`;
26965
27496
  return `Provider "${providerName}" is missing authorizationUrl, tokenUrl, or clientId.`;
26966
27497
  }
26967
27498
  try {
26968
- const tokenStore = createFileTokenStore(join35(home, ".flavor-code", "auth.json"));
27499
+ const tokenStore = createFileTokenStore(join37(home, ".flavor-code", "auth.json"));
26969
27500
  const credentialId = oauthCredentialId(oauthConfig.tokenUrl, oauthConfig.clientId);
26970
27501
  await migrateLegacyOAuthToken(tokenStore, providerName, credentialId);
26971
27502
  const oauth = new OAuthCallbackAuthProvider({
@@ -27012,7 +27543,7 @@ ${renderMemoryDocument(entries)}`;
27012
27543
  }
27013
27544
  },
27014
27545
  async logout() {
27015
- const tokenStore = createFileTokenStore(join35(home, ".flavor-code", "auth.json"));
27546
+ const tokenStore = createFileTokenStore(join37(home, ".flavor-code", "auth.json"));
27016
27547
  const tokens2 = await tokenStore.load();
27017
27548
  const hadCredentials = Object.keys(tokens2).length > 0 || effectiveLlm !== void 0;
27018
27549
  if (!hadCredentials) {
@@ -27304,13 +27835,13 @@ async function workspaceFingerprint(workspace) {
27304
27835
  let remaining = 5 * 1024 * 1024;
27305
27836
  for (const name of untracked.stdout.split("\0").filter(Boolean).sort()) {
27306
27837
  if (name === ".flavor/loops" || name.startsWith(".flavor/loops/")) continue;
27307
- const path = resolve34(workspace, name);
27308
- const relativePath = relative23(resolve34(workspace), path);
27309
- if (relativePath === ".." || relativePath.startsWith(`..${sep23}`)) continue;
27838
+ const path = resolve35(workspace, name);
27839
+ const relativePath = relative24(resolve35(workspace), path);
27840
+ if (relativePath === ".." || relativePath.startsWith(`..${sep24}`)) continue;
27310
27841
  hash.update(name);
27311
27842
  if (remaining <= 0) continue;
27312
27843
  try {
27313
- const content = await readFile27(path);
27844
+ const content = await readFile28(path);
27314
27845
  const slice = content.subarray(0, remaining);
27315
27846
  hash.update(slice);
27316
27847
  remaining -= slice.length;
@@ -27540,7 +28071,7 @@ async function registerConfiguredAdapters(providers, registry, environment, diag
27540
28071
  const configured = { ...providers };
27541
28072
  if (configured.openai === void 0 && environment.OPENAI_API_KEY) configured.openai = { type: "openai", apiKey: environment.OPENAI_API_KEY };
27542
28073
  if (configured.anthropic === void 0 && environment.ANTHROPIC_API_KEY) configured.anthropic = { type: "anthropic", apiKey: environment.ANTHROPIC_API_KEY };
27543
- const oauthTokenStore = createFileTokenStore(join35(home, ".flavor-code", "auth.json"));
28074
+ const oauthTokenStore = createFileTokenStore(join37(home, ".flavor-code", "auth.json"));
27544
28075
  const registered = [];
27545
28076
  let effectiveLlm;
27546
28077
  for (const [name, provider] of Object.entries(configured)) {
@@ -27809,7 +28340,7 @@ async function generateSleepReview(registry, modelId, prompt, signal) {
27809
28340
  }
27810
28341
  async function optionalText2(path) {
27811
28342
  try {
27812
- return await readFile27(path, "utf8");
28343
+ return await readFile28(path, "utf8");
27813
28344
  } catch (error) {
27814
28345
  if (typeof error === "object" && error !== null && "code" in error && error.code === "ENOENT") return void 0;
27815
28346
  throw error;
@@ -27971,13 +28502,13 @@ function createMemoryTaskId() {
27971
28502
 
27972
28503
  // src/desktop/runtime-controller.ts
27973
28504
  import { createHash as createHash27 } from "crypto";
27974
- import { mkdir as mkdir27, readFile as readFile35, readdir as readdir12, rm as rm15, stat as stat15, writeFile as writeFile17 } from "fs/promises";
27975
- import { homedir as homedir4 } from "os";
27976
- import { basename as basename9, join as join45, resolve as resolve37 } from "path";
28505
+ import { mkdir as mkdir27, readFile as readFile36, readdir as readdir13, rm as rm15, stat as stat16, writeFile as writeFile17 } from "fs/promises";
28506
+ import { homedir as homedir5 } from "os";
28507
+ import { basename as basename10, join as join47, resolve as resolve38 } from "path";
27977
28508
 
27978
28509
  // src/d2c/workflow.ts
27979
28510
  import { createHash as createHash22 } from "crypto";
27980
- import { join as join36 } from "path";
28511
+ import { join as join38 } from "path";
27981
28512
  import { z as z42 } from "zod";
27982
28513
 
27983
28514
  // src/d2c/workflow-shared.ts
@@ -28123,7 +28654,7 @@ function reviewsFor(report, previous) {
28123
28654
  function implementationDirectory(report) {
28124
28655
  const source = report.implementation.source;
28125
28656
  if (!/^https?:\/\//i.test(source)) return source;
28126
- return join36("src", "d2c-output", report.task);
28657
+ return join38("src", "d2c-output", report.task);
28127
28658
  }
28128
28659
  function createWorkflow(report, framework) {
28129
28660
  const now = (/* @__PURE__ */ new Date()).toISOString();
@@ -28247,7 +28778,7 @@ function applyQualityIssueDecision(workflow, issueId2, decision) {
28247
28778
  };
28248
28779
  }
28249
28780
  function workflowPath(workspace, task) {
28250
- return join36(taskDir(workspace, task), "workflow.json");
28781
+ return join38(taskDir(workspace, task), "workflow.json");
28251
28782
  }
28252
28783
  async function readWorkflow(workspace, task) {
28253
28784
  return (await readRecoverableFile(workflowPath(workspace, task), decodeWorkflow))?.value;
@@ -28500,8 +29031,8 @@ function confirmApiMapping(mapping, operationKey, operations) {
28500
29031
  }
28501
29032
 
28502
29033
  // src/d2c/modules.ts
28503
- import { readFile as readFile28 } from "fs/promises";
28504
- import { isAbsolute as isAbsolute16, join as join37, normalize as normalize2, posix, sep as sep24, win32 as win322 } from "path";
29034
+ import { readFile as readFile29 } from "fs/promises";
29035
+ import { isAbsolute as isAbsolute17, join as join39, normalize as normalize2, posix, sep as sep25, win32 as win322 } from "path";
28505
29036
  import { z as z43 } from "zod";
28506
29037
  var ModuleSchema = z43.object({
28507
29038
  id: z43.string().regex(/^[A-Za-z0-9][A-Za-z0-9._-]{0,127}$/),
@@ -28513,20 +29044,20 @@ var ModuleSchema = z43.object({
28513
29044
  }).strict();
28514
29045
  var ManifestSchema3 = z43.object({ schema: z43.literal(1), modules: z43.array(ModuleSchema).min(1).max(500) }).passthrough();
28515
29046
  function safeSource(path) {
28516
- if (isAbsolute16(path) || posix.isAbsolute(path) || win322.isAbsolute(path) || path.includes("\0")) return false;
29047
+ if (isAbsolute17(path) || posix.isAbsolute(path) || win322.isAbsolute(path) || path.includes("\0")) return false;
28517
29048
  const value = normalize2(path);
28518
29049
  const portableSegments = path.replaceAll("\\", "/").split("/");
28519
- return value !== ".." && !value.startsWith(`..${sep24}`) && !portableSegments.includes("..");
29050
+ return value !== ".." && !value.startsWith(`..${sep25}`) && !portableSegments.includes("..");
28520
29051
  }
28521
29052
  function d2cOutputDirectory(workspace, task) {
28522
29053
  if (!D2C_TASK_PATTERN.test(task)) throw new Error(`Invalid D2C task: ${task}`);
28523
- return join37(workspace, "src", "d2c-output", task);
29054
+ return join39(workspace, "src", "d2c-output", task);
28524
29055
  }
28525
29056
  async function readD2cModules(workspace, task) {
28526
29057
  const project = d2cOutputDirectory(workspace, task);
28527
29058
  let raw;
28528
29059
  try {
28529
- raw = await readFile28(join37(project, "d2c.modules.json"), "utf8");
29060
+ raw = await readFile29(join39(project, "d2c.modules.json"), "utf8");
28530
29061
  } catch {
28531
29062
  return [{ id: "page", label: task, sourceFiles: ["src/App.vue", "src/App.jsx"], keywords: [task] }];
28532
29063
  }
@@ -28546,8 +29077,8 @@ async function readD2cModules(workspace, task) {
28546
29077
  }
28547
29078
 
28548
29079
  // src/d2c/integration.ts
28549
- import { mkdir as mkdir24, readFile as readFile29, writeFile as writeFile13 } from "fs/promises";
28550
- import { join as join38 } from "path";
29080
+ import { mkdir as mkdir24, readFile as readFile30, writeFile as writeFile13 } from "fs/promises";
29081
+ import { join as join40 } from "path";
28551
29082
  function record2(value) {
28552
29083
  return typeof value === "object" && value !== null && !Array.isArray(value) ? value : void 0;
28553
29084
  }
@@ -28683,19 +29214,19 @@ async def reset_acceptance(request: Request):
28683
29214
  }
28684
29215
  async function generateIntegrationArtifacts(projectDir, document, mappings, options = {}) {
28685
29216
  if (mappings.some((mapping) => mapping.status === "needs-confirmation")) throw new Error("Confirm every uncertain OpenAPI mapping before generating integration code");
28686
- const packagePath = join38(projectDir, "package.json");
29217
+ const packagePath = join40(projectDir, "package.json");
28687
29218
  let pkg;
28688
29219
  try {
28689
- pkg = JSON.parse(await readFile29(packagePath, "utf8"));
29220
+ pkg = JSON.parse(await readFile30(packagePath, "utf8"));
28690
29221
  } catch {
28691
29222
  throw new Error(`D2C integration requires a valid package.json at ${packagePath}`);
28692
29223
  }
28693
29224
  const scripts = options.pythonServer ? { ...record2(pkg.scripts) ?? {} } : { ...record2(pkg.scripts) ?? {}, mock: "node mock/server.mjs" };
28694
29225
  const dependencies2 = options.pythonServer ? { ...record2(pkg.dependencies) ?? {}, axios: "^1.7.9" } : { ...record2(pkg.dependencies) ?? {}, axios: "^1.7.9", express: "^5.2.1" };
28695
29226
  const selected = [...new Set(mappings.map((mapping) => mapping.operationKey))].map((key) => document.operations.find((operation) => operation.id === key)).filter((operation) => operation !== void 0);
28696
- const apiDir = join38(projectDir, "src", "api");
28697
- const mockDir = join38(projectDir, "mock");
28698
- const serverDir = join38(projectDir, "server");
29227
+ const apiDir = join40(projectDir, "src", "api");
29228
+ const mockDir = join40(projectDir, "mock");
29229
+ const serverDir = join40(projectDir, "server");
28699
29230
  await Promise.all([
28700
29231
  mkdir24(apiDir, { recursive: true }),
28701
29232
  ...options.pythonServer ? [] : [mkdir24(mockDir, { recursive: true })],
@@ -28705,23 +29236,23 @@ async function generateIntegrationArtifacts(projectDir, document, mappings, opti
28705
29236
  if (options.pythonServer) files.push("server/main.py", "server/requirements.txt", "server/README.md", "server/flavor-runtime.json");
28706
29237
  else files.push("mock/server.mjs");
28707
29238
  await Promise.all([
28708
- writeFile13(join38(apiDir, "http.js"), `import axios from "axios";
29239
+ writeFile13(join40(apiDir, "http.js"), `import axios from "axios";
28709
29240
 
28710
29241
  export const http = axios.create({ baseURL: import.meta.env.VITE_API_BASE_URL || ${JSON.stringify(document.baseUrl ?? "")}, timeout: 15000 });
28711
29242
 
28712
29243
  http.interceptors.response.use((response) => response, (error) => Promise.reject({ name: "ApiError", status: error.response?.status, message: error.response?.data?.message || error.message, cause: error }));
28713
29244
  `),
28714
- writeFile13(join38(apiDir, "d2c-api.js"), apiModule(selected)),
28715
- writeFile13(join38(apiDir, "d2c-bindings.json"), `${JSON.stringify({ schema: 1, generatedAt: (/* @__PURE__ */ new Date()).toISOString(), mappings }, null, 2)}
29245
+ writeFile13(join40(apiDir, "d2c-api.js"), apiModule(selected)),
29246
+ writeFile13(join40(apiDir, "d2c-bindings.json"), `${JSON.stringify({ schema: 1, generatedAt: (/* @__PURE__ */ new Date()).toISOString(), mappings }, null, 2)}
28716
29247
  `),
28717
- ...options.pythonServer ? [] : [writeFile13(join38(mockDir, "server.mjs"), mockModule({ ...document, operations: selected }))],
29248
+ ...options.pythonServer ? [] : [writeFile13(join40(mockDir, "server.mjs"), mockModule({ ...document, operations: selected }))],
28718
29249
  writeFile13(packagePath, `${JSON.stringify({ ...pkg, scripts, dependencies: dependencies2 }, null, 2)}
28719
29250
  `),
28720
29251
  ...options.pythonServer ? [
28721
- writeFile13(join38(serverDir, "main.py"), pythonServerModule({ ...document, operations: selected })),
28722
- writeFile13(join38(serverDir, "requirements.txt"), "fastapi>=0.116,<1\nuvicorn[standard]>=0.35,<1\nsqlalchemy>=2.0,<3\n"),
28723
- writeFile13(join38(serverDir, "README.md"), "# Python API service\n\nThis is the real integration service, not a mock. Development defaults to SQLite through `DATABASE_URL=sqlite:///./data/app.db`. Set the same environment variable to a SQLAlchemy MySQL or PostgreSQL URL for deployment; keep schema changes in migrations instead of database-specific SQL. Flavor Code starts the service automatically for acceptance.\n"),
28724
- writeFile13(join38(serverDir, "flavor-runtime.json"), `${JSON.stringify({
29252
+ writeFile13(join40(serverDir, "main.py"), pythonServerModule({ ...document, operations: selected })),
29253
+ writeFile13(join40(serverDir, "requirements.txt"), "fastapi>=0.116,<1\nuvicorn[standard]>=0.35,<1\nsqlalchemy>=2.0,<3\n"),
29254
+ writeFile13(join40(serverDir, "README.md"), "# Python API service\n\nThis is the real integration service, not a mock. Development defaults to SQLite through `DATABASE_URL=sqlite:///./data/app.db`. Set the same environment variable to a SQLAlchemy MySQL or PostgreSQL URL for deployment; keep schema changes in migrations instead of database-specific SQL. Flavor Code starts the service automatically for acceptance.\n"),
29255
+ writeFile13(join40(serverDir, "flavor-runtime.json"), `${JSON.stringify({
28725
29256
  schema: 1,
28726
29257
  kind: "python-fastapi",
28727
29258
  cwd: "server",
@@ -28740,12 +29271,12 @@ http.interceptors.response.use((response) => response, (error) => Promise.reject
28740
29271
 
28741
29272
  // src/d2c/mock-runner.ts
28742
29273
  import { spawn as spawn8 } from "child_process";
28743
- import { access as access3, readFile as readFile30 } from "fs/promises";
29274
+ import { access as access4, readFile as readFile31 } from "fs/promises";
28744
29275
  import { createServer as createServer5 } from "net";
28745
- import { join as join39 } from "path";
29276
+ import { join as join41 } from "path";
28746
29277
  async function exists3(path) {
28747
29278
  try {
28748
- await access3(path);
29279
+ await access4(path);
28749
29280
  return true;
28750
29281
  } catch {
28751
29282
  return false;
@@ -28850,7 +29381,7 @@ ${output.trim()}` : ""}`);
28850
29381
  }
28851
29382
  }
28852
29383
  async function runPythonBackend(projectDir, options) {
28853
- const serverDir = join39(projectDir, "server");
29384
+ const serverDir = join41(projectDir, "server");
28854
29385
  if (options.installDependencies !== false) await installPython(serverDir, options.signal);
28855
29386
  const selectedPort = await port();
28856
29387
  const url = `http://127.0.0.1:${selectedPort}`;
@@ -28896,20 +29427,20 @@ ${managed.output().slice(-4e3)}`);
28896
29427
  };
28897
29428
  }
28898
29429
  async function runD2cMockServer(projectDir, options = {}) {
28899
- const runtimePath = join39(projectDir, "server", "flavor-runtime.json");
29430
+ const runtimePath = join41(projectDir, "server", "flavor-runtime.json");
28900
29431
  if (await exists3(runtimePath)) {
28901
29432
  let runtime;
28902
29433
  try {
28903
- runtime = JSON.parse(await readFile30(runtimePath, "utf8"));
29434
+ runtime = JSON.parse(await readFile31(runtimePath, "utf8"));
28904
29435
  } catch {
28905
29436
  throw new Error(`E2E backend runtime manifest is invalid at ${runtimePath}`);
28906
29437
  }
28907
29438
  if (runtime.schema !== 1 || runtime.kind !== "python-fastapi") throw new Error("Unsupported E2E backend runtime manifest");
28908
29439
  return runPythonBackend(projectDir, options);
28909
29440
  }
28910
- const serverPath = join39(projectDir, "mock", "server.mjs");
29441
+ const serverPath = join41(projectDir, "mock", "server.mjs");
28911
29442
  if (!await exists3(serverPath)) throw new Error(`D2C mock server is not generated at ${serverPath}`);
28912
- if (options.installDependencies === false && !await exists3(join39(projectDir, "node_modules", "express", "package.json"))) {
29443
+ if (options.installDependencies === false && !await exists3(join41(projectDir, "node_modules", "express", "package.json"))) {
28913
29444
  throw new Error("D2C mock server dependencies are not installed");
28914
29445
  }
28915
29446
  if (options.installDependencies !== false) await install(projectDir, options.signal);
@@ -28951,13 +29482,13 @@ ${managed.output().slice(-4e3)}`);
28951
29482
 
28952
29483
  // src/d2c/product.ts
28953
29484
  import { createHash as createHash24, randomUUID as randomUUID27 } from "crypto";
28954
- import { copyFile as copyFile2, mkdir as mkdir25, readFile as readFile32, rename as rename13, stat as stat13, unlink as unlink7, writeFile as writeFile15 } from "fs/promises";
28955
- import { join as join40 } from "path";
29485
+ import { copyFile as copyFile2, mkdir as mkdir25, readFile as readFile33, rename as rename13, stat as stat14, unlink as unlink7, writeFile as writeFile15 } from "fs/promises";
29486
+ import { join as join42 } from "path";
28956
29487
  import { z as z44 } from "zod";
28957
29488
 
28958
29489
  // src/e2e/prd-governance.ts
28959
29490
  import { createHash as createHash23, randomUUID as randomUUID26 } from "crypto";
28960
- import { readFile as readFile31, rename as rename12, rm as rm13, writeFile as writeFile14 } from "fs/promises";
29491
+ import { readFile as readFile32, rename as rename12, rm as rm13, writeFile as writeFile14 } from "fs/promises";
28961
29492
  function hashPrd(markdown) {
28962
29493
  return createHash23("sha256").update(markdown, "utf8").digest("hex");
28963
29494
  }
@@ -29034,14 +29565,14 @@ function approvePrd(markdown, now = /* @__PURE__ */ new Date()) {
29034
29565
  return { hash: hashPrd(markdown), approvedAt: now.toISOString(), criteria };
29035
29566
  }
29036
29567
  async function assertApprovedPrd(path, approved) {
29037
- const current = await readFile31(path, "utf8");
29568
+ const current = await readFile32(path, "utf8");
29038
29569
  if (hashPrd(current) !== approved.hash) {
29039
29570
  throw new Error("PRD_LOCK_VIOLATION: confirmed PRD content changed after approval");
29040
29571
  }
29041
29572
  return approved;
29042
29573
  }
29043
29574
  async function updatePrdSectionFile(path, input) {
29044
- const current = await readFile31(path, "utf8");
29575
+ const current = await readFile32(path, "utf8");
29045
29576
  if (hashPrd(current) !== input.expectedHash) {
29046
29577
  throw new Error("PRD_EDIT_CONFLICT: the PRD changed; refresh before saving this section");
29047
29578
  }
@@ -29107,13 +29638,13 @@ var PlanSchema2 = z44.object({
29107
29638
  }).strict();
29108
29639
  function d2cProductDirectory(workspace, task) {
29109
29640
  if (!D2C_TASK_PATTERN.test(task)) throw new Error("Invalid D2C task name");
29110
- return join40(taskDir(workspace, task), "product");
29641
+ return join42(taskDir(workspace, task), "product");
29111
29642
  }
29112
29643
  function d2cProductPrototypeDirectory(workspace, task) {
29113
- return join40(d2cProductDirectory(workspace, task), "prototype");
29644
+ return join42(d2cProductDirectory(workspace, task), "prototype");
29114
29645
  }
29115
29646
  function planPath(workspace, task) {
29116
- return join40(taskDir(workspace, task), "product-plan.json");
29647
+ return join42(taskDir(workspace, task), "product-plan.json");
29117
29648
  }
29118
29649
  function resolveD2cProductTechnology(requirement, fallbackFramework = "vue") {
29119
29650
  const frontendCandidates = [
@@ -29201,18 +29732,18 @@ function buildD2cProductOpenApi(task, modules) {
29201
29732
  `;
29202
29733
  }
29203
29734
  async function fileTimestamp(path) {
29204
- const info = await stat13(path).catch(() => void 0);
29735
+ const info = await stat14(path).catch(() => void 0);
29205
29736
  return info?.isFile() ? info.mtime.toISOString() : void 0;
29206
29737
  }
29207
29738
  async function fileHash(path) {
29208
- const buffer = await readFile32(path).catch((error) => {
29739
+ const buffer = await readFile33(path).catch((error) => {
29209
29740
  if (error.code === "ENOENT") return void 0;
29210
29741
  throw error;
29211
29742
  });
29212
29743
  return buffer === void 0 ? void 0 : createHash24("sha256").update(buffer).digest("hex");
29213
29744
  }
29214
29745
  async function validateAndNormalizePrototypeManifest(path) {
29215
- const raw = await readFile32(path, "utf8").catch((error) => {
29746
+ const raw = await readFile33(path, "utf8").catch((error) => {
29216
29747
  if (error.code === "ENOENT") return void 0;
29217
29748
  throw error;
29218
29749
  });
@@ -29288,7 +29819,7 @@ async function createD2cProductPlan(workspace, input, now = () => /* @__PURE__ *
29288
29819
  });
29289
29820
  }
29290
29821
  async function readD2cProductPlan(workspace, task) {
29291
- const raw = await readFile32(planPath(workspace, task), "utf8").catch((error) => {
29822
+ const raw = await readFile33(planPath(workspace, task), "utf8").catch((error) => {
29292
29823
  if (error.code === "ENOENT") return void 0;
29293
29824
  throw error;
29294
29825
  });
@@ -29296,16 +29827,16 @@ async function readD2cProductPlan(workspace, task) {
29296
29827
  }
29297
29828
  async function discoverD2cProductArtifacts(workspace, plan) {
29298
29829
  const product = d2cProductDirectory(workspace, plan.task);
29299
- const prdUpdatedAt = await fileTimestamp(join40(product, "prd.md"));
29300
- const prdHash = await fileHash(join40(product, "prd.md"));
29301
- const prototypeUpdatedAt = await fileTimestamp(join40(product, "prototype", "index.html"));
29302
- const prototypeHash = await fileHash(join40(product, "prototype", "index.html"));
29303
- const expectedManifest = join40(product, "prototype", "interaction-manifest.json");
29830
+ const prdUpdatedAt = await fileTimestamp(join42(product, "prd.md"));
29831
+ const prdHash = await fileHash(join42(product, "prd.md"));
29832
+ const prototypeUpdatedAt = await fileTimestamp(join42(product, "prototype", "index.html"));
29833
+ const prototypeHash = await fileHash(join42(product, "prototype", "index.html"));
29834
+ const expectedManifest = join42(product, "prototype", "interaction-manifest.json");
29304
29835
  let manifestUpdatedAt = await fileTimestamp(expectedManifest);
29305
29836
  if (manifestUpdatedAt === void 0) {
29306
- const misplacedManifest = join40(product, "interaction-manifest.json");
29837
+ const misplacedManifest = join42(product, "interaction-manifest.json");
29307
29838
  if (await fileTimestamp(misplacedManifest) !== void 0) {
29308
- await mkdir25(join40(product, "prototype"), { recursive: true });
29839
+ await mkdir25(join42(product, "prototype"), { recursive: true });
29309
29840
  await copyFile2(misplacedManifest, expectedManifest);
29310
29841
  manifestUpdatedAt = await fileTimestamp(expectedManifest);
29311
29842
  }
@@ -29338,8 +29869,8 @@ async function readD2cProductPlanView(workspace, task) {
29338
29869
  if (stored === void 0) return void 0;
29339
29870
  const plan = await discoverD2cProductArtifacts(workspace, stored);
29340
29871
  if (plan.revision !== stored.revision) await writeD2cProductPlan(workspace, plan);
29341
- const prdMarkdown = plan.prd === void 0 ? void 0 : await readFile32(join40(taskDir(workspace, task), plan.prd.path), "utf8");
29342
- const manifestPath = join40(d2cProductDirectory(workspace, task), "prototype", "interaction-manifest.json");
29872
+ const prdMarkdown = plan.prd === void 0 ? void 0 : await readFile33(join42(taskDir(workspace, task), plan.prd.path), "utf8");
29873
+ const manifestPath = join42(d2cProductDirectory(workspace, task), "prototype", "interaction-manifest.json");
29343
29874
  const validationError = plan.phase === "design-generating" && await fileTimestamp(manifestPath) !== void 0 ? await validateAndNormalizePrototypeManifest(manifestPath) : void 0;
29344
29875
  return {
29345
29876
  plan,
@@ -29429,9 +29960,9 @@ ${prdMarkdown.slice(0, 2e4)}`
29429
29960
 
29430
29961
  // src/e2e/acceptance-baseline.ts
29431
29962
  import { createHash as createHash25 } from "crypto";
29432
- import { readFile as readFile33 } from "fs/promises";
29963
+ import { readFile as readFile34 } from "fs/promises";
29433
29964
  async function fileArtifact(path) {
29434
- const content = await readFile33(path);
29965
+ const content = await readFile34(path);
29435
29966
  return { path, hash: createHash25("sha256").update(content).digest("hex"), bytes: content.byteLength };
29436
29967
  }
29437
29968
  async function verifyAcceptanceArtifact(path, expected, label) {
@@ -29498,7 +30029,7 @@ function createAcceptanceEvidence(coverage, run, now = /* @__PURE__ */ new Date(
29498
30029
 
29499
30030
  // src/e2e/delivery-run.ts
29500
30031
  import { createHash as createHash26 } from "crypto";
29501
- import { join as join41 } from "path";
30032
+ import { join as join43 } from "path";
29502
30033
  import { z as z45 } from "zod";
29503
30034
  var DELIVERY_NODE_IDS = ["requirement", "prd", "design", "d2c", "api", "acceptance", "delivery"];
29504
30035
  var ArtifactSchema = z45.object({
@@ -29615,7 +30146,7 @@ function completeDeliveryNode(run, node, outputs, now = /* @__PURE__ */ new Date
29615
30146
  return { ...run, nodes, updatedAt: timestamp2 };
29616
30147
  }
29617
30148
  function deliveryRunPath(workspace, task) {
29618
- return join41(taskDir(workspace, task), "delivery-run.json");
30149
+ return join43(taskDir(workspace, task), "delivery-run.json");
29619
30150
  }
29620
30151
  function decodeDeliveryRun(raw) {
29621
30152
  return DeliveryRunSchema.parse(JSON.parse(raw));
@@ -29656,16 +30187,16 @@ async function updateDeliveryRun(workspace, task, expectedRevision, mutation) {
29656
30187
  }
29657
30188
 
29658
30189
  // src/skills/manager.ts
29659
- import { lstat as lstat13, mkdir as mkdir26, readFile as readFile34, readdir as readdir11, realpath as realpath13, rm as rm14, writeFile as writeFile16 } from "fs/promises";
29660
- import { homedir as homedir3 } from "os";
29661
- import { isAbsolute as isAbsolute17, join as join42, relative as relative24, resolve as resolve35, sep as sep25 } from "path";
30190
+ import { lstat as lstat14, mkdir as mkdir26, readFile as readFile35, readdir as readdir12, realpath as realpath13, rm as rm14, writeFile as writeFile16 } from "fs/promises";
30191
+ import { homedir as homedir4 } from "os";
30192
+ import { isAbsolute as isAbsolute18, join as join44, relative as relative25, resolve as resolve36, sep as sep26 } from "path";
29662
30193
  import { stringify } from "yaml";
29663
30194
  var SkillManager = class {
29664
30195
  #workspace;
29665
30196
  #home;
29666
30197
  constructor(options) {
29667
- this.#workspace = resolve35(options.workspace);
29668
- this.#home = resolve35(options.home ?? homedir3());
30198
+ this.#workspace = resolve36(options.workspace);
30199
+ this.#home = resolve36(options.home ?? homedir4());
29669
30200
  }
29670
30201
  async list() {
29671
30202
  const { registry, disabled } = await this.#registry();
@@ -29681,7 +30212,7 @@ var SkillManager = class {
29681
30212
  async create(draft) {
29682
30213
  const value = parseDraft(draft);
29683
30214
  const projectRoot = this.#projectRoot();
29684
- const directory = join42(projectRoot, value.name);
30215
+ const directory = join44(projectRoot, value.name);
29685
30216
  await mkdir26(projectRoot, { recursive: true, mode: 448 });
29686
30217
  try {
29687
30218
  await mkdir26(directory, { mode: 448 });
@@ -29690,7 +30221,7 @@ var SkillManager = class {
29690
30221
  throw error;
29691
30222
  }
29692
30223
  try {
29693
- await writeFile16(join42(directory, "SKILL.md"), renderSkill(value), { encoding: "utf8", flag: "wx", mode: 384 });
30224
+ await writeFile16(join44(directory, "SKILL.md"), renderSkill(value), { encoding: "utf8", flag: "wx", mode: 384 });
29694
30225
  await this.#persistEnabled(value.name, true);
29695
30226
  return this.get(value.name);
29696
30227
  } catch (error) {
@@ -29703,13 +30234,13 @@ var SkillManager = class {
29703
30234
  const value = parseDraft(draft);
29704
30235
  if (value.name !== validName) throw new Error("Rename is not supported; create a new skill instead");
29705
30236
  const directory = this.#projectSkillDirectory(validName);
29706
- const info = await lstat13(directory).catch((error) => {
30237
+ const info = await lstat14(directory).catch((error) => {
29707
30238
  if (isCode11(error, "ENOENT")) throw new Error(`Project skill not found: ${validName}`);
29708
30239
  throw error;
29709
30240
  });
29710
30241
  if (!info.isDirectory() || info.isSymbolicLink()) throw new Error(`Project skill path is not a real directory: ${validName}`);
29711
- const skillFile = join42(directory, "SKILL.md");
29712
- const fileInfo = await lstat13(skillFile);
30242
+ const skillFile = join44(directory, "SKILL.md");
30243
+ const fileInfo = await lstat14(skillFile);
29713
30244
  if (!fileInfo.isFile() || fileInfo.isSymbolicLink()) throw new Error(`Project skill file is not a real file: ${validName}`);
29714
30245
  await writeFile16(skillFile, renderSkill(value), { encoding: "utf8", mode: 384 });
29715
30246
  return this.get(validName);
@@ -29717,7 +30248,7 @@ var SkillManager = class {
29717
30248
  async delete(name) {
29718
30249
  const validName = parseName(name);
29719
30250
  const directory = this.#projectSkillDirectory(validName);
29720
- const info = await lstat13(directory).catch((error) => {
30251
+ const info = await lstat14(directory).catch((error) => {
29721
30252
  if (isCode11(error, "ENOENT")) throw new Error(`Project skill not found: ${validName}`);
29722
30253
  throw error;
29723
30254
  });
@@ -29740,12 +30271,12 @@ var SkillManager = class {
29740
30271
  async #registry() {
29741
30272
  const loaded = await loadConfig({ cwd: this.#workspace, home: this.#home });
29742
30273
  const [globalPluginRoots, projectPluginRoots] = await Promise.all([
29743
- discoverPluginSkillRoots(join42(this.#home, ".flavor-code", "plugins")),
29744
- discoverPluginSkillRoots(join42(this.#workspace, ".flavor", "plugins"))
30274
+ discoverPluginSkillRoots(join44(this.#home, ".flavor-code", "plugins")),
30275
+ discoverPluginSkillRoots(join44(this.#workspace, ".flavor", "plugins"))
29745
30276
  ]);
29746
30277
  return {
29747
30278
  registry: new SkillRegistry({
29748
- globalRoots: [join42(this.#home, ".flavor-code", "skills"), ...globalPluginRoots],
30279
+ globalRoots: [join44(this.#home, ".flavor-code", "skills"), ...globalPluginRoots],
29749
30280
  projectRoots: [this.#projectRoot(), ...projectPluginRoots]
29750
30281
  }),
29751
30282
  disabled: new Set(loaded.config.skills.disabled)
@@ -29761,10 +30292,10 @@ var SkillManager = class {
29761
30292
  };
29762
30293
  }
29763
30294
  #projectRoot() {
29764
- return join42(this.#workspace, ".flavor", "skills");
30295
+ return join44(this.#workspace, ".flavor", "skills");
29765
30296
  }
29766
30297
  #projectSkillDirectory(name) {
29767
- return join42(this.#projectRoot(), name);
30298
+ return join44(this.#projectRoot(), name);
29768
30299
  }
29769
30300
  };
29770
30301
  function parseName(value) {
@@ -29797,7 +30328,7 @@ function isCode11(error, code) {
29797
30328
  async function discoverPluginSkillRoots(directory) {
29798
30329
  let entries;
29799
30330
  try {
29800
- entries = await readdir11(directory, { withFileTypes: true });
30331
+ entries = await readdir12(directory, { withFileTypes: true });
29801
30332
  } catch (error) {
29802
30333
  if (isCode11(error, "ENOENT")) return [];
29803
30334
  throw error;
@@ -29805,16 +30336,16 @@ async function discoverPluginSkillRoots(directory) {
29805
30336
  const roots = [];
29806
30337
  for (const entry2 of entries) {
29807
30338
  if (!entry2.isDirectory() || entry2.isSymbolicLink()) continue;
29808
- const pluginRoot = await realpath13(join42(directory, entry2.name));
30339
+ const pluginRoot = await realpath13(join44(directory, entry2.name));
29809
30340
  try {
29810
- const manifest = PluginManifestSchema.parse(JSON.parse(await readFile34(join42(pluginRoot, "flavor-plugin.json"), "utf8")));
30341
+ const manifest = PluginManifestSchema.parse(JSON.parse(await readFile35(join44(pluginRoot, "flavor-plugin.json"), "utf8")));
29811
30342
  for (const contribution of manifest.contributes.skillRoots) {
29812
- const candidate = resolve35(pluginRoot, contribution.path);
29813
- const info = await lstat13(candidate);
30343
+ const candidate = resolve36(pluginRoot, contribution.path);
30344
+ const info = await lstat14(candidate);
29814
30345
  if (!info.isDirectory() || info.isSymbolicLink()) continue;
29815
30346
  const physical = await realpath13(candidate);
29816
- const relation = relative24(pluginRoot, physical);
29817
- if (relation === ".." || relation.startsWith(`..${sep25}`) || isAbsolute17(relation)) continue;
30347
+ const relation = relative25(pluginRoot, physical);
30348
+ if (relation === ".." || relation.startsWith(`..${sep26}`) || isAbsolute18(relation)) continue;
29818
30349
  roots.push(physical);
29819
30350
  }
29820
30351
  } catch {
@@ -29882,11 +30413,11 @@ async function createProjectMemoryManager(options) {
29882
30413
  }
29883
30414
 
29884
30415
  // src/mcp/config-manager.ts
29885
- import { join as join43 } from "path";
30416
+ import { join as join45 } from "path";
29886
30417
  var ProjectMcpConfigManager = class {
29887
30418
  path;
29888
30419
  constructor(workspace) {
29889
- this.path = join43(workspace, ".flavor", "flavor.json");
30420
+ this.path = join45(workspace, ".flavor", "flavor.json");
29890
30421
  }
29891
30422
  async list() {
29892
30423
  const document = await readRecoverableFile(this.path, (raw) => parseDocument2(this.path, raw));
@@ -29983,7 +30514,7 @@ function isRecord7(value) {
29983
30514
  }
29984
30515
 
29985
30516
  // src/desktop/model-config.ts
29986
- import { join as join44 } from "path";
30517
+ import { join as join46 } from "path";
29987
30518
  var DEFAULT_DESKTOP_MODELS = [
29988
30519
  {
29989
30520
  id: "anthropic:deepseek-v4-pro",
@@ -30004,7 +30535,7 @@ var DEFAULT_DESKTOP_MODELS = [
30004
30535
  ];
30005
30536
  async function loadDesktopModels(workspace, home) {
30006
30537
  try {
30007
- const tokens2 = await createFileTokenStore(join44(home, ".flavor-code", "auth.json")).load();
30538
+ const tokens2 = await createFileTokenStore(join46(home, ".flavor-code", "auth.json")).load();
30008
30539
  const pkceModels = [];
30009
30540
  const knownPkce = /* @__PURE__ */ new Set();
30010
30541
  for (const token of Object.values(tokens2)) {
@@ -30072,9 +30603,9 @@ function protocolLabel(type) {
30072
30603
 
30073
30604
  // src/desktop/d2c-product-preview.ts
30074
30605
  import { createReadStream as createReadStream2 } from "fs";
30075
- import { realpath as realpath14, stat as stat14 } from "fs/promises";
30606
+ import { realpath as realpath14, stat as stat15 } from "fs/promises";
30076
30607
  import { createServer as createServer6 } from "http";
30077
- import { extname as extname4, isAbsolute as isAbsolute18, relative as relative25, resolve as resolve36, sep as sep26 } from "path";
30608
+ import { extname as extname4, isAbsolute as isAbsolute19, relative as relative26, resolve as resolve37, sep as sep27 } from "path";
30078
30609
  var MIME_TYPES = {
30079
30610
  ".css": "text/css; charset=utf-8",
30080
30611
  ".gif": "image/gif",
@@ -30092,12 +30623,12 @@ function sendNotFound(response2) {
30092
30623
  response2.end("Not found");
30093
30624
  }
30094
30625
  function isInside(root, candidate) {
30095
- const delta = relative25(root, candidate);
30096
- return delta === "" || !delta.startsWith(`..${sep26}`) && delta !== ".." && !isAbsolute18(delta);
30626
+ const delta = relative26(root, candidate);
30627
+ return delta === "" || !delta.startsWith(`..${sep27}`) && delta !== ".." && !isAbsolute19(delta);
30097
30628
  }
30098
30629
  async function startD2cProductPreview(rootDirectory) {
30099
- const root = await realpath14(resolve36(rootDirectory));
30100
- const rootInfo = await stat14(root);
30630
+ const root = await realpath14(resolve37(rootDirectory));
30631
+ const rootInfo = await stat15(root);
30101
30632
  if (!rootInfo.isDirectory()) throw new Error("D2C product prototype directory is unavailable");
30102
30633
  const server = createServer6(async (request, response2) => {
30103
30634
  response2.setHeader("cache-control", "no-store");
@@ -30108,11 +30639,11 @@ async function startD2cProductPreview(rootDirectory) {
30108
30639
  let pathname = decodeURIComponent(url.pathname);
30109
30640
  if (pathname === "/") pathname = "/index.html";
30110
30641
  if (pathname.includes("\0")) return sendNotFound(response2);
30111
- const unresolved = resolve36(root, `.${pathname}`);
30642
+ const unresolved = resolve37(root, `.${pathname}`);
30112
30643
  if (!isInside(root, unresolved)) return sendNotFound(response2);
30113
30644
  const target = await realpath14(unresolved).catch(() => void 0);
30114
30645
  if (target === void 0 || !isInside(root, target)) return sendNotFound(response2);
30115
- const info = await stat14(target);
30646
+ const info = await stat15(target);
30116
30647
  if (!info.isFile()) return sendNotFound(response2);
30117
30648
  response2.writeHead(200, {
30118
30649
  "content-length": info.size,
@@ -30149,33 +30680,33 @@ function pngDataUrl(png) {
30149
30680
  }
30150
30681
  async function detectD2cFramework(workspace, task) {
30151
30682
  try {
30152
- const pkg = JSON.parse(await readFile35(join45(d2cOutputDirectory(workspace, task), "package.json"), "utf8"));
30683
+ const pkg = JSON.parse(await readFile36(join47(d2cOutputDirectory(workspace, task), "package.json"), "utf8"));
30153
30684
  return pkg.dependencies?.react === void 0 ? "vue" : "react";
30154
30685
  } catch {
30155
30686
  return "vue";
30156
30687
  }
30157
30688
  }
30158
30689
  function integrationDirectory(workspace, task) {
30159
- return join45(workspace, ".flavor", "d2c", task, "integration");
30690
+ return join47(workspace, ".flavor", "d2c", task, "integration");
30160
30691
  }
30161
30692
  var D2C_BACKEND_SOURCE_EXTENSIONS = /* @__PURE__ */ new Set([".cfg", ".env", ".ini", ".js", ".json", ".lock", ".mjs", ".py", ".toml", ".txt", ".yaml", ".yml"]);
30162
30693
  var D2C_BACKEND_IGNORED_DIRECTORIES = /* @__PURE__ */ new Set([".git", ".venv", "__pycache__", "data", "node_modules", "venv"]);
30163
30694
  async function d2cBackendSourceFingerprint(project) {
30164
- const runtimeManifest = join45(project, "server", "flavor-runtime.json");
30165
- const realBackend = (await stat15(runtimeManifest).catch(() => void 0))?.isFile() === true;
30166
- const root = realBackend ? join45(project, "server") : join45(project, "mock");
30167
- if ((await stat15(root).catch(() => void 0))?.isDirectory() !== true) return void 0;
30695
+ const runtimeManifest = join47(project, "server", "flavor-runtime.json");
30696
+ const realBackend = (await stat16(runtimeManifest).catch(() => void 0))?.isFile() === true;
30697
+ const root = realBackend ? join47(project, "server") : join47(project, "mock");
30698
+ if ((await stat16(root).catch(() => void 0))?.isDirectory() !== true) return void 0;
30168
30699
  const files = [];
30169
30700
  const visit = async (directory) => {
30170
- const entries = await readdir12(directory, { withFileTypes: true });
30701
+ const entries = await readdir13(directory, { withFileTypes: true });
30171
30702
  for (const entry2 of entries) {
30172
30703
  if (entry2.isDirectory()) {
30173
- if (!D2C_BACKEND_IGNORED_DIRECTORIES.has(entry2.name)) await visit(join45(directory, entry2.name));
30704
+ if (!D2C_BACKEND_IGNORED_DIRECTORIES.has(entry2.name)) await visit(join47(directory, entry2.name));
30174
30705
  continue;
30175
30706
  }
30176
30707
  if (!entry2.isFile()) continue;
30177
30708
  const extension2 = entry2.name.includes(".") ? `.${entry2.name.split(".").pop().toLowerCase()}` : "";
30178
- if (D2C_BACKEND_SOURCE_EXTENSIONS.has(extension2)) files.push(join45(directory, entry2.name));
30709
+ if (D2C_BACKEND_SOURCE_EXTENSIONS.has(extension2)) files.push(join47(directory, entry2.name));
30179
30710
  }
30180
30711
  };
30181
30712
  await visit(root);
@@ -30183,7 +30714,7 @@ async function d2cBackendSourceFingerprint(project) {
30183
30714
  for (const path of files.sort()) {
30184
30715
  hash.update(path.slice(root.length));
30185
30716
  hash.update("\0");
30186
- hash.update(await readFile35(path));
30717
+ hash.update(await readFile36(path));
30187
30718
  hash.update("\0");
30188
30719
  }
30189
30720
  return hash.digest("hex");
@@ -30227,7 +30758,7 @@ var DesktopRuntimeController = class {
30227
30758
  /** Backend source digest observed when the managed process was started. */
30228
30759
  #d2cMockSourceFingerprints = /* @__PURE__ */ new Map();
30229
30760
  constructor(options) {
30230
- this.#home = resolve37(options.home ?? homedir4());
30761
+ this.#home = resolve38(options.home ?? homedir5());
30231
30762
  this.#createRuntime = options.createRuntime ?? (async (runtimeOptions) => createProductionRuntime(runtimeOptions));
30232
30763
  this.#createTerminalService = options.createTerminalService ?? ((workspace) => new TerminalService(workspace));
30233
30764
  this.#listSessions = options.listSessions ?? (async (workspace) => {
@@ -30302,7 +30833,7 @@ var DesktopRuntimeController = class {
30302
30833
  };
30303
30834
  }
30304
30835
  async openWorkspace(path) {
30305
- const workspace = resolve37(path);
30836
+ const workspace = resolve38(path);
30306
30837
  if (workspace !== this.#workspace) {
30307
30838
  await this.#disposeRuntime();
30308
30839
  await this.#stopAllD2cPreviews();
@@ -30729,8 +31260,8 @@ var DesktopRuntimeController = class {
30729
31260
  if (workflow === void 0 || !reviewProgress(workflow).complete || workflow.stage === "visual-review") {
30730
31261
  throw new Error("Complete visual review before importing OpenAPI");
30731
31262
  }
30732
- const moduleManifest = join45(d2cOutputDirectory(workspace, task), "d2c.modules.json");
30733
- const moduleManifestInfo = await stat15(moduleManifest).catch(() => void 0);
31263
+ const moduleManifest = join47(d2cOutputDirectory(workspace, task), "d2c.modules.json");
31264
+ const moduleManifestInfo = await stat16(moduleManifest).catch(() => void 0);
30734
31265
  if (moduleManifestInfo === void 0 || !moduleManifestInfo.isFile()) {
30735
31266
  throw new Error("D2C implementation must write d2c.modules.json before API mapping; ask the agent to split the project into modules first");
30736
31267
  }
@@ -30742,8 +31273,8 @@ var DesktopRuntimeController = class {
30742
31273
  await mkdir27(dir, { recursive: true });
30743
31274
  const hash = createHash27("sha256").update(raw).digest("hex");
30744
31275
  await Promise.all([
30745
- writeFile17(join45(dir, "swagger.json"), raw),
30746
- writeFile17(join45(dir, "openapi.normalized.json"), `${JSON.stringify(document, null, 2)}
31276
+ writeFile17(join47(dir, "swagger.json"), raw),
31277
+ writeFile17(join47(dir, "openapi.normalized.json"), `${JSON.stringify(document, null, 2)}
30747
31278
  `)
30748
31279
  ]);
30749
31280
  const next = {
@@ -30767,9 +31298,9 @@ var DesktopRuntimeController = class {
30767
31298
  const workspace = this.#workspace;
30768
31299
  if (workspace === void 0) throw new Error("Open a project before importing OpenAPI");
30769
31300
  await this.#assertProductPrdLocked(workspace, task);
30770
- const info = await stat15(sourcePath);
31301
+ const info = await stat16(sourcePath);
30771
31302
  if (!info.isFile() || info.size > 8 * 1024 * 1024) throw new Error("OpenAPI JSON must be a file no larger than 8 MiB");
30772
- return this.#storeD2cOpenApi(workspace, task, basename9(sourcePath), await readFile35(sourcePath, "utf8"), false);
31303
+ return this.#storeD2cOpenApi(workspace, task, basename10(sourcePath), await readFile36(sourcePath, "utf8"), false);
30773
31304
  }
30774
31305
  async getD2cIntegration(task) {
30775
31306
  const workspace = this.#workspace;
@@ -30778,8 +31309,8 @@ var DesktopRuntimeController = class {
30778
31309
  let workflow = await readWorkflow(workspace, task);
30779
31310
  if (workflow?.openapi === void 0 && workflow !== void 0 && reviewProgress(workflow).complete && await readD2cProductPlanView(workspace, task) !== void 0) {
30780
31311
  const modules = await readD2cModules(workspace, task);
30781
- const productOpenApiPath = join45(taskDir(workspace, task), "product", "openapi.json");
30782
- const authored = await readFile35(productOpenApiPath, "utf8").catch(() => void 0);
31312
+ const productOpenApiPath = join47(taskDir(workspace, task), "product", "openapi.json");
31313
+ const authored = await readFile36(productOpenApiPath, "utf8").catch(() => void 0);
30783
31314
  const generated = authored === void 0;
30784
31315
  const prepared = await this.#storeD2cOpenApi(
30785
31316
  workspace,
@@ -30791,7 +31322,7 @@ var DesktopRuntimeController = class {
30791
31322
  return prepared;
30792
31323
  }
30793
31324
  if (workflow?.openapi === void 0 || workflow.mappings === void 0) return void 0;
30794
- const document = JSON.parse(await readFile35(join45(integrationDirectory(workspace, task), "openapi.normalized.json"), "utf8"));
31325
+ const document = JSON.parse(await readFile36(join47(integrationDirectory(workspace, task), "openapi.normalized.json"), "utf8"));
30795
31326
  return { workflow, document, mappings: workflow.mappings };
30796
31327
  }
30797
31328
  async confirmD2cMapping(task, moduleId, operationKey) {
@@ -30858,7 +31389,7 @@ var DesktopRuntimeController = class {
30858
31389
  ].join("\n");
30859
31390
  const delivery = await readDeliveryRun(workspace, task);
30860
31391
  if (delivery?.nodes.api.status === "running") {
30861
- const apiArtifacts = await Promise.all(generated.files.map(async (file) => artifactRef(file, await readFile35(join45(project, file)))));
31392
+ const apiArtifacts = await Promise.all(generated.files.map(async (file) => artifactRef(file, await readFile36(join47(project, file)))));
30862
31393
  const completed = await updateDeliveryRun(
30863
31394
  workspace,
30864
31395
  task,
@@ -30885,8 +31416,8 @@ var DesktopRuntimeController = class {
30885
31416
  try {
30886
31417
  const fingerprint2 = await d2cBackendSourceFingerprint(project);
30887
31418
  if (fingerprint2 !== void 0) this.#d2cMockSourceFingerprints.set(task, fingerprint2);
30888
- const environmentPath = join45(project, ".env.local");
30889
- const existingEnvironment = await readFile35(environmentPath, "utf8").catch(() => "");
31419
+ const environmentPath = join47(project, ".env.local");
31420
+ const existingEnvironment = await readFile36(environmentPath, "utf8").catch(() => "");
30890
31421
  const environment = `${existingEnvironment.split(/\r?\n/).filter((line) => !/^\s*VITE_API_BASE_URL\s*=/.test(line)).join("\n").trim()}
30891
31422
  VITE_API_BASE_URL=${running.url}
30892
31423
  `.replace(/^\n/, "");
@@ -30966,8 +31497,8 @@ VITE_API_BASE_URL=${running.url}
30966
31497
  const readyPreview = this.#d2cPreviews.get(task);
30967
31498
  const readyMock = this.#d2cMocks.get(task);
30968
31499
  if (readyPreview === void 0 || readyMock === void 0) throw new Error("D2C preview or backend service is unavailable after synchronization");
30969
- const manifestPath = join45(taskDir(workspace, task), "design", "interaction-manifest.json");
30970
- const manifest = parseInteractionManifest(await readFile35(manifestPath, "utf8"));
31500
+ const manifestPath = join47(taskDir(workspace, task), "design", "interaction-manifest.json");
31501
+ const manifest = parseInteractionManifest(await readFile36(manifestPath, "utf8"));
30971
31502
  const baseline = await this.#verifyAcceptanceBaseline(workspace, task);
30972
31503
  const coverage = baseline === void 0 ? void 0 : verifyRequirementCoverage(baseline.approvedPrd.criteria, manifest);
30973
31504
  let executableManifest = manifest;
@@ -30978,7 +31509,7 @@ VITE_API_BASE_URL=${running.url}
30978
31509
  await mkdir27(planDir, { recursive: true });
30979
31510
  try {
30980
31511
  const observations = await this.#observeD2cPages(manifest, readyPreview.url);
30981
- const prd = await readFile35(join45(taskDir(workspace, task), "product", "prd.md"), "utf8").catch(() => void 0);
31512
+ const prd = await readFile36(join47(taskDir(workspace, task), "product", "prd.md"), "utf8").catch(() => void 0);
30982
31513
  const apiContext = JSON.stringify({ openapi: workflow.openapi, mappings: workflow.mappings }, null, 2);
30983
31514
  const plan = await this.#d2cJudge.planInteractions({ task, seed: manifest, observations, ...prd === void 0 ? {} : { prd }, apiContext });
30984
31515
  executableManifest = mergeInteractionManifests(manifest, plan.manifest);
@@ -30989,9 +31520,9 @@ VITE_API_BASE_URL=${running.url}
30989
31520
  plannedScenarios: executableManifest.pages.reduce((total, page) => total + page.scenarios.length, 0),
30990
31521
  pageAnalyses: plan.pageAnalyses
30991
31522
  };
30992
- await writeFile17(join45(planDir, "autonomous-interaction-plan.json"), `${JSON.stringify({ ...plan, manifest: executableManifest }, null, 2)}
31523
+ await writeFile17(join47(planDir, "autonomous-interaction-plan.json"), `${JSON.stringify({ ...plan, manifest: executableManifest }, null, 2)}
30993
31524
  `, { encoding: "utf8", mode: 384 });
30994
- await rm15(join45(planDir, "autonomous-interaction-diagnostic.json"), { force: true });
31525
+ await rm15(join47(planDir, "autonomous-interaction-diagnostic.json"), { force: true });
30995
31526
  } catch (cause) {
30996
31527
  const detail = message(cause);
30997
31528
  review = {
@@ -30999,8 +31530,8 @@ VITE_API_BASE_URL=${running.url}
30999
31530
  plannedScenarios: manifest.pages.reduce((total, page) => total + page.scenarios.length, 0),
31000
31531
  warning: `\u591A\u6A21\u6001\u81EA\u4E3B\u89C4\u5212\u5931\u8D25\uFF0C\u5DF2\u7EE7\u7EED\u6267\u884C\u5DF2\u786E\u8BA4\u7684\u8BBE\u8BA1\u5951\u7EA6\uFF1A${detail}`
31001
31532
  };
31002
- await rm15(join45(planDir, "autonomous-interaction-plan.json"), { force: true });
31003
- await writeFile17(join45(planDir, "autonomous-interaction-diagnostic.json"), `${JSON.stringify({
31533
+ await rm15(join47(planDir, "autonomous-interaction-plan.json"), { force: true });
31534
+ await writeFile17(join47(planDir, "autonomous-interaction-diagnostic.json"), `${JSON.stringify({
31004
31535
  schema: 1,
31005
31536
  stage: "planning",
31006
31537
  occurredAt: (/* @__PURE__ */ new Date()).toISOString(),
@@ -31014,13 +31545,13 @@ VITE_API_BASE_URL=${running.url}
31014
31545
  let result = { ...rawResult, evidenceMode: review.mode };
31015
31546
  if (coverage !== void 0) {
31016
31547
  const evidence = createAcceptanceEvidence(coverage, result);
31017
- await writeFile17(join45(taskDir(workspace, task), "acceptance-evidence.json"), `${JSON.stringify({ ...evidence, baseline }, null, 2)}
31548
+ await writeFile17(join47(taskDir(workspace, task), "acceptance-evidence.json"), `${JSON.stringify({ ...evidence, baseline }, null, 2)}
31018
31549
  `, { encoding: "utf8", mode: 384 });
31019
31550
  if (!evidence.passed) result = { ...result, passed: false, failures: Math.max(1, result.failures) };
31020
31551
  }
31021
31552
  const dir = integrationDirectory(workspace, task);
31022
31553
  await mkdir27(dir, { recursive: true });
31023
- await writeFile17(join45(dir, "interaction-results.json"), `${JSON.stringify(result, null, 2)}
31554
+ await writeFile17(join47(dir, "interaction-results.json"), `${JSON.stringify(result, null, 2)}
31024
31555
  `);
31025
31556
  const next = await updateWorkflow(workspace, task, workflow.revision, (current) => applyInteractionRun(current, result));
31026
31557
  return { workflow: next, result, review };
@@ -31033,7 +31564,7 @@ VITE_API_BASE_URL=${running.url}
31033
31564
  if (mock === void 0 || !mock.exited() || result.failures === 0) return result;
31034
31565
  const output = mock.output().trim();
31035
31566
  try {
31036
- const dir = join45(d2cOutputDirectory(workspace, task), "mock-server.log");
31567
+ const dir = join47(d2cOutputDirectory(workspace, task), "mock-server.log");
31037
31568
  await writeFile17(dir, `${output}
31038
31569
  `);
31039
31570
  } catch {
@@ -31128,7 +31659,7 @@ VITE_API_BASE_URL=${running.url}
31128
31659
  if (current === void 0) throw new Error("D2C product plan is unavailable");
31129
31660
  if (current.plan.approvedPrd !== void 0) throw new Error("The approved PRD is immutable and cannot be edited");
31130
31661
  if (current.plan.phase !== "prd-review") throw new Error("PRD sections can only be edited during PRD review");
31131
- const path = join45(taskDir(workspace, task), "product", "prd.md");
31662
+ const path = join47(taskDir(workspace, task), "product", "prd.md");
31132
31663
  const updated = await updatePrdSectionFile(path, { sectionId, body, expectedHash });
31133
31664
  const now = (/* @__PURE__ */ new Date()).toISOString();
31134
31665
  await writeD2cProductPlan(workspace, {
@@ -31148,33 +31679,33 @@ VITE_API_BASE_URL=${running.url}
31148
31679
  async #assertProductPrdLocked(workspace, task) {
31149
31680
  const current = await readD2cProductPlanView(workspace, task);
31150
31681
  if (current?.plan.approvedPrd === void 0) return;
31151
- await assertApprovedPrd(join45(taskDir(workspace, task), "product", "prd.md"), current.plan.approvedPrd);
31682
+ await assertApprovedPrd(join47(taskDir(workspace, task), "product", "prd.md"), current.plan.approvedPrd);
31152
31683
  }
31153
31684
  async #acceptancePaths(workspace, task) {
31154
- const product = join45(taskDir(workspace, task), "product");
31155
- const openapi = join45(product, "openapi.json");
31685
+ const product = join47(taskDir(workspace, task), "product");
31686
+ const openapi = join47(product, "openapi.json");
31156
31687
  return {
31157
- prd: join45(product, "prd.md"),
31158
- prototype: join45(product, "prototype", "index.html"),
31159
- interaction: join45(product, "prototype", "interaction-manifest.json"),
31160
- ...await stat15(openapi).catch(() => void 0) === void 0 ? {} : { openapi }
31688
+ prd: join47(product, "prd.md"),
31689
+ prototype: join47(product, "prototype", "index.html"),
31690
+ interaction: join47(product, "prototype", "interaction-manifest.json"),
31691
+ ...await stat16(openapi).catch(() => void 0) === void 0 ? {} : { openapi }
31161
31692
  };
31162
31693
  }
31163
31694
  async #verifyAcceptanceBaseline(workspace, task) {
31164
31695
  const product = await readD2cProductPlanView(workspace, task);
31165
31696
  if (product?.plan.approvedPrd === void 0) return void 0;
31166
31697
  await this.#assertProductPrdLocked(workspace, task);
31167
- const baseline = JSON.parse(await readFile35(join45(taskDir(workspace, task), "acceptance-baseline.json"), "utf8"));
31698
+ const baseline = JSON.parse(await readFile36(join47(taskDir(workspace, task), "acceptance-baseline.json"), "utf8"));
31168
31699
  await verifyAcceptanceBaseline(await this.#acceptancePaths(workspace, task), baseline);
31169
- const design = join45(taskDir(workspace, task), "design");
31700
+ const design = join47(taskDir(workspace, task), "design");
31170
31701
  await Promise.all([
31171
- verifyAcceptanceArtifact(join45(design, "index.html"), baseline.artifacts.prototype, "runtime prototype"),
31172
- verifyAcceptanceArtifact(join45(design, "interaction-manifest.json"), baseline.artifacts.interaction, "runtime interaction contract")
31702
+ verifyAcceptanceArtifact(join47(design, "index.html"), baseline.artifacts.prototype, "runtime prototype"),
31703
+ verifyAcceptanceArtifact(join47(design, "interaction-manifest.json"), baseline.artifacts.interaction, "runtime interaction contract")
31173
31704
  ]);
31174
31705
  const workflow = await readWorkflow(workspace, task);
31175
31706
  if (workflow?.openapi !== void 0) {
31176
31707
  await verifyAcceptanceArtifact(
31177
- join45(taskDir(workspace, task), "integration", "swagger.json"),
31708
+ join47(taskDir(workspace, task), "integration", "swagger.json"),
31178
31709
  { hash: workflow.openapi.hash },
31179
31710
  "runtime OpenAPI contract"
31180
31711
  );
@@ -31195,14 +31726,14 @@ VITE_API_BASE_URL=${running.url}
31195
31726
  throw new Error("D2C PRD is empty and cannot be accepted");
31196
31727
  }
31197
31728
  if (stage === "design" && accepted) {
31198
- const manifest = join45(d2cProductPrototypeDirectory(workspace, task), "interaction-manifest.json");
31199
- const normalizedManifest = parseInteractionManifest(await readFile35(manifest, "utf8"));
31729
+ const manifest = join47(d2cProductPrototypeDirectory(workspace, task), "interaction-manifest.json");
31730
+ const normalizedManifest = parseInteractionManifest(await readFile36(manifest, "utf8"));
31200
31731
  if (current.plan.approvedPrd === void 0) throw new Error("Approve the PRD before confirming the design");
31201
31732
  verifyRequirementCoverage(current.plan.approvedPrd.criteria, normalizedManifest);
31202
31733
  await writeFile17(manifest, `${JSON.stringify(normalizedManifest, null, 2)}
31203
31734
  `, { encoding: "utf8", mode: 384 });
31204
31735
  const baseline = await captureAcceptanceBaseline(await this.#acceptancePaths(workspace, task), current.plan.approvedPrd);
31205
- await writeFile17(join45(taskDir(workspace, task), "acceptance-baseline.json"), `${JSON.stringify(baseline, null, 2)}
31736
+ await writeFile17(join47(taskDir(workspace, task), "acceptance-baseline.json"), `${JSON.stringify(baseline, null, 2)}
31206
31737
  `, { encoding: "utf8", mode: 384 });
31207
31738
  }
31208
31739
  const plan = await writeD2cProductPlan(
@@ -31236,8 +31767,8 @@ VITE_API_BASE_URL=${running.url}
31236
31767
  const imported = await this.importD2cDesign(task, d2cProductPrototypeDirectory(workspace, task));
31237
31768
  const delivery = await readDeliveryRun(workspace, task);
31238
31769
  if (delivery !== void 0) {
31239
- const prototype = await readFile35(join45(d2cProductPrototypeDirectory(workspace, task), "index.html"), "utf8");
31240
- const interaction = await readFile35(join45(d2cProductPrototypeDirectory(workspace, task), "interaction-manifest.json"), "utf8");
31770
+ const prototype = await readFile36(join47(d2cProductPrototypeDirectory(workspace, task), "index.html"), "utf8");
31771
+ const interaction = await readFile36(join47(d2cProductPrototypeDirectory(workspace, task), "interaction-manifest.json"), "utf8");
31241
31772
  const completed = await updateDeliveryRun(workspace, task, delivery.revision, (run) => completeDeliveryNode(run, "design", [
31242
31773
  artifactRef("product/prototype/index.html", prototype),
31243
31774
  artifactRef("product/prototype/interaction-manifest.json", interaction)
@@ -31321,10 +31852,10 @@ VITE_API_BASE_URL=${running.url}
31321
31852
  const next = await updateWorkflow(workspace, task, workflow.revision, (current) => applyQualityJudgment(current, judgment));
31322
31853
  const qualityContent = `${JSON.stringify(judgment, null, 2)}
31323
31854
  `;
31324
- await writeFile17(join45(taskDir(workspace, task), "quality-judge.json"), qualityContent, { encoding: "utf8", mode: 384 });
31855
+ await writeFile17(join47(taskDir(workspace, task), "quality-judge.json"), qualityContent, { encoding: "utf8", mode: 384 });
31325
31856
  const delivery = await readDeliveryRun(workspace, task);
31326
31857
  if (next.stage === "completed" && delivery?.nodes.acceptance.status === "running") {
31327
- const interactionContent = await readFile35(join45(integrationDirectory(workspace, task), "interaction-results.json"), "utf8");
31858
+ const interactionContent = await readFile36(join47(integrationDirectory(workspace, task), "interaction-results.json"), "utf8");
31328
31859
  const accepted = await updateDeliveryRun(workspace, task, delivery.revision, (run) => completeDeliveryNode(run, "acceptance", [
31329
31860
  artifactRef("integration/interaction-results.json", interactionContent),
31330
31861
  artifactRef("quality-judge.json", qualityContent)
@@ -31504,9 +32035,27 @@ function desktopWindowChrome(platform = process.platform) {
31504
32035
  };
31505
32036
  }
31506
32037
 
32038
+ // src/utils/login-shell-environment.ts
32039
+ import { delimiter as delimiter3 } from "path";
32040
+ async function loginShellEnvironment(environment, platform = process.platform, dependencies2 = {}) {
32041
+ if (platform !== "darwin") return { ...environment };
32042
+ const shell2 = environment.SHELL || "/bin/zsh";
32043
+ const result = await (dependencies2.execute ?? execFileNoThrow)(
32044
+ shell2,
32045
+ ["-l", "-c", "command env"],
32046
+ { timeout: 2e3, useCwd: false, env: environment }
32047
+ );
32048
+ if (result.code !== 0) return { ...environment };
32049
+ const loginPath = result.stdout.split(/\r?\n/u).filter((line) => line.startsWith("PATH=")).at(-1)?.slice(5);
32050
+ if (!loginPath) return { ...environment };
32051
+ const currentPath = environment.PATH ?? "";
32052
+ const entries = [...loginPath.split(delimiter3), ...currentPath.split(delimiter3)].filter((entry2, index, values) => entry2.length > 0 && values.indexOf(entry2) === index);
32053
+ return { ...environment, PATH: entries.join(delimiter3) };
32054
+ }
32055
+
31507
32056
  // src/desktop/git-manager.ts
31508
- import { readFile as readFile36, rm as rm16 } from "fs/promises";
31509
- import { isAbsolute as isAbsolute19, relative as relative26, resolve as resolve38 } from "path";
32057
+ import { readFile as readFile37, rm as rm16 } from "fs/promises";
32058
+ import { isAbsolute as isAbsolute20, relative as relative27, resolve as resolve39 } from "path";
31510
32059
  function failure(action, stderr, stdout) {
31511
32060
  return new Error(`${action}\u5931\u8D25\uFF1A${(stderr.trim() || stdout.trim() || "\u672A\u77E5 Git \u9519\u8BEF").split("\n")[0]}`);
31512
32061
  }
@@ -31537,7 +32086,7 @@ async function desktopGitDiff(workspace, path, staged = false) {
31537
32086
  if (file === void 0) throw new Error("\u8BE5\u6587\u4EF6\u5DF2\u4E0D\u5728\u53D8\u66F4\u5217\u8868\u4E2D");
31538
32087
  if (file.untracked) {
31539
32088
  const target = safeWorkspacePath(workspace, path);
31540
- const value = await readFile36(target, "utf8");
32089
+ const value = await readFile37(target, "utf8");
31541
32090
  return value.length > 12e4 ? `${value.slice(0, 12e4)}
31542
32091
  \u2026[\u5185\u5BB9\u5DF2\u622A\u65AD]` : value;
31543
32092
  }
@@ -31599,13 +32148,13 @@ async function desktopLastTurnDiff(workspace, sessionId, paths = []) {
31599
32148
  return path.replaceAll("\\", "/");
31600
32149
  }));
31601
32150
  try {
31602
- const tree = JSON.parse(await readFile36(resolve38(workspace, ".flavor", "session-trees", sessionId, "tree.json"), "utf8"));
32151
+ const tree = JSON.parse(await readFile37(resolve39(workspace, ".flavor", "session-trees", sessionId, "tree.json"), "utf8"));
31603
32152
  const leaf = tree.nodes?.find((node) => node.id === tree.leafId);
31604
32153
  const parent = leaf?.parentId == null ? void 0 : tree.nodes?.find((node) => node.id === leaf.parentId);
31605
32154
  if (leaf === void 0 || parent === void 0) return desktopGitReviewDiff(workspace, { scope: "last-turn", paths });
31606
32155
  const load = async (id) => {
31607
32156
  if (!/^checkpoint-[A-Za-z0-9-]+$/.test(id)) throw new Error("Invalid checkpoint id");
31608
- return JSON.parse(await readFile36(resolve38(workspace, ".flavor", "checkpoints", "manifests", `${id}.json`), "utf8"));
32157
+ return JSON.parse(await readFile37(resolve39(workspace, ".flavor", "checkpoints", "manifests", `${id}.json`), "utf8"));
31609
32158
  };
31610
32159
  const [before, after] = await Promise.all([load(parent.checkpointId), load(leaf.checkpointId)]);
31611
32160
  const oldFiles = new Map(before.files.map((file) => [file.path, file.digest]));
@@ -31614,7 +32163,7 @@ async function desktopLastTurnDiff(workspace, sessionId, paths = []) {
31614
32163
  const object = async (digest) => {
31615
32164
  if (digest === void 0) return "";
31616
32165
  if (!/^[a-f0-9]{64}$/.test(digest)) throw new Error("Invalid checkpoint object");
31617
- const value = await readFile36(resolve38(workspace, ".flavor", "checkpoints", "objects", digest));
32166
+ const value = await readFile37(resolve39(workspace, ".flavor", "checkpoints", "objects", digest));
31618
32167
  return value.includes(0) ? "[binary content]" : value.toString("utf8");
31619
32168
  };
31620
32169
  const chunks = await Promise.all(changed.map(async (path) => wholeFileDiff(path, await object(oldFiles.get(path)), await object(newFiles.get(path)))));
@@ -31638,11 +32187,11 @@ async function assertListed(workspace, path) {
31638
32187
  return file;
31639
32188
  }
31640
32189
  function safeWorkspacePath(workspace, path) {
31641
- if (isAbsolute19(path)) throw new Error("Git \u6587\u4EF6\u8DEF\u5F84\u5FC5\u987B\u662F\u9879\u76EE\u5185\u7684\u76F8\u5BF9\u8DEF\u5F84");
31642
- const root = resolve38(workspace);
31643
- const target = resolve38(root, path);
31644
- const child = relative26(root, target);
31645
- if (child === "" || child.startsWith("..") || isAbsolute19(child)) throw new Error("\u62D2\u7EDD\u8BBF\u95EE\u9879\u76EE\u4E4B\u5916\u7684\u8DEF\u5F84");
32190
+ if (isAbsolute20(path)) throw new Error("Git \u6587\u4EF6\u8DEF\u5F84\u5FC5\u987B\u662F\u9879\u76EE\u5185\u7684\u76F8\u5BF9\u8DEF\u5F84");
32191
+ const root = resolve39(workspace);
32192
+ const target = resolve39(root, path);
32193
+ const child = relative27(root, target);
32194
+ if (child === "" || child.startsWith("..") || isAbsolute20(child)) throw new Error("\u62D2\u7EDD\u8BBF\u95EE\u9879\u76EE\u4E4B\u5916\u7684\u8DEF\u5F84");
31646
32195
  return target;
31647
32196
  }
31648
32197
  function safeGitTarget(value) {
@@ -31653,14 +32202,14 @@ function safeGitTarget(value) {
31653
32202
 
31654
32203
  // src/desktop/worktree-manager.ts
31655
32204
  import { randomUUID as randomUUID28 } from "crypto";
31656
- import { mkdir as mkdir28, readFile as readFile37, readdir as readdir13, rm as rm17, stat as stat16, writeFile as writeFile18 } from "fs/promises";
31657
- import { basename as basename10, isAbsolute as isAbsolute20, join as join46, relative as relative27, resolve as resolve39, sep as sep27 } from "path";
32205
+ import { mkdir as mkdir28, readFile as readFile38, readdir as readdir14, rm as rm17, stat as stat17, writeFile as writeFile18 } from "fs/promises";
32206
+ import { basename as basename11, isAbsolute as isAbsolute21, join as join48, relative as relative28, resolve as resolve40, sep as sep28 } from "path";
31658
32207
  var DesktopWorktreeManager = class {
31659
32208
  #repository;
31660
32209
  #storage;
31661
32210
  constructor(options) {
31662
- this.#repository = resolve39(options.repository);
31663
- this.#storage = resolve39(options.storage);
32211
+ this.#repository = resolve40(options.repository);
32212
+ this.#storage = resolve40(options.storage);
31664
32213
  }
31665
32214
  async create(label) {
31666
32215
  if (!await isGitRepository(this.#repository)) throw new Error("Task worktrees require a Git repository");
@@ -31693,15 +32242,15 @@ var DesktopWorktreeManager = class {
31693
32242
  async list() {
31694
32243
  let names;
31695
32244
  try {
31696
- names = await readdir13(this.#storage);
32245
+ names = await readdir14(this.#storage);
31697
32246
  } catch (error) {
31698
32247
  if (error.code === "ENOENT") return [];
31699
32248
  throw error;
31700
32249
  }
31701
32250
  const values = await Promise.all(names.filter((name) => name.endsWith(".json")).map(async (name) => {
31702
32251
  try {
31703
- const stored = validateStored(JSON.parse(await readFile37(join46(this.#storage, name), "utf8")), this.#repository, this.#storage);
31704
- if ((await stat16(stored.path).catch(() => void 0))?.isDirectory() !== true) return void 0;
32252
+ const stored = validateStored(JSON.parse(await readFile38(join48(this.#storage, name), "utf8")), this.#repository, this.#storage);
32253
+ if ((await stat17(stored.path).catch(() => void 0))?.isDirectory() !== true) return void 0;
31705
32254
  const status = await git2(stored.path, ["status", "--porcelain"]);
31706
32255
  const merged = await git2(this.#repository, ["merge-base", "--is-ancestor", stored.branch, "HEAD"]);
31707
32256
  const { version: _version, ...item } = stored;
@@ -31734,45 +32283,45 @@ var DesktopWorktreeManager = class {
31734
32283
  }
31735
32284
  async #load(id) {
31736
32285
  const path = this.#metadataPath(id);
31737
- return validateStored(JSON.parse(await readFile37(path, "utf8")), this.#repository, this.#storage);
32286
+ return validateStored(JSON.parse(await readFile38(path, "utf8")), this.#repository, this.#storage);
31738
32287
  }
31739
32288
  #metadataPath(id) {
31740
32289
  if (!/^worktree-[a-z0-9._-]{1,80}$/.test(id)) throw new Error("Invalid worktree id");
31741
32290
  return this.#safeStoragePath(`${id}.json`);
31742
32291
  }
31743
32292
  #safeStoragePath(name) {
31744
- const target = resolve39(this.#storage, name);
31745
- const delta = relative27(this.#storage, target);
31746
- if (delta === "" || delta === ".." || delta.startsWith(`..${sep27}`) || isAbsolute20(delta)) throw new Error("Worktree path escapes storage");
32293
+ const target = resolve40(this.#storage, name);
32294
+ const delta = relative28(this.#storage, target);
32295
+ if (delta === "" || delta === ".." || delta.startsWith(`..${sep28}`) || isAbsolute21(delta)) throw new Error("Worktree path escapes storage");
31747
32296
  return target;
31748
32297
  }
31749
32298
  };
31750
32299
  function validateStored(value, repository, storage) {
31751
32300
  if (typeof value !== "object" || value === null) throw new Error("Invalid worktree metadata");
31752
32301
  const item = value;
31753
- if (item.version !== 1 || item.mode !== "worktree" || typeof item.id !== "string" || typeof item.path !== "string" || typeof item.branch !== "string" || typeof item.createdAt !== "string" || resolve39(item.repository ?? "") !== repository) {
32302
+ if (item.version !== 1 || item.mode !== "worktree" || typeof item.id !== "string" || typeof item.path !== "string" || typeof item.branch !== "string" || typeof item.createdAt !== "string" || resolve40(item.repository ?? "") !== repository) {
31754
32303
  throw new Error("Invalid worktree metadata");
31755
32304
  }
31756
- const path = resolve39(item.path);
31757
- const delta = relative27(storage, path);
31758
- if (delta === "" || delta === ".." || delta.startsWith(`..${sep27}`) || isAbsolute20(delta) || basename10(path) !== item.id) {
32305
+ const path = resolve40(item.path);
32306
+ const delta = relative28(storage, path);
32307
+ if (delta === "" || delta === ".." || delta.startsWith(`..${sep28}`) || isAbsolute21(delta) || basename11(path) !== item.id) {
31759
32308
  throw new Error("Worktree metadata escapes storage");
31760
32309
  }
31761
32310
  return { version: 1, id: item.id, mode: "worktree", repository, path, branch: item.branch, createdAt: item.createdAt };
31762
32311
  }
31763
32312
 
31764
32313
  // src/desktop/workbench-service.ts
31765
- import { readFile as readFile38, readdir as readdir14, stat as stat17 } from "fs/promises";
31766
- import { homedir as homedir5 } from "os";
31767
- import { join as join47, resolve as resolve40 } from "path";
32314
+ import { readFile as readFile39, readdir as readdir15, stat as stat18 } from "fs/promises";
32315
+ import { homedir as homedir6 } from "os";
32316
+ import { join as join49, resolve as resolve41 } from "path";
31768
32317
  var MAX_TEXT = 128e3;
31769
32318
  var MAX_ITEMS = 200;
31770
32319
  var DesktopWorkbenchService = class {
31771
32320
  #workspace;
31772
32321
  #home;
31773
- constructor(workspace, home = homedir5()) {
31774
- this.#workspace = resolve40(workspace);
31775
- this.#home = resolve40(home);
32322
+ constructor(workspace, home = homedir6()) {
32323
+ this.#workspace = resolve41(workspace);
32324
+ this.#home = resolve41(home);
31776
32325
  }
31777
32326
  async inspect(sessionId) {
31778
32327
  const [goals, instructions, permissionFiles, audit, context] = await Promise.all([
@@ -31797,11 +32346,11 @@ var DesktopWorkbenchService = class {
31797
32346
  return url.toString().replace(/\/$/, input.trim().endsWith("/") ? "/" : "");
31798
32347
  }
31799
32348
  async #goals() {
31800
- const root = join47(this.#workspace, ".flavor", "goals");
31801
- const names = await readdir14(root).catch((error) => error.code === "ENOENT" ? [] : Promise.reject(error));
32349
+ const root = join49(this.#workspace, ".flavor", "goals");
32350
+ const names = await readdir15(root).catch((error) => error.code === "ENOENT" ? [] : Promise.reject(error));
31802
32351
  const values = await Promise.all(names.filter((name) => /^goal-[A-Za-z0-9._-]+\.json$/.test(name)).slice(0, MAX_ITEMS).map(async (name) => {
31803
32352
  try {
31804
- return GoalStateSchema.parse(JSON.parse(await boundedRead2(join47(root, name))));
32353
+ return GoalStateSchema.parse(JSON.parse(await boundedRead2(join49(root, name))));
31805
32354
  } catch {
31806
32355
  return void 0;
31807
32356
  }
@@ -31811,7 +32360,7 @@ var DesktopWorkbenchService = class {
31811
32360
  async #instructions() {
31812
32361
  const names = ["FLAVOR.md", "AGENTS.md", "CLAUDE.md", "AGENTS.local.md", "CLAUDE.local.md"];
31813
32362
  const items = await Promise.all(names.map(async (name) => {
31814
- const path = join47(this.#workspace, name);
32363
+ const path = join49(this.#workspace, name);
31815
32364
  try {
31816
32365
  return { name, path, content: redactErrorText(await boundedRead2(path)) };
31817
32366
  } catch (error) {
@@ -31823,10 +32372,10 @@ var DesktopWorkbenchService = class {
31823
32372
  }
31824
32373
  async #permissionFiles() {
31825
32374
  const candidates = [
31826
- ...process.env.FLAVOR_MANAGED_PERMISSIONS ? [{ tier: "managed", path: resolve40(process.env.FLAVOR_MANAGED_PERMISSIONS), content: "" }] : [],
31827
- { tier: "user", path: join47(this.#home, ".flavor-code", "permissions.json"), content: "" },
31828
- { tier: "project", path: join47(this.#workspace, ".flavor", "permissions.json"), content: "" },
31829
- { tier: "local", path: join47(this.#workspace, ".flavor", "permissions.local.json"), content: "" }
32375
+ ...process.env.FLAVOR_MANAGED_PERMISSIONS ? [{ tier: "managed", path: resolve41(process.env.FLAVOR_MANAGED_PERMISSIONS), content: "" }] : [],
32376
+ { tier: "user", path: join49(this.#home, ".flavor-code", "permissions.json"), content: "" },
32377
+ { tier: "project", path: join49(this.#workspace, ".flavor", "permissions.json"), content: "" },
32378
+ { tier: "local", path: join49(this.#workspace, ".flavor", "permissions.local.json"), content: "" }
31830
32379
  ];
31831
32380
  const values = await Promise.all(candidates.map(async (item) => {
31832
32381
  try {
@@ -31841,7 +32390,7 @@ var DesktopWorkbenchService = class {
31841
32390
  async #audit() {
31842
32391
  let raw;
31843
32392
  try {
31844
- raw = await boundedRead2(join47(this.#workspace, ".flavor", "audit.jsonl"));
32393
+ raw = await boundedRead2(join49(this.#workspace, ".flavor", "audit.jsonl"));
31845
32394
  } catch (error) {
31846
32395
  if (error.code === "ENOENT") return [];
31847
32396
  throw error;
@@ -31856,7 +32405,7 @@ var DesktopWorkbenchService = class {
31856
32405
  }).reverse();
31857
32406
  }
31858
32407
  async #context(sessionId) {
31859
- const usage = await readJsonLines2(join47(this.#workspace, ".flavor", "usage.jsonl"));
32408
+ const usage = await readJsonLines2(join49(this.#workspace, ".flavor", "usage.jsonl"));
31860
32409
  if (sessionId === void 0) return { visibility: [], usage };
31861
32410
  try {
31862
32411
  const document = await new SessionStore({ workspace: this.#workspace }).load(sessionId);
@@ -31867,10 +32416,10 @@ var DesktopWorkbenchService = class {
31867
32416
  }
31868
32417
  };
31869
32418
  async function boundedRead2(path) {
31870
- const info = await stat17(path);
32419
+ const info = await stat18(path);
31871
32420
  if (!info.isFile()) throw new Error(`Expected a file: ${path}`);
31872
32421
  if (info.size > MAX_TEXT) throw new Error(`Workbench file exceeds ${MAX_TEXT} bytes: ${path}`);
31873
- return readFile38(path, "utf8");
32422
+ return readFile39(path, "utf8");
31874
32423
  }
31875
32424
  async function readJsonLines2(path) {
31876
32425
  try {
@@ -31903,7 +32452,7 @@ var appMenu;
31903
32452
  var quitting = false;
31904
32453
  function logStartup(step, detail) {
31905
32454
  try {
31906
- const logPath = join48(dirname25(process.execPath), "flavor-code-startup.log");
32455
+ const logPath = join50(dirname25(process.execPath), "flavor-code-startup.log");
31907
32456
  const ts = (/* @__PURE__ */ new Date()).toISOString();
31908
32457
  appendFileSync(logPath, `[${ts}] ${step}${detail ? ` | ${detail}` : ""}
31909
32458
  `);
@@ -31928,7 +32477,7 @@ var d2cEmbedded = createEmbeddedD2cAutomation(() => {
31928
32477
  };
31929
32478
  });
31930
32479
  var d2cJudgeClient = createD2cJudgeClient();
31931
- var judgeStore = () => createD2cJudgeConfigStore(join48(app.getPath("userData"), "d2c-judge.json"));
32480
+ var judgeStore = () => createD2cJudgeConfigStore(join50(app.getPath("userData"), "d2c-judge.json"));
31932
32481
  function judgeImage(png) {
31933
32482
  const image = nativeImage.createFromBuffer(png);
31934
32483
  const size = image.getSize();
@@ -31953,8 +32502,8 @@ function sessionMetaKey(workspace, sessionId) {
31953
32502
  return `${workspace}\0${sessionId}`;
31954
32503
  }
31955
32504
  function worktreeManager(workspace) {
31956
- const key = createHash28("sha256").update(resolve41(workspace)).digest("hex").slice(0, 20);
31957
- return new DesktopWorktreeManager({ repository: workspace, storage: join48(app.getPath("userData"), "task-worktrees", key) });
32505
+ const key = createHash28("sha256").update(resolve42(workspace)).digest("hex").slice(0, 20);
32506
+ return new DesktopWorktreeManager({ repository: workspace, storage: join50(app.getPath("userData"), "task-worktrees", key) });
31958
32507
  }
31959
32508
  function activeWorkingDirectory() {
31960
32509
  if (activeWorkspace === void 0) throw new Error("\u8BF7\u5148\u6253\u5F00\u9879\u76EE");
@@ -32096,10 +32645,10 @@ function createDesktopController(workspace, owner2) {
32096
32645
  },
32097
32646
  collaboration: {
32098
32647
  instanceId: collaborationId,
32099
- alias: `desktop-${basename11(workspace ?? runtimeOptions.workspace ?? "flavor")}`.slice(0, 64),
32648
+ alias: `desktop-${basename12(workspace ?? runtimeOptions.workspace ?? "flavor")}`.slice(0, 64),
32100
32649
  optional: true,
32101
32650
  startBroker: (address) => {
32102
- const entry2 = app.isPackaged ? join48(process.resourcesPath, "app.asar.unpacked", "dist", "desktop", "pals-broker-entry.js") : join48(moduleDirectory, "pals-broker-entry.js");
32651
+ const entry2 = app.isPackaged ? join50(process.resourcesPath, "app.asar.unpacked", "dist", "desktop", "pals-broker-entry.js") : join50(moduleDirectory, "pals-broker-entry.js");
32103
32652
  return spawnPalBrokerProcess({
32104
32653
  address,
32105
32654
  executable: process.execPath,
@@ -32121,11 +32670,11 @@ function createDesktopController(workspace, owner2) {
32121
32670
  var detachedController = createDesktopController();
32122
32671
  var controller = detachedController;
32123
32672
  function statePath() {
32124
- return join48(app.getPath("userData"), "desktop-state.json");
32673
+ return join50(app.getPath("userData"), "desktop-state.json");
32125
32674
  }
32126
32675
  async function loadPersistedProjects() {
32127
32676
  try {
32128
- const raw = await readFile39(statePath(), "utf8");
32677
+ const raw = await readFile40(statePath(), "utf8");
32129
32678
  if (raw.length > 1e5) return { projects: [] };
32130
32679
  const parsed = JSON.parse(raw);
32131
32680
  Object.assign(workbench, {
@@ -32166,10 +32715,10 @@ function validRecord(value) {
32166
32715
  return typeof value === "object" && value !== null && !Array.isArray(value) ? value : {};
32167
32716
  }
32168
32717
  async function assertDirectory(path) {
32169
- if (!(await stat18(path)).isDirectory()) throw new Error("The selected project is not a directory");
32718
+ if (!(await stat19(path)).isDirectory()) throw new Error("The selected project is not a directory");
32170
32719
  }
32171
32720
  async function openWorkspace(path) {
32172
- const workspace = resolve41(path);
32721
+ const workspace = resolve42(path);
32173
32722
  await assertDirectory(workspace);
32174
32723
  let project = managedProjects.get(workspace);
32175
32724
  if (project === void 0) {
@@ -32180,10 +32729,10 @@ async function openWorkspace(path) {
32180
32729
  activeWorkspace = workspace;
32181
32730
  controller = nextController;
32182
32731
  project.snapshot = await nextController.openWorkspace(workspace);
32183
- for (const saved of workbench.taskCheckouts.filter((item) => resolve41(item.workspace) === workspace)) {
32732
+ for (const saved of workbench.taskCheckouts.filter((item) => resolve42(item.workspace) === workspace)) {
32184
32733
  try {
32185
32734
  await assertDirectory(saved.workingDirectory);
32186
- const task = { environment: "worktree", workingDirectory: resolve41(saved.workingDirectory), worktreeId: saved.worktreeId };
32735
+ const task = { environment: "worktree", workingDirectory: resolve42(saved.workingDirectory), worktreeId: saved.worktreeId };
32187
32736
  task.controller = createDesktopController(workspace, task);
32188
32737
  task.snapshot = await task.controller.openWorkspace(task.workingDirectory);
32189
32738
  task.sessionId = saved.sessionId;
@@ -32297,7 +32846,7 @@ async function chooseAndOpenWorkspace() {
32297
32846
  var cachedAppIconDataUrl;
32298
32847
  function appIconDataUrl() {
32299
32848
  if (cachedAppIconDataUrl !== void 0) return cachedAppIconDataUrl;
32300
- const icon = nativeImage.createFromPath(join48(app.getAppPath(), "assets", "icon.png"));
32849
+ const icon = nativeImage.createFromPath(join50(app.getAppPath(), "assets", "icon.png"));
32301
32850
  if (icon.isEmpty()) return void 0;
32302
32851
  cachedAppIconDataUrl = icon.toDataURL();
32303
32852
  return cachedAppIconDataUrl;
@@ -32313,7 +32862,7 @@ function installIpcHandlers() {
32313
32862
  } catch {
32314
32863
  }
32315
32864
  }
32316
- if (persisted.workspace !== void 0 && managedProjects.has(resolve41(persisted.workspace))) {
32865
+ if (persisted.workspace !== void 0 && managedProjects.has(resolve42(persisted.workspace))) {
32317
32866
  try {
32318
32867
  return await openWorkspace(persisted.workspace);
32319
32868
  } catch {
@@ -32374,7 +32923,7 @@ function installIpcHandlers() {
32374
32923
  });
32375
32924
  ipcMain.handle(DESKTOP_CHANNELS.updateProject, async (_event, value) => {
32376
32925
  const input = UpdateProjectInputSchema.parse(value);
32377
- const workspace = resolve41(input.workspace);
32926
+ const workspace = resolve42(input.workspace);
32378
32927
  if (!managedProjects.has(workspace)) throw new Error("\u9879\u76EE\u672A\u6253\u5F00");
32379
32928
  workbench.projectMeta[workspace] = { ...workbench.projectMeta[workspace], ...input.label === void 0 ? {} : { label: input.label || void 0 }, ...input.pinned === void 0 ? {} : { pinned: input.pinned } };
32380
32929
  await savePersistedProjects();
@@ -32382,7 +32931,7 @@ function installIpcHandlers() {
32382
32931
  });
32383
32932
  ipcMain.handle(DESKTOP_CHANNELS.closeProject, async (_event, value) => {
32384
32933
  const input = CloseProjectInputSchema.parse(value);
32385
- const workspace = resolve41(input.workspace);
32934
+ const workspace = resolve42(input.workspace);
32386
32935
  const project = managedProjects.get(workspace);
32387
32936
  if (project === void 0) return decorateSnapshot(controller.snapshot());
32388
32937
  if ([...project.tasks].some((task) => task.snapshot.activeSession?.busy) && !input.force) throw new Error("\u9879\u76EE\u4E2D\u4ECD\u6709\u4EFB\u52A1\u8FD0\u884C\uFF1B\u786E\u8BA4\u540E\u518D\u5173\u95ED\u9879\u76EE");
@@ -32398,12 +32947,12 @@ function installIpcHandlers() {
32398
32947
  return decorateSnapshot(controller.snapshot());
32399
32948
  });
32400
32949
  ipcMain.handle(DESKTOP_CHANNELS.revealProject, async (_event, value) => {
32401
- const workspace = resolve41(ProjectPathInputSchema.parse(value).workspace);
32950
+ const workspace = resolve42(ProjectPathInputSchema.parse(value).workspace);
32402
32951
  await assertDirectory(workspace);
32403
32952
  await shell.openPath(workspace);
32404
32953
  });
32405
32954
  ipcMain.handle(DESKTOP_CHANNELS.copyProjectPath, async (_event, value) => {
32406
- clipboard.writeText(resolve41(ProjectPathInputSchema.parse(value).workspace));
32955
+ clipboard.writeText(resolve42(ProjectPathInputSchema.parse(value).workspace));
32407
32956
  });
32408
32957
  ipcMain.handle(DESKTOP_CHANNELS.dismissRecovery, async (_event, value) => {
32409
32958
  const { sessionId } = DeleteSessionInputSchema.parse(value);
@@ -32715,7 +33264,7 @@ function applicationMenu() {
32715
33264
  ]);
32716
33265
  }
32717
33266
  async function createWindow() {
32718
- const rendererPath = join48(app.getAppPath(), "dist", "desktop-renderer", "index.html");
33267
+ const rendererPath = join50(app.getAppPath(), "dist", "desktop-renderer", "index.html");
32719
33268
  mainWindow = new BrowserWindow2({
32720
33269
  title: "Flavor Code",
32721
33270
  width: 1280,
@@ -32726,14 +33275,14 @@ async function createWindow() {
32726
33275
  show: false,
32727
33276
  ...desktopWindowChrome(),
32728
33277
  webPreferences: {
32729
- preload: join48(moduleDirectory, "preload.cjs"),
33278
+ preload: join50(moduleDirectory, "preload.cjs"),
32730
33279
  contextIsolation: true,
32731
33280
  nodeIntegration: false,
32732
33281
  sandbox: false,
32733
33282
  webSecurity: true
32734
33283
  }
32735
33284
  });
32736
- const iconPath = join48(app.getAppPath(), "assets", "icon.png");
33285
+ const iconPath = join50(app.getAppPath(), "assets", "icon.png");
32737
33286
  const icon = nativeImage.createFromPath(iconPath);
32738
33287
  if (!icon.isEmpty()) {
32739
33288
  mainWindow.setIcon(icon);
@@ -32772,6 +33321,8 @@ async function createWindow() {
32772
33321
  });
32773
33322
  }
32774
33323
  app.whenReady().then(async () => {
33324
+ const desktopEnvironment = await loginShellEnvironment(process.env);
33325
+ if (desktopEnvironment.PATH !== void 0) process.env.PATH = desktopEnvironment.PATH;
32775
33326
  installIpcHandlers();
32776
33327
  appMenu = applicationMenu();
32777
33328
  Menu.setApplicationMenu(appMenu);