@4pm/cli 1.25.0 → 1.28.0-b

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (2) hide show
  1. package/dist/index.js +928 -477
  2. package/package.json +1 -1
package/dist/index.js CHANGED
@@ -5713,14 +5713,14 @@ var require_react_development = __commonJS({
5713
5713
  var thenableResult = result;
5714
5714
  var wasAwaited = false;
5715
5715
  var thenable = {
5716
- then: function(resolve12, reject) {
5716
+ then: function(resolve13, reject) {
5717
5717
  wasAwaited = true;
5718
5718
  thenableResult.then(function(returnValue2) {
5719
5719
  popActScope(prevActScopeDepth);
5720
5720
  if (actScopeDepth === 0) {
5721
- recursivelyFlushAsyncActWork(returnValue2, resolve12, reject);
5721
+ recursivelyFlushAsyncActWork(returnValue2, resolve13, reject);
5722
5722
  } else {
5723
- resolve12(returnValue2);
5723
+ resolve13(returnValue2);
5724
5724
  }
5725
5725
  }, function(error2) {
5726
5726
  popActScope(prevActScopeDepth);
@@ -5750,20 +5750,20 @@ var require_react_development = __commonJS({
5750
5750
  ReactCurrentActQueue.current = null;
5751
5751
  }
5752
5752
  var _thenable = {
5753
- then: function(resolve12, reject) {
5753
+ then: function(resolve13, reject) {
5754
5754
  if (ReactCurrentActQueue.current === null) {
5755
5755
  ReactCurrentActQueue.current = [];
5756
- recursivelyFlushAsyncActWork(returnValue, resolve12, reject);
5756
+ recursivelyFlushAsyncActWork(returnValue, resolve13, reject);
5757
5757
  } else {
5758
- resolve12(returnValue);
5758
+ resolve13(returnValue);
5759
5759
  }
5760
5760
  }
5761
5761
  };
5762
5762
  return _thenable;
5763
5763
  } else {
5764
5764
  var _thenable2 = {
5765
- then: function(resolve12, reject) {
5766
- resolve12(returnValue);
5765
+ then: function(resolve13, reject) {
5766
+ resolve13(returnValue);
5767
5767
  }
5768
5768
  };
5769
5769
  return _thenable2;
@@ -5779,7 +5779,7 @@ var require_react_development = __commonJS({
5779
5779
  actScopeDepth = prevActScopeDepth;
5780
5780
  }
5781
5781
  }
5782
- function recursivelyFlushAsyncActWork(returnValue, resolve12, reject) {
5782
+ function recursivelyFlushAsyncActWork(returnValue, resolve13, reject) {
5783
5783
  {
5784
5784
  var queue = ReactCurrentActQueue.current;
5785
5785
  if (queue !== null) {
@@ -5788,16 +5788,16 @@ var require_react_development = __commonJS({
5788
5788
  enqueueTask(function() {
5789
5789
  if (queue.length === 0) {
5790
5790
  ReactCurrentActQueue.current = null;
5791
- resolve12(returnValue);
5791
+ resolve13(returnValue);
5792
5792
  } else {
5793
- recursivelyFlushAsyncActWork(returnValue, resolve12, reject);
5793
+ recursivelyFlushAsyncActWork(returnValue, resolve13, reject);
5794
5794
  }
5795
5795
  });
5796
5796
  } catch (error2) {
5797
5797
  reject(error2);
5798
5798
  }
5799
5799
  } else {
5800
- resolve12(returnValue);
5800
+ resolve13(returnValue);
5801
5801
  }
5802
5802
  }
5803
5803
  }
@@ -16203,9 +16203,9 @@ var require_react_reconciler_development = __commonJS({
16203
16203
  var node = row;
16204
16204
  while (node !== null) {
16205
16205
  if (node.tag === SuspenseComponent) {
16206
- var state2 = node.memoizedState;
16207
- if (state2 !== null) {
16208
- var dehydrated = state2.dehydrated;
16206
+ var state3 = node.memoizedState;
16207
+ if (state3 !== null) {
16208
+ var dehydrated = state3.dehydrated;
16209
16209
  if (dehydrated === null || isSuspenseInstancePending(dehydrated) || isSuspenseInstanceFallback(dehydrated)) {
16210
16210
  return node;
16211
16211
  }
@@ -16549,8 +16549,8 @@ var require_react_reconciler_development = __commonJS({
16549
16549
  stores: null
16550
16550
  };
16551
16551
  }
16552
- function basicStateReducer(state2, action) {
16553
- return typeof action === "function" ? action(state2) : action;
16552
+ function basicStateReducer(state3, action) {
16553
+ return typeof action === "function" ? action(state3) : action;
16554
16554
  }
16555
16555
  function mountReducer(reducer, initialArg, init) {
16556
16556
  var hook = mountWorkInProgressHook();
@@ -18537,10 +18537,10 @@ var require_react_reconciler_development = __commonJS({
18537
18537
  }
18538
18538
  }
18539
18539
  }
18540
- var state2 = workInProgress2.memoizedState = instance.state !== null && instance.state !== void 0 ? instance.state : null;
18540
+ var state3 = workInProgress2.memoizedState = instance.state !== null && instance.state !== void 0 ? instance.state : null;
18541
18541
  adoptClassInstance(workInProgress2, instance);
18542
18542
  {
18543
- if (typeof ctor.getDerivedStateFromProps === "function" && state2 === null) {
18543
+ if (typeof ctor.getDerivedStateFromProps === "function" && state3 === null) {
18544
18544
  var componentName = getComponentNameFromType(ctor) || "Component";
18545
18545
  if (!didWarnAboutUninitializedState.has(componentName)) {
18546
18546
  didWarnAboutUninitializedState.add(componentName);
@@ -19463,8 +19463,8 @@ var require_react_reconciler_development = __commonJS({
19463
19463
  var _instance = workInProgress2.stateNode;
19464
19464
  var ctor = workInProgress2.type;
19465
19465
  var tempInstance = new ctor(workInProgress2.memoizedProps, _instance.context);
19466
- var state2 = tempInstance.state;
19467
- _instance.updater.enqueueSetState(_instance, state2, null);
19466
+ var state3 = tempInstance.state;
19467
+ _instance.updater.enqueueSetState(_instance, state3, null);
19468
19468
  break;
19469
19469
  }
19470
19470
  case true: {
@@ -20239,8 +20239,8 @@ var require_react_reconciler_development = __commonJS({
20239
20239
  var node = firstChild;
20240
20240
  while (node !== null) {
20241
20241
  if (node.tag === SuspenseComponent) {
20242
- var state2 = node.memoizedState;
20243
- if (state2 !== null) {
20242
+ var state3 = node.memoizedState;
20243
+ if (state3 !== null) {
20244
20244
  scheduleSuspenseWorkOnFiber(node, renderLanes2, workInProgress2);
20245
20245
  }
20246
20246
  } else if (node.tag === SuspenseListComponent) {
@@ -20669,9 +20669,9 @@ var require_react_reconciler_development = __commonJS({
20669
20669
  }
20670
20670
  break;
20671
20671
  case SuspenseComponent: {
20672
- var state2 = workInProgress2.memoizedState;
20673
- if (state2 !== null) {
20674
- if (state2.dehydrated !== null) {
20672
+ var state3 = workInProgress2.memoizedState;
20673
+ if (state3 !== null) {
20674
+ if (state3.dehydrated !== null) {
20675
20675
  pushSuspenseContext(workInProgress2, setDefaultShallowSuspenseContext(suspenseStackCursor.current));
20676
20676
  workInProgress2.flags |= DidCapture;
20677
20677
  return null;
@@ -28557,11 +28557,12 @@ function fetchWhoami(serverUrl, hashcode3) {
28557
28557
  function selfUnlink(serverUrl, hashcode3) {
28558
28558
  return post(serverUrl, "/machine-links/self-unlink", { hashcode3 });
28559
28559
  }
28560
- async function fetchCliVersion(serverUrl) {
28560
+ async function fetchCliVersion(serverUrl, version) {
28561
28561
  const ctrl = new AbortController();
28562
28562
  const timer = setTimeout(() => ctrl.abort(), 2e4);
28563
28563
  try {
28564
- const res = await fetch(`${serverUrl}/meta/cli-version`, { signal: ctrl.signal });
28564
+ const query = version ? `?version=${encodeURIComponent(version)}` : "";
28565
+ const res = await fetch(`${serverUrl}/meta/cli-version${query}`, { signal: ctrl.signal });
28565
28566
  return unwrap(res);
28566
28567
  } finally {
28567
28568
  clearTimeout(timer);
@@ -28798,6 +28799,8 @@ var en_default = {
28798
28799
  "update.updating": "Updating {from} \u2192 {to}\u2026",
28799
28800
  "update.checkVersionFailed": "Could not check cli version: {error}",
28800
28801
  "update.newVersionAutoUpdateOff": "A new version {latest} is available (running {current}) \u2014 autoUpdate is off.",
28802
+ "update.unsupportedSoon": "cli {current} stops being supported on {date} \u2014 update before then (4pm update).",
28803
+ "update.policyNote": "Note from 4PM: {note}",
28801
28804
  "update.applying": "{kind} cli update {from} \u2192 {to}\u2026",
28802
28805
  "update.kindMandatory": "Mandatory",
28803
28806
  "update.kindAutomatic": "Automatic",
@@ -28981,6 +28984,8 @@ var vi_default = {
28981
28984
  "update.updating": "\u0110ang c\u1EADp nh\u1EADt {from} \u2192 {to}\u2026",
28982
28985
  "update.checkVersionFailed": "Kh\xF4ng th\u1EC3 ki\u1EC3m tra phi\xEAn b\u1EA3n cli: {error}",
28983
28986
  "update.newVersionAutoUpdateOff": "C\xF3 phi\xEAn b\u1EA3n m\u1EDBi {latest} (\u0111ang ch\u1EA1y {current}) \u2014 autoUpdate \u0111ang t\u1EAFt.",
28987
+ "update.unsupportedSoon": "cli {current} s\u1EBD ng\u1EEBng \u0111\u01B0\u1EE3c h\u1ED7 tr\u1EE3 t\u1EEB {date} \u2014 h\xE3y c\u1EADp nh\u1EADt tr\u01B0\u1EDBc ng\xE0y \u0111\xF3 (4pm update).",
28988
+ "update.policyNote": "Ghi ch\xFA t\u1EEB 4PM: {note}",
28984
28989
  "update.applying": "C\u1EADp nh\u1EADt cli {kind} {from} \u2192 {to}\u2026",
28985
28990
  "update.kindMandatory": "b\u1EAFt bu\u1ED9c",
28986
28991
  "update.kindAutomatic": "t\u1EF1 \u0111\u1ED9ng",
@@ -29164,6 +29169,8 @@ var ja_default = {
29164
29169
  "update.updating": "\u66F4\u65B0\u4E2D {from} \u2192 {to}\u2026",
29165
29170
  "update.checkVersionFailed": "cli \u306E\u30D0\u30FC\u30B8\u30E7\u30F3\u3092\u78BA\u8A8D\u3067\u304D\u307E\u305B\u3093\u3067\u3057\u305F: {error}",
29166
29171
  "update.newVersionAutoUpdateOff": "\u65B0\u3057\u3044\u30D0\u30FC\u30B8\u30E7\u30F3 {latest} \u304C\u5229\u7528\u53EF\u80FD\u3067\u3059\uFF08\u5B9F\u884C\u4E2D {current}\uFF09\u2014 autoUpdate \u306F\u30AA\u30D5\u3067\u3059\u3002",
29172
+ "update.unsupportedSoon": "cli {current} \u306F {date} \u306B\u30B5\u30DD\u30FC\u30C8\u304C\u7D42\u4E86\u3057\u307E\u3059 \u2014 \u305D\u308C\u307E\u3067\u306B\u66F4\u65B0\u3057\u3066\u304F\u3060\u3055\u3044\uFF084pm update\uFF09\u3002",
29173
+ "update.policyNote": "4PM \u304B\u3089\u306E\u304A\u77E5\u3089\u305B: {note}",
29167
29174
  "update.applying": "{kind} \u306E cli \u66F4\u65B0 {from} \u2192 {to}\u2026",
29168
29175
  "update.kindMandatory": "\u5FC5\u9808",
29169
29176
  "update.kindAutomatic": "\u81EA\u52D5",
@@ -29347,6 +29354,8 @@ var zh_default = {
29347
29354
  "update.updating": "\u6B63\u5728\u66F4\u65B0 {from} \u2192 {to}\u2026",
29348
29355
  "update.checkVersionFailed": "\u65E0\u6CD5\u68C0\u67E5 cli \u7248\u672C\uFF1A{error}",
29349
29356
  "update.newVersionAutoUpdateOff": "\u6709\u65B0\u7248\u672C {latest} \u53EF\u7528\uFF08\u5F53\u524D\u8FD0\u884C {current}\uFF09\u2014 autoUpdate \u5DF2\u5173\u95ED\u3002",
29357
+ "update.unsupportedSoon": "cli {current} \u5C06\u4E8E {date} \u505C\u6B62\u652F\u6301 \u2014 \u8BF7\u5728\u6B64\u4E4B\u524D\u66F4\u65B0\uFF084pm update\uFF09\u3002",
29358
+ "update.policyNote": "\u6765\u81EA 4PM \u7684\u8BF4\u660E\uFF1A{note}",
29350
29359
  "update.applying": "{kind} cli \u66F4\u65B0 {from} \u2192 {to}\u2026",
29351
29360
  "update.kindMandatory": "\u5F3A\u5236",
29352
29361
  "update.kindAutomatic": "\u81EA\u52A8",
@@ -29541,7 +29550,7 @@ async function runLink(serverUrl, explicitProfile, token = null) {
29541
29550
 
29542
29551
  // src/commands/start.ts
29543
29552
  import { spawn as spawn11 } from "child_process";
29544
- import { join as join37 } from "path";
29553
+ import { join as join38 } from "path";
29545
29554
 
29546
29555
  // src/core/update.ts
29547
29556
  import { execSync as execSync2 } from "child_process";
@@ -32701,12 +32710,18 @@ async function checkAndUpdate(serverUrl, autoUpdate) {
32701
32710
  if (CLI_VERSION.startsWith("0.0.0")) return { action: "none" };
32702
32711
  let meta;
32703
32712
  try {
32704
- meta = await fetchCliVersion(serverUrl);
32713
+ meta = await fetchCliVersion(serverUrl, CLI_VERSION);
32705
32714
  } catch (err) {
32706
32715
  console.warn(t("update.checkVersionFailed", { error: String(err) }));
32707
32716
  return { action: "none" };
32708
32717
  }
32709
- const mandatory = compareSemver(CLI_VERSION, meta.minSupported) < 0;
32718
+ const mandatory = compareSemver(CLI_VERSION, meta.minSupported) < 0 || meta.status === "unsupported";
32719
+ if (meta.status === "warning" && meta.unsupportedFrom) {
32720
+ console.warn(t("update.unsupportedSoon", { current: CLI_VERSION, date: meta.unsupportedFrom.slice(0, 10) }));
32721
+ if (meta.note) console.warn(t("update.policyNote", { note: meta.note }));
32722
+ } else if (meta.status === "unsupported" && meta.note) {
32723
+ console.warn(t("update.policyNote", { note: meta.note }));
32724
+ }
32710
32725
  const outdated = shouldUpdateTo(CLI_VERSION, meta.latest);
32711
32726
  if (!outdated) return { action: "none" };
32712
32727
  if (!mandatory && !autoUpdate) {
@@ -32754,7 +32769,7 @@ async function checkAndUpdate(serverUrl, autoUpdate) {
32754
32769
  // src/core/ws-client.ts
32755
32770
  init_wrapper();
32756
32771
  import { randomUUID as randomUUID4 } from "crypto";
32757
- import { basename as basename7, join as join32 } from "path";
32772
+ import { basename as basename7, join as join33 } from "path";
32758
32773
  import { existsSync as existsSync18, mkdirSync as mkdirSync9 } from "fs";
32759
32774
 
32760
32775
  // vendor/@4pm/utils/helpers.ts
@@ -37049,6 +37064,17 @@ function githubHostKind(host) {
37049
37064
  if (h.endsWith(".ghe.com")) return "ghe-cloud";
37050
37065
  return "ghes";
37051
37066
  }
37067
+ var scaffoldPublishRequestSchema = external_exports.object({
37068
+ machineLinkId: external_exports.string().uuid().optional()
37069
+ });
37070
+
37071
+ // vendor/@4pm/dto/git-overview.ts
37072
+ var GIT_SNAPSHOT_LIMITS = {
37073
+ localCommits: 50,
37074
+ filesPerCommit: 100,
37075
+ dirtyFiles: 200,
37076
+ remoteCommits: 200
37077
+ };
37052
37078
 
37053
37079
  // vendor/@4pm/validation/schemas.ts
37054
37080
  var passwordSchema = external_exports.string().min(8, "password: at least 8 characters").max(32, "password: at most 32 characters").regex(/[a-zA-Z]/, "password: needs at least 1 letter").regex(/[0-9]/, "password: needs at least 1 digit");
@@ -37255,6 +37281,67 @@ var agentToolsWriteRequestSchema = external_exports.object({
37255
37281
  permissions: agentToolsPermissionsSchema
37256
37282
  });
37257
37283
 
37284
+ // vendor/@4pm/dto/meta.ts
37285
+ var cliVersionString = external_exports.string().trim().regex(/^\d+\.\d+\.\d+([-+][0-9A-Za-z.-]+)?$/);
37286
+ var setCliMinVersionRequestSchema = external_exports.object({
37287
+ minSupported: cliVersionString.nullable(),
37288
+ disconnect: external_exports.boolean().optional()
37289
+ });
37290
+ var setCliVersionOverrideRequestSchema = external_exports.object({
37291
+ status: external_exports.enum(["supported", "warning", "unsupported"]),
37292
+ unsupportedFrom: external_exports.string().datetime({ offset: true }).nullable().optional(),
37293
+ note: external_exports.string().max(500).optional(),
37294
+ disconnect: external_exports.boolean().optional()
37295
+ });
37296
+
37297
+ // vendor/@4pm/dto/storage.ts
37298
+ var STORAGE_KINDS = [
37299
+ "messageText",
37300
+ "postText",
37301
+ "commentText",
37302
+ "memoText",
37303
+ "messageImages",
37304
+ "messageFiles",
37305
+ "postImages",
37306
+ "postFiles",
37307
+ "commentImages",
37308
+ "commentFiles",
37309
+ "memoImages",
37310
+ "memoFiles",
37311
+ "templates",
37312
+ "aiDocImages",
37313
+ "artifacts",
37314
+ "commandHistory"
37315
+ ];
37316
+ var COMMUNITY_STORAGE_KINDS = [
37317
+ "messageText",
37318
+ "postText",
37319
+ "commentText",
37320
+ "messageImages",
37321
+ "messageFiles",
37322
+ "postImages",
37323
+ "postFiles",
37324
+ "commentImages",
37325
+ "commentFiles"
37326
+ ];
37327
+ var SERVER_CLEARABLE_STORAGE_KINDS = [
37328
+ "memoText",
37329
+ "memoImages",
37330
+ "memoFiles",
37331
+ "templates",
37332
+ "commandHistory"
37333
+ ];
37334
+ var CLEARABLE_STORAGE_KINDS = [
37335
+ ...COMMUNITY_STORAGE_KINDS,
37336
+ ...SERVER_CLEARABLE_STORAGE_KINDS
37337
+ ];
37338
+ var storageClearRequestSchema = external_exports.object({
37339
+ kinds: external_exports.array(external_exports.enum(STORAGE_KINDS)).min(1).max(STORAGE_KINDS.length),
37340
+ before: external_exports.string().datetime({ offset: true }),
37341
+ /** org-0009 / community-0015 only — must equal the org name. */
37342
+ confirm: external_exports.string().max(200).optional()
37343
+ });
37344
+
37258
37345
  // vendor/@4pm/dto/org.ts
37259
37346
  var CLI_SETTINGS_BOUNDS = {
37260
37347
  reconnectMaxBackoffSec: { min: 5, max: 60 },
@@ -37274,6 +37361,8 @@ var TEMPLATE_KINDS = ["report", "est", "wbs", "proposal", "feasibility"];
37274
37361
  // vendor/@4pm/dto/project.ts
37275
37362
  var listProjectsQuerySchema = baseRequestSchema.extend({
37276
37363
  pinned: pinnedFilterSchema,
37364
+ /** Only projects this team is attached to (project-0001; the public v1 list — ADR-0364). */
37365
+ teamId: external_exports.string().uuid().optional(),
37277
37366
  /** ADMIN-only Trash: list soft-deleted projects (ADR-0109). */
37278
37367
  deleted: deletedFilterSchema
37279
37368
  });
@@ -37562,7 +37651,7 @@ var userCardsQuerySchema = external_exports.object({
37562
37651
  });
37563
37652
 
37564
37653
  // src/core/scaffold.ts
37565
- import { cp, mkdir, mkdtemp, rm, writeFile } from "fs/promises";
37654
+ import { cp, mkdir, mkdtemp, readFile, rm, writeFile } from "fs/promises";
37566
37655
  import { existsSync as existsSync4 } from "fs";
37567
37656
  import { execFile } from "child_process";
37568
37657
  import { tmpdir as tmpdir2 } from "os";
@@ -37671,45 +37760,98 @@ function providerSlug(url) {
37671
37760
  const m2 = /(?:github|gitlab)\.com[/:]+([^/]+\/.+?)(?:\.git)?\/?$/i.exec(url);
37672
37761
  return { provider, slug: m2?.[1] ?? "" };
37673
37762
  }
37674
- async function commitAndOpenPr(root, repo, emit) {
37675
- emit("commit", "Committing the scaffolded project\u2026");
37676
- await run("git", ["add", "-A"], { cwd: root, timeout: 6e4 });
37677
- const staged = (await run("git", ["diff", "--cached", "--name-only"], { cwd: root, timeout: 3e4 })).stdout.trim();
37678
- if (!staged) {
37679
- emit("commit", "Nothing to commit \u2014 skipping the pull request.");
37680
- return;
37681
- }
37682
- const commitArgs = ["commit", "-m", "chore: scaffold project (4PM)"];
37763
+ function errText(err) {
37764
+ const e = err;
37765
+ const raw = (e?.stderr || e?.message || String(err)).trim();
37766
+ return raw.split("\n").filter(Boolean).slice(-3).join(" ").slice(0, 500);
37767
+ }
37768
+ async function gitOut(cwd2, args) {
37683
37769
  try {
37684
- await run("git", commitArgs, { cwd: root, timeout: 6e4 });
37770
+ return (await run("git", args, { cwd: cwd2, timeout: 3e4 })).stdout.trim();
37685
37771
  } catch {
37686
- await run("git", ["-c", "user.name=4PM", "-c", "user.email=noreply@4pm.app", ...commitArgs], { cwd: root, timeout: 6e4 });
37772
+ return "";
37687
37773
  }
37688
- const branch = (repo.branch ?? "").trim();
37774
+ }
37775
+ async function commitAndOpenPr(root, repo, emit) {
37776
+ const result = { committed: false, pushed: false, branch: null, prUrl: null, step: null, error: null };
37777
+ const fail = (step, error) => {
37778
+ result.step = step;
37779
+ result.error = error;
37780
+ return result;
37781
+ };
37782
+ emit("commit", "Committing the scaffolded project\u2026");
37783
+ try {
37784
+ await run("git", ["add", "-A"], { cwd: root, timeout: 6e4 });
37785
+ const staged = (await run("git", ["diff", "--cached", "--name-only"], { cwd: root, timeout: 3e4 })).stdout.trim();
37786
+ if (staged) {
37787
+ const commitArgs = ["commit", "-m", "chore: scaffold project (4PM)"];
37788
+ try {
37789
+ await run("git", commitArgs, { cwd: root, timeout: 6e4 });
37790
+ } catch {
37791
+ await run("git", ["-c", "user.name=4PM", "-c", "user.email=noreply@4pm.app", ...commitArgs], { cwd: root, timeout: 6e4 });
37792
+ }
37793
+ } else {
37794
+ emit("commit", "Nothing new to commit.");
37795
+ }
37796
+ } catch (err) {
37797
+ const msg = errText(err);
37798
+ emit("commit-failed", `Could not commit the scaffold: ${msg}`);
37799
+ return fail("commit", msg);
37800
+ }
37801
+ result.committed = !!await gitOut(root, ["rev-parse", "--verify", "HEAD"]);
37802
+ const declared = (repo.branch ?? "").trim();
37803
+ const head = await gitOut(root, ["rev-parse", "--abbrev-ref", "HEAD"]);
37804
+ const branch = declared || (head && head !== "HEAD" ? head : "");
37805
+ result.branch = branch || null;
37689
37806
  emit("push", "Pushing the scaffold branch\u2026");
37690
37807
  try {
37691
- await run("git", ["push", "-u", "origin", ...branch ? [branch] : ["HEAD"]], { cwd: root, timeout: 12e4 });
37808
+ await run("git", ["push", "-u", "origin", branch || "HEAD"], { cwd: root, timeout: 12e4 });
37809
+ result.pushed = true;
37692
37810
  } catch (err) {
37693
- const msg = err instanceof Error ? err.message : String(err);
37694
- emit("push-failed", `Scaffold committed locally but the push failed: ${msg} \u2014 push it from the Git tab when credentials are available.`);
37695
- return;
37811
+ const msg = errText(err);
37812
+ emit("push-failed", `Scaffold committed locally but the push failed: ${msg} \u2014 retry from the project page when credentials are available.`);
37813
+ return fail("push", msg);
37696
37814
  }
37697
- const { provider, slug } = providerSlug(repo.url ?? "");
37815
+ const remoteHead = (await gitOut(root, ["symbolic-ref", "--short", "refs/remotes/origin/HEAD"])).replace(/^origin\//, "");
37816
+ if (branch && remoteHead && branch === remoteHead) {
37817
+ emit("pr", `Pushed to the default branch (${branch}) \u2014 no pull request needed.`);
37818
+ return result;
37819
+ }
37820
+ const url = (repo.url ?? "").trim() || await gitOut(root, ["remote", "get-url", "origin"]);
37821
+ const { provider, slug } = providerSlug(url);
37698
37822
  if (!provider) {
37699
37823
  emit("pr", "Unknown git host \u2014 skipping the pull request.");
37700
- return;
37824
+ return result;
37701
37825
  }
37702
37826
  emit("pr", `Opening a pull request via ${provider}\u2026`);
37703
37827
  try {
37704
- const args = provider === "glab" ? ["mr", "create", "--fill", ...slug ? ["-R", slug] : []] : ["pr", "create", "--fill", ...slug ? ["-R", slug] : []];
37828
+ const args = provider === "glab" ? ["mr", "create", "--fill", "--yes", ...branch ? ["--source-branch", branch] : [], ...slug ? ["-R", slug] : []] : ["pr", "create", "--fill", ...branch ? ["--head", branch] : [], ...slug ? ["-R", slug] : []];
37705
37829
  const { stdout } = await run(provider, args, { cwd: root, timeout: 12e4 });
37706
- const link2 = stdout.trim().split(/\s+/).find((s3) => /^https?:\/\//.test(s3)) ?? stdout.trim().split("\n").pop() ?? "";
37830
+ const link2 = stdout.trim().split(/\s+/).find((x) => /^https?:\/\//.test(x)) ?? null;
37831
+ result.prUrl = link2;
37707
37832
  emit("pr", `Pull request opened${link2 ? `: ${link2}` : "."}`);
37833
+ return result;
37708
37834
  } catch (err) {
37709
- const msg = err instanceof Error ? err.message : String(err);
37835
+ const msg = errText(err);
37836
+ const existing = /already exists/i.test(msg) ? msg.match(/https?:\/\/\S+/)?.[0] ?? null : null;
37837
+ if (existing) {
37838
+ result.prUrl = existing;
37839
+ emit("pr", `A pull request is already open: ${existing}`);
37840
+ return result;
37841
+ }
37710
37842
  emit("pr-failed", `Could not open a pull request (${provider}): ${msg}`);
37843
+ return fail("pr", msg);
37711
37844
  }
37712
37845
  }
37846
+ async function publishScaffold(root, emit) {
37847
+ let repo = {};
37848
+ try {
37849
+ const spec = JSON.parse(await readFile(join6(root, "project.spec.json"), "utf8"));
37850
+ repo = singleRepo(reposOf(spec)) ?? {};
37851
+ } catch {
37852
+ }
37853
+ return commitAndOpenPr(root, repo, emit);
37854
+ }
37713
37855
  function cloneArgs(url, dest, branch) {
37714
37856
  const b2 = (branch ?? "").trim();
37715
37857
  return b2 ? ["clone", "-b", b2, url, dest] : ["clone", url, dest];
@@ -37802,7 +37944,7 @@ function sampleDir() {
37802
37944
  }
37803
37945
  return bundled;
37804
37946
  }
37805
- async function scaffoldProject(payload, profileDir2, onProgress, ai2) {
37947
+ async function scaffoldProject(payload, profileDir2, onProgress, ai2, applyGitAuth2) {
37806
37948
  let lastStep = "start";
37807
37949
  const emit = (step, message) => {
37808
37950
  lastStep = step;
@@ -37814,15 +37956,16 @@ async function scaffoldProject(payload, profileDir2, onProgress, ai2) {
37814
37956
  await mkdir(target, { recursive: true });
37815
37957
  const repo = singleRepo(reposOf(payload.spec));
37816
37958
  if (!repo) throw new Error("A project must declare one repo (ADR-0314).");
37959
+ if (payload.gitAuth !== void 0) applyGitAuth2?.(payload.gitAuth ?? null, payload.gitAuthHost ?? null);
37817
37960
  emit("git", "Cloning repository\u2026");
37818
37961
  await provisionRepo(target, repo, emit, { createMissingBranch: true });
37819
37962
  await scaffoldRepo(target, payload.projectName, payload.spec, emit, { resetClaude: true, ai: ai2 });
37820
37963
  await attachSubmodules(target, submodulesOf(reposOf(payload.spec)), emit, true);
37821
37964
  emit("version", "Writing .4pm/.4pm.json\u2026");
37822
37965
  await writeTemplateMarker(target);
37823
- await commitAndOpenPr(target, repo, emit);
37966
+ const publish = await commitAndOpenPr(target, repo, emit);
37824
37967
  onProgress?.({ projectId: payload.projectId, step: "done", message: "Scaffold complete.", done: true });
37825
- return { ok: true, path: target };
37968
+ return { ok: true, path: target, publish };
37826
37969
  } catch (err) {
37827
37970
  return { ok: false, error: err instanceof Error ? err.message : String(err), step: lastStep };
37828
37971
  }
@@ -37956,7 +38099,7 @@ async function addProject(payload, profileDir2, onProgress, ai2) {
37956
38099
  }
37957
38100
 
37958
38101
  // src/core/worker-metrics.ts
37959
- import { statfs, readFile } from "fs/promises";
38102
+ import { statfs, readFile as readFile2 } from "fs/promises";
37960
38103
  import { existsSync as existsSync5 } from "fs";
37961
38104
  import { totalmem, freemem, cpus, platform, arch } from "os";
37962
38105
  var CGROUP = "/sys/fs/cgroup";
@@ -37964,7 +38107,7 @@ var MB = 1024 * 1024;
37964
38107
  var GB = 1024 * 1024 * 1024;
37965
38108
  async function readCgroup(file) {
37966
38109
  try {
37967
- return (await readFile(`${CGROUP}/${file}`, "utf8")).trim();
38110
+ return (await readFile2(`${CGROUP}/${file}`, "utf8")).trim();
37968
38111
  } catch {
37969
38112
  return null;
37970
38113
  }
@@ -38326,9 +38469,15 @@ var WsChannels = {
38326
38469
  // cli → server (reply): a short-lived GitHub-App installation token for the served project's repo,
38327
38470
  // per job scope; also the cli's "scope revoked" report (ADR-0356). The App key stays on the server.
38328
38471
  GIT_TOKEN: "git.token",
38472
+ // cli → server (one-way): the worker's per-repo git state of the served project (ADR-0369) — head,
38473
+ // branches + tracking, remote, dirty, worker-only commits, origin window. Latest kept per physic × repo.
38474
+ GIT_SNAPSHOT: "git.snapshot",
38329
38475
  PROJECT_CREATE: "project.create",
38330
38476
  PROJECT_ADD: "project.add",
38331
38477
  PROJECT_PROGRESS: "project.progress",
38478
+ // server → cli (reply): retry the scaffold commit → push → PR in the project folder (ADR-0368,
38479
+ // project-0074); the reply carries the new outcome, stored on `projects.scaffold_publish`.
38480
+ PROJECT_PUBLISH: "project.publish",
38332
38481
  // server → cli: keep the physic folder name in sync with the project (folder =
38333
38482
  // project name — ADR-0064) when the project is renamed.
38334
38483
  PHYSIC_SYNC: "physic.sync",
@@ -38370,6 +38519,9 @@ var WsChannels = {
38370
38519
  QUOTA_CHECK: "quota.check",
38371
38520
  // cli → server (reply): per-org concurrent AI-run slot — acquire / renew / release (ADR-0359)
38372
38521
  RUN_SLOT: "run.slot",
38522
+ // cli → server (reply): read / add the org's monthly autonomous-book counters — USER_TODO / USER_QA /
38523
+ // AI_TODO rows vs `autonomousRequestsPerMonth` (ADR-0365)
38524
+ AUTONOMOUS_BOOK_USAGE: "autonomous.bookUsage",
38373
38525
  USAGE_REPORT: "usage.report",
38374
38526
  // cli → server: Claude subscription usage snapshot (5h / weekly — ADR-0072)
38375
38527
  MACHINE_USAGE: "machine.usage",
@@ -38590,8 +38742,8 @@ function resolveInstallTimeoutMs(sec) {
38590
38742
  return sec && sec > 0 ? sec * 1e3 : TOOL_INSTALL_TIMEOUT_DEFAULT_MS;
38591
38743
  }
38592
38744
  function sleep(ms2) {
38593
- return new Promise((resolve12) => {
38594
- const t2 = setTimeout(resolve12, ms2);
38745
+ return new Promise((resolve13) => {
38746
+ const t2 = setTimeout(resolve13, ms2);
38595
38747
  t2.unref?.();
38596
38748
  });
38597
38749
  }
@@ -38619,14 +38771,14 @@ function classifyToolFailure(code, out) {
38619
38771
  return { reason: "other", retryable: false };
38620
38772
  }
38621
38773
  function run2(cmd, args, onLine, timeoutMs = 18e4) {
38622
- return new Promise((resolve12) => {
38774
+ return new Promise((resolve13) => {
38623
38775
  let out = "";
38624
38776
  let buf = "";
38625
38777
  let child = null;
38626
38778
  const done = (code) => {
38627
38779
  clearTimeout(timer);
38628
38780
  if (onLine && buf.trim()) onLine(buf.trim());
38629
- resolve12({ code, out });
38781
+ resolve13({ code, out });
38630
38782
  };
38631
38783
  const timer = setTimeout(() => {
38632
38784
  child?.kill();
@@ -39009,11 +39161,11 @@ function getWorkingProfile(profileDir2, cmd) {
39009
39161
  return readState(profileDir2)[cmd] ?? null;
39010
39162
  }
39011
39163
  function setWorkingProfile(profileDir2, cmd, dir2) {
39012
- const state2 = readState(profileDir2);
39013
- if (state2[cmd] === dir2) return;
39014
- state2[cmd] = dir2;
39164
+ const state3 = readState(profileDir2);
39165
+ if (state3[cmd] === dir2) return;
39166
+ state3[cmd] = dir2;
39015
39167
  try {
39016
- writeFileSync4(statePath(profileDir2), JSON.stringify(state2, null, 2), "utf8");
39168
+ writeFileSync4(statePath(profileDir2), JSON.stringify(state3, null, 2), "utf8");
39017
39169
  } catch {
39018
39170
  }
39019
39171
  }
@@ -39022,11 +39174,11 @@ function getWorkingCredential(profileDir2) {
39022
39174
  return readState(profileDir2)[WORKING_CREDENTIAL_KEY] ?? null;
39023
39175
  }
39024
39176
  function setWorkingCredential(profileDir2, key) {
39025
- const state2 = readState(profileDir2);
39026
- if (state2[WORKING_CREDENTIAL_KEY] === key) return;
39027
- state2[WORKING_CREDENTIAL_KEY] = key;
39177
+ const state3 = readState(profileDir2);
39178
+ if (state3[WORKING_CREDENTIAL_KEY] === key) return;
39179
+ state3[WORKING_CREDENTIAL_KEY] = key;
39028
39180
  try {
39029
- writeFileSync4(statePath(profileDir2), JSON.stringify(state2, null, 2), "utf8");
39181
+ writeFileSync4(statePath(profileDir2), JSON.stringify(state3, null, 2), "utf8");
39030
39182
  } catch {
39031
39183
  }
39032
39184
  }
@@ -39035,20 +39187,20 @@ function getPinnedCredential(profileDir2) {
39035
39187
  return readState(profileDir2)[PINNED_CREDENTIAL_KEY] ?? null;
39036
39188
  }
39037
39189
  function setPinnedCredential(profileDir2, key) {
39038
- const state2 = readState(profileDir2);
39039
- if (state2[PINNED_CREDENTIAL_KEY] === key) return;
39040
- state2[PINNED_CREDENTIAL_KEY] = key;
39190
+ const state3 = readState(profileDir2);
39191
+ if (state3[PINNED_CREDENTIAL_KEY] === key) return;
39192
+ state3[PINNED_CREDENTIAL_KEY] = key;
39041
39193
  try {
39042
- writeFileSync4(statePath(profileDir2), JSON.stringify(state2, null, 2), "utf8");
39194
+ writeFileSync4(statePath(profileDir2), JSON.stringify(state3, null, 2), "utf8");
39043
39195
  } catch {
39044
39196
  }
39045
39197
  }
39046
39198
  function clearPinnedCredential(profileDir2) {
39047
- const state2 = readState(profileDir2);
39048
- if (!(PINNED_CREDENTIAL_KEY in state2)) return;
39049
- delete state2[PINNED_CREDENTIAL_KEY];
39199
+ const state3 = readState(profileDir2);
39200
+ if (!(PINNED_CREDENTIAL_KEY in state3)) return;
39201
+ delete state3[PINNED_CREDENTIAL_KEY];
39050
39202
  try {
39051
- writeFileSync4(statePath(profileDir2), JSON.stringify(state2, null, 2), "utf8");
39203
+ writeFileSync4(statePath(profileDir2), JSON.stringify(state3, null, 2), "utf8");
39052
39204
  } catch {
39053
39205
  }
39054
39206
  }
@@ -39433,16 +39585,16 @@ async function fetchUsage(token) {
39433
39585
  }
39434
39586
  }
39435
39587
  function runUsageCli(dir2, aiCli2) {
39436
- return new Promise((resolve12) => {
39588
+ return new Promise((resolve13) => {
39437
39589
  let out = "";
39438
39590
  let child = null;
39439
39591
  const finish = () => {
39440
39592
  clearTimeout(timer);
39441
- resolve12(out);
39593
+ resolve13(out);
39442
39594
  };
39443
39595
  const timer = setTimeout(() => {
39444
39596
  child?.kill();
39445
- resolve12(out);
39597
+ resolve13(out);
39446
39598
  }, 3e4);
39447
39599
  timer.unref?.();
39448
39600
  try {
@@ -40138,7 +40290,7 @@ function parseModeratedAnswer(text) {
40138
40290
  return { body: text.trim() };
40139
40291
  }
40140
40292
  function runClaudeOnce(cmd, profile, prompt, extraEnv, imageDir, denyDirs = [], timeoutMs = ANSWER_TIMEOUT_MS) {
40141
- return new Promise((resolve12, reject) => {
40293
+ return new Promise((resolve13, reject) => {
40142
40294
  const isClaude = cmd.includes("claude");
40143
40295
  const args = [
40144
40296
  "-p",
@@ -40182,7 +40334,7 @@ function runClaudeOnce(cmd, profile, prompt, extraEnv, imageDir, denyDirs = [],
40182
40334
  clearTimeout(timer);
40183
40335
  cleanup();
40184
40336
  out += parser.flush();
40185
- resolve12({ code: code ?? -1, out, err, usage: parser.usage() });
40337
+ resolve13({ code: code ?? -1, out, err, usage: parser.usage() });
40186
40338
  });
40187
40339
  child.stdin.write(prompt);
40188
40340
  child.stdin.end();
@@ -40273,14 +40425,14 @@ function reportToolResult(tool, ok, message) {
40273
40425
  }
40274
40426
 
40275
40427
  // src/core/autonomous.ts
40276
- import { readFile as readFile4, writeFile as writeFile4, chmod } from "fs/promises";
40428
+ import { readFile as readFile5, writeFile as writeFile4, chmod } from "fs/promises";
40277
40429
  import { readFileSync as readFileSync14 } from "fs";
40278
40430
  import { execFile as execFile3, spawn as spawn5 } from "child_process";
40279
40431
  import { join as join17 } from "path";
40280
40432
  import { promisify as promisify3 } from "util";
40281
40433
 
40282
40434
  // src/core/autonomous-config.ts
40283
- import { readFile as readFile2, writeFile as writeFile2 } from "fs/promises";
40435
+ import { readFile as readFile3, writeFile as writeFile2 } from "fs/promises";
40284
40436
  import { readFileSync as readFileSync13 } from "fs";
40285
40437
  import { join as join15 } from "path";
40286
40438
  var DEFAULT_AUTONOMOUS_CONFIG = {
@@ -40316,7 +40468,7 @@ function coerce2(raw) {
40316
40468
  }
40317
40469
  async function readAutonomousConfig(profileDir2) {
40318
40470
  try {
40319
- return coerce2(JSON.parse(await readFile2(autonomousConfigPath(profileDir2), "utf8")));
40471
+ return coerce2(JSON.parse(await readFile3(autonomousConfigPath(profileDir2), "utf8")));
40320
40472
  } catch {
40321
40473
  return { ...DEFAULT_AUTONOMOUS_CONFIG };
40322
40474
  }
@@ -40330,7 +40482,7 @@ function readAutonomousConfigSync(profileDir2) {
40330
40482
  }
40331
40483
  async function readAutonomousConfigText(profileDir2) {
40332
40484
  try {
40333
- return await readFile2(autonomousConfigPath(profileDir2), "utf8");
40485
+ return await readFile3(autonomousConfigPath(profileDir2), "utf8");
40334
40486
  } catch {
40335
40487
  return JSON.stringify(DEFAULT_AUTONOMOUS_CONFIG, null, 2) + "\n";
40336
40488
  }
@@ -40350,7 +40502,7 @@ function isInQuietHours(quietHours, now = /* @__PURE__ */ new Date()) {
40350
40502
  }
40351
40503
 
40352
40504
  // src/core/autonomous-history.ts
40353
- import { readFile as readFile3, writeFile as writeFile3 } from "fs/promises";
40505
+ import { readFile as readFile4, writeFile as writeFile3 } from "fs/promises";
40354
40506
  import { join as join16 } from "path";
40355
40507
  var HISTORIES_REL = ".claude/.autonomous.histories.json";
40356
40508
  var MAX_RECORDS = 50;
@@ -40360,7 +40512,7 @@ function historiesPath(root) {
40360
40512
  }
40361
40513
  async function readHistories(root) {
40362
40514
  try {
40363
- const d = JSON.parse(await readFile3(historiesPath(root), "utf8"));
40515
+ const d = JSON.parse(await readFile4(historiesPath(root), "utf8"));
40364
40516
  const ticks = d.ticks ?? {};
40365
40517
  return {
40366
40518
  cronApplied: typeof d.cronApplied === "string" ? d.cronApplied : typeof d.cron_applied === "string" ? d.cron_applied : "",
@@ -40402,7 +40554,7 @@ var BOOK_FILES = {
40402
40554
  };
40403
40555
  async function readText(path, fallback = "") {
40404
40556
  try {
40405
- return await readFile4(path, "utf8");
40557
+ return await readFile5(path, "utf8");
40406
40558
  } catch {
40407
40559
  return fallback;
40408
40560
  }
@@ -40447,6 +40599,14 @@ function tableRowsById(md) {
40447
40599
  }
40448
40600
  return rows;
40449
40601
  }
40602
+ var CAPPED_BOOK_FILES = { USER_TODO: "USER_TODO.md", USER_QA: "USER_QA.md", AI_TODO: "AI_TODO.md" };
40603
+ async function cappedBookIds(root) {
40604
+ const out = {};
40605
+ for (const [book, file] of Object.entries(CAPPED_BOOK_FILES)) {
40606
+ out[book] = new Set(tableRowsById(await readText(join17(root, file))).keys());
40607
+ }
40608
+ return out;
40609
+ }
40450
40610
  function tickScript(root) {
40451
40611
  return join17(root, TICK_REL);
40452
40612
  }
@@ -40459,10 +40619,10 @@ async function crontabList() {
40459
40619
  }
40460
40620
  }
40461
40621
  async function crontabSet(content) {
40462
- return new Promise((resolve12) => {
40622
+ return new Promise((resolve13) => {
40463
40623
  const child = spawn5("crontab", ["-"], { stdio: ["pipe", "ignore", "ignore"] });
40464
- child.on("error", () => resolve12(false));
40465
- child.on("close", (code) => resolve12(code === 0));
40624
+ child.on("error", () => resolve13(false));
40625
+ child.on("close", (code) => resolve13(code === 0));
40466
40626
  child.stdin.end(content.endsWith("\n") ? content : content + "\n");
40467
40627
  });
40468
40628
  }
@@ -40538,6 +40698,7 @@ async function uninstallCron(root) {
40538
40698
  await crontabSet(kept.join("\n"));
40539
40699
  }
40540
40700
  async function writeAutonomous(root, profileDir2, req, by) {
40701
+ let added;
40541
40702
  try {
40542
40703
  switch (req.kind) {
40543
40704
  case "settings": {
@@ -40582,6 +40743,7 @@ async function writeAutonomous(root, profileDir2, req, by) {
40582
40743
  const row = `| ${id} | | | ${tableCell(req.content)} |
40583
40744
  `;
40584
40745
  await writeFile4(join17(root, BOOK_FILES.userTodo), cur.replace(/\s*$/, "\n") + row, "utf8");
40746
+ added = 1;
40585
40747
  const authors = await readJsonMap(join17(root, AUTHORS_REL));
40586
40748
  authors[id] = { by, byLabel: req.byLabel ?? by, at: (/* @__PURE__ */ new Date()).toISOString() };
40587
40749
  await writeFile4(join17(root, AUTHORS_REL), JSON.stringify(authors, null, 2) + "\n", "utf8");
@@ -40594,8 +40756,10 @@ async function writeAutonomous(root, profileDir2, req, by) {
40594
40756
  const authors = await readJsonMap(join17(root, AUTHORS_REL));
40595
40757
  const at2 = (/* @__PURE__ */ new Date()).toISOString();
40596
40758
  const byLabel = req.byLabel ?? by;
40759
+ added = 0;
40597
40760
  for (const [id, cells] of next) {
40598
40761
  if (prev.get(id) !== cells) authors[id] = { by, byLabel, at: at2 };
40762
+ if (!prev.has(id)) added += 1;
40599
40763
  }
40600
40764
  await writeFile4(join17(root, file), req.content, "utf8");
40601
40765
  await writeFile4(join17(root, AUTHORS_REL), JSON.stringify(authors, null, 2) + "\n", "utf8");
@@ -40607,7 +40771,7 @@ async function writeAutonomous(root, profileDir2, req, by) {
40607
40771
  break;
40608
40772
  }
40609
40773
  }
40610
- return { ok: true, status: await getAutonomousStatus(root, profileDir2) };
40774
+ return { ok: true, status: await getAutonomousStatus(root, profileDir2), ...added !== void 0 ? { added } : {} };
40611
40775
  } catch (err) {
40612
40776
  return { ok: false, error: err instanceof Error ? err.message : String(err) };
40613
40777
  }
@@ -40631,14 +40795,14 @@ function detectContainerized() {
40631
40795
  }
40632
40796
  }
40633
40797
  function canReach(host, port) {
40634
- return new Promise((resolve12) => {
40798
+ return new Promise((resolve13) => {
40635
40799
  const socket = connect({ host, port });
40636
40800
  let done = false;
40637
40801
  const finish = (ok) => {
40638
40802
  if (done) return;
40639
40803
  done = true;
40640
40804
  socket.destroy();
40641
- resolve12(ok);
40805
+ resolve13(ok);
40642
40806
  };
40643
40807
  socket.setTimeout(PROBE_TIMEOUT_MS);
40644
40808
  socket.once("connect", () => finish(true));
@@ -40823,7 +40987,7 @@ async function issueGitToken(scope, host, path) {
40823
40987
  }
40824
40988
  }
40825
40989
  function revokeAtGithub(t2) {
40826
- return new Promise((resolve12) => {
40990
+ return new Promise((resolve13) => {
40827
40991
  try {
40828
40992
  const req = httpsRequest(
40829
40993
  new URL(`${t2.apiBase.replace(/\/+$/, "")}/installation/token`),
@@ -40834,14 +40998,14 @@ function revokeAtGithub(t2) {
40834
40998
  },
40835
40999
  (res) => {
40836
41000
  res.resume();
40837
- res.on("end", () => resolve12());
41001
+ res.on("end", () => resolve13());
40838
41002
  }
40839
41003
  );
40840
41004
  req.on("timeout", () => req.destroy());
40841
- req.on("error", () => resolve12());
41005
+ req.on("error", () => resolve13());
40842
41006
  req.end();
40843
41007
  } catch {
40844
- resolve12();
41008
+ resolve13();
40845
41009
  }
40846
41010
  });
40847
41011
  }
@@ -40863,26 +41027,113 @@ async function revokeAll() {
40863
41027
  await Promise.all([...state.scopes.keys()].map((s3) => endGitScope(s3)));
40864
41028
  }
40865
41029
 
40866
- // src/core/ws-client/transcript.ts
40867
- function toDtoEntry(e) {
40868
- return {
40869
- id: e.id,
40870
- source: e.source,
40871
- kind: e.kind,
40872
- text: e.text,
40873
- level: e.level,
40874
- resultKind: e.resultKind,
40875
- // Processing time on a terminal `exit` entry (ADR-0249) — the web renders it inline.
40876
- ...e.durationMs != null ? { durationMs: e.durationMs } : {},
40877
- // AI-run metadata on an `aireq` marker — powers the web's clickable CLI name → details modal.
40878
- ...e.aiMeta ? { aiMeta: e.aiMeta } : {}
40879
- };
41030
+ // src/core/git-snapshot.ts
41031
+ import { execFile as execFile6 } from "child_process";
41032
+ import { stat } from "fs/promises";
41033
+ import { join as join20, resolve as resolve5 } from "path";
41034
+ import { promisify as promisify6 } from "util";
41035
+
41036
+ // src/core/git-history.ts
41037
+ import { execFile as execFile4 } from "child_process";
41038
+ import { resolve as resolve4, sep as sep2 } from "path";
41039
+ import { promisify as promisify4 } from "util";
41040
+ var run4 = promisify4(execFile4);
41041
+ var FS = "";
41042
+ var RS = "";
41043
+ function resolveRepo(root, repo) {
41044
+ if (!root) return null;
41045
+ const base = resolve4(root);
41046
+ const requested = resolve4(base, repo && repo.trim() ? repo : ".");
41047
+ return requested === base || requested.startsWith(base + sep2) ? requested : base;
40880
41048
  }
40881
- function envReconnectMaxSec() {
40882
- const raw = Number(process.env.FOURPM_RECONNECT_MAX_BACKOFF_SEC);
40883
- if (!Number.isFinite(raw)) return null;
40884
- const { min, max } = CLI_SETTINGS_BOUNDS.reconnectMaxBackoffSec;
40885
- return Math.min(Math.max(Math.floor(raw), min), max);
41049
+ async function git(cwd2, args) {
41050
+ try {
41051
+ const { stdout } = await run4("git", args, {
41052
+ cwd: cwd2,
41053
+ timeout: 15e3,
41054
+ maxBuffer: 8 * 1024 * 1024
41055
+ });
41056
+ return stdout;
41057
+ } catch {
41058
+ return "";
41059
+ }
41060
+ }
41061
+ async function gitRepos(root) {
41062
+ if (!root) return { repos: [] };
41063
+ const base = resolve4(root);
41064
+ const top = (await git(base, ["rev-parse", "--show-toplevel"])).trim();
41065
+ if (!top || !(top === base || top.startsWith(base + sep2))) return { repos: [] };
41066
+ const repos = [];
41067
+ const rootRemote = (await git(base, ["remote", "get-url", "origin"])).trim();
41068
+ const rootBranch = (await git(base, ["rev-parse", "--abbrev-ref", "HEAD"])).trim();
41069
+ repos.push({ subdir: "", name: "", remote: rootRemote || null, branch: rootBranch || null });
41070
+ const subOut = await git(base, ["submodule", "status"]);
41071
+ for (const line of subOut.split("\n")) {
41072
+ const m2 = line.trim().match(/^[-+U ]?[0-9a-f]{7,40}\s+(\S+)/);
41073
+ const subPath = m2?.[1];
41074
+ if (!subPath) continue;
41075
+ const abs = resolve4(base, subPath);
41076
+ if (!(abs === base || abs.startsWith(base + sep2))) continue;
41077
+ const remote = (await git(abs, ["remote", "get-url", "origin"])).trim();
41078
+ const branch = (await git(abs, ["rev-parse", "--abbrev-ref", "HEAD"])).trim();
41079
+ repos.push({ subdir: subPath, name: subPath, remote: remote || null, branch: branch || null });
41080
+ }
41081
+ return { repos };
41082
+ }
41083
+ async function gitLog(root, repo, skip, limit) {
41084
+ const cwd2 = resolveRepo(root, repo);
41085
+ if (!cwd2) return { entries: [], hasMore: false };
41086
+ const capped = Math.min(Math.max(limit, 1), 200);
41087
+ const format = ["%H", "%h", "%an", "%aI", "%s"].join(FS) + RS;
41088
+ const stdout = await git(cwd2, [
41089
+ "log",
41090
+ `--skip=${Math.max(skip, 0)}`,
41091
+ `-n`,
41092
+ String(capped + 1),
41093
+ `--pretty=format:${format}`
41094
+ ]);
41095
+ const rows = stdout.split(RS).map((r) => r.replace(/^\n/, "")).filter((r) => r.length > 0);
41096
+ const entries2 = rows.slice(0, capped).map((row) => {
41097
+ const [hash = "", shortHash = "", author = "", date = "", subject = ""] = row.split(FS);
41098
+ return { hash, shortHash, author, date, subject };
41099
+ });
41100
+ return { entries: entries2, hasMore: rows.length > capped };
41101
+ }
41102
+ async function gitCommit(root, repo, hash) {
41103
+ const cwd2 = resolveRepo(root, repo);
41104
+ const empty = { hash, author: "", date: "", subject: "", files: [] };
41105
+ if (!cwd2 || !hash) return empty;
41106
+ const format = ["%H", "%an", "%aI", "%s"].join(FS);
41107
+ const stdout = await git(cwd2, [
41108
+ "show",
41109
+ "--name-status",
41110
+ "-M",
41111
+ `--pretty=format:${format}`,
41112
+ hash
41113
+ ]);
41114
+ if (!stdout) return empty;
41115
+ const lines = stdout.split("\n");
41116
+ const [full = hash, author = "", date = "", subject = ""] = (lines[0] ?? "").split(FS);
41117
+ const files = [];
41118
+ for (const line of lines.slice(1)) {
41119
+ if (!line.trim()) continue;
41120
+ const parts = line.split(" ");
41121
+ const status = (parts[0] ?? "").charAt(0);
41122
+ if (!status) continue;
41123
+ if (status === "R" || status === "C") {
41124
+ files.push({ path: parts[2] ?? "", status, oldPath: parts[1] ?? void 0 });
41125
+ } else {
41126
+ files.push({ path: parts[1] ?? "", status });
41127
+ }
41128
+ }
41129
+ return { hash: full, author, date, subject, files };
41130
+ }
41131
+ async function gitCommitDiff(root, repo, hash, path) {
41132
+ const cwd2 = resolveRepo(root, repo);
41133
+ if (!cwd2 || !hash || !path) return { path, oldContent: "", newContent: "" };
41134
+ const oldContent = await git(cwd2, ["show", `${hash}^:${path}`]);
41135
+ const newContent = await git(cwd2, ["show", `${hash}:${path}`]);
41136
+ return { path, oldContent, newContent };
40886
41137
  }
40887
41138
 
40888
41139
  // src/core/ws-client/command-dispatch.ts
@@ -40971,7 +41222,7 @@ async function runCommand(dispatch, emitRaw, opts) {
40971
41222
  });
40972
41223
  child.stdout?.on("data", (data) => batcher.push(data.toString("utf8")));
40973
41224
  child.stderr?.on("data", (data) => batcher.push(data.toString("utf8")));
40974
- await new Promise((resolve12) => {
41225
+ await new Promise((resolve13) => {
40975
41226
  child.on("close", (exitCode) => {
40976
41227
  clearTimers();
40977
41228
  batcher.flush();
@@ -40985,7 +41236,7 @@ async function runCommand(dispatch, emitRaw, opts) {
40985
41236
  // note instead of spinning.
40986
41237
  exitCode: cancelled ? COMMAND_CANCELLED_EXIT_CODE : timedOut ? 124 : exitCode ?? -1
40987
41238
  });
40988
- resolve12();
41239
+ resolve13();
40989
41240
  });
40990
41241
  child.on("error", (err) => {
40991
41242
  clearTimers();
@@ -40997,7 +41248,7 @@ async function runCommand(dispatch, emitRaw, opts) {
40997
41248
  done: true,
40998
41249
  exitCode: -1
40999
41250
  });
41000
- resolve12();
41251
+ resolve13();
41001
41252
  });
41002
41253
  });
41003
41254
  }
@@ -41020,15 +41271,15 @@ function isOutOfCredits(text) {
41020
41271
  );
41021
41272
  }
41022
41273
  function runAttempt(commandId, cmd, args, stdin, cwd2, env3, onChunk, timeoutMs, signal) {
41023
- return new Promise((resolve12) => {
41274
+ return new Promise((resolve13) => {
41024
41275
  runCommand(
41025
41276
  { commandId, cmd, args, path: cwd2, env: env3 },
41026
41277
  (out) => {
41027
41278
  if (out.chunk) onChunk(out.chunk);
41028
- if (out.done) resolve12(out.exitCode ?? -1);
41279
+ if (out.done) resolve13(out.exitCode ?? -1);
41029
41280
  },
41030
41281
  { stdin, ...timeoutMs > 0 ? { timeoutMs } : {}, ...signal ? { signal } : {} }
41031
- ).catch(() => resolve12(-1));
41282
+ ).catch(() => resolve13(-1));
41032
41283
  });
41033
41284
  }
41034
41285
  async function runAiFailover(plan, commandId, cwd2, handlers, timeoutMs = 0, signal) {
@@ -41249,8 +41500,8 @@ function runHistory(ctx) {
41249
41500
  ctx.print(t("slash.history.header", { n: recent.length, total: all.length }));
41250
41501
  recent.forEach((e, i) => {
41251
41502
  const time = new Date(e.startedAt).toLocaleTimeString();
41252
- const state2 = e.status === "done" ? "\u2713" : e.status === "failed" ? `\u2717 exit ${e.exitCode}` : t("slash.history.running");
41253
- ctx.print(` [${i + 1}] ${time} ${commandLine(e.cmd, e.args)} \xB7 ${state2}`);
41503
+ const state3 = e.status === "done" ? "\u2713" : e.status === "failed" ? `\u2717 exit ${e.exitCode}` : t("slash.history.running");
41504
+ ctx.print(` [${i + 1}] ${time} ${commandLine(e.cmd, e.args)} \xB7 ${state3}`);
41254
41505
  });
41255
41506
  }
41256
41507
  function runOutput(ctx) {
@@ -41261,8 +41512,8 @@ function runOutput(ctx) {
41261
41512
  return;
41262
41513
  }
41263
41514
  const entry = all.slice().reverse()[n - 1];
41264
- const state2 = entry.exitCode != null ? ` (exit ${entry.exitCode})` : "";
41265
- ctx.print(t("slash.output.header", { n, cmd: commandLine(entry.cmd, entry.args), status: entry.status, state: state2 }));
41515
+ const state3 = entry.exitCode != null ? ` (exit ${entry.exitCode})` : "";
41516
+ ctx.print(t("slash.output.header", { n, cmd: commandLine(entry.cmd, entry.args), status: entry.status, state: state3 }));
41266
41517
  const out = readCommandOutput(entry.commandId);
41267
41518
  if (out == null) {
41268
41519
  ctx.print(t("slash.output.none"), "warn");
@@ -41557,7 +41808,7 @@ function buildPrompt2(input) {
41557
41808
  ].join("\n");
41558
41809
  }
41559
41810
  function runOnce(cmd, profile, prompt, cwd2, extraEnv, denyDirs = []) {
41560
- return new Promise((resolve12, reject) => {
41811
+ return new Promise((resolve13, reject) => {
41561
41812
  const args = [
41562
41813
  "-p",
41563
41814
  "--output-format",
@@ -41592,7 +41843,7 @@ function runOnce(cmd, profile, prompt, cwd2, extraEnv, denyDirs = []) {
41592
41843
  });
41593
41844
  child.on("close", (code) => {
41594
41845
  clearTimeout(timer);
41595
- resolve12({ code: code ?? -1, out, err });
41846
+ resolve13({ code: code ?? -1, out, err });
41596
41847
  });
41597
41848
  child.stdin.write(prompt);
41598
41849
  child.stdin.end();
@@ -41619,13 +41870,13 @@ async function runMemoryCompaction(ai2, cwd2, input) {
41619
41870
  }
41620
41871
 
41621
41872
  // src/core/git-commit-identity.ts
41622
- import { execFile as execFile4 } from "child_process";
41623
- import { promisify as promisify4 } from "util";
41624
- var run4 = promisify4(execFile4);
41873
+ import { execFile as execFile5 } from "child_process";
41874
+ import { promisify as promisify5 } from "util";
41875
+ var run5 = promisify5(execFile5);
41625
41876
  async function setCommitAuthor(cwd2, machineUsername) {
41626
41877
  if (!machineUsername) return;
41627
41878
  try {
41628
- await run4("git", ["config", "--local", "user.name", machineUsername], { cwd: cwd2, timeout: 8e3 });
41879
+ await run5("git", ["config", "--local", "user.name", machineUsername], { cwd: cwd2, timeout: 8e3 });
41629
41880
  } catch {
41630
41881
  }
41631
41882
  }
@@ -41633,13 +41884,13 @@ async function setCommitAuthor(cwd2, machineUsername) {
41633
41884
  // src/core/ws-client/run-slot.ts
41634
41885
  var NO_SLOT = { release: () => void 0 };
41635
41886
  function sleep2(ms2, signal) {
41636
- return new Promise((resolve12) => {
41637
- if (signal?.aborted) return resolve12();
41887
+ return new Promise((resolve13) => {
41888
+ if (signal?.aborted) return resolve13();
41638
41889
  const timer = setTimeout(done, ms2);
41639
41890
  function done() {
41640
41891
  clearTimeout(timer);
41641
41892
  signal?.removeEventListener("abort", done);
41642
- resolve12();
41893
+ resolve13();
41643
41894
  }
41644
41895
  signal?.addEventListener("abort", done, { once: true });
41645
41896
  });
@@ -41682,6 +41933,7 @@ async function acquireRunSlot(ctx, onQueued, signal) {
41682
41933
  }
41683
41934
  return { kind: "granted", handle, queued: ticket !== void 0 };
41684
41935
  }
41936
+ if ("denied" in reply) return { kind: "denied", reason: reply.denied };
41685
41937
  ticket = reply.ticket;
41686
41938
  limit = reply.limit;
41687
41939
  if (reply.position !== lastPosition) {
@@ -41700,11 +41952,41 @@ function leaveQueue(ctx, ticket) {
41700
41952
  // src/core/ws-client/command-dispatch.ts
41701
41953
  var MEMORY_SEED_HEADER = "CONTEXT MEMORY from earlier in this conversation (may span prior sessions/accounts). Use it as background; do not repeat it back unless relevant:";
41702
41954
  var activeRuns = /* @__PURE__ */ new Map();
41955
+ var GIT_STATE_SUBCOMMANDS = /* @__PURE__ */ new Set([
41956
+ "add",
41957
+ "branch",
41958
+ "checkout",
41959
+ "cherry-pick",
41960
+ "commit",
41961
+ "fetch",
41962
+ "merge",
41963
+ "pull",
41964
+ "push",
41965
+ "rebase",
41966
+ "reset",
41967
+ "restore",
41968
+ "revert",
41969
+ "rm",
41970
+ "stash",
41971
+ "switch",
41972
+ "tag",
41973
+ "mv"
41974
+ ]);
41975
+ function changesGitState(cmd, args) {
41976
+ if (cmd === "gh" || cmd === "glab") return args.includes("create") || args.includes("merge");
41977
+ if (cmd !== "git") return false;
41978
+ let i = 0;
41979
+ while (args[i] === "-C" || args[i] === "-c") i += 2;
41980
+ return GIT_STATE_SUBCOMMANDS.has(args[i] ?? "");
41981
+ }
41982
+ function activeAiRunCount() {
41983
+ return activeRuns.size;
41984
+ }
41703
41985
  function cancelActiveRun(commandId) {
41704
- const run11 = activeRuns.get(commandId);
41705
- if (!run11) return { ok: false, state: "unknown" };
41706
- run11.controller.abort();
41707
- return { ok: true, state: run11.state };
41986
+ const run12 = activeRuns.get(commandId);
41987
+ if (!run12) return { ok: false, state: "unknown" };
41988
+ run12.controller.abort();
41989
+ return { ok: true, state: run12.state };
41708
41990
  }
41709
41991
  function handleCommandChannels(ctx, message, payload) {
41710
41992
  if (message.channel === WsChannels.COMMAND_CANCEL) {
@@ -41769,6 +42051,7 @@ function handleCommandChannels(ctx, message, payload) {
41769
42051
  ctx.bus.endBusy(dispatch.cmd);
41770
42052
  const code = out.exitCode ?? -1;
41771
42053
  finishCommand(out.commandId, code);
42054
+ if (changesGitState(dispatch.cmd, dispatch.args)) requestGitSnapshot("git-write");
41772
42055
  ctx.bus.push({
41773
42056
  source: "server",
41774
42057
  kind: "exit",
@@ -41800,15 +42083,16 @@ async function runLocalCommand(ctx, input) {
41800
42083
  await runAiPrompt(ctx, prompt, commandId, "local");
41801
42084
  }
41802
42085
  async function runAiPrompt(ctx, prompt, commandId, origin, oneShot = false, images, aiConfig, readOnly = false, bypass = false) {
41803
- const run11 = { controller: new AbortController(), state: "queued" };
41804
- activeRuns.set(commandId, run11);
42086
+ const run12 = { controller: new AbortController(), state: "queued" };
42087
+ activeRuns.set(commandId, run12);
41805
42088
  try {
41806
- await runAiPromptInner(ctx, prompt, commandId, origin, run11, oneShot, images, aiConfig, readOnly, bypass);
42089
+ await runAiPromptInner(ctx, prompt, commandId, origin, run12, oneShot, images, aiConfig, readOnly, bypass);
41807
42090
  } finally {
41808
42091
  activeRuns.delete(commandId);
42092
+ if (!readOnly) requestGitSnapshot("ai-run");
41809
42093
  }
41810
42094
  }
41811
- async function runAiPromptInner(ctx, prompt, commandId, origin, run11, oneShot = false, images, aiConfig, readOnly = false, bypass = false) {
42095
+ async function runAiPromptInner(ctx, prompt, commandId, origin, run12, oneShot = false, images, aiConfig, readOnly = false, bypass = false) {
41812
42096
  const config = readProfileConfig(ctx.profileDir);
41813
42097
  const projectTimeoutSec = config.projectAiRunTimeoutSec ?? 0;
41814
42098
  const machineTimeoutSec = config.aiRunTimeoutSec ?? 0;
@@ -41989,13 +42273,20 @@ ${guardedPrompt}`;
41989
42273
  queue: { position, limit, running: running2 }
41990
42274
  });
41991
42275
  },
41992
- run11.controller.signal
42276
+ run12.controller.signal
41993
42277
  );
41994
42278
  if (slot.kind === "timeout") {
41995
42279
  neverStarted = true;
41996
42280
  const text = `RUN_QUEUE_TIMEOUT: waited 30 min for a free run slot (plan limit ${slot.limit} parallel runs) \u2014 not run`;
41997
42281
  ctx.bus.push({ source: origin, kind: "log", text, level: "warn" });
41998
42282
  ctx.send(WsChannels.COMMAND_OUTPUT, { commandId, seq: seq++, chunk: `${text}
42283
+ `, log: true });
42284
+ result = { exitCode: 1, workedDir: null, workedKey: null, workedCmd: null, usage: { tokens: 0, input: 0, output: 0, cacheRead: 0, cacheCreation: 0 }, sessionId: "" };
42285
+ } else if (slot.kind === "denied") {
42286
+ neverStarted = true;
42287
+ const text = "STORAGE_QUOTA_EXCEEDED: the organization's hosted storage is full \u2014 clear storage in Settings \u203A Storage, then retry";
42288
+ ctx.bus.push({ source: origin, kind: "log", text, level: "warn" });
42289
+ ctx.send(WsChannels.COMMAND_OUTPUT, { commandId, seq: seq++, chunk: `${text}
41999
42290
  `, log: true });
42000
42291
  result = { exitCode: 1, workedDir: null, workedKey: null, workedCmd: null, usage: { tokens: 0, input: 0, output: 0, cacheRead: 0, cacheCreation: 0 }, sessionId: "" };
42001
42292
  } else if (slot.kind === "cancelled") {
@@ -42003,9 +42294,9 @@ ${guardedPrompt}`;
42003
42294
  result = { exitCode: COMMAND_CANCELLED_EXIT_CODE, workedDir: null, workedKey: null, workedCmd: null, usage: { tokens: 0, input: 0, output: 0, cacheRead: 0, cacheCreation: 0 }, sessionId: "", cancelled: true };
42004
42295
  } else {
42005
42296
  if (slot.queued) ctx.send(WsChannels.COMMAND_OUTPUT, { commandId, seq: seq++, chunk: "", log: true, queue: null });
42006
- run11.state = "running";
42297
+ run12.state = "running";
42007
42298
  try {
42008
- result = await runAiFailover(plan, commandId, cwd2, handlers, aiRunTimeoutMs, run11.controller.signal);
42299
+ result = await runAiFailover(plan, commandId, cwd2, handlers, aiRunTimeoutMs, run12.controller.signal);
42009
42300
  } finally {
42010
42301
  slot.handle.release();
42011
42302
  }
@@ -42316,16 +42607,211 @@ function rejectByReview(ctx, commandId, origin, prompt, reasons) {
42316
42607
  });
42317
42608
  }
42318
42609
 
42610
+ // src/core/git-snapshot.ts
42611
+ var run6 = promisify6(execFile6);
42612
+ var US = "";
42613
+ var RS2 = "";
42614
+ var MIN_SPACING_MS = 15e3;
42615
+ var DEFAULT_INTERVAL_MIN = 10;
42616
+ async function git2(cwd2, args, timeout = 2e4) {
42617
+ try {
42618
+ const { stdout } = await run6("git", args, { cwd: cwd2, timeout, maxBuffer: 16 * 1024 * 1024 });
42619
+ return stdout;
42620
+ } catch {
42621
+ return "";
42622
+ }
42623
+ }
42624
+ function parseTrack(track) {
42625
+ const ahead = /ahead (\d+)/.exec(track)?.[1];
42626
+ const behind = /behind (\d+)/.exec(track)?.[1];
42627
+ return { ahead: ahead ? Number(ahead) : 0, behind: behind ? Number(behind) : 0 };
42628
+ }
42629
+ function parseStatus(out) {
42630
+ const dirty = { staged: 0, unstaged: 0, untracked: 0, conflicted: 0 };
42631
+ const files = [];
42632
+ for (const line of out.split("\n")) {
42633
+ if (!line || line.startsWith("#")) continue;
42634
+ const kind = line[0];
42635
+ if (kind === "?") {
42636
+ dirty.untracked++;
42637
+ files.push({ status: "?", path: line.slice(2) });
42638
+ } else if (kind === "u") {
42639
+ dirty.conflicted++;
42640
+ files.push({ status: "U", path: line.split(" ").slice(10).join(" ") });
42641
+ } else if (kind === "1" || kind === "2") {
42642
+ const xy = line.slice(2, 4);
42643
+ const x = xy[0] ?? ".";
42644
+ const y = xy[1] ?? ".";
42645
+ if (x !== ".") dirty.staged++;
42646
+ if (y !== ".") dirty.unstaged++;
42647
+ const parts = line.split(" ");
42648
+ const path = (kind === "1" ? parts.slice(8) : parts.slice(9)).join(" ").split(" ")[0] ?? "";
42649
+ files.push({ status: y !== "." ? y : x, path });
42650
+ }
42651
+ }
42652
+ return { dirty, files };
42653
+ }
42654
+ function parseLog(out, withFiles) {
42655
+ const commits = [];
42656
+ for (const rec of out.split(RS2)) {
42657
+ if (!rec.trim()) continue;
42658
+ const [header = "", ...rest] = rec.split("\n");
42659
+ const [sha = "", parents = "", author = "", date = "", subject = ""] = header.split(US);
42660
+ if (!sha) continue;
42661
+ const files = withFiles ? rest.map((l) => l.trim()).filter(Boolean).map((l) => {
42662
+ const cols = l.split(" ");
42663
+ return { status: (cols[0] ?? "M").charAt(0), path: cols[cols.length - 1] ?? "" };
42664
+ }) : [];
42665
+ commits.push({ sha, parents: parents.split(" ").filter(Boolean), author, date, subject, files });
42666
+ }
42667
+ return commits;
42668
+ }
42669
+ async function buildRepoSnapshot(dir2, subdir) {
42670
+ const L2 = GIT_SNAPSHOT_LIMITS;
42671
+ const fmt = `--format=${RS2}%H${US}%P${US}%an${US}%aI${US}%s`;
42672
+ const [abbrev, headSha, heads, remotes, remoteHead, status, gitDir] = await Promise.all([
42673
+ git2(dir2, ["rev-parse", "--abbrev-ref", "HEAD"]),
42674
+ git2(dir2, ["rev-parse", "--verify", "HEAD"]),
42675
+ git2(dir2, ["for-each-ref", "refs/heads", `--format=%(refname:short)${US}%(objectname)${US}%(upstream:short)${US}%(upstream:track,nobracket)`]),
42676
+ git2(dir2, ["for-each-ref", "refs/remotes/origin", `--format=%(refname:short)${US}%(objectname)${US}%(symref)`]),
42677
+ git2(dir2, ["symbolic-ref", "--short", "refs/remotes/origin/HEAD"]),
42678
+ git2(dir2, ["status", "--porcelain=v2", "--untracked-files=all"]),
42679
+ git2(dir2, ["rev-parse", "--absolute-git-dir"])
42680
+ ]);
42681
+ const branchName = abbrev.trim();
42682
+ const detached = branchName === "HEAD";
42683
+ const branches = heads.split("\n").filter(Boolean).map((l) => {
42684
+ const [name = "", sha = "", upstream2 = "", track = ""] = l.split(US);
42685
+ return { name, sha, upstream: upstream2 || null, ...parseTrack(track) };
42686
+ });
42687
+ const remoteBranches = remotes.split("\n").filter(Boolean).map((l) => l.split(US)).filter(([, , symref]) => !symref).map(([name = "", sha = ""]) => ({ name: name.replace(/^origin\//, ""), sha })).filter((b2) => b2.name && b2.name !== "origin");
42688
+ const { dirty, files } = parseStatus(status);
42689
+ const [localOut, remoteOut] = await Promise.all([
42690
+ git2(dir2, ["log", "--branches", "--not", "--remotes", "-n", String(L2.localCommits + 1), "--name-status", fmt], 3e4),
42691
+ git2(dir2, ["log", "--remotes", "--date-order", "-n", String(L2.remoteCommits), fmt], 3e4)
42692
+ ]);
42693
+ const localAll = parseLog(localOut, true);
42694
+ const localCommits = localAll.slice(0, L2.localCommits).map((c) => ({
42695
+ ...c,
42696
+ branches: branches.filter((b2) => b2.sha === c.sha).map((b2) => b2.name),
42697
+ files: c.files.slice(0, L2.filesPerCommit),
42698
+ filesTruncated: c.files.length > L2.filesPerCommit
42699
+ }));
42700
+ const remoteCommits = parseLog(remoteOut, false).map(({ files: _f, ...c }) => c);
42701
+ const defaultBranch = remoteHead.trim().replace(/^origin\//, "") || null;
42702
+ const upstream = branches.find((b2) => b2.name === branchName)?.upstream;
42703
+ const baseRef = upstream ?? (defaultBranch ? `origin/${defaultBranch}` : null);
42704
+ const baseSha = headSha.trim() && baseRef ? (await git2(dir2, ["merge-base", "HEAD", baseRef])).trim() || null : null;
42705
+ let lastFetchAt = null;
42706
+ try {
42707
+ if (gitDir.trim()) lastFetchAt = (await stat(join20(gitDir.trim(), "FETCH_HEAD"))).mtime.toISOString();
42708
+ } catch {
42709
+ lastFetchAt = null;
42710
+ }
42711
+ return {
42712
+ subdir,
42713
+ head: { branch: detached || !branchName ? null : branchName, sha: headSha.trim() || null, detached },
42714
+ branches,
42715
+ remote: { name: "origin", defaultBranch, branches: remoteBranches },
42716
+ dirty,
42717
+ dirtyFiles: files.slice(0, L2.dirtyFiles),
42718
+ dirtyTruncated: files.length > L2.dirtyFiles,
42719
+ localCommits,
42720
+ localTruncated: localAll.length > L2.localCommits,
42721
+ remoteCommits,
42722
+ baseSha,
42723
+ lastFetchAt
42724
+ };
42725
+ }
42726
+ async function buildGitSnapshot(root) {
42727
+ const base = resolve5(root);
42728
+ const { repos } = await gitRepos(base);
42729
+ const snaps = await Promise.all(repos.map((r) => buildRepoSnapshot(r.subdir ? resolve5(base, r.subdir) : base, r.subdir)));
42730
+ return { takenAt: (/* @__PURE__ */ new Date()).toISOString(), repos: snaps };
42731
+ }
42732
+ async function fetchAll(root) {
42733
+ const base = resolve5(root);
42734
+ const { repos } = await gitRepos(base);
42735
+ for (const r of repos) await git2(r.subdir ? resolve5(base, r.subdir) : base, ["fetch", "--prune", "--quiet", "origin"], 6e4);
42736
+ }
42737
+ var state2 = { host: null, lastSentAt: 0, pending: null, periodic: null, running: false };
42738
+ async function reportNow(fetchFirst) {
42739
+ const host = state2.host;
42740
+ const root = host?.physicRoot;
42741
+ if (!host || !root || state2.running || activeAiRunCount() > 0) return;
42742
+ state2.running = true;
42743
+ try {
42744
+ if (fetchFirst) await fetchAll(root);
42745
+ const payload = await buildGitSnapshot(root);
42746
+ if (payload.repos.length === 0) return;
42747
+ host.send(WsChannels.GIT_SNAPSHOT, payload);
42748
+ state2.lastSentAt = Date.now();
42749
+ } catch (err) {
42750
+ logger.warn("git.snapshot.failed", { error: String(err) });
42751
+ } finally {
42752
+ state2.running = false;
42753
+ }
42754
+ }
42755
+ function requestGitSnapshot(reason) {
42756
+ if (!state2.host || state2.pending) return;
42757
+ const wait = Math.max(0, state2.lastSentAt + MIN_SPACING_MS - Date.now());
42758
+ state2.pending = setTimeout(() => {
42759
+ state2.pending = null;
42760
+ logger.debug("git.snapshot.request", { reason });
42761
+ void reportNow(false);
42762
+ }, wait || 1e3);
42763
+ state2.pending.unref?.();
42764
+ }
42765
+ function startGitSnapshots(host) {
42766
+ state2.host = host;
42767
+ if (state2.periodic) clearInterval(state2.periodic);
42768
+ state2.periodic = null;
42769
+ const raw = readProfileConfig(host.profileDir).gitSnapshotIntervalMin;
42770
+ const minutes = typeof raw === "number" && Number.isFinite(raw) && raw >= 0 ? raw : DEFAULT_INTERVAL_MIN;
42771
+ if (minutes > 0) {
42772
+ state2.periodic = setInterval(() => void reportNow(true), minutes * 6e4);
42773
+ state2.periodic.unref?.();
42774
+ }
42775
+ requestGitSnapshot("connect");
42776
+ }
42777
+
42778
+ // src/core/ws-client/transcript.ts
42779
+ function toDtoEntry(e) {
42780
+ return {
42781
+ id: e.id,
42782
+ source: e.source,
42783
+ kind: e.kind,
42784
+ text: e.text,
42785
+ level: e.level,
42786
+ resultKind: e.resultKind,
42787
+ // Processing time on a terminal `exit` entry (ADR-0249) — the web renders it inline.
42788
+ ...e.durationMs != null ? { durationMs: e.durationMs } : {},
42789
+ // AI-run metadata on an `aireq` marker — powers the web's clickable CLI name → details modal.
42790
+ ...e.aiMeta ? { aiMeta: e.aiMeta } : {}
42791
+ };
42792
+ }
42793
+ function envReconnectMaxSec() {
42794
+ const raw = Number(process.env.FOURPM_RECONNECT_MAX_BACKOFF_SEC);
42795
+ if (!Number.isFinite(raw)) return null;
42796
+ const { min, max } = CLI_SETTINGS_BOUNDS.reconnectMaxBackoffSec;
42797
+ return Math.min(Math.max(Math.floor(raw), min), max);
42798
+ }
42799
+
42319
42800
  // src/core/autonomous-cycle.ts
42320
42801
  import { randomUUID } from "crypto";
42321
- import { appendFile as appendFile2, mkdir as mkdir3, readFile as readFile5, readdir as readdir2, rm as rm2, stat } from "fs/promises";
42322
- import { join as join20 } from "path";
42323
- function buildAutonomousCyclePrompt() {
42802
+ import { appendFile as appendFile2, mkdir as mkdir3, readFile as readFile6, readdir as readdir2, rm as rm2, stat as stat2 } from "fs/promises";
42803
+ import { join as join21 } from "path";
42804
+ function buildAutonomousCyclePrompt(opts = {}) {
42805
+ const capNote = opts.intakeBlocked ? `
42806
+
42807
+ **Step 2 is DISABLED this cycle** \u2014 the organization reached its monthly plan limit for new \`AI_TODO\` /
42808
+ \`USER_QA\` rows. Do NOT add any row to \`AI_TODO.md\` or \`USER_QA.md\`; skip Step 2 entirely and continue
42809
+ with the already-approved work.` : "";
42324
42810
  return `You are running UNATTENDED as the 4PM autonomous cycle (headless, one tick). Do EXACTLY ONE small
42325
42811
  work cycle with the steps below, then STOP. Keep each cycle to ONE small task so you never run out of
42326
42812
  tokens mid-way. If a step fails, append a row to the **Incidents** table of \`AI_DONE.md\` and STOP \u2014
42327
42813
  do not push on. Write all books/docs/code in the language the project's \`CLAUDE.md\` specifies (default
42328
- English).
42814
+ English).${capNote}
42329
42815
 
42330
42816
  ## Books & approval (MUST read the approvals file)
42331
42817
  The five "book" files at the project root \u2014 \`USER_TODO.md\`, \`USER_QA.md\`, \`AI_TODO.md\`,
@@ -42459,8 +42945,16 @@ async function runAutonomousCycle(ctx) {
42459
42945
  await log(`[run] cycle (tick ${hist.ticks.count}/${today})`);
42460
42946
  const commandId = randomUUID();
42461
42947
  const override = cfg.model ? { model: cfg.model } : void 0;
42948
+ const usage = await readBookUsage(ctx);
42949
+ const intakeBlocked = ["AI_TODO", "USER_QA"].some((b2) => {
42950
+ const c = usage?.books[b2];
42951
+ return !!c && c.limit !== null && c.used >= c.limit;
42952
+ });
42953
+ if (intakeBlocked) await log("[cap] monthly AI_TODO/USER_QA limit reached \u2014 intake skipped this cycle");
42954
+ const booksBefore = await cappedBookIds(root);
42462
42955
  try {
42463
- await runAiPrompt(ctx, buildAutonomousCyclePrompt(), commandId, "local", false, void 0, override, false, true);
42956
+ await runAiPrompt(ctx, buildAutonomousCyclePrompt({ intakeBlocked }), commandId, "local", false, void 0, override, false, true);
42957
+ await reportNewBookRows(ctx, root, booksBefore);
42464
42958
  hist = await readHistories(root);
42465
42959
  hist.consecutiveFails = 0;
42466
42960
  await recordRun(root, hist, "success", "cycle complete", today);
@@ -42468,6 +42962,7 @@ async function runAutonomousCycle(ctx) {
42468
42962
  ctx.bus.autonomousDone(true);
42469
42963
  } catch (err) {
42470
42964
  const msg = err instanceof Error ? err.message : String(err);
42965
+ await reportNewBookRows(ctx, root, booksBefore);
42471
42966
  hist = await readHistories(root);
42472
42967
  hist.consecutiveFails += 1;
42473
42968
  await recordRun(root, hist, "failure", msg, today);
@@ -42484,13 +42979,32 @@ async function runAutonomousCycle(ctx) {
42484
42979
  running.delete(root);
42485
42980
  }
42486
42981
  }
42982
+ async function readBookUsage(ctx) {
42983
+ try {
42984
+ return await ctx.request(WsChannels.AUTONOMOUS_BOOK_USAGE, {});
42985
+ } catch {
42986
+ return null;
42987
+ }
42988
+ }
42989
+ async function reportNewBookRows(ctx, root, before) {
42990
+ try {
42991
+ const after = await cappedBookIds(root);
42992
+ const added = {};
42993
+ for (const book of Object.keys(after)) {
42994
+ const n = [...after[book]].filter((id) => !before[book].has(id)).length;
42995
+ if (n > 0) added[book] = n;
42996
+ }
42997
+ if (Object.keys(added).length > 0) await ctx.request(WsChannels.AUTONOMOUS_BOOK_USAGE, { added });
42998
+ } catch {
42999
+ }
43000
+ }
42487
43001
  async function dayLog(root, line) {
42488
43002
  try {
42489
- const dir2 = join20(root, ".claude", "logs");
43003
+ const dir2 = join21(root, ".claude", "logs");
42490
43004
  await mkdir3(dir2, { recursive: true });
42491
43005
  const day = (/* @__PURE__ */ new Date()).toISOString().slice(0, 10);
42492
43006
  const ts2 = (/* @__PURE__ */ new Date()).toISOString().slice(0, 19).replace("T", " ");
42493
- await appendFile2(join20(dir2, `autonomous-tick-${day}.log`), `${ts2} ${line}
43007
+ await appendFile2(join21(dir2, `autonomous-tick-${day}.log`), `${ts2} ${line}
42494
43008
  `, "utf8");
42495
43009
  } catch {
42496
43010
  }
@@ -42498,12 +43012,12 @@ async function dayLog(root, line) {
42498
43012
  async function pruneLogs(root, retentionDays) {
42499
43013
  if (!(retentionDays > 0)) return;
42500
43014
  try {
42501
- const dir2 = join20(root, ".claude", "logs");
43015
+ const dir2 = join21(root, ".claude", "logs");
42502
43016
  const cutoff = Date.now() - retentionDays * 864e5;
42503
43017
  for (const f2 of await readdir2(dir2)) {
42504
43018
  if (!/^autonomous-tick-.*\.log$/.test(f2)) continue;
42505
- const st2 = await stat(join20(dir2, f2)).catch(() => null);
42506
- if (st2 && st2.mtimeMs < cutoff) await rm2(join20(dir2, f2), { force: true }).catch(() => void 0);
43019
+ const st2 = await stat2(join21(dir2, f2)).catch(() => null);
43020
+ if (st2 && st2.mtimeMs < cutoff) await rm2(join21(dir2, f2), { force: true }).catch(() => void 0);
42507
43021
  }
42508
43022
  } catch {
42509
43023
  }
@@ -42515,15 +43029,15 @@ async function recordRun(root, hist, status, note, today) {
42515
43029
  }
42516
43030
  async function readJsonObject(path) {
42517
43031
  try {
42518
- return JSON.parse(await readFile5(path, "utf8"));
43032
+ return JSON.parse(await readFile6(path, "utf8"));
42519
43033
  } catch {
42520
43034
  return {};
42521
43035
  }
42522
43036
  }
42523
43037
  async function hasWork(root) {
42524
- const approvals = await readJsonObject(join20(root, ".claude/.autonomous.approvals.json"));
43038
+ const approvals = await readJsonObject(join21(root, ".claude/.autonomous.approvals.json"));
42525
43039
  const approved = Object.entries(approvals).filter(([, v2]) => v2 && typeof v2 === "object" && v2.approved === true).map(([k2]) => k2);
42526
- const read = (f2) => readFile5(join20(root, f2), "utf8").catch(() => "");
43040
+ const read = (f2) => readFile6(join21(root, f2), "utf8").catch(() => "");
42527
43041
  const [userTodo, userQa, aiTodo, aiProgress] = await Promise.all([
42528
43042
  read("USER_TODO.md"),
42529
43043
  read("USER_QA.md"),
@@ -42561,12 +43075,12 @@ async function allAiProfilesExhausted(ctx, cfg) {
42561
43075
 
42562
43076
  // src/core/fs-browse.ts
42563
43077
  import { readdir as readdir3 } from "fs/promises";
42564
- import { resolve as resolve4, sep as sep2 } from "path";
43078
+ import { resolve as resolve6, sep as sep3 } from "path";
42565
43079
  async function listDir(path, root) {
42566
43080
  if (!root) return { path: "", entries: [] };
42567
- const base = resolve4(root);
42568
- const requested = resolve4(base, path && path.trim() ? path : ".");
42569
- const target = requested === base || requested.startsWith(base + sep2) ? requested : base;
43081
+ const base = resolve6(root);
43082
+ const requested = resolve6(base, path && path.trim() ? path : ".");
43083
+ const target = requested === base || requested.startsWith(base + sep3) ? requested : base;
42570
43084
  try {
42571
43085
  const dirents = await readdir3(target, { withFileTypes: true });
42572
43086
  const entries2 = dirents.map((d) => ({
@@ -42583,14 +43097,14 @@ async function listDir(path, root) {
42583
43097
  }
42584
43098
 
42585
43099
  // src/core/fs-read.ts
42586
- import { readFile as readFile6 } from "fs/promises";
42587
- import { resolve as resolve5, sep as sep3 } from "path";
43100
+ import { readFile as readFile7 } from "fs/promises";
43101
+ import { resolve as resolve7, sep as sep4 } from "path";
42588
43102
  var MAX_BYTES = 256 * 1024;
42589
43103
  function resolveInRoot(root, relPath) {
42590
43104
  if (!root) return null;
42591
- const base = resolve5(root);
42592
- const target = resolve5(base, relPath || ".");
42593
- return target === base || target.startsWith(base + sep3) ? target : null;
43105
+ const base = resolve7(root);
43106
+ const target = resolve7(base, relPath || ".");
43107
+ return target === base || target.startsWith(base + sep4) ? target : null;
42594
43108
  }
42595
43109
  async function readWorkerFile(root, path) {
42596
43110
  const target = resolveInRoot(root, path);
@@ -42598,7 +43112,7 @@ async function readWorkerFile(root, path) {
42598
43112
  return { path, content: "", truncated: false };
42599
43113
  }
42600
43114
  try {
42601
- const buf = await readFile6(target);
43115
+ const buf = await readFile7(target);
42602
43116
  const truncated = buf.byteLength > MAX_BYTES;
42603
43117
  const content = buf.subarray(0, MAX_BYTES).toString("utf8");
42604
43118
  return { path: target, content, truncated };
@@ -42609,13 +43123,13 @@ async function readWorkerFile(root, path) {
42609
43123
 
42610
43124
  // src/core/fs-write.ts
42611
43125
  import { mkdir as mkdir4, writeFile as writeFile5 } from "fs/promises";
42612
- import { dirname as dirname5, resolve as resolve6, sep as sep4 } from "path";
43126
+ import { dirname as dirname5, resolve as resolve8, sep as sep5 } from "path";
42613
43127
  var MAX_BYTES2 = 2 * 1024 * 1024;
42614
43128
  function resolveInRoot2(root, relPath) {
42615
43129
  if (!root) return null;
42616
- const base = resolve6(root);
42617
- const target = resolve6(base, relPath || ".");
42618
- return target === base || target.startsWith(base + sep4) ? target : null;
43130
+ const base = resolve8(root);
43131
+ const target = resolve8(base, relPath || ".");
43132
+ return target === base || target.startsWith(base + sep5) ? target : null;
42619
43133
  }
42620
43134
  async function writeWorkerFile(root, relPath, content) {
42621
43135
  const target = resolveInRoot2(root, relPath);
@@ -42636,24 +43150,24 @@ async function writeWorkerFile(root, relPath, content) {
42636
43150
  }
42637
43151
 
42638
43152
  // src/core/fs-mutate.ts
42639
- import { mkdir as mkdir5, writeFile as writeFile6, rename, rm as rm3, stat as stat2 } from "fs/promises";
42640
- import { basename as basename3, dirname as dirname6, resolve as resolve7, sep as sep5 } from "path";
43153
+ import { mkdir as mkdir5, writeFile as writeFile6, rename, rm as rm3, stat as stat3 } from "fs/promises";
43154
+ import { basename as basename3, dirname as dirname6, resolve as resolve9, sep as sep6 } from "path";
42641
43155
  var MAX_BYTES3 = 2 * 1024 * 1024;
42642
43156
  function resolveInRoot3(root, rel) {
42643
- const base = resolve7(root);
42644
- const target = resolve7(base, rel || ".");
42645
- return target === base || target.startsWith(base + sep5) ? target : null;
43157
+ const base = resolve9(root);
43158
+ const target = resolve9(base, rel || ".");
43159
+ return target === base || target.startsWith(base + sep6) ? target : null;
42646
43160
  }
42647
43161
  async function isDir(p2) {
42648
43162
  try {
42649
- return (await stat2(p2)).isDirectory();
43163
+ return (await stat3(p2)).isDirectory();
42650
43164
  } catch {
42651
43165
  return false;
42652
43166
  }
42653
43167
  }
42654
43168
  async function exists(p2) {
42655
43169
  try {
42656
- await stat2(p2);
43170
+ await stat3(p2);
42657
43171
  return true;
42658
43172
  } catch {
42659
43173
  return false;
@@ -42666,13 +43180,13 @@ async function mutateFs(root, req) {
42666
43180
  switch (req.op) {
42667
43181
  case "mkdir": {
42668
43182
  const target = resolveInRoot3(root, req.path ?? "");
42669
- if (!target || target === resolve7(root)) return escaped(req.path ?? "");
43183
+ if (!target || target === resolve9(root)) return escaped(req.path ?? "");
42670
43184
  await mkdir5(target, { recursive: true });
42671
43185
  return { ok: true, path: target };
42672
43186
  }
42673
43187
  case "create": {
42674
43188
  const target = resolveInRoot3(root, req.path ?? "");
42675
- if (!target || target === resolve7(root)) return escaped(req.path ?? "");
43189
+ if (!target || target === resolve9(root)) return escaped(req.path ?? "");
42676
43190
  if (await exists(target)) return { ok: false, path: target, error: "already exists" };
42677
43191
  const content = req.content ?? "";
42678
43192
  if (Buffer.byteLength(content, "utf8") > MAX_BYTES3) {
@@ -42686,9 +43200,9 @@ async function mutateFs(root, req) {
42686
43200
  const from = resolveInRoot3(root, req.from ?? "");
42687
43201
  let to2 = resolveInRoot3(root, req.to ?? "");
42688
43202
  if (!from || !to2) return escaped(req.from ?? req.to ?? "");
42689
- if (from === resolve7(root)) return { ok: false, path: from, error: "cannot move the project root" };
42690
- if (await isDir(to2)) to2 = resolve7(to2, basename3(from));
42691
- if (to2 !== resolve7(root) && !to2.startsWith(resolve7(root) + sep5)) return escaped(req.to ?? "");
43203
+ if (from === resolve9(root)) return { ok: false, path: from, error: "cannot move the project root" };
43204
+ if (await isDir(to2)) to2 = resolve9(to2, basename3(from));
43205
+ if (to2 !== resolve9(root) && !to2.startsWith(resolve9(root) + sep6)) return escaped(req.to ?? "");
42692
43206
  if (await exists(to2)) return { ok: false, path: to2, error: "destination already exists" };
42693
43207
  await mkdir5(dirname6(to2), { recursive: true });
42694
43208
  await rename(from, to2);
@@ -42697,7 +43211,7 @@ async function mutateFs(root, req) {
42697
43211
  case "delete": {
42698
43212
  const target = resolveInRoot3(root, req.path ?? "");
42699
43213
  if (!target) return escaped(req.path ?? "");
42700
- if (target === resolve7(root)) return { ok: false, path: target, error: "cannot delete the project root" };
43214
+ if (target === resolve9(root)) return { ok: false, path: target, error: "cannot delete the project root" };
42701
43215
  await rm3(target, { recursive: true, force: true });
42702
43216
  return { ok: true, path: target };
42703
43217
  }
@@ -42710,13 +43224,13 @@ async function mutateFs(root, req) {
42710
43224
  }
42711
43225
 
42712
43226
  // src/core/fs-transfer.ts
42713
- import { mkdir as mkdir6, readFile as readFile7, stat as stat3, writeFile as writeFile7 } from "fs/promises";
42714
- import { basename as basename4, dirname as dirname7, extname, resolve as resolve8, sep as sep6 } from "path";
43227
+ import { mkdir as mkdir6, readFile as readFile8, stat as stat4, writeFile as writeFile7 } from "fs/promises";
43228
+ import { basename as basename4, dirname as dirname7, extname, resolve as resolve10, sep as sep7 } from "path";
42715
43229
  function resolveInRoot4(root, relPath) {
42716
43230
  if (!root) return null;
42717
- const base = resolve8(root);
42718
- const target = resolve8(base, relPath || ".");
42719
- return target === base || target.startsWith(base + sep6) ? target : null;
43231
+ const base = resolve10(root);
43232
+ const target = resolve10(base, relPath || ".");
43233
+ return target === base || target.startsWith(base + sep7) ? target : null;
42720
43234
  }
42721
43235
  var MIME_BY_EXT = {
42722
43236
  ".png": "image/png",
@@ -42765,12 +43279,12 @@ async function downloadBinaryFile(root, relPath) {
42765
43279
  const target = resolveInRoot4(root, relPath);
42766
43280
  if (!target) return { error: "path escapes the project root" };
42767
43281
  try {
42768
- const info = await stat3(target);
43282
+ const info = await stat4(target);
42769
43283
  if (!info.isFile()) return { error: "not a file" };
42770
43284
  if (info.size > FS_TRANSFER_MAX_BYTES) {
42771
43285
  return { error: `file too large (max ${FS_TRANSFER_MAX_BYTES} bytes)` };
42772
43286
  }
42773
- const buf = await readFile7(target);
43287
+ const buf = await readFile8(target);
42774
43288
  return {
42775
43289
  contentBase64: buf.toString("base64"),
42776
43290
  contentType: guessMime(target),
@@ -42829,16 +43343,16 @@ function handleFsChannels(ctx, message, payload) {
42829
43343
  }
42830
43344
 
42831
43345
  // src/core/git-diff.ts
42832
- import { readFile as readFile8 } from "fs/promises";
42833
- import { execFile as execFile5 } from "child_process";
42834
- import { basename as basename5, dirname as dirname8, resolve as resolve9, sep as sep7 } from "path";
42835
- import { promisify as promisify5 } from "util";
42836
- var run5 = promisify5(execFile5);
43346
+ import { readFile as readFile9 } from "fs/promises";
43347
+ import { execFile as execFile7 } from "child_process";
43348
+ import { basename as basename5, dirname as dirname8, resolve as resolve11, sep as sep8 } from "path";
43349
+ import { promisify as promisify7 } from "util";
43350
+ var run7 = promisify7(execFile7);
42837
43351
  function resolveInRoot5(root, relPath) {
42838
43352
  if (!root) return null;
42839
- const base = resolve9(root);
42840
- const target = resolve9(base, relPath || ".");
42841
- return target === base || target.startsWith(base + sep7) ? target : null;
43353
+ const base = resolve11(root);
43354
+ const target = resolve11(base, relPath || ".");
43355
+ return target === base || target.startsWith(base + sep8) ? target : null;
42842
43356
  }
42843
43357
  function looksBinary(buf) {
42844
43358
  return buf.subarray(0, 8e3).includes(0);
@@ -42856,12 +43370,12 @@ async function gitDiff(root, path) {
42856
43370
  const base = basename5(target);
42857
43371
  let newBuf = null;
42858
43372
  try {
42859
- newBuf = await readFile8(target);
43373
+ newBuf = await readFile9(target);
42860
43374
  } catch {
42861
43375
  }
42862
43376
  let oldBuf = null;
42863
43377
  try {
42864
- const { stdout } = await run5("git", ["show", `HEAD:./${base}`], {
43378
+ const { stdout } = await run7("git", ["show", `HEAD:./${base}`], {
42865
43379
  cwd: dir2,
42866
43380
  timeout: 15e3,
42867
43381
  maxBuffer: FS_TRANSFER_MAX_BYTES + 1024,
@@ -42891,12 +43405,12 @@ async function gitDiff(root, path) {
42891
43405
  }
42892
43406
 
42893
43407
  // src/core/git-env.ts
42894
- import { execFile as execFile6 } from "child_process";
42895
- import { promisify as promisify6 } from "util";
42896
- var run6 = promisify6(execFile6);
43408
+ import { execFile as execFile8 } from "child_process";
43409
+ import { promisify as promisify8 } from "util";
43410
+ var run8 = promisify8(execFile8);
42897
43411
  async function hasBinary(bin, args = ["--version"]) {
42898
43412
  try {
42899
- await run6(bin, args, { timeout: 5e3 });
43413
+ await run8(bin, args, { timeout: 5e3 });
42900
43414
  return true;
42901
43415
  } catch {
42902
43416
  return false;
@@ -42909,7 +43423,7 @@ async function checkGitEnv(provider) {
42909
43423
  let account = null;
42910
43424
  if (installed) {
42911
43425
  try {
42912
- const { stdout, stderr } = await run6(bin, ["auth", "status"], { timeout: 8e3 });
43426
+ const { stdout, stderr } = await run8(bin, ["auth", "status"], { timeout: 8e3 });
42913
43427
  const out = `${stdout}
42914
43428
  ${stderr}`;
42915
43429
  authenticated = /logged in|logged into|✓/i.test(out);
@@ -42922,33 +43436,33 @@ ${stderr}`;
42922
43436
  }
42923
43437
 
42924
43438
  // src/core/git-ssh-key.ts
42925
- import { execFile as execFile7 } from "child_process";
42926
- import { readFile as readFile9, rm as rm4, mkdir as mkdir7 } from "fs/promises";
42927
- import { join as join21 } from "path";
42928
- import { promisify as promisify7 } from "util";
42929
- var run7 = promisify7(execFile7);
43439
+ import { execFile as execFile9 } from "child_process";
43440
+ import { readFile as readFile10, rm as rm4, mkdir as mkdir7 } from "fs/promises";
43441
+ import { join as join22 } from "path";
43442
+ import { promisify as promisify9 } from "util";
43443
+ var run9 = promisify9(execFile9);
42930
43444
  function keyPaths(profileDir2) {
42931
- const dir2 = join21(profileDir2, ".ssh");
42932
- const key = join21(dir2, "id_4pm");
43445
+ const dir2 = join22(profileDir2, ".ssh");
43446
+ const key = join22(dir2, "id_4pm");
42933
43447
  return { dir: dir2, key, pub: `${key}.pub` };
42934
43448
  }
42935
43449
  async function readPublicKey(pub) {
42936
43450
  try {
42937
- return (await readFile9(pub, "utf8")).trim();
43451
+ return (await readFile10(pub, "utf8")).trim();
42938
43452
  } catch {
42939
43453
  return null;
42940
43454
  }
42941
43455
  }
42942
43456
  async function fingerprintOf(pub) {
42943
43457
  try {
42944
- const { stdout } = await run7("ssh-keygen", ["-lf", pub], { timeout: 5e3 });
43458
+ const { stdout } = await run9("ssh-keygen", ["-lf", pub], { timeout: 5e3 });
42945
43459
  return stdout.match(/SHA256:[A-Za-z0-9+/=]+/)?.[0] ?? null;
42946
43460
  } catch {
42947
43461
  return null;
42948
43462
  }
42949
43463
  }
42950
43464
  async function configureGit(key) {
42951
- await run7(
43465
+ await run9(
42952
43466
  "git",
42953
43467
  ["config", "--global", "core.sshCommand", `ssh -i ${key} -o IdentitiesOnly=yes`],
42954
43468
  { timeout: 5e3 }
@@ -42959,7 +43473,7 @@ async function generate(profileDir2) {
42959
43473
  await mkdir7(dir2, { recursive: true });
42960
43474
  await rm4(key, { force: true });
42961
43475
  await rm4(pub, { force: true });
42962
- await run7("ssh-keygen", ["-t", "ed25519", "-f", key, "-N", "", "-C", "4pm-deploy-key"], {
43476
+ await run9("ssh-keygen", ["-t", "ed25519", "-f", key, "-N", "", "-C", "4pm-deploy-key"], {
42963
43477
  timeout: 15e3
42964
43478
  });
42965
43479
  await configureGit(key);
@@ -42969,7 +43483,7 @@ async function remove(profileDir2) {
42969
43483
  const { key, pub } = keyPaths(profileDir2);
42970
43484
  await rm4(key, { force: true });
42971
43485
  await rm4(pub, { force: true });
42972
- await run7("git", ["config", "--global", "--unset", "core.sshCommand"], { timeout: 5e3 }).catch(
43486
+ await run9("git", ["config", "--global", "--unset", "core.sshCommand"], { timeout: 5e3 }).catch(
42973
43487
  () => void 0
42974
43488
  );
42975
43489
  return { publicKey: null, fingerprint: null };
@@ -42989,109 +43503,6 @@ async function manageSshKey(op, profileDir2) {
42989
43503
  }
42990
43504
  }
42991
43505
 
42992
- // src/core/git-history.ts
42993
- import { execFile as execFile8 } from "child_process";
42994
- import { resolve as resolve10, sep as sep8 } from "path";
42995
- import { promisify as promisify8 } from "util";
42996
- var run8 = promisify8(execFile8);
42997
- var FS = "";
42998
- var RS = "";
42999
- function resolveRepo(root, repo) {
43000
- if (!root) return null;
43001
- const base = resolve10(root);
43002
- const requested = resolve10(base, repo && repo.trim() ? repo : ".");
43003
- return requested === base || requested.startsWith(base + sep8) ? requested : base;
43004
- }
43005
- async function git(cwd2, args) {
43006
- try {
43007
- const { stdout } = await run8("git", args, {
43008
- cwd: cwd2,
43009
- timeout: 15e3,
43010
- maxBuffer: 8 * 1024 * 1024
43011
- });
43012
- return stdout;
43013
- } catch {
43014
- return "";
43015
- }
43016
- }
43017
- async function gitRepos(root) {
43018
- if (!root) return { repos: [] };
43019
- const base = resolve10(root);
43020
- const top = (await git(base, ["rev-parse", "--show-toplevel"])).trim();
43021
- if (!top || !(top === base || top.startsWith(base + sep8))) return { repos: [] };
43022
- const repos = [];
43023
- const rootRemote = (await git(base, ["remote", "get-url", "origin"])).trim();
43024
- const rootBranch = (await git(base, ["rev-parse", "--abbrev-ref", "HEAD"])).trim();
43025
- repos.push({ subdir: "", name: "", remote: rootRemote || null, branch: rootBranch || null });
43026
- const subOut = await git(base, ["submodule", "status"]);
43027
- for (const line of subOut.split("\n")) {
43028
- const m2 = line.trim().match(/^[-+U ]?[0-9a-f]{7,40}\s+(\S+)/);
43029
- const subPath = m2?.[1];
43030
- if (!subPath) continue;
43031
- const abs = resolve10(base, subPath);
43032
- if (!(abs === base || abs.startsWith(base + sep8))) continue;
43033
- const remote = (await git(abs, ["remote", "get-url", "origin"])).trim();
43034
- const branch = (await git(abs, ["rev-parse", "--abbrev-ref", "HEAD"])).trim();
43035
- repos.push({ subdir: subPath, name: subPath, remote: remote || null, branch: branch || null });
43036
- }
43037
- return { repos };
43038
- }
43039
- async function gitLog(root, repo, skip, limit) {
43040
- const cwd2 = resolveRepo(root, repo);
43041
- if (!cwd2) return { entries: [], hasMore: false };
43042
- const capped = Math.min(Math.max(limit, 1), 200);
43043
- const format = ["%H", "%h", "%an", "%aI", "%s"].join(FS) + RS;
43044
- const stdout = await git(cwd2, [
43045
- "log",
43046
- `--skip=${Math.max(skip, 0)}`,
43047
- `-n`,
43048
- String(capped + 1),
43049
- `--pretty=format:${format}`
43050
- ]);
43051
- const rows = stdout.split(RS).map((r) => r.replace(/^\n/, "")).filter((r) => r.length > 0);
43052
- const entries2 = rows.slice(0, capped).map((row) => {
43053
- const [hash = "", shortHash = "", author = "", date = "", subject = ""] = row.split(FS);
43054
- return { hash, shortHash, author, date, subject };
43055
- });
43056
- return { entries: entries2, hasMore: rows.length > capped };
43057
- }
43058
- async function gitCommit(root, repo, hash) {
43059
- const cwd2 = resolveRepo(root, repo);
43060
- const empty = { hash, author: "", date: "", subject: "", files: [] };
43061
- if (!cwd2 || !hash) return empty;
43062
- const format = ["%H", "%an", "%aI", "%s"].join(FS);
43063
- const stdout = await git(cwd2, [
43064
- "show",
43065
- "--name-status",
43066
- "-M",
43067
- `--pretty=format:${format}`,
43068
- hash
43069
- ]);
43070
- if (!stdout) return empty;
43071
- const lines = stdout.split("\n");
43072
- const [full = hash, author = "", date = "", subject = ""] = (lines[0] ?? "").split(FS);
43073
- const files = [];
43074
- for (const line of lines.slice(1)) {
43075
- if (!line.trim()) continue;
43076
- const parts = line.split(" ");
43077
- const status = (parts[0] ?? "").charAt(0);
43078
- if (!status) continue;
43079
- if (status === "R" || status === "C") {
43080
- files.push({ path: parts[2] ?? "", status, oldPath: parts[1] ?? void 0 });
43081
- } else {
43082
- files.push({ path: parts[1] ?? "", status });
43083
- }
43084
- }
43085
- return { hash: full, author, date, subject, files };
43086
- }
43087
- async function gitCommitDiff(root, repo, hash, path) {
43088
- const cwd2 = resolveRepo(root, repo);
43089
- if (!cwd2 || !hash || !path) return { path, oldContent: "", newContent: "" };
43090
- const oldContent = await git(cwd2, ["show", `${hash}^:${path}`]);
43091
- const newContent = await git(cwd2, ["show", `${hash}:${path}`]);
43092
- return { path, oldContent, newContent };
43093
- }
43094
-
43095
43506
  // src/core/ws-client/handlers/git.ts
43096
43507
  function handleGitChannels(ctx, message, payload) {
43097
43508
  switch (message.channel) {
@@ -43144,12 +43555,12 @@ function handleGitChannels(ctx, message, payload) {
43144
43555
  }
43145
43556
 
43146
43557
  // src/core/ws-client/handlers/misc.ts
43147
- import { join as join22 } from "path";
43558
+ import { join as join23 } from "path";
43148
43559
 
43149
43560
  // src/core/ai-assist.ts
43150
- import { execFile as execFile9 } from "child_process";
43151
- import { promisify as promisify9 } from "util";
43152
- var run9 = promisify9(execFile9);
43561
+ import { execFile as execFile10 } from "child_process";
43562
+ import { promisify as promisify10 } from "util";
43563
+ var run10 = promisify10(execFile10);
43153
43564
  function aiCli() {
43154
43565
  return process.env.AI_CLI || "claude";
43155
43566
  }
@@ -43192,7 +43603,7 @@ function parseAi(cli, stdout, prompt) {
43192
43603
  }
43193
43604
  async function runAi(prompt) {
43194
43605
  const cli = aiCli();
43195
- const { stdout } = await run9(cli, aiArgs(cli, prompt), {
43606
+ const { stdout } = await run10(cli, aiArgs(cli, prompt), {
43196
43607
  timeout: 12e4,
43197
43608
  maxBuffer: 8 * 1024 * 1024
43198
43609
  });
@@ -43294,7 +43705,7 @@ function handleMiscChannels(ctx, message, payload) {
43294
43705
  }
43295
43706
  case WsChannels.LOG_READ: {
43296
43707
  const limit = Math.min(Math.max(payload.limit ?? 200, 1), 2e3);
43297
- const lines = readRecentLogLines(join22(ctx.profileDir, "logs"), limit);
43708
+ const lines = readRecentLogLines(join23(ctx.profileDir, "logs"), limit);
43298
43709
  ctx.send(WsChannels.LOG_READ, { lines }, message.id);
43299
43710
  return true;
43300
43711
  }
@@ -43326,7 +43737,7 @@ function handleMiscChannels(ctx, message, payload) {
43326
43737
 
43327
43738
  // src/core/ws-client/handlers/project.ts
43328
43739
  import { existsSync as existsSync14, mkdirSync as mkdirSync7, renameSync, rmSync as rmSync7 } from "fs";
43329
- import { join as join23 } from "path";
43740
+ import { join as join24 } from "path";
43330
43741
 
43331
43742
  // src/core/ai-task.ts
43332
43743
  import { randomUUID as randomUUID2 } from "crypto";
@@ -43398,8 +43809,8 @@ function handleProjectChannels(ctx, message, payload) {
43398
43809
  case WsChannels.PHYSIC_SYNC: {
43399
43810
  const sync = payload;
43400
43811
  const oldName = sync.oldName;
43401
- const oldPath = oldName ? join23(ctx.profileDir, oldName) : null;
43402
- const newPath = join23(ctx.profileDir, sync.newName);
43812
+ const oldPath = oldName ? join24(ctx.profileDir, oldName) : null;
43813
+ const newPath = join24(ctx.profileDir, sync.newName);
43403
43814
  if (oldName) {
43404
43815
  const oldRoot = ctx.physicFolderPath(oldName);
43405
43816
  if (oldRoot) void uninstallCron(oldRoot).catch(() => void 0);
@@ -43411,6 +43822,7 @@ function handleProjectChannels(ctx, message, payload) {
43411
43822
  mkdirSync7(newPath, { recursive: true });
43412
43823
  }
43413
43824
  ctx.physicRoot = ctx.physicFolderPath(sync.newName);
43825
+ requestGitSnapshot("physic-sync");
43414
43826
  ctx.bus.setProject(sync.newName);
43415
43827
  ctx.bus.log(t("project.folderSynced", { name: sync.newName }));
43416
43828
  void ctx.cloneServingRepos().catch(() => void 0);
@@ -43421,7 +43833,7 @@ function handleProjectChannels(ctx, message, payload) {
43421
43833
  if (del.name) {
43422
43834
  const delRoot = ctx.physicFolderPath(del.name);
43423
43835
  if (delRoot) void uninstallCron(delRoot).catch(() => void 0);
43424
- rmSync7(join23(ctx.profileDir, del.name), { recursive: true, force: true });
43836
+ rmSync7(join24(ctx.profileDir, del.name), { recursive: true, force: true });
43425
43837
  ctx.bus.log(t("project.folderDeleted", { name: del.name }));
43426
43838
  }
43427
43839
  void manageSshKey("delete", ctx.profileDir).catch(() => void 0);
@@ -43440,6 +43852,7 @@ function handleProjectChannels(ctx, message, payload) {
43440
43852
  projectAiMemoryBudgetChars: tokens.memory?.budgetChars ?? 0
43441
43853
  });
43442
43854
  ctx.setRestrictToFolder(tokens.restrictToFolder === true);
43855
+ if (tokens.gitAuth !== void 0) applyGitAuth(ctx, tokens.gitAuth, tokens.gitAuthHost ?? null);
43443
43856
  logger.info("project.tokens.applied", {
43444
43857
  aiRunTimeoutSec: tokens.aiRunTimeoutSec ?? 0,
43445
43858
  autoClearIdleMinutes: tokens.autoClearIdleMinutes ?? 0
@@ -43452,9 +43865,28 @@ function handleProjectChannels(ctx, message, payload) {
43452
43865
  ctx.profileDir,
43453
43866
  (p2) => ctx.send(WsChannels.PROJECT_PROGRESS, p2),
43454
43867
  // AI init on the standard AI path + one org run slot (ADR-0362).
43455
- createAiTaskRunner(ctx)
43456
- ).then((reply) => ctx.send(WsChannels.PROJECT_CREATE, reply, message.id));
43868
+ createAiTaskRunner(ctx),
43869
+ (method, host) => applyGitAuth(ctx, method, host)
43870
+ ).then((reply) => {
43871
+ ctx.send(WsChannels.PROJECT_CREATE, reply, message.id);
43872
+ requestGitSnapshot("scaffold");
43873
+ });
43457
43874
  return true;
43875
+ case WsChannels.PROJECT_PUBLISH: {
43876
+ const root = ctx.physicRoot;
43877
+ if (!root) {
43878
+ ctx.send(WsChannels.PROJECT_PUBLISH, { publish: null, error: "This worker has no project folder." }, message.id);
43879
+ return true;
43880
+ }
43881
+ const projectId = payload.projectId;
43882
+ void publishScaffold(root, (step, msg) => ctx.send(WsChannels.PROJECT_PROGRESS, { projectId, step, message: msg })).then((publish) => {
43883
+ ctx.send(WsChannels.PROJECT_PUBLISH, { publish }, message.id);
43884
+ requestGitSnapshot("publish");
43885
+ }).catch(
43886
+ (err) => ctx.send(WsChannels.PROJECT_PUBLISH, { publish: null, error: String(err) }, message.id)
43887
+ );
43888
+ return true;
43889
+ }
43458
43890
  case WsChannels.PROJECT_ADD:
43459
43891
  void addProject(
43460
43892
  payload,
@@ -43462,12 +43894,23 @@ function handleProjectChannels(ctx, message, payload) {
43462
43894
  (p2) => ctx.send(WsChannels.PROJECT_PROGRESS, p2),
43463
43895
  // An add-with-scaffold runs AI init too (ADR-0362).
43464
43896
  createAiTaskRunner(ctx)
43465
- ).then((reply) => ctx.send(WsChannels.PROJECT_ADD, reply, message.id));
43897
+ ).then((reply) => {
43898
+ ctx.send(WsChannels.PROJECT_ADD, reply, message.id);
43899
+ requestGitSnapshot("provision");
43900
+ });
43466
43901
  return true;
43467
43902
  default:
43468
43903
  return false;
43469
43904
  }
43470
43905
  }
43906
+ function applyGitAuth(ctx, method, host) {
43907
+ configureGitAuth(
43908
+ method ?? null,
43909
+ host,
43910
+ ctx.profileDir,
43911
+ (req) => ctx.request(WsChannels.GIT_TOKEN, req)
43912
+ );
43913
+ }
43471
43914
 
43472
43915
  // src/core/knowledge-compose.ts
43473
43916
  import { spawn as spawn8 } from "child_process";
@@ -43489,7 +43932,7 @@ Extra focus from the author: ${req.prompt}` : "";
43489
43932
  ].join("\n");
43490
43933
  }
43491
43934
  function runOnce2(cmd, profile, prompt, cwd2, extraEnv, denyDirs = []) {
43492
- return new Promise((resolve12, reject) => {
43935
+ return new Promise((resolve13, reject) => {
43493
43936
  const args = ["-p", ...profile?.model ? ["--model", profile.model] : [], ...denySettingsArgs(cmd, [...denyDirs, profile?.dir])];
43494
43937
  const env3 = {
43495
43938
  ...process.env,
@@ -43512,7 +43955,7 @@ function runOnce2(cmd, profile, prompt, cwd2, extraEnv, denyDirs = []) {
43512
43955
  });
43513
43956
  child.on("close", (code) => {
43514
43957
  clearTimeout(timer);
43515
- resolve12({ code: code ?? -1, out, err });
43958
+ resolve13({ code: code ?? -1, out, err });
43516
43959
  });
43517
43960
  child.stdin.write(prompt);
43518
43961
  child.stdin.end();
@@ -43542,13 +43985,13 @@ async function runKnowledgeCompose(req, ai2, physicRoot) {
43542
43985
  }
43543
43986
 
43544
43987
  // src/core/faq-compose.ts
43545
- import { spawn as spawn9, execFile as execFile10 } from "child_process";
43988
+ import { spawn as spawn9, execFile as execFile11 } from "child_process";
43546
43989
  import { existsSync as existsSync15, mkdirSync as mkdirSync8, rmSync as rmSync8 } from "fs";
43547
43990
  import { randomUUID as randomUUID3 } from "crypto";
43548
- import { join as join24 } from "path";
43991
+ import { join as join25 } from "path";
43549
43992
  import { tmpdir as tmpdir4 } from "os";
43550
- import { promisify as promisify10 } from "util";
43551
- var execFileAsync2 = promisify10(execFile10);
43993
+ import { promisify as promisify11 } from "util";
43994
+ var execFileAsync2 = promisify11(execFile11);
43552
43995
  var NO_USAGE2 = { tokens: 0, input: 0, output: 0, cacheRead: 0, cacheCreation: 0 };
43553
43996
  var COMPOSE_TIMEOUT_MS2 = 10 * 60 * 1e3;
43554
43997
  var AGENT_DENIED_TOOLS = ["Bash", "WebFetch", "WebSearch", "NotebookEdit", "Task"];
@@ -43613,7 +44056,7 @@ function parsePerTicket(text) {
43613
44056
  return [];
43614
44057
  }
43615
44058
  function runClaudeOnce2(cmd, profile, prompt, cwd2, extraEnv, denyDirs = []) {
43616
- return new Promise((resolve12, reject) => {
44059
+ return new Promise((resolve13, reject) => {
43617
44060
  const isClaude = cmd.includes("claude");
43618
44061
  const args = [
43619
44062
  "-p",
@@ -43646,7 +44089,7 @@ function runClaudeOnce2(cmd, profile, prompt, cwd2, extraEnv, denyDirs = []) {
43646
44089
  child.on("close", (code) => {
43647
44090
  clearTimeout(timer);
43648
44091
  out += parser.flush();
43649
- resolve12({ code: code ?? -1, out, err, usage: parser.usage() });
44092
+ resolve13({ code: code ?? -1, out, err, usage: parser.usage() });
43650
44093
  });
43651
44094
  child.stdin.write(prompt);
43652
44095
  child.stdin.end();
@@ -43666,7 +44109,7 @@ async function runClaudeWithFailover2(prompt, cwd2, ai2) {
43666
44109
  var MAX_OUTPUT_CHARS = 8e3;
43667
44110
  async function runFaqCompose(req, ai2) {
43668
44111
  if (!req.repo.token) return { prUrl: "", error: "no write token" };
43669
- const workDir = join24(tmpdir4(), "4pm-faq-sync", randomUUID3());
44112
+ const workDir = join25(tmpdir4(), "4pm-faq-sync", randomUUID3());
43670
44113
  const gitEnv = { ...process.env, GIT_TERMINAL_PROMPT: "0" };
43671
44114
  const auth = gitAuthArgs("x-access-token", req.repo.token);
43672
44115
  try {
@@ -43894,8 +44337,8 @@ function handleToolsChannels(ctx, message, payload) {
43894
44337
  }
43895
44338
 
43896
44339
  // src/core/agents.ts
43897
- import { readFile as readFile10, writeFile as writeFile8, mkdir as mkdir8, readdir as readdir4, rm as rm5 } from "fs/promises";
43898
- import { join as join25 } from "path";
44340
+ import { readFile as readFile11, writeFile as writeFile8, mkdir as mkdir8, readdir as readdir4, rm as rm5 } from "fs/promises";
44341
+ import { join as join26 } from "path";
43899
44342
  var AGENTS_REL = ".claude/agents";
43900
44343
  var SKILLS_REL = ".claude/skills";
43901
44344
  var MEMORY_DIRS = /* @__PURE__ */ new Set(["short-memory", "long-memory"]);
@@ -43904,7 +44347,7 @@ function safeName(name) {
43904
44347
  }
43905
44348
  async function readText2(path, fallback = "") {
43906
44349
  try {
43907
- return await readFile10(path, "utf8");
44350
+ return await readFile11(path, "utf8");
43908
44351
  } catch {
43909
44352
  return fallback;
43910
44353
  }
@@ -43919,17 +44362,17 @@ async function listAgents(root) {
43919
44362
  const subagents = [];
43920
44363
  const skills = [];
43921
44364
  try {
43922
- for (const e of await readdir4(join25(root, AGENTS_REL), { withFileTypes: true })) {
44365
+ for (const e of await readdir4(join26(root, AGENTS_REL), { withFileTypes: true })) {
43923
44366
  if (!e.isFile() || !e.name.endsWith(".md") || MEMORY_DIRS.has(e.name)) continue;
43924
- const fm = frontmatter(await readText2(join25(root, AGENTS_REL, e.name)));
44367
+ const fm = frontmatter(await readText2(join26(root, AGENTS_REL, e.name)));
43925
44368
  subagents.push({ name: e.name.replace(/\.md$/, ""), description: fm.description });
43926
44369
  }
43927
44370
  } catch {
43928
44371
  }
43929
44372
  try {
43930
- for (const e of await readdir4(join25(root, SKILLS_REL), { withFileTypes: true })) {
44373
+ for (const e of await readdir4(join26(root, SKILLS_REL), { withFileTypes: true })) {
43931
44374
  if (!e.isDirectory()) continue;
43932
- const fm = frontmatter(await readText2(join25(root, SKILLS_REL, e.name, "SKILL.md")));
44375
+ const fm = frontmatter(await readText2(join26(root, SKILLS_REL, e.name, "SKILL.md")));
43933
44376
  skills.push({ name: e.name, description: fm.description });
43934
44377
  }
43935
44378
  } catch {
@@ -43940,15 +44383,15 @@ async function listAgents(root) {
43940
44383
  }
43941
44384
  function itemPath(root, kind, name) {
43942
44385
  const n = safeName(name);
43943
- return kind === "subagent" ? join25(root, AGENTS_REL, `${n}.md`) : join25(root, SKILLS_REL, n, "SKILL.md");
44386
+ return kind === "subagent" ? join26(root, AGENTS_REL, `${n}.md`) : join26(root, SKILLS_REL, n, "SKILL.md");
43944
44387
  }
43945
44388
  async function readAgent(root, kind, name) {
43946
44389
  const content = await readText2(itemPath(root, kind, name));
43947
44390
  if (kind !== "subagent") return { content };
43948
44391
  const n = safeName(name);
43949
44392
  const [short, long] = await Promise.all([
43950
- readText2(join25(root, AGENTS_REL, "short-memory", `${n}.md`)),
43951
- readText2(join25(root, AGENTS_REL, "long-memory", `${n}.md`))
44393
+ readText2(join26(root, AGENTS_REL, "short-memory", `${n}.md`)),
44394
+ readText2(join26(root, AGENTS_REL, "long-memory", `${n}.md`))
43952
44395
  ]);
43953
44396
  return { content, memory: { short, long } };
43954
44397
  }
@@ -43958,12 +44401,12 @@ async function writeAgent(root, req) {
43958
44401
  try {
43959
44402
  if (req.action === "delete") {
43960
44403
  if (req.kind === "subagent") await rm5(itemPath(root, "subagent", n), { force: true });
43961
- else await rm5(join25(root, SKILLS_REL, n), { recursive: true, force: true });
44404
+ else await rm5(join26(root, SKILLS_REL, n), { recursive: true, force: true });
43962
44405
  return { ok: true };
43963
44406
  }
43964
44407
  if (req.content === void 0) return { ok: false, error: "content is required" };
43965
44408
  const path = itemPath(root, req.kind, n);
43966
- await mkdir8(join25(path, ".."), { recursive: true });
44409
+ await mkdir8(join26(path, ".."), { recursive: true });
43967
44410
  await writeFile8(path, req.content, "utf8");
43968
44411
  return { ok: true };
43969
44412
  } catch (err) {
@@ -43972,21 +44415,21 @@ async function writeAgent(root, req) {
43972
44415
  }
43973
44416
 
43974
44417
  // src/core/secrets.ts
43975
- import { readFile as readFile11, writeFile as writeFile9 } from "fs/promises";
43976
- import { join as join26 } from "path";
44418
+ import { readFile as readFile12, writeFile as writeFile9 } from "fs/promises";
44419
+ import { join as join27 } from "path";
43977
44420
  var SECURITY_REL = "AI_SECURITY.md";
43978
44421
  var PLACEHOLDER_REL = "AI_PLACEHOLDER.md";
43979
44422
  var SECRETS_REL = "project.secrets.json";
43980
44423
  async function readText3(path, fallback = "") {
43981
44424
  try {
43982
- return await readFile11(path, "utf8");
44425
+ return await readFile12(path, "utf8");
43983
44426
  } catch {
43984
44427
  return fallback;
43985
44428
  }
43986
44429
  }
43987
44430
  async function readSecretsObject(root) {
43988
44431
  try {
43989
- const v2 = JSON.parse(await readFile11(join26(root, SECRETS_REL), "utf8"));
44432
+ const v2 = JSON.parse(await readFile12(join27(root, SECRETS_REL), "utf8"));
43990
44433
  return v2 && typeof v2 === "object" ? v2 : {};
43991
44434
  } catch {
43992
44435
  return {};
@@ -44005,8 +44448,8 @@ function placeholderKeys(text) {
44005
44448
  }
44006
44449
  async function readSecrets(root) {
44007
44450
  const [security, placeholder, secrets] = await Promise.all([
44008
- readText3(join26(root, SECURITY_REL)),
44009
- readText3(join26(root, PLACEHOLDER_REL)),
44451
+ readText3(join27(root, SECURITY_REL)),
44452
+ readText3(join27(root, PLACEHOLDER_REL)),
44010
44453
  readSecretsObject(root)
44011
44454
  ]);
44012
44455
  const names = /* @__PURE__ */ new Set([...placeholderKeys(placeholder), ...Object.keys(secrets)]);
@@ -44019,14 +44462,14 @@ async function readSecrets(root) {
44019
44462
  async function writeSecrets(root, req) {
44020
44463
  try {
44021
44464
  if (req.kind === "security") {
44022
- await writeFile9(join26(root, SECURITY_REL), req.content, "utf8");
44465
+ await writeFile9(join27(root, SECURITY_REL), req.content, "utf8");
44023
44466
  } else if (req.kind === "placeholder") {
44024
- await writeFile9(join26(root, PLACEHOLDER_REL), req.content, "utf8");
44467
+ await writeFile9(join27(root, PLACEHOLDER_REL), req.content, "utf8");
44025
44468
  } else {
44026
44469
  const secrets = await readSecretsObject(root);
44027
44470
  if (req.kind === "secret") secrets[req.key] = req.value;
44028
44471
  else delete secrets[req.key];
44029
- await writeFile9(join26(root, SECRETS_REL), JSON.stringify(secrets, null, 2) + "\n", "utf8");
44472
+ await writeFile9(join27(root, SECRETS_REL), JSON.stringify(secrets, null, 2) + "\n", "utf8");
44030
44473
  }
44031
44474
  return { ok: true };
44032
44475
  } catch (err) {
@@ -44035,17 +44478,17 @@ async function writeSecrets(root, req) {
44035
44478
  }
44036
44479
 
44037
44480
  // src/core/agent-tools.ts
44038
- import { readFile as readFile12, writeFile as writeFile10, mkdir as mkdir9 } from "fs/promises";
44039
- import { dirname as dirname9, join as join27 } from "path";
44481
+ import { readFile as readFile13, writeFile as writeFile10, mkdir as mkdir9 } from "fs/promises";
44482
+ import { dirname as dirname9, join as join28 } from "path";
44040
44483
  var SHARED_REL = ".claude/settings.json";
44041
44484
  var LOCAL_REL = ".claude/settings.local.json";
44042
44485
  var SECRETS_DENY = "Read(./project.secrets.json)";
44043
44486
  function fileFor(root, scope) {
44044
- return join27(root, scope === "shared" ? SHARED_REL : LOCAL_REL);
44487
+ return join28(root, scope === "shared" ? SHARED_REL : LOCAL_REL);
44045
44488
  }
44046
44489
  async function readSettingsObject(path) {
44047
44490
  try {
44048
- const v2 = JSON.parse(await readFile12(path, "utf8"));
44491
+ const v2 = JSON.parse(await readFile13(path, "utf8"));
44049
44492
  return v2 && typeof v2 === "object" ? v2 : {};
44050
44493
  } catch {
44051
44494
  return {};
@@ -44077,8 +44520,8 @@ async function writeAgentTools(root, scope, permissions) {
44077
44520
 
44078
44521
  // src/core/packages.ts
44079
44522
  import { createHash as createHash4 } from "crypto";
44080
- import { readFile as readFile13, writeFile as writeFile11, mkdir as mkdir10, readdir as readdir5, rm as rm6, stat as stat4 } from "fs/promises";
44081
- import { dirname as dirname10, join as join28, relative } from "path";
44523
+ import { readFile as readFile14, writeFile as writeFile11, mkdir as mkdir10, readdir as readdir5, rm as rm6, stat as stat5 } from "fs/promises";
44524
+ import { dirname as dirname10, join as join29, relative } from "path";
44082
44525
  var AGENTS_REL2 = ".claude/agents";
44083
44526
  var SKILLS_REL2 = ".claude/skills";
44084
44527
  var MANIFEST_REL = ".claude/.4pm-packages.json";
@@ -44087,7 +44530,7 @@ function safeName2(name) {
44087
44530
  }
44088
44531
  async function readMaybe(path) {
44089
44532
  try {
44090
- return await readFile13(path);
44533
+ return await readFile14(path);
44091
44534
  } catch {
44092
44535
  return null;
44093
44536
  }
@@ -44102,7 +44545,7 @@ async function listFilesRel(dir2) {
44102
44545
  return;
44103
44546
  }
44104
44547
  for (const e of entries2) {
44105
- const full = join28(d, e.name);
44548
+ const full = join29(d, e.name);
44106
44549
  if (e.isDirectory()) await walk2(full);
44107
44550
  else if (e.isFile()) out.push(relative(dir2, full));
44108
44551
  }
@@ -44113,16 +44556,16 @@ async function listFilesRel(dir2) {
44113
44556
  async function readArtifactFiles(root, kind, slug) {
44114
44557
  const files = [];
44115
44558
  if (kind === "subagent") {
44116
- const agent = await readMaybe(join28(root, AGENTS_REL2, `${slug}.md`));
44559
+ const agent = await readMaybe(join29(root, AGENTS_REL2, `${slug}.md`));
44117
44560
  if (agent) files.push({ path: "agent.md", contentBase64: agent.toString("base64") });
44118
- const short = await readMaybe(join28(root, AGENTS_REL2, "short-memory", `${slug}.md`));
44561
+ const short = await readMaybe(join29(root, AGENTS_REL2, "short-memory", `${slug}.md`));
44119
44562
  if (short) files.push({ path: "short-memory.md", contentBase64: short.toString("base64") });
44120
- const long = await readMaybe(join28(root, AGENTS_REL2, "long-memory", `${slug}.md`));
44563
+ const long = await readMaybe(join29(root, AGENTS_REL2, "long-memory", `${slug}.md`));
44121
44564
  if (long) files.push({ path: "long-memory.md", contentBase64: long.toString("base64") });
44122
44565
  } else {
44123
- const base = join28(root, SKILLS_REL2, slug);
44566
+ const base = join29(root, SKILLS_REL2, slug);
44124
44567
  for (const rel of await listFilesRel(base)) {
44125
- const buf = await readMaybe(join28(base, rel));
44568
+ const buf = await readMaybe(join29(base, rel));
44126
44569
  if (buf) files.push({ path: rel.split("\\").join("/"), contentBase64: buf.toString("base64") });
44127
44570
  }
44128
44571
  }
@@ -44140,16 +44583,16 @@ async function artifactSha256(root, kind, slug) {
44140
44583
  return h.digest("hex");
44141
44584
  }
44142
44585
  async function artifactExists(root, kind, slug) {
44143
- const target = kind === "subagent" ? join28(root, AGENTS_REL2, `${slug}.md`) : join28(root, SKILLS_REL2, slug);
44586
+ const target = kind === "subagent" ? join29(root, AGENTS_REL2, `${slug}.md`) : join29(root, SKILLS_REL2, slug);
44144
44587
  try {
44145
- await stat4(target);
44588
+ await stat5(target);
44146
44589
  return true;
44147
44590
  } catch {
44148
44591
  return false;
44149
44592
  }
44150
44593
  }
44151
44594
  async function readManifest(root) {
44152
- const buf = await readMaybe(join28(root, MANIFEST_REL));
44595
+ const buf = await readMaybe(join29(root, MANIFEST_REL));
44153
44596
  if (!buf) return {};
44154
44597
  try {
44155
44598
  return JSON.parse(buf.toString("utf8"));
@@ -44158,7 +44601,7 @@ async function readManifest(root) {
44158
44601
  }
44159
44602
  }
44160
44603
  async function writeManifest(root, manifest) {
44161
- const path = join28(root, MANIFEST_REL);
44604
+ const path = join29(root, MANIFEST_REL);
44162
44605
  await mkdir10(dirname10(path), { recursive: true });
44163
44606
  await writeFile11(path, JSON.stringify(manifest, null, 2), "utf8");
44164
44607
  }
@@ -44167,24 +44610,24 @@ async function writeArtifactFiles(root, kind, slug, files) {
44167
44610
  if (kind === "subagent") {
44168
44611
  const agent = byPath.get("agent.md");
44169
44612
  if (agent) {
44170
- await mkdir10(join28(root, AGENTS_REL2), { recursive: true });
44171
- await writeFile11(join28(root, AGENTS_REL2, `${slug}.md`), agent);
44613
+ await mkdir10(join29(root, AGENTS_REL2), { recursive: true });
44614
+ await writeFile11(join29(root, AGENTS_REL2, `${slug}.md`), agent);
44172
44615
  }
44173
44616
  const short = byPath.get("short-memory.md");
44174
44617
  if (short) {
44175
- await mkdir10(join28(root, AGENTS_REL2, "short-memory"), { recursive: true });
44176
- await writeFile11(join28(root, AGENTS_REL2, "short-memory", `${slug}.md`), short);
44618
+ await mkdir10(join29(root, AGENTS_REL2, "short-memory"), { recursive: true });
44619
+ await writeFile11(join29(root, AGENTS_REL2, "short-memory", `${slug}.md`), short);
44177
44620
  }
44178
44621
  const long = byPath.get("long-memory.md");
44179
44622
  if (long) {
44180
- await mkdir10(join28(root, AGENTS_REL2, "long-memory"), { recursive: true });
44181
- await writeFile11(join28(root, AGENTS_REL2, "long-memory", `${slug}.md`), long);
44623
+ await mkdir10(join29(root, AGENTS_REL2, "long-memory"), { recursive: true });
44624
+ await writeFile11(join29(root, AGENTS_REL2, "long-memory", `${slug}.md`), long);
44182
44625
  }
44183
44626
  } else {
44184
- const base = join28(root, SKILLS_REL2, slug);
44627
+ const base = join29(root, SKILLS_REL2, slug);
44185
44628
  for (const [rel, buf] of byPath) {
44186
44629
  if (rel.includes("..") || rel.startsWith("/")) continue;
44187
- const dest = join28(base, rel);
44630
+ const dest = join29(base, rel);
44188
44631
  await mkdir10(dirname10(dest), { recursive: true });
44189
44632
  await writeFile11(dest, buf);
44190
44633
  }
@@ -44250,11 +44693,11 @@ async function removePackage(root, slug) {
44250
44693
  const entry = manifest[n];
44251
44694
  if (!entry) return { ok: false, error: "not installed" };
44252
44695
  if (entry.kind === "subagent") {
44253
- await rm6(join28(root, AGENTS_REL2, `${n}.md`), { force: true });
44254
- await rm6(join28(root, AGENTS_REL2, "short-memory", `${n}.md`), { force: true });
44255
- await rm6(join28(root, AGENTS_REL2, "long-memory", `${n}.md`), { force: true });
44696
+ await rm6(join29(root, AGENTS_REL2, `${n}.md`), { force: true });
44697
+ await rm6(join29(root, AGENTS_REL2, "short-memory", `${n}.md`), { force: true });
44698
+ await rm6(join29(root, AGENTS_REL2, "long-memory", `${n}.md`), { force: true });
44256
44699
  } else {
44257
- await rm6(join28(root, SKILLS_REL2, n), { recursive: true, force: true });
44700
+ await rm6(join29(root, SKILLS_REL2, n), { recursive: true, force: true });
44258
44701
  }
44259
44702
  delete manifest[n];
44260
44703
  await writeManifest(root, manifest);
@@ -44265,8 +44708,8 @@ async function removePackage(root, slug) {
44265
44708
  }
44266
44709
 
44267
44710
  // src/core/graph.ts
44268
- import { readFile as readFile14, readdir as readdir6 } from "fs/promises";
44269
- import { join as join29, relative as relative2, dirname as dirname11, resolve as resolve11, extname as extname2, basename as basename6 } from "path";
44711
+ import { readFile as readFile15, readdir as readdir6 } from "fs/promises";
44712
+ import { join as join30, relative as relative2, dirname as dirname11, resolve as resolve12, extname as extname2, basename as basename6 } from "path";
44270
44713
  var MAX_FILES = 2e3;
44271
44714
  var MAX_NODES = 3e3;
44272
44715
  var SKIP_DIRS = /* @__PURE__ */ new Set(["node_modules", ".git", "dist", "build", ".next", "out", "coverage", ".turbo"]);
@@ -44286,7 +44729,7 @@ async function walk(root, accept) {
44286
44729
  if (e.name.startsWith(".") && e.name !== ".claude") {
44287
44730
  if (e.isDirectory()) continue;
44288
44731
  }
44289
- const full = join29(dir2, e.name);
44732
+ const full = join30(dir2, e.name);
44290
44733
  if (e.isDirectory()) {
44291
44734
  if (!SKIP_DIRS.has(e.name)) stack.push(full);
44292
44735
  } else if (accept(full)) {
@@ -44314,14 +44757,14 @@ async function buildDocs(root) {
44314
44757
  const rel = relative2(root, f2).split("\\").join("/");
44315
44758
  let text = "";
44316
44759
  try {
44317
- text = await readFile14(f2, "utf8");
44760
+ text = await readFile15(f2, "utf8");
44318
44761
  } catch {
44319
44762
  continue;
44320
44763
  }
44321
44764
  for (const m2 of text.matchAll(/\[[^\]]*\]\(([^)]+)\)/g)) {
44322
44765
  const href = (m2[1] ?? "").split("#")[0].trim();
44323
44766
  if (!href || !href.endsWith(".md") || href.startsWith("http")) continue;
44324
- const target = relative2(root, resolve11(dirname11(f2), href)).split("\\").join("/");
44767
+ const target = relative2(root, resolve12(dirname11(f2), href)).split("\\").join("/");
44325
44768
  if (ids.has(target) && target !== rel) {
44326
44769
  const key = `${rel}->${target}`;
44327
44770
  if (!seen.has(key)) {
@@ -44353,7 +44796,7 @@ async function buildCode(root) {
44353
44796
  const rel = relative2(root, f2).split("\\").join("/");
44354
44797
  let text = "";
44355
44798
  try {
44356
- text = await readFile14(f2, "utf8");
44799
+ text = await readFile15(f2, "utf8");
44357
44800
  } catch {
44358
44801
  continue;
44359
44802
  }
@@ -44405,17 +44848,17 @@ async function buildGraph(root, mode) {
44405
44848
  }
44406
44849
 
44407
44850
  // src/core/rag.ts
44408
- import { readFile as readFile15, writeFile as writeFile12, statfs as statfs2, mkdir as mkdir11 } from "fs/promises";
44851
+ import { readFile as readFile16, writeFile as writeFile12, statfs as statfs2, mkdir as mkdir11 } from "fs/promises";
44409
44852
  import { existsSync as existsSync16 } from "fs";
44410
- import { execFile as execFile11, spawn as spawn10 } from "child_process";
44853
+ import { execFile as execFile12, spawn as spawn10 } from "child_process";
44411
44854
  import { totalmem as totalmem2, cpus as cpus2 } from "os";
44412
- import { join as join30 } from "path";
44413
- import { promisify as promisify11 } from "util";
44414
- var run10 = promisify11(execFile11);
44855
+ import { join as join31 } from "path";
44856
+ import { promisify as promisify12 } from "util";
44857
+ var run11 = promisify12(execFile12);
44415
44858
  var RAG_REL = ".claude/rag";
44416
44859
  async function tryRun(cmd, args) {
44417
44860
  try {
44418
- const { stdout } = await run10(cmd, args, { timeout: 8e3 });
44861
+ const { stdout } = await run11(cmd, args, { timeout: 8e3 });
44419
44862
  return { ok: true, out: stdout.trim() };
44420
44863
  } catch {
44421
44864
  return { ok: false, out: "" };
@@ -44430,7 +44873,7 @@ async function detectPython() {
44430
44873
  }
44431
44874
  async function installLog(root) {
44432
44875
  try {
44433
- const text = await readFile15(join30(root, RAG_REL, "install.log"), "utf8");
44876
+ const text = await readFile16(join31(root, RAG_REL, "install.log"), "utf8");
44434
44877
  return text.split("\n").filter((l) => l.length > 0).slice(-200);
44435
44878
  } catch {
44436
44879
  return [];
@@ -44492,14 +44935,14 @@ async function ragStatus(root) {
44492
44935
  };
44493
44936
  let modelName = "";
44494
44937
  try {
44495
- modelName = (await readFile15(join30(root, RAG_REL, ".installed"), "utf8")).trim();
44938
+ modelName = (await readFile16(join31(root, RAG_REL, ".installed"), "utf8")).trim();
44496
44939
  } catch {
44497
44940
  modelName = "";
44498
44941
  }
44499
44942
  const modelPresent = modelName.length > 0;
44500
44943
  return {
44501
44944
  installed: fastembed && sqliteVec && modelPresent,
44502
- installing: existsSync16(join30(root, RAG_REL, ".installing")),
44945
+ installing: existsSync16(join31(root, RAG_REL, ".installing")),
44503
44946
  python: { found: py.found, version: py.version },
44504
44947
  deps: { pip, fastembed, sqliteVec },
44505
44948
  model: { present: modelPresent, name: modelName },
@@ -44510,9 +44953,9 @@ async function ragStatus(root) {
44510
44953
  };
44511
44954
  }
44512
44955
  async function readIndexMeta(root) {
44513
- const indexing = existsSync16(join30(root, RAG_REL, ".indexing"));
44956
+ const indexing = existsSync16(join31(root, RAG_REL, ".indexing"));
44514
44957
  try {
44515
- const m2 = JSON.parse(await readFile15(join30(root, RAG_REL, ".index.json"), "utf8"));
44958
+ const m2 = JSON.parse(await readFile16(join31(root, RAG_REL, ".index.json"), "utf8"));
44516
44959
  return {
44517
44960
  present: true,
44518
44961
  indexing,
@@ -44579,14 +45022,14 @@ if __name__ == "__main__":
44579
45022
  elif cmd == "query": query(sys.argv[2], int(sys.argv[3]) if len(sys.argv) > 3 else 8)
44580
45023
  `;
44581
45024
  async function ensureEngine(root) {
44582
- await mkdir11(join30(root, RAG_REL), { recursive: true }).catch(() => void 0);
44583
- await writeFile12(join30(root, RAG_REL, "rag_engine.py"), RAG_ENGINE_PY, "utf8");
45025
+ await mkdir11(join31(root, RAG_REL), { recursive: true }).catch(() => void 0);
45026
+ await writeFile12(join31(root, RAG_REL, "rag_engine.py"), RAG_ENGINE_PY, "utf8");
44584
45027
  }
44585
45028
  async function ragReindex(root) {
44586
45029
  const py = await detectPython();
44587
45030
  if (!py.found) return { ok: false, started: false, error: "python3 not found" };
44588
- if (!existsSync16(join30(root, RAG_REL, ".installed"))) return { ok: false, started: false, error: "RAG is not installed" };
44589
- if (existsSync16(join30(root, RAG_REL, ".indexing"))) return { ok: true, started: true };
45031
+ if (!existsSync16(join31(root, RAG_REL, ".installed"))) return { ok: false, started: false, error: "RAG is not installed" };
45032
+ if (existsSync16(join31(root, RAG_REL, ".indexing"))) return { ok: true, started: true };
44590
45033
  await ensureEngine(root);
44591
45034
  const script = `
44592
45035
  set +e
@@ -44606,12 +45049,12 @@ rm -f "${RAG_REL}/.indexing"
44606
45049
  async function ragQuery(root, queryText, k2 = 8) {
44607
45050
  const py = await detectPython();
44608
45051
  if (!py.found) return { results: [], error: "python3 not found" };
44609
- if (!existsSync16(join30(root, RAG_REL, "index.db"))) return { results: [], error: "no index \u2014 reindex first" };
45052
+ if (!existsSync16(join31(root, RAG_REL, "index.db"))) return { results: [], error: "no index \u2014 reindex first" };
44610
45053
  await ensureEngine(root);
44611
45054
  try {
44612
- const { stdout } = await run10(
45055
+ const { stdout } = await run11(
44613
45056
  py.bin,
44614
- [join30(RAG_REL, "rag_engine.py"), "query", queryText, String(Math.min(Math.max(k2, 1), 20))],
45057
+ [join31(RAG_REL, "rag_engine.py"), "query", queryText, String(Math.min(Math.max(k2, 1), 20))],
44615
45058
  { cwd: root, timeout: 3e4, maxBuffer: 4 * 1024 * 1024 }
44616
45059
  );
44617
45060
  const parsed = JSON.parse(stdout.trim() || '{"results":[]}');
@@ -44626,9 +45069,9 @@ async function ragInstall(root, model) {
44626
45069
  }
44627
45070
  const py = await detectPython();
44628
45071
  if (!py.found) return { ok: false, started: false, error: "python3 not found on the worker" };
44629
- if (existsSync16(join30(root, RAG_REL, ".installing"))) return { ok: true, started: true };
45072
+ if (existsSync16(join31(root, RAG_REL, ".installing"))) return { ok: true, started: true };
44630
45073
  try {
44631
- await mkdir11(join30(root, RAG_REL), { recursive: true });
45074
+ await mkdir11(join31(root, RAG_REL), { recursive: true });
44632
45075
  } catch {
44633
45076
  }
44634
45077
  const script = `
@@ -44656,7 +45099,7 @@ rm -f "${RAG_REL}/.installing"
44656
45099
 
44657
45100
  // src/core/config-sync.ts
44658
45101
  import { existsSync as existsSync17, readFileSync as readFileSync16 } from "fs";
44659
- import { join as join31 } from "path";
45102
+ import { join as join32 } from "path";
44660
45103
  var SERVER_MANAGED_KEYS = [
44661
45104
  "physicPath",
44662
45105
  "sessionSwitchPct",
@@ -44673,7 +45116,7 @@ var SERVER_MANAGED_KEYS = [
44673
45116
  "projectAiMemoryBudgetChars"
44674
45117
  ];
44675
45118
  function readConfigText(profileDir2) {
44676
- const path = join31(profileDir2, "config.json");
45119
+ const path = join32(profileDir2, "config.json");
44677
45120
  if (existsSync17(path)) {
44678
45121
  try {
44679
45122
  return readFileSync16(path, "utf8");
@@ -45160,14 +45603,14 @@ var WsClient = class {
45160
45603
  */
45161
45604
  awaitConnected(timeoutMs) {
45162
45605
  if (this.sessionKey && this.bus.status === "connected") return Promise.resolve(true);
45163
- return new Promise((resolve12) => {
45606
+ return new Promise((resolve13) => {
45164
45607
  const waiter = () => {
45165
45608
  clearTimeout(timer);
45166
- resolve12(true);
45609
+ resolve13(true);
45167
45610
  };
45168
45611
  const timer = setTimeout(() => {
45169
45612
  this.connectWaiters = this.connectWaiters.filter((w2) => w2 !== waiter);
45170
- resolve12(false);
45613
+ resolve13(false);
45171
45614
  }, timeoutMs);
45172
45615
  this.connectWaiters.push(waiter);
45173
45616
  });
@@ -45194,16 +45637,16 @@ var WsClient = class {
45194
45637
  this.forceReconnect = false;
45195
45638
  return Promise.resolve();
45196
45639
  }
45197
- return new Promise((resolve12) => {
45640
+ return new Promise((resolve13) => {
45198
45641
  const timer = setTimeout(() => {
45199
45642
  this.wakeReconnect = null;
45200
- resolve12();
45643
+ resolve13();
45201
45644
  }, ms2);
45202
45645
  this.wakeReconnect = () => {
45203
45646
  clearTimeout(timer);
45204
45647
  this.wakeReconnect = null;
45205
45648
  this.forceReconnect = false;
45206
- resolve12();
45649
+ resolve13();
45207
45650
  };
45208
45651
  });
45209
45652
  }
@@ -45257,7 +45700,7 @@ var WsClient = class {
45257
45700
  * One WS session: token → connect → handshake → handle messages until close.
45258
45701
  */
45259
45702
  connectOnce() {
45260
- return new Promise((resolve12, reject) => {
45703
+ return new Promise((resolve13, reject) => {
45261
45704
  void (async () => {
45262
45705
  this.sessionEstablished = false;
45263
45706
  this.bus.setStatus("connecting");
@@ -45358,7 +45801,7 @@ var WsClient = class {
45358
45801
  if (code === CLOSE_DRAINING) {
45359
45802
  this.bus.log(t("error.serverDraining"));
45360
45803
  }
45361
- resolve12();
45804
+ resolve13();
45362
45805
  });
45363
45806
  socket.on("error", (err) => {
45364
45807
  this.clearHeartbeat();
@@ -45396,6 +45839,14 @@ var WsClient = class {
45396
45839
  this.startUsagePolling();
45397
45840
  this.startLogUpload();
45398
45841
  this.startKbRefresh();
45842
+ const servedRoot = () => this.physicRoot;
45843
+ startGitSnapshots({
45844
+ get physicRoot() {
45845
+ return servedRoot();
45846
+ },
45847
+ profileDir: this.context.profileDir,
45848
+ send: (channel, data) => this.send(channel, data, null)
45849
+ });
45399
45850
  setToolHealthSink((report) => this.send(WsChannels.TOOL_HEALTH, report));
45400
45851
  this.connectWaiters.splice(0).forEach((fn) => fn());
45401
45852
  return;
@@ -45484,7 +45935,7 @@ var WsClient = class {
45484
45935
  * Used for quota.check before spawning an AI cli (ADR-0020).
45485
45936
  */
45486
45937
  request(channel, data) {
45487
- return new Promise((resolve12, reject) => {
45938
+ return new Promise((resolve13, reject) => {
45488
45939
  if (!this.socket || !this.sessionKey) {
45489
45940
  reject(new Error("Not connected to the server."));
45490
45941
  return;
@@ -45504,7 +45955,7 @@ var WsClient = class {
45504
45955
  }, 15e3);
45505
45956
  timer.unref?.();
45506
45957
  this.pending.set(envelope.id, {
45507
- resolve: (value) => resolve12(value),
45958
+ resolve: (value) => resolve13(value),
45508
45959
  timer
45509
45960
  });
45510
45961
  this.socket.send(JSON.stringify(envelope));
@@ -45573,7 +46024,7 @@ var WsClient = class {
45573
46024
  */
45574
46025
  physicFolderPath(projectName) {
45575
46026
  const safe = projectName.replace(/[/\\]/g, "_").replace(/\.\./g, "_").trim();
45576
- return safe ? join32(this.context.profileDir, safe) : null;
46027
+ return safe ? join33(this.context.profileDir, safe) : null;
45577
46028
  }
45578
46029
  /**
45579
46030
  * Report machine.status: version + the state of the physic project being served
@@ -45685,7 +46136,7 @@ var WsClient = class {
45685
46136
  */
45686
46137
  uploadLog() {
45687
46138
  try {
45688
- const upload = readLogUpload(join32(this.context.profileDir, "logs"));
46139
+ const upload = readLogUpload(join33(this.context.profileDir, "logs"));
45689
46140
  if (!upload) return;
45690
46141
  const sig = `${upload.fileName}:${upload.totalBytes}`;
45691
46142
  if (sig === this.lastLogSig) return;
@@ -45848,9 +46299,9 @@ var WsClient = class {
45848
46299
 
45849
46300
  // src/core/instance-lock.ts
45850
46301
  import { readFileSync as readFileSync17, rmSync as rmSync9, writeFileSync as writeFileSync9 } from "fs";
45851
- import { join as join33 } from "path";
46302
+ import { join as join34 } from "path";
45852
46303
  function lockPath(profileDir2) {
45853
- return join33(profileDir2, "start.lock");
46304
+ return join34(profileDir2, "start.lock");
45854
46305
  }
45855
46306
  function isAlive(pid) {
45856
46307
  try {
@@ -46172,7 +46623,7 @@ var SessionBus = class {
46172
46623
  // src/core/control-server.ts
46173
46624
  import { createServer } from "net";
46174
46625
  import { chmodSync as chmodSync5, rmSync as rmSync10 } from "fs";
46175
- import { join as join35 } from "path";
46626
+ import { join as join36 } from "path";
46176
46627
 
46177
46628
  // src/core/control-protocol.ts
46178
46629
  var CONTROL_SOCKET_FILE = "control.sock";
@@ -46203,9 +46654,9 @@ function createFrameParser() {
46203
46654
  // src/core/control-token.ts
46204
46655
  import { chmodSync as chmodSync4, readFileSync as readFileSync18, writeFileSync as writeFileSync10 } from "fs";
46205
46656
  import { randomBytes as randomBytes3, timingSafeEqual } from "crypto";
46206
- import { join as join34 } from "path";
46657
+ import { join as join35 } from "path";
46207
46658
  function controlTokenPath(profileDir2) {
46208
- return join34(profileDir2, CONTROL_TOKEN_FILE);
46659
+ return join35(profileDir2, CONTROL_TOKEN_FILE);
46209
46660
  }
46210
46661
  function writeControlToken(profileDir2) {
46211
46662
  const token = randomBytes3(32).toString("hex");
@@ -46234,7 +46685,7 @@ function tokenMatches(expected, given) {
46234
46685
 
46235
46686
  // src/core/control-server.ts
46236
46687
  function startControlServer(bus, profileDir2, info) {
46237
- const socketPath = join35(profileDir2, CONTROL_SOCKET_FILE);
46688
+ const socketPath = join36(profileDir2, CONTROL_SOCKET_FILE);
46238
46689
  try {
46239
46690
  rmSync10(socketPath, { force: true });
46240
46691
  } catch {
@@ -51410,24 +51861,24 @@ var App = class extends import_react9.PureComponent {
51410
51861
  })
51411
51862
  }));
51412
51863
  };
51413
- findNextFocusable = (state2) => {
51414
- const activeIndex = state2.focusables.findIndex((focusable) => {
51415
- return focusable.id === state2.activeFocusId;
51864
+ findNextFocusable = (state3) => {
51865
+ const activeIndex = state3.focusables.findIndex((focusable) => {
51866
+ return focusable.id === state3.activeFocusId;
51416
51867
  });
51417
- for (let index = activeIndex + 1; index < state2.focusables.length; index++) {
51418
- const focusable = state2.focusables[index];
51868
+ for (let index = activeIndex + 1; index < state3.focusables.length; index++) {
51869
+ const focusable = state3.focusables[index];
51419
51870
  if (focusable?.isActive) {
51420
51871
  return focusable.id;
51421
51872
  }
51422
51873
  }
51423
51874
  return void 0;
51424
51875
  };
51425
- findPreviousFocusable = (state2) => {
51426
- const activeIndex = state2.focusables.findIndex((focusable) => {
51427
- return focusable.id === state2.activeFocusId;
51876
+ findPreviousFocusable = (state3) => {
51877
+ const activeIndex = state3.focusables.findIndex((focusable) => {
51878
+ return focusable.id === state3.activeFocusId;
51428
51879
  });
51429
51880
  for (let index = activeIndex - 1; index >= 0; index--) {
51430
- const focusable = state2.focusables[index];
51881
+ const focusable = state3.focusables[index];
51431
51882
  if (focusable?.isActive) {
51432
51883
  return focusable.id;
51433
51884
  }
@@ -51623,8 +52074,8 @@ var Ink = class {
51623
52074
  }
51624
52075
  }
51625
52076
  async waitUntilExit() {
51626
- this.exitPromise ||= new Promise((resolve12, reject) => {
51627
- this.resolveExitPromise = resolve12;
52077
+ this.exitPromise ||= new Promise((resolve13, reject) => {
52078
+ this.resolveExitPromise = resolve13;
51628
52079
  this.rejectExitPromise = reject;
51629
52080
  });
51630
52081
  return this.exitPromise;
@@ -51992,10 +52443,10 @@ var import_react26 = __toESM(require_react(), 1);
51992
52443
 
51993
52444
  // src/core/input-history.ts
51994
52445
  import { existsSync as existsSync20, readFileSync as readFileSync20, writeFileSync as writeFileSync11 } from "fs";
51995
- import { join as join36 } from "path";
52446
+ import { join as join37 } from "path";
51996
52447
  var MAX_HISTORY = 200;
51997
52448
  function historyPath(profileDir2) {
51998
- return join36(profileDir2, "input-history.json");
52449
+ return join37(profileDir2, "input-history.json");
51999
52450
  }
52000
52451
  function loadInputHistory(profileDir2) {
52001
52452
  const path = historyPath(profileDir2);
@@ -52609,7 +53060,7 @@ async function runStart(profileDir2, profileName) {
52609
53060
  return;
52610
53061
  }
52611
53062
  logger.configure({
52612
- dir: join37(profileDir2, "logs"),
53063
+ dir: join38(profileDir2, "logs"),
52613
53064
  level: resolveLogLevel(),
52614
53065
  base: { version: CLI_VERSION, profile: profileName, scope: credential.scope }
52615
53066
  });
@@ -52677,7 +53128,7 @@ async function runStart(profileDir2, profileName) {
52677
53128
  logger.error("ws.run.crash", { message: e.message, stack: e.stack });
52678
53129
  if (client.isStopped) return;
52679
53130
  bus.log(t("start.recovered", { message: e.message }), "error");
52680
- await new Promise((resolve12) => setTimeout(resolve12, 1e3));
53131
+ await new Promise((resolve13) => setTimeout(resolve13, 1e3));
52681
53132
  }
52682
53133
  }
52683
53134
  };
@@ -52719,12 +53170,12 @@ async function runStart(profileDir2, profileName) {
52719
53170
  }
52720
53171
 
52721
53172
  // src/commands/attach.ts
52722
- import { join as join38 } from "path";
53173
+ import { join as join39 } from "path";
52723
53174
 
52724
53175
  // src/core/control-client.ts
52725
53176
  import { connect as connect2 } from "net";
52726
53177
  function connectControl(socketPath, bus, token) {
52727
- return new Promise((resolve12, reject) => {
53178
+ return new Promise((resolve13, reject) => {
52728
53179
  const socket = connect2(socketPath);
52729
53180
  socket.setEncoding("utf8");
52730
53181
  let ready = false;
@@ -52777,7 +53228,7 @@ function connectControl(socketPath, bus, token) {
52777
53228
  bus.setTokens(frame.tokens);
52778
53229
  for (const entry of frame.transcript) bus.pushEntry(entry);
52779
53230
  ready = true;
52780
- resolve12({ info: frame.info, close });
53231
+ resolve13({ info: frame.info, close });
52781
53232
  break;
52782
53233
  case "transcript":
52783
53234
  bus.pushEntry(frame.entry);
@@ -52828,7 +53279,7 @@ async function runAttach(profileDir2, profileName) {
52828
53279
  process.exitCode = 1;
52829
53280
  return;
52830
53281
  }
52831
- const socketPath = join38(profileDir2, CONTROL_SOCKET_FILE);
53282
+ const socketPath = join39(profileDir2, CONTROL_SOCKET_FILE);
52832
53283
  const bus = new SessionBus();
52833
53284
  let conn;
52834
53285
  try {
@@ -52859,13 +53310,13 @@ async function runAttach(profileDir2, profileName) {
52859
53310
 
52860
53311
  // src/commands/auto-run.ts
52861
53312
  import { connect as connect3 } from "net";
52862
- import { join as join39 } from "path";
53313
+ import { join as join40 } from "path";
52863
53314
  var AUTO_RUN_TIMEOUT_MS = 3 * 60 * 60 * 1e3;
52864
53315
  async function runAutoRun(profileDir2, profileName) {
52865
53316
  initI18n(readProfileConfig(profileDir2).locale);
52866
- const socketPath = join39(profileDir2, CONTROL_SOCKET_FILE);
53317
+ const socketPath = join40(profileDir2, CONTROL_SOCKET_FILE);
52867
53318
  const token = readControlToken(profileDir2);
52868
- const code = await new Promise((resolve12) => {
53319
+ const code = await new Promise((resolve13) => {
52869
53320
  let settled = false;
52870
53321
  const done = (c) => {
52871
53322
  if (settled) return;
@@ -52875,7 +53326,7 @@ async function runAutoRun(profileDir2, profileName) {
52875
53326
  socket.destroy();
52876
53327
  } catch {
52877
53328
  }
52878
- resolve12(c);
53329
+ resolve13(c);
52879
53330
  };
52880
53331
  const socket = connect3(socketPath);
52881
53332
  socket.setEncoding("utf8");
@@ -52920,22 +53371,22 @@ async function runAutoRun(profileDir2, profileName) {
52920
53371
 
52921
53372
  // src/commands/git-credential.ts
52922
53373
  import { connect as connect4 } from "net";
52923
- import { join as join40 } from "path";
53374
+ import { join as join41 } from "path";
52924
53375
  var REQUEST_TIMEOUT_MS = 2e4;
52925
53376
  function requestToken(host, path) {
52926
53377
  const profileDir2 = process.env[PROFILE_DIR_ENV];
52927
53378
  if (!profileDir2) return Promise.resolve(null);
52928
53379
  const token = readControlToken(profileDir2);
52929
- return new Promise((resolve12) => {
53380
+ return new Promise((resolve13) => {
52930
53381
  let done = false;
52931
53382
  const finish = (value) => {
52932
53383
  if (done) return;
52933
53384
  done = true;
52934
53385
  clearTimeout(timer);
52935
53386
  socket.destroy();
52936
- resolve12(value);
53387
+ resolve13(value);
52937
53388
  };
52938
- const socket = connect4(join40(profileDir2, CONTROL_SOCKET_FILE));
53389
+ const socket = connect4(join41(profileDir2, CONTROL_SOCKET_FILE));
52939
53390
  socket.setEncoding("utf8");
52940
53391
  const timer = setTimeout(() => finish(null), REQUEST_TIMEOUT_MS);
52941
53392
  socket.on("connect", () => {
@@ -52993,16 +53444,16 @@ import { spawn as spawn12 } from "child_process";
52993
53444
  import { mkdirSync as mkdirSync10 } from "fs";
52994
53445
  import { basename as basename8 } from "path";
52995
53446
  function runProviderLogin(cmd, envVar, dir2) {
52996
- return new Promise((resolve12) => {
53447
+ return new Promise((resolve13) => {
52997
53448
  const child = spawn12(cmd, [], {
52998
53449
  stdio: "inherit",
52999
53450
  env: { ...process.env, [envVar]: dir2 }
53000
53451
  });
53001
53452
  child.on("error", (err) => {
53002
53453
  console.error(t("aiLogin.launchFailed", { cmd, message: err.message }));
53003
- resolve12(1);
53454
+ resolve13(1);
53004
53455
  });
53005
- child.on("exit", (code) => resolve12(code ?? 0));
53456
+ child.on("exit", (code) => resolve13(code ?? 0));
53006
53457
  });
53007
53458
  }
53008
53459
  async function runAiLogin(profileDir2, profileName, aiFilter) {
@@ -53142,10 +53593,10 @@ function Picker({
53142
53593
  ] });
53143
53594
  }
53144
53595
  function selectFromList(items, title) {
53145
- return new Promise((resolve12) => {
53596
+ return new Promise((resolve13) => {
53146
53597
  let chosen = null;
53147
53598
  const app = render_default(/* @__PURE__ */ (0, import_jsx_runtime7.jsx)(Picker, { items, title, onDone: (v2) => chosen = v2 }));
53148
- void app.waitUntilExit().then(() => resolve12(chosen));
53599
+ void app.waitUntilExit().then(() => resolve13(chosen));
53149
53600
  });
53150
53601
  }
53151
53602