@wrongstack/core 0.303.0 → 0.305.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (76) hide show
  1. package/dist/chronicle/project-server.js +18 -48
  2. package/dist/coordination/agents/index.js +530 -130
  3. package/dist/coordination/agents/project-agent-consolidation.d.ts +5 -0
  4. package/dist/coordination/agents/project-agent-directive-outcome.d.ts +57 -0
  5. package/dist/coordination/agents/project-agent-identity.d.ts +26 -12
  6. package/dist/coordination/agents/project-agent-learning-policy.d.ts +22 -1
  7. package/dist/coordination/agents/project-agent-learning-structured.d.ts +46 -1
  8. package/dist/coordination/agents/project-agent-quarantine.d.ts +63 -0
  9. package/dist/coordination/agents/project-agent-skill-layer.d.ts +55 -10
  10. package/dist/coordination/agents/types.d.ts +10 -2
  11. package/dist/coordination/director-prompts.d.ts +19 -6
  12. package/dist/coordination/director-tools.d.ts +2 -2
  13. package/dist/coordination/fleet.d.ts +0 -6
  14. package/dist/coordination/index.d.ts +2 -1
  15. package/dist/coordination/index.js +1581 -955
  16. package/dist/coordination/mailbox-project-server.js +28 -57
  17. package/dist/core/agent-types.d.ts +4 -2
  18. package/dist/core/agent.d.ts +1 -0
  19. package/dist/core/context.d.ts +15 -0
  20. package/dist/core/conversation-state.d.ts +14 -0
  21. package/dist/core/fallback-profile-manager.d.ts +70 -2
  22. package/dist/core/index.js +308 -108
  23. package/dist/core/system-prompt-blocks.d.ts +1 -1
  24. package/dist/core/system-prompt-builder.d.ts +13 -1
  25. package/dist/core/system-prompt-glossary.d.ts +73 -0
  26. package/dist/core/system-prompt-memory-skills.d.ts +2 -2
  27. package/dist/defaults/index.js +910 -693
  28. package/dist/execution/council-orchestrator.d.ts +3 -13
  29. package/dist/execution/index.js +211 -75
  30. package/dist/execution/one-shot-llm.d.ts +5 -0
  31. package/dist/hq/index.js +17 -7
  32. package/dist/hq/protocol/kanban.d.ts +21 -0
  33. package/dist/hq/protocol.js +5 -1
  34. package/dist/hq/redaction.d.ts +14 -0
  35. package/dist/index.d.ts +1 -0
  36. package/dist/index.js +3505 -2539
  37. package/dist/infrastructure/index.js +247 -122
  38. package/dist/plugin/index.js +101 -3
  39. package/dist/registry/index.js +11 -0
  40. package/dist/registry/tool-registry.d.ts +8 -0
  41. package/dist/replay/hash.d.ts +9 -0
  42. package/dist/replay/index.js +14 -4
  43. package/dist/replay/replay-provider-runner.d.ts +31 -1
  44. package/dist/security/index.js +25 -20
  45. package/dist/security/secret-vault.d.ts +2 -0
  46. package/dist/session-catalog/index.js +62 -8
  47. package/dist/session-catalog/project-server.js +109 -78
  48. package/dist/session-catalog/protocol.d.ts +11 -4
  49. package/dist/session-catalog/store.d.ts +2 -2
  50. package/dist/storage/index.js +224 -67
  51. package/dist/storage/memory-consolidator.d.ts +4 -2
  52. package/dist/storage/session-resume-validation.d.ts +24 -0
  53. package/dist/storage/session-store/directory-scan.d.ts +5 -1
  54. package/dist/storage/session-store/fork-session.d.ts +13 -1
  55. package/dist/storage/session-store/load-cache.d.ts +11 -0
  56. package/dist/storage/session-store/prune-helpers.d.ts +5 -0
  57. package/dist/storage/session-store.d.ts +18 -0
  58. package/dist/tools/index.js +174 -74
  59. package/dist/types/config/mcp-features.d.ts +31 -1
  60. package/dist/types/config/root.d.ts +12 -0
  61. package/dist/types/config/tools.d.ts +22 -0
  62. package/dist/types/config/ui.d.ts +7 -4
  63. package/dist/types/default-config.d.ts +1 -0
  64. package/dist/types/index.js +24 -1
  65. package/dist/types/session.d.ts +9 -1
  66. package/dist/utils/index.d.ts +1 -0
  67. package/dist/utils/index.js +214 -76
  68. package/dist/utils/project-state-guard.d.ts +21 -0
  69. package/dist/utils/session-scoped-path.d.ts +17 -0
  70. package/dist/utils/todos-format.d.ts +20 -0
  71. package/instructions/leader-after-task.md +3 -4
  72. package/instructions/system-lite.md +10 -13
  73. package/instructions/system-pro.md +18 -25
  74. package/instructions/system.md +18 -23
  75. package/package.json +3 -3
  76. package/skills/wrongstack-kanban/SKILL.md +95 -124
@@ -30,9 +30,9 @@ var init_protocol = __esm({
30
30
  // src/session-catalog/endpoint.ts
31
31
  import { createHash as createHash2 } from "node:crypto";
32
32
  import * as os3 from "node:os";
33
- import * as path8 from "node:path";
33
+ import * as path9 from "node:path";
34
34
  function normalizedPath(value) {
35
- const resolved = path8.resolve(value);
35
+ const resolved = path9.resolve(value);
36
36
  return process.platform === "win32" ? resolved.toLowerCase() : resolved;
37
37
  }
38
38
  function sessionCatalogProjectServerKey(projectDir) {
@@ -43,10 +43,10 @@ function sessionCatalogProjectServerEndpoint(projectDir) {
43
43
  if (process.platform === "win32") {
44
44
  return `\\\\.\\pipe\\wrongstack-session-catalog-v${SESSION_CATALOG_PROTOCOL_VERSION}-${key}`;
45
45
  }
46
- return path8.join(os3.tmpdir(), `wssc-v${SESSION_CATALOG_PROTOCOL_VERSION}`, `${key}.sock`);
46
+ return path9.join(os3.tmpdir(), `wssc-v${SESSION_CATALOG_PROTOCOL_VERSION}`, `${key}.sock`);
47
47
  }
48
48
  function sessionCatalogProjectServerMetadataPath(projectDir) {
49
- return path8.join(projectDir, SESSION_CATALOG_METADATA_FILE);
49
+ return path9.join(projectDir, SESSION_CATALOG_METADATA_FILE);
50
50
  }
51
51
  var SESSION_CATALOG_METADATA_FILE;
52
52
  var init_endpoint = __esm({
@@ -59,9 +59,9 @@ var init_endpoint = __esm({
59
59
 
60
60
  // src/session-catalog/client.ts
61
61
  import { spawn } from "node:child_process";
62
- import * as fs6 from "node:fs";
62
+ import * as fs7 from "node:fs";
63
63
  import * as net from "node:net";
64
- import * as path9 from "node:path";
64
+ import * as path10 from "node:path";
65
65
  import { fileURLToPath } from "node:url";
66
66
  function locateServer(moduleUrl, exists) {
67
67
  for (const candidate of [
@@ -78,22 +78,22 @@ function locateServer(moduleUrl, exists) {
78
78
  }
79
79
  return null;
80
80
  }
81
- function resolveSessionCatalogDaemonAvailability(moduleUrl = import.meta.url, exists = fs6.existsSync) {
81
+ function resolveSessionCatalogDaemonAvailability(moduleUrl = import.meta.url, exists = fs7.existsSync) {
82
82
  if (process.env["WRONGSTACK_SESSION_CATALOG_INLINE"] || process.env["WRONGSTACK_SESSION_CATALOG_SERVER"] === "0")
83
83
  return { kind: "inline-requested" };
84
84
  const url = locateServer(moduleUrl, exists);
85
85
  return url ? { kind: "available", url } : { kind: "missing-build" };
86
86
  }
87
- function resolveSessionCatalogProjectServerUrl(moduleUrl = import.meta.url, exists = fs6.existsSync) {
87
+ function resolveSessionCatalogProjectServerUrl(moduleUrl = import.meta.url, exists = fs7.existsSync) {
88
88
  const availability = resolveSessionCatalogDaemonAvailability(moduleUrl, exists);
89
89
  return availability.kind === "available" ? availability.url : null;
90
90
  }
91
91
  function normalize2(value) {
92
- const resolved = path9.resolve(value);
92
+ const resolved = path10.resolve(value);
93
93
  return process.platform === "win32" ? resolved.toLowerCase() : resolved;
94
94
  }
95
95
  function delay(ms) {
96
- return new Promise((resolve8) => setTimeout(resolve8, ms));
96
+ return new Promise((resolve9) => setTimeout(resolve9, ms));
97
97
  }
98
98
  var CONNECT_TIMEOUT_MS, START_TIMEOUT_MS, CALL_TIMEOUT_MS, MAX_PENDING_REQUESTS, MAX_EVENT_LISTENERS, SessionCatalogProjectClient;
99
99
  var init_client = __esm({
@@ -172,7 +172,7 @@ var init_client = __esm({
172
172
  if (result.stopped) {
173
173
  const metadataPath = sessionCatalogProjectServerMetadataPath(this.options.projectDir);
174
174
  const deadline = Date.now() + 5e3;
175
- while (fs6.existsSync(metadataPath) && Date.now() < deadline) await delay(20);
175
+ while (fs7.existsSync(metadataPath) && Date.now() < deadline) await delay(20);
176
176
  if (result.pid && result.pid !== process.pid) {
177
177
  while (Date.now() < deadline) {
178
178
  try {
@@ -194,8 +194,8 @@ var init_client = __esm({
194
194
  this.socket = null;
195
195
  this.info = null;
196
196
  if (socket && !socket.destroyed)
197
- await new Promise((resolve8) => {
198
- socket.once("close", resolve8);
197
+ await new Promise((resolve9) => {
198
+ socket.once("close", resolve9);
199
199
  socket.end();
200
200
  });
201
201
  }
@@ -233,7 +233,7 @@ var init_client = __esm({
233
233
  this.info = null;
234
234
  this.authToken = void 0;
235
235
  this.buffer = "";
236
- return new Promise((resolve8, reject) => {
236
+ return new Promise((resolve9, reject) => {
237
237
  const socket = net.createConnection(this.endpoint);
238
238
  this.socket = socket;
239
239
  socket.setEncoding("utf8");
@@ -246,7 +246,7 @@ var init_client = __esm({
246
246
  clearTimeout(timer);
247
247
  this.connectResolve = null;
248
248
  this.connectReject = null;
249
- resolve8();
249
+ resolve9();
250
250
  };
251
251
  this.connectReject = (error) => {
252
252
  clearTimeout(timer);
@@ -265,7 +265,7 @@ var init_client = __esm({
265
265
  if (this.authToken === void 0) {
266
266
  try {
267
267
  const parsed = JSON.parse(
268
- fs6.readFileSync(sessionCatalogProjectServerMetadataPath(this.options.projectDir), "utf8")
268
+ fs7.readFileSync(sessionCatalogProjectServerMetadataPath(this.options.projectDir), "utf8")
269
269
  );
270
270
  if (typeof parsed.authToken === "string" && parsed.authToken)
271
271
  this.authToken = parsed.authToken;
@@ -291,7 +291,7 @@ var init_client = __esm({
291
291
  });
292
292
  if (encoded.length > SESSION_CATALOG_MAX_FRAME_CHARS)
293
293
  return Promise.reject(new Error("Session Catalog request exceeded frame limit"));
294
- return new Promise((resolve8, reject) => {
294
+ return new Promise((resolve9, reject) => {
295
295
  const timer = setTimeout(() => {
296
296
  const pending = this.pending.get(id);
297
297
  if (!pending) return;
@@ -303,7 +303,7 @@ var init_client = __esm({
303
303
  );
304
304
  }, timeoutMs);
305
305
  timer.unref?.();
306
- this.pending.set(id, { resolve: resolve8, reject, timer });
306
+ this.pending.set(id, { resolve: resolve9, reject, timer });
307
307
  socket.write(encoded);
308
308
  });
309
309
  }
@@ -420,10 +420,10 @@ __export(registry_exports, {
420
420
  hasProjectSessionRegistry: () => hasProjectSessionRegistry
421
421
  });
422
422
  import { randomUUID } from "node:crypto";
423
- import * as fs7 from "node:fs/promises";
424
- import * as path10 from "node:path";
423
+ import * as fs8 from "node:fs/promises";
424
+ import * as path11 from "node:path";
425
425
  function getProjectSessionRegistry(globalRoot) {
426
- const key = globalRoot !== void 0 ? path10.resolve(globalRoot) : lastRegistryKey;
426
+ const key = globalRoot !== void 0 ? path11.resolve(globalRoot) : lastRegistryKey;
427
427
  if (!key)
428
428
  throw new Error("SessionRegistry not initialized. Call getSessionRegistry(globalRoot) first.");
429
429
  let registry = registries.get(key);
@@ -446,7 +446,7 @@ function getProjectSessionRegistry(globalRoot) {
446
446
  }
447
447
  function hasProjectSessionRegistry(globalRoot) {
448
448
  if (globalRoot === void 0) return registries.size > 0;
449
- return registries.has(path10.resolve(globalRoot));
449
+ return registries.has(path11.resolve(globalRoot));
450
450
  }
451
451
  var HEARTBEAT_INTERVAL_MS, AGENT_WRITE_THROTTLE_MS, MAX_PROJECT_CLIENTS, MAX_GLOBAL_ROOTS, ProjectSessionRegistry, registries, lastRegistryKey;
452
452
  var init_registry = __esm({
@@ -472,7 +472,7 @@ var init_registry = __esm({
472
472
  agentTimer;
473
473
  lastAgentWriteAt = 0;
474
474
  bindingKey(projectDir) {
475
- const resolved = path10.resolve(projectDir);
475
+ const resolved = path11.resolve(projectDir);
476
476
  return process.platform === "win32" ? resolved.toLowerCase() : resolved;
477
477
  }
478
478
  async closeBinding(binding) {
@@ -482,7 +482,7 @@ var init_registry = __esm({
482
482
  await binding.client.close();
483
483
  }
484
484
  binding(projectSlug2, projectRoot) {
485
- const projectDir = path10.join(this.globalRoot, "projects", projectSlug2);
485
+ const projectDir = path11.join(this.globalRoot, "projects", projectSlug2);
486
486
  const key = this.bindingKey(projectDir);
487
487
  let binding = this.clients.get(key);
488
488
  if (!binding) {
@@ -497,7 +497,7 @@ var init_registry = __esm({
497
497
  }
498
498
  binding = {
499
499
  projectDir,
500
- projectRoot: path10.resolve(projectRoot),
500
+ projectRoot: path11.resolve(projectRoot),
501
501
  client: new SessionCatalogProjectClient({ projectDir, projectRoot })
502
502
  };
503
503
  this.clients.set(key, binding);
@@ -658,20 +658,20 @@ var init_registry = __esm({
658
658
  }
659
659
  }
660
660
  async list() {
661
- const projectsDir = path10.join(this.globalRoot, "projects");
661
+ const projectsDir = path11.join(this.globalRoot, "projects");
662
662
  let directories = [];
663
663
  try {
664
- directories = (await fs7.readdir(projectsDir, { withFileTypes: true })).filter((entry) => entry.isDirectory()).slice(0, 1e3).map((entry) => entry.name);
664
+ directories = (await fs8.readdir(projectsDir, { withFileTypes: true })).filter((entry) => entry.isDirectory()).slice(0, 1e3).map((entry) => entry.name);
665
665
  } catch {
666
666
  return [];
667
667
  }
668
668
  const snapshots = await Promise.all(
669
669
  directories.map(async (slug) => {
670
- const projectDir = path10.join(projectsDir, slug);
670
+ const projectDir = path11.join(projectsDir, slug);
671
671
  let client;
672
672
  try {
673
673
  const metadata = JSON.parse(
674
- await fs7.readFile(sessionCatalogProjectServerMetadataPath(projectDir), "utf8")
674
+ await fs8.readFile(sessionCatalogProjectServerMetadataPath(projectDir), "utf8")
675
675
  );
676
676
  if (typeof metadata.projectRoot !== "string" || !metadata.projectRoot) return [];
677
677
  client = new SessionCatalogProjectClient({
@@ -690,14 +690,14 @@ var init_registry = __esm({
690
690
  }
691
691
  async listByProject(projectSlug2) {
692
692
  const current = this.current;
693
- if (current && path10.basename(current.binding.projectDir) === projectSlug2) {
693
+ if (current && path11.basename(current.binding.projectDir) === projectSlug2) {
694
694
  return current.binding.client.call("list_live", {}).catch(() => []);
695
695
  }
696
- const projectDir = path10.join(this.globalRoot, "projects", projectSlug2);
696
+ const projectDir = path11.join(this.globalRoot, "projects", projectSlug2);
697
697
  let client;
698
698
  try {
699
699
  const metadata = JSON.parse(
700
- await fs7.readFile(sessionCatalogProjectServerMetadataPath(projectDir), "utf8")
700
+ await fs8.readFile(sessionCatalogProjectServerMetadataPath(projectDir), "utf8")
701
701
  );
702
702
  if (typeof metadata.projectRoot !== "string") return [];
703
703
  client = new SessionCatalogProjectClient({ projectDir, projectRoot: metadata.projectRoot });
@@ -719,7 +719,7 @@ var init_registry = __esm({
719
719
  });
720
720
  }
721
721
  get registryPath() {
722
- return path10.join(this.globalRoot, "projects");
722
+ return path11.join(this.globalRoot, "projects");
723
723
  }
724
724
  async dispose() {
725
725
  await this.unregister().catch(() => void 0);
@@ -2192,9 +2192,9 @@ function createContextManagerTool(opts = {}) {
2192
2192
  var contextManagerTool = createContextManagerTool();
2193
2193
 
2194
2194
  // src/boot.ts
2195
- import * as fs8 from "node:fs/promises";
2195
+ import * as fs9 from "node:fs/promises";
2196
2196
  import * as os4 from "node:os";
2197
- import * as path11 from "node:path";
2197
+ import * as path12 from "node:path";
2198
2198
 
2199
2199
  // src/security/secret-vault.ts
2200
2200
  import { createCipheriv, createDecipheriv, randomBytes, scryptSync } from "node:crypto";
@@ -2571,23 +2571,21 @@ function unwrapDataKey(buf, keyFile) {
2571
2571
  });
2572
2572
  }
2573
2573
  }
2574
- function checkKeyFilePermissions(keyFile, opts) {
2575
- if (process.platform === "win32") return;
2574
+ function keyFileNeedsHardening(keyFile, opts) {
2575
+ if (process.platform === "win32") return false;
2576
2576
  const warn = opts?.warn ?? ((msg) => console.warn(msg));
2577
2577
  try {
2578
- const stat4 = fs2.statSync(keyFile);
2579
- const actualMode = stat4.mode & 511;
2578
+ const stat5 = fs2.statSync(keyFile);
2579
+ const actualMode = stat5.mode & 511;
2580
2580
  if (actualMode !== KEY_FILE_MODE) {
2581
- void restrictFilePermissions(keyFile, {
2582
- label: "secret-vault",
2583
- warn
2584
- }).catch(() => void 0);
2585
2581
  warn(
2586
2582
  `Key file ${keyFile} has mode ${actualMode.toString(8)} \u2014 expected ${KEY_FILE_MODE.toString(8)}. Hardening\u2026`
2587
2583
  );
2584
+ return true;
2588
2585
  }
2589
2586
  } catch {
2590
2587
  }
2588
+ return false;
2591
2589
  }
2592
2590
  function writeKeyFileAtomicSync(keyFile, content) {
2593
2591
  const tmp = `${keyFile}.${randomBytes(4).toString("hex")}.tmp`;
@@ -2653,6 +2651,14 @@ var DefaultSecretVault = class {
2653
2651
  }).catch(() => void 0);
2654
2652
  this.pendingHardening.push(p);
2655
2653
  }
2654
+ /** Detect and schedule repair of a pre-existing POSIX key with loose mode bits. */
2655
+ checkKeyFilePermissions() {
2656
+ if (keyFileNeedsHardening(this.keyFile, {
2657
+ warn: (msg) => this.logWarn(msg)
2658
+ })) {
2659
+ this.scheduleKeyHardening();
2660
+ }
2661
+ }
2656
2662
  /** Flush all pending key-file hardening promises. */
2657
2663
  flushHardening() {
2658
2664
  if (this.pendingHardening.length === 0) return Promise.resolve();
@@ -2740,7 +2746,7 @@ var DefaultSecretVault = class {
2740
2746
  writeKeyFileAtomicSync(this.keyFile, keyFileBuf);
2741
2747
  }
2742
2748
  this.scheduleKeyHardening();
2743
- checkKeyFilePermissions(this.keyFile, { warn: (msg) => this.logWarn(msg) });
2749
+ this.checkKeyFilePermissions();
2744
2750
  this.key = newKey;
2745
2751
  this._keyVersion = newVersion;
2746
2752
  return { oldVersion, newVersion };
@@ -2756,12 +2762,9 @@ var DefaultSecretVault = class {
2756
2762
  const passphrase = getVaultPassphrase();
2757
2763
  if (!passphrase || !this.key) return;
2758
2764
  try {
2759
- writeKeyFileAtomicSync(
2760
- this.keyFile,
2761
- wrapDataKey(this.key, this._keyVersion, passphrase)
2762
- );
2765
+ writeKeyFileAtomicSync(this.keyFile, wrapDataKey(this.key, this._keyVersion, passphrase));
2763
2766
  this.scheduleKeyHardening();
2764
- checkKeyFilePermissions(this.keyFile, { warn: (msg) => this.logWarn(msg) });
2767
+ this.checkKeyFilePermissions();
2765
2768
  } catch {
2766
2769
  }
2767
2770
  }
@@ -2773,13 +2776,13 @@ var DefaultSecretVault = class {
2773
2776
  const { key: key2, version } = unwrapDataKey(buf, this.keyFile);
2774
2777
  this.key = key2;
2775
2778
  this._keyVersion = version;
2776
- checkKeyFilePermissions(this.keyFile, { warn: (msg) => this.logWarn(msg) });
2779
+ this.checkKeyFilePermissions();
2777
2780
  return this.key;
2778
2781
  }
2779
2782
  if (buf.length === KEY_BYTES) {
2780
2783
  this.key = buf;
2781
2784
  this._keyVersion = 1;
2782
- checkKeyFilePermissions(this.keyFile, { warn: (msg) => this.logWarn(msg) });
2785
+ this.checkKeyFilePermissions();
2783
2786
  this.migrateToWrappedIfPassphrase();
2784
2787
  return this.key;
2785
2788
  }
@@ -2803,7 +2806,7 @@ var DefaultSecretVault = class {
2803
2806
  }
2804
2807
  this.key = Buffer.from(key2);
2805
2808
  this._keyVersion = version;
2806
- checkKeyFilePermissions(this.keyFile, { warn: (msg) => this.logWarn(msg) });
2809
+ this.checkKeyFilePermissions();
2807
2810
  this.migrateToWrappedIfPassphrase();
2808
2811
  return this.key;
2809
2812
  }
@@ -2830,13 +2833,13 @@ var DefaultSecretVault = class {
2830
2833
  const { key: winnerKey, version } = unwrapDataKey(buf, this.keyFile);
2831
2834
  this.key = winnerKey;
2832
2835
  this._keyVersion = version;
2833
- checkKeyFilePermissions(this.keyFile, { warn: (msg) => this.logWarn(msg) });
2836
+ this.checkKeyFilePermissions();
2834
2837
  return this.key;
2835
2838
  }
2836
2839
  if (buf.length === KEY_BYTES) {
2837
2840
  this.key = buf;
2838
2841
  this._keyVersion = 1;
2839
- checkKeyFilePermissions(this.keyFile, { warn: (msg) => this.logWarn(msg) });
2842
+ this.checkKeyFilePermissions();
2840
2843
  return this.key;
2841
2844
  }
2842
2845
  if (buf.length === VERSIONED_KEY_FILE_SIZE) {
@@ -2852,7 +2855,7 @@ var DefaultSecretVault = class {
2852
2855
  const winnerKey = buf.subarray(KEY_FILE_MAGIC.length + 1);
2853
2856
  this.key = Buffer.from(winnerKey);
2854
2857
  this._keyVersion = version;
2855
- checkKeyFilePermissions(this.keyFile, { warn: (msg) => this.logWarn(msg) });
2858
+ this.checkKeyFilePermissions();
2856
2859
  return this.key;
2857
2860
  }
2858
2861
  throw new ConfigError({
@@ -3190,6 +3193,9 @@ var DEFAULT_TOOLS_CONFIG = Object.freeze({
3190
3193
  disabledTools: Object.freeze([]),
3191
3194
  autoExtendLimit: true,
3192
3195
  restrictToProjectRoot: true,
3196
+ // Off by default: the board is a record of the work, not a permit for it.
3197
+ // See ToolsConfig.kanbanGovernance for what turning it on costs and gates.
3198
+ kanbanGovernance: false,
3193
3199
  loopDetection: Object.freeze({
3194
3200
  mode: "steer-then-cut",
3195
3201
  steerThreshold: 3,
@@ -3247,6 +3253,7 @@ var CONFIG_BEHAVIOR_DEFAULTS = {
3247
3253
  disabledTools: DEFAULT_TOOLS_CONFIG.disabledTools,
3248
3254
  autoExtendLimit: DEFAULT_TOOLS_CONFIG.autoExtendLimit,
3249
3255
  restrictToProjectRoot: DEFAULT_TOOLS_CONFIG.restrictToProjectRoot,
3256
+ kanbanGovernance: DEFAULT_TOOLS_CONFIG.kanbanGovernance,
3250
3257
  loopDetection: DEFAULT_TOOLS_CONFIG.loopDetection
3251
3258
  },
3252
3259
  log: { level: "info" },
@@ -3631,10 +3638,18 @@ var IN_PROJECT_DENIED_PATHS = [
3631
3638
  {
3632
3639
  path: "tools.restrictToProjectRoot",
3633
3640
  reason: "The other half of the filesystem confinement switch."
3641
+ },
3642
+ {
3643
+ // Denied for the direction that loosens: the flag defaults to false, so a
3644
+ // repo can only ever use it to turn OFF a gate the user deliberately
3645
+ // enabled. Same class as `tools.restrictToProjectRoot` — a control the
3646
+ // operator owns, not the checked-out repository.
3647
+ path: "tools.kanbanGovernance",
3648
+ reason: "Repo-committed config could disable a Kanban governance gate the operator switched on, letting product mutations run outside any managed card."
3634
3649
  }
3635
3650
  ];
3636
- function deleteNestedPath(target, path12) {
3637
- const segments = path12.split(".");
3651
+ function deleteNestedPath(target, path13) {
3652
+ const segments = path13.split(".");
3638
3653
  const last = segments[segments.length - 1];
3639
3654
  if (last === void 0) return false;
3640
3655
  let cursor = target;
@@ -3691,16 +3706,16 @@ function assertInProjectAllowListComplete() {
3691
3706
  );
3692
3707
  }
3693
3708
  const orphanedPaths = IN_PROJECT_DENIED_PATHS.filter(
3694
- ({ path: path12 }) => !IN_PROJECT_ALLOWED_KEYS.has(path12.split(".")[0] ?? "")
3695
- ).map(({ path: path12 }) => path12);
3709
+ ({ path: path13 }) => !IN_PROJECT_ALLOWED_KEYS.has(path13.split(".")[0] ?? "")
3710
+ ).map(({ path: path13 }) => path13);
3696
3711
  if (orphanedPaths.length > 0) {
3697
3712
  problems.push(
3698
3713
  `IN_PROJECT_DENIED_PATHS entr(ies) whose top-level parent is not allowed: ` + orphanedPaths.join(", ") + ". The parent is already stripped wholesale, so the nested denial is dead \u2014 remove it."
3699
3714
  );
3700
3715
  }
3701
3716
  const malformedPaths = IN_PROJECT_DENIED_PATHS.filter(
3702
- ({ path: path12 }) => path12.split(".").length < 2 || path12.split(".").some((s) => s.length === 0)
3703
- ).map(({ path: path12 }) => path12);
3717
+ ({ path: path13 }) => path13.split(".").length < 2 || path13.split(".").some((s) => s.length === 0)
3718
+ ).map(({ path: path13 }) => path13);
3704
3719
  if (malformedPaths.length > 0) {
3705
3720
  problems.push(
3706
3721
  `IN_PROJECT_DENIED_PATHS entr(ies) are not dotted nested paths: ` + malformedPaths.join(", ") + ". Top-level keys belong in KNOWN_DENIED_IN_PROJECT instead."
@@ -3728,8 +3743,8 @@ function stripUnsafeInProjectFields(inProject, sourcePath, warn = (msg) => conso
3728
3743
  }
3729
3744
  stripped.push(k);
3730
3745
  }
3731
- for (const { path: path12 } of IN_PROJECT_DENIED_PATHS) {
3732
- if (deleteNestedPath(out, path12)) stripped.push(path12);
3746
+ for (const { path: path13 } of IN_PROJECT_DENIED_PATHS) {
3747
+ if (deleteNestedPath(out, path13)) stripped.push(path13);
3733
3748
  }
3734
3749
  if (stripped.length > 0) {
3735
3750
  warn(
@@ -4338,8 +4353,8 @@ var DefaultConfigLoader = class _DefaultConfigLoader {
4338
4353
  const t0 = Date.now();
4339
4354
  let mtimeMs = null;
4340
4355
  try {
4341
- const stat4 = await fs5.stat(file);
4342
- mtimeMs = stat4.mtimeMs;
4356
+ const stat5 = await fs5.stat(file);
4357
+ mtimeMs = stat5.mtimeMs;
4343
4358
  const cached = this.jsonCache.get(file);
4344
4359
  if (cached && cached.mtimeMs === mtimeMs) {
4345
4360
  return structuredClone(cached.value);
@@ -4630,6 +4645,115 @@ function isAlreadyExistsError(error) {
4630
4645
  return error?.code === "EEXIST";
4631
4646
  }
4632
4647
 
4648
+ // src/utils/project-state-guard.ts
4649
+ import * as fs6 from "node:fs";
4650
+ import * as fsp3 from "node:fs/promises";
4651
+ import * as path8 from "node:path";
4652
+ var PROJECT_STATE_DIRECTORY = ".wrongstack";
4653
+ var DEFAULT_POLL_INTERVAL_MS = 1e3;
4654
+ var REPAIR_DEBOUNCE_MS = 25;
4655
+ var ACTIVE_GUARD_KEY = /* @__PURE__ */ Symbol.for("wrongstack.activeProjectStateGuard");
4656
+ var globalScope = globalThis;
4657
+ globalScope[ACTIVE_GUARD_KEY] ??= {};
4658
+ var activeGuard = globalScope[ACTIVE_GUARD_KEY];
4659
+ function normalizedRoot(root) {
4660
+ const resolved = path8.resolve(root);
4661
+ return process.platform === "win32" ? resolved.toLowerCase() : resolved;
4662
+ }
4663
+ function concernsProjectState(filename) {
4664
+ if (filename === null) return true;
4665
+ const firstSegment = String(filename).replaceAll("\\", "/").split("/")[0];
4666
+ return firstSegment?.toLowerCase() === PROJECT_STATE_DIRECTORY;
4667
+ }
4668
+ async function startProjectStateGuard(projectRoot, options = {}) {
4669
+ const root = path8.resolve(projectRoot);
4670
+ const directory = path8.join(root, PROJECT_STATE_DIRECTORY);
4671
+ const pollIntervalMs = Math.max(25, options.pollIntervalMs ?? DEFAULT_POLL_INTERVAL_MS);
4672
+ let closed = false;
4673
+ let repairPending = false;
4674
+ let repairInFlight;
4675
+ let repairTimer;
4676
+ const ensureDirectory = async () => {
4677
+ await fsp3.mkdir(directory, { recursive: true });
4678
+ };
4679
+ const ensureInvariant = async () => {
4680
+ await ensureDirectory();
4681
+ try {
4682
+ await ensureProjectGitignore(root);
4683
+ } catch (error) {
4684
+ options.onError?.(error);
4685
+ }
4686
+ };
4687
+ const scheduleRepair = () => {
4688
+ if (closed) return;
4689
+ repairPending = true;
4690
+ if (repairInFlight || repairTimer) return;
4691
+ repairTimer = setTimeout(() => {
4692
+ repairTimer = void 0;
4693
+ repairInFlight = (async () => {
4694
+ while (!closed && repairPending) {
4695
+ repairPending = false;
4696
+ try {
4697
+ const rootState = await fsp3.stat(root).catch(() => void 0);
4698
+ if (!rootState?.isDirectory()) return;
4699
+ await ensureInvariant();
4700
+ } catch (error) {
4701
+ options.onError?.(error);
4702
+ }
4703
+ }
4704
+ })().finally(() => {
4705
+ repairInFlight = void 0;
4706
+ if (repairPending && !closed) scheduleRepair();
4707
+ });
4708
+ }, REPAIR_DEBOUNCE_MS);
4709
+ repairTimer.unref?.();
4710
+ };
4711
+ await ensureDirectory();
4712
+ await ensureProjectGitignore(root).catch((error) => options.onError?.(error));
4713
+ let watcher;
4714
+ try {
4715
+ watcher = fs6.watch(root, { persistent: false }, (_eventType, filename) => {
4716
+ if (concernsProjectState(filename) || String(filename).toLowerCase() === ".gitignore") {
4717
+ scheduleRepair();
4718
+ }
4719
+ });
4720
+ watcher.on("error", (error) => {
4721
+ options.onError?.(error);
4722
+ watcher = void 0;
4723
+ scheduleRepair();
4724
+ });
4725
+ } catch (error) {
4726
+ options.onError?.(error);
4727
+ }
4728
+ const poll = setInterval(scheduleRepair, pollIntervalMs);
4729
+ poll.unref?.();
4730
+ return {
4731
+ directory,
4732
+ close() {
4733
+ if (closed) return;
4734
+ closed = true;
4735
+ clearInterval(poll);
4736
+ if (repairTimer) clearTimeout(repairTimer);
4737
+ repairTimer = void 0;
4738
+ try {
4739
+ watcher?.close();
4740
+ } catch {
4741
+ }
4742
+ watcher = void 0;
4743
+ }
4744
+ };
4745
+ }
4746
+ async function activateProjectStateGuard(projectRoot, options = {}) {
4747
+ const root = normalizedRoot(projectRoot);
4748
+ if (activeGuard.root === root && activeGuard.guard) return activeGuard.guard;
4749
+ const next = await startProjectStateGuard(projectRoot, options);
4750
+ const previous = activeGuard.guard;
4751
+ activeGuard.root = root;
4752
+ activeGuard.guard = next;
4753
+ previous?.close();
4754
+ return next;
4755
+ }
4756
+
4633
4757
  // src/boot.ts
4634
4758
  async function bootConfig(options = {}) {
4635
4759
  const {
@@ -4638,17 +4762,18 @@ async function bootConfig(options = {}) {
4638
4762
  loadSyncConfig = true,
4639
4763
  skipIdentityValidation = false
4640
4764
  } = options;
4641
- const cwd = typeof flags["cwd"] === "string" ? path11.resolve(flags["cwd"]) : process.cwd();
4765
+ const cwd = typeof flags["cwd"] === "string" ? path12.resolve(flags["cwd"]) : process.cwd();
4642
4766
  const pathResolver = new DefaultPathResolver(cwd);
4643
4767
  const projectRoot = pathResolver.projectRoot;
4644
4768
  const projectIdentityRoot = canonicalProjectRoot(projectRoot);
4645
4769
  const userHome = os4.homedir();
4646
4770
  const wpaths = resolveWstackPaths({ projectRoot });
4647
4771
  assertProjectRootOutsideStateDir(projectRoot, wpaths.globalRoot);
4648
- await fs8.mkdir(wpaths.globalRoot, { recursive: true });
4649
- await fs8.mkdir(wpaths.profilesDir, { recursive: true });
4650
- await fs8.mkdir(wpaths.projectDir, { recursive: true });
4651
- await fs8.mkdir(wpaths.projectSessions, { recursive: true });
4772
+ await activateProjectStateGuard(projectRoot);
4773
+ await fs9.mkdir(wpaths.globalRoot, { recursive: true });
4774
+ await fs9.mkdir(wpaths.profilesDir, { recursive: true });
4775
+ await fs9.mkdir(wpaths.projectDir, { recursive: true });
4776
+ await fs9.mkdir(wpaths.projectSessions, { recursive: true });
4652
4777
  let registeredProjectId;
4653
4778
  try {
4654
4779
  registeredProjectId = (await readProjectIdentity(projectRoot))?.projectId;
@@ -4823,7 +4948,7 @@ async function migrateLegacyConfig(wpaths) {
4823
4948
  const rootFp = wpaths.globalConfig;
4824
4949
  let legacyRaw;
4825
4950
  try {
4826
- legacyRaw = await fs8.readFile(rootFp, "utf8");
4951
+ legacyRaw = await fs9.readFile(rootFp, "utf8");
4827
4952
  } catch {
4828
4953
  return;
4829
4954
  }
@@ -4836,12 +4961,12 @@ async function migrateLegacyConfig(wpaths) {
4836
4961
  );
4837
4962
  const profileFp = wpaths.profileConfig(activeProfile);
4838
4963
  try {
4839
- await fs8.access(profileFp);
4964
+ await fs9.access(profileFp);
4840
4965
  return;
4841
4966
  } catch (err) {
4842
4967
  if (err.code !== "ENOENT") return;
4843
4968
  }
4844
- await fs8.mkdir(path11.dirname(profileFp), { recursive: true });
4969
+ await fs9.mkdir(path12.dirname(profileFp), { recursive: true });
4845
4970
  const profileContent = { ...result.value };
4846
4971
  delete profileContent["activeProfile"];
4847
4972
  await atomicWrite(profileFp, JSON.stringify(profileContent, null, 2), { mode: 384 });
@@ -4853,64 +4978,64 @@ async function migrateLegacyConfig(wpaths) {
4853
4978
  }
4854
4979
  var PROFILE_STATE_PAIRS = [
4855
4980
  {
4856
- globalSrc: (w) => path11.join(w.globalRoot, "statusline.json"),
4981
+ globalSrc: (w) => path12.join(w.globalRoot, "statusline.json"),
4857
4982
  profileDst: (w, n) => w.profileStatuslineConfig(n)
4858
4983
  },
4859
4984
  {
4860
- globalSrc: (w) => path11.join(w.globalRoot, "mode.json"),
4985
+ globalSrc: (w) => path12.join(w.globalRoot, "mode.json"),
4861
4986
  profileDst: (w, n) => w.profileModeConfig(n)
4862
4987
  },
4863
4988
  {
4864
- globalSrc: (w) => path11.join(w.globalRoot, "provider-status.json"),
4989
+ globalSrc: (w) => path12.join(w.globalRoot, "provider-status.json"),
4865
4990
  profileDst: (w, n) => w.profileProviderStatus(n)
4866
4991
  },
4867
4992
  {
4868
- globalSrc: (w) => path11.join(w.globalRoot, "update-cache.json"),
4993
+ globalSrc: (w) => path12.join(w.globalRoot, "update-cache.json"),
4869
4994
  profileDst: (w, n) => w.profileUpdateCache(n)
4870
4995
  },
4871
4996
  {
4872
- globalSrc: (w) => path11.join(w.globalRoot, "memory.md"),
4873
- profileDst: (w, n) => path11.join(w.profilesDir, n, "memory.md")
4997
+ globalSrc: (w) => path12.join(w.globalRoot, "memory.md"),
4998
+ profileDst: (w, n) => path12.join(w.profilesDir, n, "memory.md")
4874
4999
  },
4875
5000
  {
4876
- globalSrc: (w) => path11.join(w.globalRoot, "history"),
4877
- profileDst: (w, n) => path11.join(w.profilesDir, n, "history")
5001
+ globalSrc: (w) => path12.join(w.globalRoot, "history"),
5002
+ profileDst: (w, n) => path12.join(w.profilesDir, n, "history")
4878
5003
  },
4879
5004
  {
4880
- globalSrc: (w) => path11.join(w.globalRoot, "sync.json"),
4881
- profileDst: (w, n) => path11.join(w.profilesDir, n, "sync.json")
5005
+ globalSrc: (w) => path12.join(w.globalRoot, "sync.json"),
5006
+ profileDst: (w, n) => path12.join(w.profilesDir, n, "sync.json")
4882
5007
  },
4883
5008
  {
4884
- globalSrc: (w) => path11.join(w.globalRoot, "sync-state.json"),
4885
- profileDst: (w, n) => path11.join(w.profilesDir, n, "sync-state.json")
5009
+ globalSrc: (w) => path12.join(w.globalRoot, "sync-state.json"),
5010
+ profileDst: (w, n) => path12.join(w.profilesDir, n, "sync-state.json")
4886
5011
  },
4887
5012
  {
4888
- globalSrc: (w) => path11.join(w.globalRoot, "prompt-usage.json"),
4889
- profileDst: (w, n) => path11.join(w.profilesDir, n, "prompt-usage.json")
5013
+ globalSrc: (w) => path12.join(w.globalRoot, "prompt-usage.json"),
5014
+ profileDst: (w, n) => path12.join(w.profilesDir, n, "prompt-usage.json")
4890
5015
  },
4891
5016
  {
4892
- globalSrc: (w) => path11.join(w.globalRoot, "custom-context-modes.json"),
4893
- profileDst: (w, n) => path11.join(w.profilesDir, n, "custom-context-modes.json")
5017
+ globalSrc: (w) => path12.join(w.globalRoot, "custom-context-modes.json"),
5018
+ profileDst: (w, n) => path12.join(w.profilesDir, n, "custom-context-modes.json")
4894
5019
  },
4895
5020
  {
4896
- globalSrc: (w) => path11.join(w.globalRoot, "desktop.json"),
4897
- profileDst: (w, n) => path11.join(w.profilesDir, n, "desktop.json")
5021
+ globalSrc: (w) => path12.join(w.globalRoot, "desktop.json"),
5022
+ profileDst: (w, n) => path12.join(w.profilesDir, n, "desktop.json")
4898
5023
  },
4899
5024
  {
4900
- globalSrc: (w) => path11.join(w.globalRoot, "installed-skills.json"),
4901
- profileDst: (w, n) => path11.join(w.profilesDir, n, "installed-skills.json")
5025
+ globalSrc: (w) => path12.join(w.globalRoot, "installed-skills.json"),
5026
+ profileDst: (w, n) => path12.join(w.profilesDir, n, "installed-skills.json")
4902
5027
  },
4903
5028
  ...["skills", "prompts", "instructions", "design-kits", "desktop", "settings"].map(
4904
5029
  (directory) => ({
4905
- globalSrc: (w) => path11.join(w.globalRoot, directory),
4906
- profileDst: (w, n) => path11.join(w.profilesDir, n, directory)
5030
+ globalSrc: (w) => path12.join(w.globalRoot, directory),
5031
+ profileDst: (w, n) => path12.join(w.profilesDir, n, directory)
4907
5032
  })
4908
5033
  )
4909
5034
  ];
4910
5035
  async function migrateProfileFiles(wpaths) {
4911
5036
  let profileName = wpaths.profileName;
4912
5037
  try {
4913
- const raw = await fs8.readFile(wpaths.globalConfig, "utf8");
5038
+ const raw = await fs9.readFile(wpaths.globalConfig, "utf8");
4914
5039
  const parsed = safeParse(raw);
4915
5040
  if (parsed.ok && parsed.value && typeof parsed.value.activeProfile === "string") {
4916
5041
  profileName = safeProfileName(parsed.value.activeProfile);
@@ -4918,7 +5043,7 @@ async function migrateProfileFiles(wpaths) {
4918
5043
  } catch {
4919
5044
  }
4920
5045
  try {
4921
- await fs8.mkdir(path11.join(wpaths.profilesDir, profileName), { recursive: true });
5046
+ await fs9.mkdir(path12.join(wpaths.profilesDir, profileName), { recursive: true });
4922
5047
  } catch {
4923
5048
  return;
4924
5049
  }
@@ -4927,16 +5052,16 @@ async function migrateProfileFiles(wpaths) {
4927
5052
  const dst = pair.profileDst(wpaths, profileName);
4928
5053
  let srcIsDir;
4929
5054
  try {
4930
- srcIsDir = (await fs8.stat(src)).isDirectory();
5055
+ srcIsDir = (await fs9.stat(src)).isDirectory();
4931
5056
  } catch {
4932
5057
  continue;
4933
5058
  }
4934
5059
  try {
4935
- const dstStat = await fs8.stat(dst);
5060
+ const dstStat = await fs9.stat(dst);
4936
5061
  if (dstStat.isDirectory() === srcIsDir) {
4937
5062
  continue;
4938
5063
  }
4939
- await fs8.rm(dst, { recursive: true, force: true });
5064
+ await fs9.rm(dst, { recursive: true, force: true });
4940
5065
  } catch (err) {
4941
5066
  const code = err.code;
4942
5067
  if (code !== "ENOENT") {
@@ -4947,19 +5072,19 @@ async function migrateProfileFiles(wpaths) {
4947
5072
  }
4948
5073
  try {
4949
5074
  if (srcIsDir) {
4950
- await fs8.cp(src, dst, { recursive: true, errorOnExist: false, force: false });
5075
+ await fs9.cp(src, dst, { recursive: true, errorOnExist: false, force: false });
4951
5076
  } else {
4952
- const content = await fs8.readFile(src);
4953
- await fs8.writeFile(dst, content, { mode: 384 });
5077
+ const content = await fs9.readFile(src);
5078
+ await fs9.writeFile(dst, content, { mode: 384 });
4954
5079
  }
4955
5080
  } catch {
4956
5081
  }
4957
5082
  }
4958
5083
  }
4959
5084
  function assertProjectRootOutsideStateDir(projectRoot, globalRoot) {
4960
- const stateNamespace = path11.resolve(globalRoot, "projects");
4961
- const rel = path11.relative(stateNamespace, path11.resolve(projectRoot));
4962
- if (rel.startsWith("..") || path11.isAbsolute(rel)) return;
5085
+ const stateNamespace = path12.resolve(globalRoot, "projects");
5086
+ const rel = path12.relative(stateNamespace, path12.resolve(projectRoot));
5087
+ if (rel.startsWith("..") || path12.isAbsolute(rel)) return;
4963
5088
  throw new Error(
4964
5089
  `Refusing to start: the resolved project root is inside WrongStack's per-project state directory.
4965
5090
  project root: ${projectRoot}
@@ -4969,7 +5094,7 @@ This usually means a WrongStack process was spawned with a state path as its wor
4969
5094
  }
4970
5095
  async function writeProjectMeta(paths, projectRoot, projectId) {
4971
5096
  try {
4972
- await fs8.mkdir(paths.projectDir, { recursive: true });
5097
+ await fs9.mkdir(paths.projectDir, { recursive: true });
4973
5098
  const meta = {
4974
5099
  hash: paths.projectHash,
4975
5100
  slug: paths.projectSlug,
@@ -4982,10 +5107,10 @@ async function writeProjectMeta(paths, projectRoot, projectId) {
4982
5107
  }
4983
5108
  }
4984
5109
  async function registerProjectInManifest(paths, projectRoot, events, workingDir, projectId) {
4985
- const manifestPath = path11.join(paths.globalRoot, "projects.json");
5110
+ const manifestPath = path12.join(paths.globalRoot, "projects.json");
4986
5111
  try {
4987
5112
  const t0 = Date.now();
4988
- const raw = await fs8.readFile(manifestPath, "utf8");
5113
+ const raw = await fs9.readFile(manifestPath, "utf8");
4989
5114
  events?.emit("storage.read", {
4990
5115
  sessionId: "~boot~",
4991
5116
  store: "project",
@@ -5011,7 +5136,7 @@ async function registerProjectInManifest(paths, projectRoot, events, workingDir,
5011
5136
  try {
5012
5137
  let manifest;
5013
5138
  try {
5014
- const raw = await fs8.readFile(manifestPath, "utf8");
5139
+ const raw = await fs9.readFile(manifestPath, "utf8");
5015
5140
  manifest = JSON.parse(raw);
5016
5141
  } catch {
5017
5142
  manifest = { projects: [] };
@@ -5024,7 +5149,7 @@ async function registerProjectInManifest(paths, projectRoot, events, workingDir,
5024
5149
  if (workingDir) existing.lastWorkingDir = workingDir;
5025
5150
  } else {
5026
5151
  const slug = paths.projectSlug;
5027
- const name = path11.basename(projectRoot);
5152
+ const name = path12.basename(projectRoot);
5028
5153
  const entry = {
5029
5154
  name,
5030
5155
  root: projectRoot,
@@ -5058,32 +5183,32 @@ async function registerProjectInManifest(paths, projectRoot, events, workingDir,
5058
5183
  }
5059
5184
  }
5060
5185
  async function cleanupStaleProjects(wpaths) {
5061
- const projectsRoot = path11.dirname(wpaths.projectDir);
5186
+ const projectsRoot = path12.dirname(wpaths.projectDir);
5062
5187
  let entries;
5063
5188
  try {
5064
- entries = await fs8.readdir(projectsRoot, { withFileTypes: true });
5189
+ entries = await fs9.readdir(projectsRoot, { withFileTypes: true });
5065
5190
  } catch {
5066
5191
  return;
5067
5192
  }
5068
- const stateNamespace = path11.resolve(projectsRoot);
5193
+ const stateNamespace = path12.resolve(projectsRoot);
5069
5194
  for (const entry of entries) {
5070
5195
  if (!entry.isDirectory()) continue;
5071
- const projectPath = path11.join(projectsRoot, entry.name);
5072
- const metaPath = path11.join(projectPath, "meta.json");
5196
+ const projectPath = path12.join(projectsRoot, entry.name);
5197
+ const metaPath = path12.join(projectPath, "meta.json");
5073
5198
  try {
5074
- const raw = await fs8.readFile(metaPath, "utf8");
5199
+ const raw = await fs9.readFile(metaPath, "utf8");
5075
5200
  const meta = JSON.parse(raw);
5076
5201
  if (typeof meta.root !== "string") continue;
5077
- const rel = path11.relative(stateNamespace, path11.resolve(meta.root));
5078
- const nested = rel !== "" && !rel.startsWith("..") && !path11.isAbsolute(rel);
5202
+ const rel = path12.relative(stateNamespace, path12.resolve(meta.root));
5203
+ const nested = rel !== "" && !rel.startsWith("..") && !path12.isAbsolute(rel);
5079
5204
  if (nested) {
5080
- await fs8.rm(projectPath, { recursive: true, force: true });
5205
+ await fs9.rm(projectPath, { recursive: true, force: true });
5081
5206
  continue;
5082
5207
  }
5083
5208
  try {
5084
- await fs8.access(meta.root);
5209
+ await fs9.access(meta.root);
5085
5210
  } catch {
5086
- await fs8.rm(projectPath, { recursive: true, force: true });
5211
+ await fs9.rm(projectPath, { recursive: true, force: true });
5087
5212
  }
5088
5213
  } catch {
5089
5214
  }