@wrongstack/tools 0.295.0 → 0.295.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 (48) hide show
  1. package/dist/auto-proceed-loop-guard.d.ts +31 -0
  2. package/dist/auto-proceed-loop-guard.d.ts.map +1 -1
  3. package/dist/auto-proceed-loop-guard.js +24 -1
  4. package/dist/auto-proceed-loop-guard.js.map +2 -2
  5. package/dist/bash.js +39 -23
  6. package/dist/bash.js.map +2 -2
  7. package/dist/builtin.js +278 -109
  8. package/dist/builtin.js.map +4 -4
  9. package/dist/codebase-index/generic-parser.d.ts.map +1 -1
  10. package/dist/codebase-index/index.js +48 -47
  11. package/dist/codebase-index/index.js.map +2 -2
  12. package/dist/codebase-index/indexer.d.ts.map +1 -1
  13. package/dist/codebase-index/worker.js +48 -47
  14. package/dist/codebase-index/worker.js.map +2 -2
  15. package/dist/codebase-index/writer.d.ts.map +1 -1
  16. package/dist/exec.js +39 -23
  17. package/dist/exec.js.map +2 -2
  18. package/dist/glob.d.ts.map +1 -1
  19. package/dist/glob.js +80 -30
  20. package/dist/glob.js.map +4 -4
  21. package/dist/grep.d.ts.map +1 -1
  22. package/dist/grep.js +87 -20
  23. package/dist/grep.js.map +4 -4
  24. package/dist/index.d.ts +1 -0
  25. package/dist/index.d.ts.map +1 -1
  26. package/dist/index.js +281 -109
  27. package/dist/index.js.map +4 -4
  28. package/dist/kanban-evidence-bridge.d.ts +21 -0
  29. package/dist/kanban-evidence-bridge.d.ts.map +1 -0
  30. package/dist/kanban.d.ts +17 -1
  31. package/dist/kanban.d.ts.map +1 -1
  32. package/dist/kanban.js +164 -7
  33. package/dist/kanban.js.map +3 -3
  34. package/dist/pack.js +278 -109
  35. package/dist/pack.js.map +4 -4
  36. package/dist/process-registry-persistent.d.ts.map +1 -1
  37. package/dist/ps-slash.js +39 -23
  38. package/dist/ps-slash.js.map +2 -2
  39. package/dist/tool-tier.js +278 -109
  40. package/dist/tool-tier.js.map +4 -4
  41. package/dist/tree.d.ts.map +1 -1
  42. package/dist/tree.js +2 -14
  43. package/dist/tree.js.map +2 -2
  44. package/dist/win32.d.ts +10 -0
  45. package/dist/win32.d.ts.map +1 -0
  46. package/dist/win32.js +67 -0
  47. package/dist/win32.js.map +7 -0
  48. package/package.json +7 -3
package/dist/index.js CHANGED
@@ -5380,6 +5380,7 @@ function emitStructuredLog(level, event, message, error) {
5380
5380
  }
5381
5381
  var HEARTBEAT_INTERVAL_MS = 5e3;
5382
5382
  var STALE_THRESHOLD_MS = 3e4;
5383
+ var LOCK_STALE_MS = 3e4;
5383
5384
  var LOCKFILE = ".process-registry.lock";
5384
5385
  function generateInstanceId() {
5385
5386
  const hostname4 = os2.hostname();
@@ -5408,21 +5409,26 @@ async function acquireLock(lockfilePath, timeoutMs = 5e3) {
5408
5409
  try {
5409
5410
  const content = await fs6.readFile(lockfilePath, "utf-8");
5410
5411
  const parts = content.split(":");
5411
- const lockPidStr = parts[0] ?? "0";
5412
- const lockPid = parseInt(lockPidStr, 10);
5413
- if (process.platform !== "win32") {
5412
+ const lockPid = parseInt(parts[0] ?? "0", 10);
5413
+ const lockTs = Number(parts[parts.length - 1]);
5414
+ const staleByAge = Number.isFinite(lockTs) && Date.now() - lockTs > LOCK_STALE_MS;
5415
+ let holderDead = false;
5416
+ if (process.platform !== "win32" && Number.isFinite(lockPid) && lockPid > 0) {
5414
5417
  try {
5415
5418
  process.kill(lockPid, 0);
5416
5419
  } catch {
5417
- await fs6.unlink(lockfilePath);
5418
- continue;
5420
+ holderDead = true;
5419
5421
  }
5420
5422
  }
5421
- } catch {
5422
- try {
5423
- await fs6.unlink(lockfilePath);
5424
- } catch {
5423
+ if (holderDead || staleByAge) {
5424
+ await fs6.unlink(lockfilePath).catch(() => {
5425
+ });
5426
+ continue;
5425
5427
  }
5428
+ } catch {
5429
+ await fs6.unlink(lockfilePath).catch(() => {
5430
+ });
5431
+ continue;
5426
5432
  }
5427
5433
  await new Promise((r) => setTimeout(r, 100));
5428
5434
  continue;
@@ -5432,25 +5438,35 @@ async function acquireLock(lockfilePath, timeoutMs = 5e3) {
5432
5438
  }
5433
5439
  throw new Error(`Failed to acquire lock after ${timeoutMs}ms`);
5434
5440
  }
5441
+ function freshRegistryData() {
5442
+ return {
5443
+ version: 1,
5444
+ instances: /* @__PURE__ */ new Map(),
5445
+ protectedPatterns: ["wrongstack", "node"],
5446
+ lastCleanup: Date.now()
5447
+ };
5448
+ }
5435
5449
  async function readRegistryFile(filePath) {
5450
+ let content;
5436
5451
  try {
5437
- const content = await fs6.readFile(filePath, "utf-8");
5438
- const parsed = JSON.parse(content);
5439
- if (parsed.instances && Array.isArray(parsed.instances)) {
5440
- parsed.instances = new Map(parsed.instances);
5441
- }
5442
- return parsed;
5452
+ content = await fs6.readFile(filePath, "utf-8");
5443
5453
  } catch (err) {
5444
- if (isNodeError(err) && err.code === "ENOENT") {
5445
- return {
5446
- version: 1,
5447
- instances: /* @__PURE__ */ new Map(),
5448
- protectedPatterns: ["wrongstack", "node"],
5449
- lastCleanup: Date.now()
5450
- };
5451
- }
5454
+ if (isNodeError(err) && err.code === "ENOENT") return freshRegistryData();
5452
5455
  throw err;
5453
5456
  }
5457
+ try {
5458
+ const parsed = JSON.parse(content);
5459
+ if (!parsed || typeof parsed !== "object") return freshRegistryData();
5460
+ const base = freshRegistryData();
5461
+ return {
5462
+ version: 1,
5463
+ instances: Array.isArray(parsed.instances) ? new Map(parsed.instances) : base.instances,
5464
+ protectedPatterns: Array.isArray(parsed.protectedPatterns) ? parsed.protectedPatterns : base.protectedPatterns,
5465
+ lastCleanup: typeof parsed.lastCleanup === "number" ? parsed.lastCleanup : base.lastCleanup
5466
+ };
5467
+ } catch {
5468
+ return freshRegistryData();
5469
+ }
5454
5470
  }
5455
5471
  async function writeRegistryFile(filePath, data) {
5456
5472
  const tmpPath = `${filePath}.tmp.${process.pid}`;
@@ -9455,11 +9471,12 @@ var IndexStore = class _IndexStore {
9455
9471
  }
9456
9472
  }
9457
9473
  const refRows = this.stmt(
9458
- `SELECT r.call_type, sf.file AS from_file, st.file AS to_file
9474
+ `SELECT r.call_type, sf.file AS from_file, st.file AS to_file, COUNT(*) AS n
9459
9475
  FROM refs r
9460
9476
  JOIN symbols sf ON sf.id = r.from_id
9461
9477
  JOIN symbols st ON st.id = r.to_id
9462
- WHERE r.to_id IS NOT NULL AND r.call_type != 'import'`
9478
+ WHERE r.to_id IS NOT NULL AND r.call_type != 'import'
9479
+ GROUP BY r.call_type, sf.file, st.file`
9463
9480
  ).all();
9464
9481
  const edgeMap = /* @__PURE__ */ new Map();
9465
9482
  for (const r of refRows) {
@@ -9472,14 +9489,16 @@ var IndexStore = class _IndexStore {
9472
9489
  e = { weight: 0, types: /* @__PURE__ */ new Map() };
9473
9490
  edgeMap.set(key, e);
9474
9491
  }
9475
- e.weight++;
9476
- e.types.set(r.call_type, (e.types.get(r.call_type) ?? 0) + 1);
9492
+ const n = Number(r.n) || 0;
9493
+ e.weight += n;
9494
+ e.types.set(r.call_type, (e.types.get(r.call_type) ?? 0) + n);
9477
9495
  }
9478
9496
  const importRows = this.stmt(
9479
- `SELECT r.to_name, s.file AS from_file
9497
+ `SELECT r.to_name, s.file AS from_file, COUNT(*) AS n
9480
9498
  FROM refs r
9481
9499
  JOIN symbols s ON s.id = r.from_id
9482
- WHERE r.call_type = 'import'`
9500
+ WHERE r.call_type = 'import'
9501
+ GROUP BY r.to_name, s.file`
9483
9502
  ).all();
9484
9503
  for (const r of importRows) {
9485
9504
  const fromPkg = fileToPkg.get(r.from_file) ?? _IndexStore.derivePackage(r.from_file) ?? "(root)";
@@ -9491,8 +9510,9 @@ var IndexStore = class _IndexStore {
9491
9510
  edge = { weight: 0, types: /* @__PURE__ */ new Map() };
9492
9511
  edgeMap.set(key, edge);
9493
9512
  }
9494
- edge.weight++;
9495
- edge.types.set("import", (edge.types.get("import") ?? 0) + 1);
9513
+ const n = Number(r.n) || 0;
9514
+ edge.weight += n;
9515
+ edge.types.set("import", (edge.types.get("import") ?? 0) + n);
9496
9516
  }
9497
9517
  const edges = [];
9498
9518
  for (const [key, e] of edgeMap) {
@@ -9557,11 +9577,12 @@ var IndexStore = class _IndexStore {
9557
9577
  }
9558
9578
  const indexedFiles = new Set(allFiles.map((f) => f.file));
9559
9579
  const refRows = this.stmt(
9560
- `SELECT r.from_id, r.to_id, r.call_type
9580
+ `SELECT r.from_id, r.to_id, r.call_type, COUNT(*) AS n
9561
9581
  FROM refs r
9562
9582
  WHERE (r.from_id IN (SELECT id FROM symbols WHERE file IN (${filePlaceholders}))
9563
9583
  OR r.to_id IN (SELECT id FROM symbols WHERE file IN (${filePlaceholders})))
9564
- AND r.to_id IS NOT NULL`
9584
+ AND r.to_id IS NOT NULL
9585
+ GROUP BY r.from_id, r.to_id, r.call_type`
9565
9586
  ).all(...pkgFilePaths, ...pkgFilePaths);
9566
9587
  const knownSymIds = new Set(pkgSyms.map((s) => s.id));
9567
9588
  const crossRefIds = /* @__PURE__ */ new Set();
@@ -9596,14 +9617,16 @@ var IndexStore = class _IndexStore {
9596
9617
  e = { weight: 0, types: /* @__PURE__ */ new Map() };
9597
9618
  edgeMap.set(key, e);
9598
9619
  }
9599
- e.weight++;
9600
- e.types.set(r.call_type, (e.types.get(r.call_type) ?? 0) + 1);
9620
+ const n = Number(r.n) || 0;
9621
+ e.weight += n;
9622
+ e.types.set(r.call_type, (e.types.get(r.call_type) ?? 0) + n);
9601
9623
  }
9602
9624
  const importRows = this.stmt(
9603
- `SELECT r.from_id, r.to_name
9625
+ `SELECT r.from_id, r.to_name, COUNT(*) AS n
9604
9626
  FROM refs r
9605
9627
  WHERE r.call_type = 'import'
9606
- AND r.from_id IN (SELECT id FROM symbols WHERE file IN (${filePlaceholders}))`
9628
+ AND r.from_id IN (SELECT id FROM symbols WHERE file IN (${filePlaceholders}))
9629
+ GROUP BY r.from_id, r.to_name`
9607
9630
  ).all(...pkgFilePaths);
9608
9631
  for (const r of importRows) {
9609
9632
  const fromFile = symToFile.get(r.from_id);
@@ -9618,8 +9641,9 @@ var IndexStore = class _IndexStore {
9618
9641
  edge = { weight: 0, types: /* @__PURE__ */ new Map() };
9619
9642
  edgeMap.set(key, edge);
9620
9643
  }
9621
- edge.weight++;
9622
- edge.types.set("import", (edge.types.get("import") ?? 0) + 1);
9644
+ const n = Number(r.n) || 0;
9645
+ edge.weight += n;
9646
+ edge.types.set("import", (edge.types.get("import") ?? 0) + n);
9623
9647
  }
9624
9648
  const edges = [];
9625
9649
  for (const [key, e] of edgeMap) {
@@ -9666,22 +9690,25 @@ var IndexStore = class _IndexStore {
9666
9690
  scope: s.scope,
9667
9691
  external: s.file !== fileFilter
9668
9692
  });
9669
- const symIds = new Set(syms.map((s) => s.id));
9670
9693
  const refRows = this.stmt(
9671
- `SELECT r.from_id, r.to_id, r.to_name, r.call_type, r.line
9672
- FROM refs r
9673
- JOIN symbols s ON s.id = r.from_id
9674
- WHERE s.file = ?
9675
- UNION
9676
- SELECT r.from_id, r.to_id, r.to_name, r.call_type, r.line
9677
- FROM refs r
9678
- JOIN symbols s ON s.id = r.to_id
9679
- WHERE s.file = ?`
9694
+ `SELECT from_id, to_id, call_type, COUNT(*) AS n
9695
+ FROM (
9696
+ SELECT r.from_id, r.to_id, r.to_name, r.call_type, r.line
9697
+ FROM refs r
9698
+ JOIN symbols s ON s.id = r.from_id
9699
+ WHERE s.file = ?
9700
+ UNION
9701
+ SELECT r.from_id, r.to_id, r.to_name, r.call_type, r.line
9702
+ FROM refs r
9703
+ JOIN symbols s ON s.id = r.to_id
9704
+ WHERE s.file = ?
9705
+ )
9706
+ WHERE to_id IS NOT NULL
9707
+ GROUP BY from_id, to_id, call_type`
9680
9708
  ).all(fileFilter, fileFilter);
9681
9709
  const edgeMap = /* @__PURE__ */ new Map();
9682
9710
  for (const r of refRows) {
9683
- if (r.to_id === null) continue;
9684
- if (!symIds.has(r.from_id) && !symIds.has(r.to_id)) continue;
9711
+ if (r.to_id == null) continue;
9685
9712
  relatedIds.add(r.from_id);
9686
9713
  relatedIds.add(r.to_id);
9687
9714
  const key = `${r.from_id}\0${r.to_id}`;
@@ -9690,8 +9717,9 @@ var IndexStore = class _IndexStore {
9690
9717
  e = { weight: 0, types: /* @__PURE__ */ new Map() };
9691
9718
  edgeMap.set(key, e);
9692
9719
  }
9693
- e.weight++;
9694
- e.types.set(r.call_type, (e.types.get(r.call_type) ?? 0) + 1);
9720
+ const n = Number(r.n) || 0;
9721
+ e.weight += n;
9722
+ e.types.set(r.call_type, (e.types.get(r.call_type) ?? 0) + n);
9695
9723
  }
9696
9724
  const edges = [];
9697
9725
  for (const [key, e] of edgeMap) {
@@ -9748,7 +9776,7 @@ import { expectDefined as expectDefined6 } from "@wrongstack/core/utils";
9748
9776
  import * as fs14 from "node:fs/promises";
9749
9777
  import * as path20 from "node:path";
9750
9778
  import { availableParallelism } from "node:os";
9751
- import { indexParallelBatchSize, isFrugalPerf } from "@wrongstack/core/utils";
9779
+ import { DEFAULT_WALK_IGNORE_DIRS, indexParallelBatchSize, isFrugalPerf } from "@wrongstack/core/utils";
9752
9780
 
9753
9781
  // src/codebase-index/ts-parser.ts
9754
9782
  import * as ts from "@typescript/typescript6";
@@ -10406,7 +10434,7 @@ import * as path16 from "node:path";
10406
10434
  var C_LIKE = [
10407
10435
  { re: /\b(?:class|struct|enum|interface|union)\s+([A-Za-z_]\w*)/g, kind: "class" },
10408
10436
  {
10409
- re: /\b(?:public|private|protected|static|final|async|override|virtual|inline|export)?\s*(?:[\w:<>\[\]\s\*&]+)\s+([A-Za-z_]\w*)\s*\([^;{]*\)\s*(?:const)?\s*[{;]/g,
10437
+ re: /\b(?:public|private|protected|static|final|async|override|virtual|inline|export)?\s*(?:[\w:<>[\]\s*&]+)\s+([A-Za-z_]\w*)\s*\([^;{]*\)\s*(?:const)?\s*[{;]/g,
10410
10438
  kind: "function"
10411
10439
  },
10412
10440
  { re: /\b(?:namespace)\s+([A-Za-z_]\w*)/g, kind: "namespace" }
@@ -10437,7 +10465,7 @@ var LANG_PATTERNS = {
10437
10465
  java: [
10438
10466
  { re: /\b(?:class|interface|enum|record)\s+([A-Za-z_]\w*)/g, kind: "class" },
10439
10467
  {
10440
- re: /\b(?:public|private|protected|static|final|abstract|synchronized|native|default|\s)+\s*[\w.<>,\[\]\s]+\s+([A-Za-z_]\w*)\s*\(/g,
10468
+ re: /\b(?:public|private|protected|static|final|abstract|synchronized|native|default|\s)+\s*[\w.<>,[\]\s]+\s+([A-Za-z_]\w*)\s*\(/g,
10441
10469
  kind: "method"
10442
10470
  }
10443
10471
  ],
@@ -10445,7 +10473,7 @@ var LANG_PATTERNS = {
10445
10473
  { re: /\b(?:class|interface|struct|enum|record)\s+([A-Za-z_]\w*)/g, kind: "class" },
10446
10474
  { re: /\bnamespace\s+([A-Za-z_.\w]+)/g, kind: "namespace" },
10447
10475
  {
10448
- re: /\b(?:public|private|protected|internal|static|async|override|virtual|\s)+\s*[\w.<>,\[\]\s]+\s+([A-Za-z_]\w*)\s*\(/g,
10476
+ re: /\b(?:public|private|protected|internal|static|async|override|virtual|\s)+\s*[\w.<>,[\]\s]+\s+([A-Za-z_]\w*)\s*\(/g,
10449
10477
  kind: "method"
10450
10478
  }
10451
10479
  ],
@@ -10650,8 +10678,7 @@ function parseGeneric2(opts) {
10650
10678
  for (const pattern of patterns) {
10651
10679
  const re = new RegExp(pattern.re.source, pattern.re.flags.includes("g") ? pattern.re.flags : `${pattern.re.flags}g`);
10652
10680
  re.lastIndex = 0;
10653
- let match;
10654
- while ((match = re.exec(content)) !== null) {
10681
+ for (const match of content.matchAll(re)) {
10655
10682
  if (symbols.length >= maxSymbols) break;
10656
10683
  let name = (match[1] ?? match[2] ?? "").trim();
10657
10684
  if (lang === "md" && match[2]) name = match[2].trim();
@@ -11607,17 +11634,7 @@ function throwIfAborted(signal) {
11607
11634
  function isAbortError(err) {
11608
11635
  return err instanceof DOMException && err.name === "AbortError";
11609
11636
  }
11610
- var DEFAULT_IGNORE = [
11611
- "node_modules",
11612
- ".git",
11613
- "dist",
11614
- "build",
11615
- ".next",
11616
- "coverage",
11617
- ".turbo",
11618
- "__snapshots__",
11619
- ".nyc_output"
11620
- ];
11637
+ var DEFAULT_IGNORE = DEFAULT_WALK_IGNORE_DIRS;
11621
11638
  var DEFAULT_IGNORE_FILES = /* @__PURE__ */ new Set(["package-lock.json", "pnpm-lock.yaml", "pnpm-lock.yml"]);
11622
11639
  var MAX_INDEX_FILE_BYTES = 5 * 1024 * 1024;
11623
11640
  function isWithinProject(projectRoot, file) {
@@ -16266,7 +16283,7 @@ function runGit2(args, cwd, signal) {
16266
16283
  // src/glob.ts
16267
16284
  import * as fs20 from "node:fs/promises";
16268
16285
  import * as path26 from "node:path";
16269
- import { compileGlob as compileGlob2 } from "@wrongstack/core/utils";
16286
+ import { compileGlob as compileGlob2, DEFAULT_WALK_IGNORE_DIRS as DEFAULT_WALK_IGNORE_DIRS2 } from "@wrongstack/core/utils";
16270
16287
 
16271
16288
  // src/_concurrency.ts
16272
16289
  async function mapWithConcurrency(items, limit, fn) {
@@ -16287,7 +16304,7 @@ async function mapWithConcurrency(items, limit, fn) {
16287
16304
 
16288
16305
  // src/glob.ts
16289
16306
  init_util();
16290
- var DEFAULT_IGNORE2 = ["node_modules", ".git", "dist", "build", ".next", "coverage", ".turbo"];
16307
+ var DEFAULT_IGNORE2 = DEFAULT_WALK_IGNORE_DIRS2;
16291
16308
  var WALK_CONCURRENCY = 16;
16292
16309
  var globTool = {
16293
16310
  name: "glob",
@@ -16327,7 +16344,7 @@ var globTool = {
16327
16344
  const signal = opts?.signal;
16328
16345
  const base = input.path ? await safeResolveReal(input.path, ctx) : ctx.cwd;
16329
16346
  const limit = Math.max(1, Math.min(input.limit ?? 1e3, 5e3));
16330
- const ignored = await readGitignore(base);
16347
+ const isGitIgnored = await loadGitignoreMatcher(base);
16331
16348
  const re = compileGlob2(input.pattern);
16332
16349
  const results = [];
16333
16350
  let truncated = false;
@@ -16367,12 +16384,13 @@ var globTool = {
16367
16384
  for (const e of entries) {
16368
16385
  const name = e.name;
16369
16386
  if (DEFAULT_IGNORE2.includes(name)) continue;
16370
- if (ignored.includes(name)) continue;
16371
16387
  const rel = relPrefix ? `${relPrefix}/${name}` : name;
16372
16388
  const full = path26.join(dir, name);
16373
16389
  if (e.isDirectory()) {
16390
+ if (isGitIgnored(rel, true)) continue;
16374
16391
  subdirs.push({ full, rel });
16375
16392
  } else if (e.isFile()) {
16393
+ if (isGitIgnored(rel, false)) continue;
16376
16394
  re.lastIndex = 0;
16377
16395
  const relMatch = re.test(rel);
16378
16396
  re.lastIndex = 0;
@@ -16384,10 +16402,12 @@ var globTool = {
16384
16402
  try {
16385
16403
  const st = await fs20.stat(full);
16386
16404
  if (st.isDirectory()) {
16405
+ if (isGitIgnored(rel, true)) continue;
16387
16406
  const real = await fs20.realpath(full);
16388
16407
  await assertRealInsideRoot(real, ctx);
16389
16408
  subdirs.push({ full, rel });
16390
16409
  } else if (st.isFile()) {
16410
+ if (isGitIgnored(rel, false)) continue;
16391
16411
  const real = await fs20.realpath(full);
16392
16412
  await assertRealInsideRoot(real, ctx);
16393
16413
  re.lastIndex = 0;
@@ -16411,14 +16431,6 @@ var globTool = {
16411
16431
  return { files: results.map((r) => r.rel), truncated };
16412
16432
  }
16413
16433
  };
16414
- async function readGitignore(dir) {
16415
- try {
16416
- const raw = await fs20.readFile(path26.join(dir, ".gitignore"), "utf8");
16417
- return raw.split("\n").map((l) => l.trim()).filter((l) => l && !l.startsWith("#"));
16418
- } catch {
16419
- return [];
16420
- }
16421
- }
16422
16434
 
16423
16435
  // src/grep.ts
16424
16436
  import { expectDefined as expectDefined7 } from "@wrongstack/core/utils";
@@ -16426,7 +16438,7 @@ import { spawn as spawn10 } from "node:child_process";
16426
16438
  import * as fs21 from "node:fs/promises";
16427
16439
  import * as path27 from "node:path";
16428
16440
  import { ToolValidationError as ToolValidationError4 } from "@wrongstack/core/types";
16429
- import { buildChildEnv as buildChildEnv5, compileGlob as compileGlob3 } from "@wrongstack/core/utils";
16441
+ import { buildChildEnv as buildChildEnv5, compileGlob as compileGlob3, DEFAULT_WALK_IGNORE_DIRS as DEFAULT_WALK_IGNORE_DIRS3 } from "@wrongstack/core/utils";
16430
16442
 
16431
16443
  // src/_regex.ts
16432
16444
  var MAX_PATTERN_LEN = 256;
@@ -16475,7 +16487,7 @@ function capSubject(line) {
16475
16487
 
16476
16488
  // src/grep.ts
16477
16489
  init_util();
16478
- var DEFAULT_IGNORE3 = ["node_modules", ".git", "dist", "build", ".next", "coverage"];
16490
+ var DEFAULT_IGNORE3 = DEFAULT_WALK_IGNORE_DIRS3;
16479
16491
  var NATIVE_SCAN_CONCURRENCY = 32;
16480
16492
  var NATIVE_READ_CHUNK_BYTES = 64 * 1024;
16481
16493
  var NATIVE_MAX_FILE_BYTES = 1e6;
@@ -16592,6 +16604,10 @@ async function* runRgStream(input, base, mode, limit, signal) {
16592
16604
  for (const ignored of DEFAULT_IGNORE3) {
16593
16605
  args.push("--glob", `!${ignored}/**`, "--glob", `!**/${ignored}/**`);
16594
16606
  }
16607
+ const gitignorePath = path27.join(base, ".gitignore");
16608
+ if (await fs21.access(gitignorePath).then(() => true, () => false)) {
16609
+ args.push("--ignore-file", gitignorePath);
16610
+ }
16595
16611
  if (input.glob) args.push("--glob", input.glob);
16596
16612
  args.push("--", input.pattern, base);
16597
16613
  const matches = [];
@@ -16717,6 +16733,7 @@ async function runNative(input, base, mode, limit, signal) {
16717
16733
  }
16718
16734
  const re = compiled.regex;
16719
16735
  const globRe = input.glob ? compileGlob3(input.glob) : null;
16736
+ const isGitIgnored = await loadGitignoreMatcher(base);
16720
16737
  const matches = [];
16721
16738
  const countOnlyFirstHit = mode === "count" && limit === 1;
16722
16739
  const maxBytes = mode === "content" ? NATIVE_MAX_FILE_BYTES : Math.min(NATIVE_MAX_FILE_BYTES, 256 * 1024);
@@ -16804,7 +16821,7 @@ async function runNative(input, base, mode, limit, signal) {
16804
16821
  } catch {
16805
16822
  }
16806
16823
  };
16807
- const walk2 = async (dir) => {
16824
+ const walk2 = async (dir, relPrefix) => {
16808
16825
  if (stopped || signal.aborted) return;
16809
16826
  let entries;
16810
16827
  try {
@@ -16818,17 +16835,24 @@ async function runNative(input, base, mode, limit, signal) {
16818
16835
  if (stopped) return;
16819
16836
  if (DEFAULT_IGNORE3.includes(e.name)) continue;
16820
16837
  if (e.isSymbolicLink()) continue;
16838
+ const rel = relPrefix ? `${relPrefix}/${e.name}` : e.name;
16821
16839
  const full = path27.join(dir, e.name);
16822
16840
  if (e.isDirectory()) {
16823
- subdirs.push(full);
16841
+ if (isGitIgnored(rel, true)) continue;
16842
+ subdirs.push({ full, rel });
16824
16843
  } else if (e.isFile()) {
16844
+ if (isGitIgnored(rel, false)) continue;
16825
16845
  files.push({ full, name: e.name });
16826
16846
  }
16827
16847
  }
16828
16848
  await mapWithConcurrency(files, NATIVE_SCAN_CONCURRENCY, ({ full, name }) => scanFile(full, name));
16829
- await mapWithConcurrency(subdirs, Math.min(16, NATIVE_SCAN_CONCURRENCY), walk2);
16849
+ await mapWithConcurrency(
16850
+ subdirs,
16851
+ Math.min(16, NATIVE_SCAN_CONCURRENCY),
16852
+ ({ full, rel }) => walk2(full, rel)
16853
+ );
16830
16854
  };
16831
- await walk2(base);
16855
+ await walk2(base, "");
16832
16856
  return {
16833
16857
  matches,
16834
16858
  count: total,
@@ -17558,9 +17582,35 @@ import {
17558
17582
  updateGoalMetricOnTask,
17559
17583
  updateTask,
17560
17584
  updateTaskAssignment,
17561
- verifyTaskCompletion
17585
+ verifyTaskCompletion,
17586
+ finalizeTaskCompletion,
17587
+ assessTaskAtomicity,
17588
+ proposeTaskDecomposition
17562
17589
  } from "@wrongstack/kanban";
17563
17590
 
17591
+ // src/kanban-evidence-bridge.ts
17592
+ import { recordCompletedWorkEvidence } from "@wrongstack/core/utils";
17593
+ function kanbanEvidenceKey(boardId, taskId) {
17594
+ return `kanban:${boardId}:${taskId}`;
17595
+ }
17596
+ function kanbanEvidencePointer(boardId, taskId) {
17597
+ return `kanban://${boardId}/${taskId}#verificationReport`;
17598
+ }
17599
+ function recordKanbanVerificationEvidence(ctx, report) {
17600
+ try {
17601
+ const passed = report.checks.filter((check) => check.status === "passed").length;
17602
+ const completedAt = Date.parse(report.completedAt);
17603
+ recordCompletedWorkEvidence(ctx, {
17604
+ key: kanbanEvidenceKey(report.boardId, report.taskId),
17605
+ source: "verification",
17606
+ summary: `${report.taskTitle} \u2014 verification ${report.verdict} (${passed}/${report.checks.length} checks)`,
17607
+ ...Number.isFinite(completedAt) ? { completedAt } : {},
17608
+ evidence: kanbanEvidencePointer(report.boardId, report.taskId)
17609
+ });
17610
+ } catch {
17611
+ }
17612
+ }
17613
+
17564
17614
  // src/session-kanban.ts
17565
17615
  import { watch } from "node:fs";
17566
17616
  import { basename as basename11, dirname as dirname13 } from "node:path";
@@ -18296,7 +18346,9 @@ var kanbanTool = {
18296
18346
  "add_note",
18297
18347
  "add_link",
18298
18348
  "verify_completion",
18299
- "split_atomic"
18349
+ "split_atomic",
18350
+ "assess_atomicity",
18351
+ "propose_decomposition"
18300
18352
  ]
18301
18353
  },
18302
18354
  boardId: { type: "string" },
@@ -18432,7 +18484,24 @@ var kanbanTool = {
18432
18484
  includeCompletedTasks: { type: "boolean" },
18433
18485
  preserveAssignment: { type: "boolean" },
18434
18486
  preserveDependencies: { type: "boolean" },
18435
- moveTasksToColumnId: { type: "string" }
18487
+ moveTasksToColumnId: { type: "string" },
18488
+ atomicityMode: { type: "string", enum: ["off", "assess", "enforce"] },
18489
+ atomicityDecomposition: { type: "string", enum: ["auto", "propose"] },
18490
+ gateEnforcement: { type: "string", enum: ["strict", "soft", "off"] },
18491
+ subtasks: {
18492
+ type: "array",
18493
+ minItems: 2,
18494
+ items: {
18495
+ type: "object",
18496
+ properties: {
18497
+ title: { type: "string" },
18498
+ description: { type: "string" },
18499
+ successCriteria: { type: "array", items: { type: "string" } },
18500
+ dependsOnIndex: { type: "array", items: { type: "number" } }
18501
+ },
18502
+ required: ["title"]
18503
+ }
18504
+ }
18436
18505
  },
18437
18506
  required: ["action"]
18438
18507
  },
@@ -18472,7 +18541,14 @@ var kanbanTool = {
18472
18541
  title: input.title,
18473
18542
  ...input.description !== void 0 ? { description: input.description } : {},
18474
18543
  ...input.tags !== void 0 ? { tags: input.tags } : {},
18475
- ...input.generatedBy !== void 0 ? { generatedBy: input.generatedBy } : {}
18544
+ ...input.generatedBy !== void 0 ? { generatedBy: input.generatedBy } : {},
18545
+ ...input.atomicityMode !== void 0 ? {
18546
+ atomicity: {
18547
+ mode: input.atomicityMode,
18548
+ decomposition: input.atomicityDecomposition ?? "propose"
18549
+ }
18550
+ } : {},
18551
+ ...input.gateEnforcement !== void 0 ? { completionGate: { enforcement: input.gateEnforcement } } : {}
18476
18552
  });
18477
18553
  return { ok: true, message: `Board created: ${board.title}`, board };
18478
18554
  }
@@ -18481,7 +18557,14 @@ var kanbanTool = {
18481
18557
  const board = await updateBoard2(projectRoot, input.boardId, {
18482
18558
  ...input.title !== void 0 ? { title: input.title } : {},
18483
18559
  ...input.description !== void 0 ? { description: input.description } : {},
18484
- ...input.tags !== void 0 ? { tags: input.tags } : {}
18560
+ ...input.tags !== void 0 ? { tags: input.tags } : {},
18561
+ ...input.atomicityMode !== void 0 ? {
18562
+ atomicity: {
18563
+ mode: input.atomicityMode,
18564
+ decomposition: input.atomicityDecomposition ?? "propose"
18565
+ }
18566
+ } : {},
18567
+ ...input.gateEnforcement !== void 0 ? { completionGate: { enforcement: input.gateEnforcement } } : {}
18485
18568
  });
18486
18569
  return board ? okBoard(board, "Board updated.") : fail("Board not found.");
18487
18570
  }
@@ -18689,7 +18772,12 @@ var kanbanTool = {
18689
18772
  case "add_task": {
18690
18773
  if (!input.boardId || !input.title) return fail("add_task requires boardId and title.");
18691
18774
  const result2 = await addTask(projectRoot, input.boardId, taskInput(input));
18692
- return result2 ? okTask(result2.board, result2.task, "Task added.") : fail("Board not found.");
18775
+ if (!result2) return fail("Board not found.");
18776
+ return okTask(
18777
+ result2.board,
18778
+ result2.task,
18779
+ `Task added.${atomicityNudge(result2.task)}`
18780
+ );
18693
18781
  }
18694
18782
  case "split_task": {
18695
18783
  if (!input.boardId || !input.taskId || !input.childTitles?.length) {
@@ -18769,6 +18857,19 @@ var kanbanTool = {
18769
18857
  "transition_task requires boardId, taskId, lifecycleStage, author, and transitionComment."
18770
18858
  );
18771
18859
  }
18860
+ if (input.lifecycleStage === "done") {
18861
+ const boardBefore = await getBoard2(projectRoot, input.boardId);
18862
+ const taskBefore = boardBefore ? await getTask(projectRoot, input.boardId, input.taskId) : null;
18863
+ if (boardBefore && taskBefore && !taskBefore.verificationReport && (taskBefore.atomic || Boolean(taskBefore.successCriteria?.length))) {
18864
+ const preGate = await verifyTaskCompletion(projectRoot, input.boardId, taskBefore.id, {
18865
+ persist: false
18866
+ });
18867
+ await updateTask(projectRoot, input.boardId, taskBefore.id, {
18868
+ verificationReport: preGate.report,
18869
+ successCriteria: preGate.task.successCriteria
18870
+ });
18871
+ }
18872
+ }
18772
18873
  const result2 = await transitionTask(projectRoot, input.boardId, input.taskId, {
18773
18874
  to: input.lifecycleStage,
18774
18875
  actor: input.author,
@@ -18783,6 +18884,9 @@ var kanbanTool = {
18783
18884
  } : {},
18784
18885
  patch: taskPatch(input)
18785
18886
  });
18887
+ if (result2 && input.lifecycleStage === "done" && result2.task.verificationReport) {
18888
+ recordKanbanVerificationEvidence(ctx, result2.task.verificationReport);
18889
+ }
18786
18890
  return result2 ? okTask(result2.board, result2.task, `Task advanced to ${result2.transition.to}.`) : fail("Board or task not found.");
18787
18891
  }
18788
18892
  case "move_task": {
@@ -18896,7 +19000,31 @@ var kanbanTool = {
18896
19000
  // is atomic inside updateTaskAssignment's mutateBoard lock.
18897
19001
  input.expectedLeaseId !== void 0 ? { expectedLeaseId: input.expectedLeaseId } : {}
18898
19002
  );
18899
- return board ? okBoard(board, "Assignment updated.") : fail("Task not found.");
19003
+ if (!board) return fail("Task not found.");
19004
+ if (assignmentStatus === "completed") {
19005
+ const envGate = readEnvGateEnforcement();
19006
+ const finalized = await finalizeTaskCompletion(projectRoot, board.id, input.taskId, {
19007
+ ...board.completionGate === void 0 && envGate !== void 0 ? { enforcement: envGate } : {},
19008
+ ...ctx.agentId !== void 0 ? { eventContext: { actor: ctx.agentId } } : {}
19009
+ });
19010
+ if (finalized) {
19011
+ if (finalized.gate.report) {
19012
+ recordKanbanVerificationEvidence(ctx, finalized.gate.report);
19013
+ }
19014
+ const gateSummary = {
19015
+ enforcement: finalized.gate.enforcement,
19016
+ allowed: finalized.gate.allowed,
19017
+ verdict: finalized.gate.verdict,
19018
+ issues: finalized.gate.issues.map((issue) => issue.message)
19019
+ };
19020
+ const gateMessage = finalized.gate.allowed ? `Completion gate ${finalized.gate.verdict === "skipped" ? "skipped" : "passed"}; task completed.` : finalized.gate.enforcement === "strict" ? `Completion gate BLOCKED (verdict: ${finalized.gate.verdict}); task parked in review. Issues: ${gateSummary.issues.join(" | ")}` : `Completion gate failed softly (verdict: ${finalized.gate.verdict}); task completed with warnings. Issues: ${gateSummary.issues.join(" | ")}`;
19021
+ return {
19022
+ ...okTask(finalized.board, finalized.task, `Assignment updated. ${gateMessage}`),
19023
+ gate: gateSummary
19024
+ };
19025
+ }
19026
+ }
19027
+ return okBoard(board, "Assignment updated.");
18900
19028
  }
18901
19029
  case "heartbeat_assignment": {
18902
19030
  if (!input.boardId || !input.taskId) {
@@ -19060,6 +19188,49 @@ var kanbanTool = {
19060
19188
  });
19061
19189
  return board ? okBoard(board, "Link added.") : fail("Task not found.");
19062
19190
  }
19191
+ case "assess_atomicity": {
19192
+ if (!input.boardId || !input.taskId) {
19193
+ return fail("assess_atomicity requires boardId and taskId.");
19194
+ }
19195
+ const result2 = await assessTaskAtomicity(projectRoot, input.boardId, input.taskId, {
19196
+ assessedBy: "agent",
19197
+ ...ctx.agentId !== void 0 ? { eventContext: { actor: ctx.agentId } } : {}
19198
+ });
19199
+ if (!result2) return fail("Task not found.");
19200
+ const failing = result2.assessment.criteria.filter((entry) => entry.score < 1).map((entry) => entry.reason);
19201
+ const guidance = result2.assessment.verdict === "needs_decomposition" ? ` This task should be split before dispatch \u2014 call propose_decomposition with 2+ subtasks (each with one verifiable success criterion). Reasons: ${failing.join(" | ")}` : result2.assessment.verdict === "composite" ? " Container task: work happens in its children; it is verified via subtask aggregation." : "";
19202
+ return okTask(
19203
+ result2.board,
19204
+ result2.task,
19205
+ `Atomicity verdict: ${result2.assessment.verdict} (score ${result2.assessment.score}).${guidance}`
19206
+ );
19207
+ }
19208
+ case "propose_decomposition": {
19209
+ if (!input.boardId || !input.taskId || !input.subtasks?.length) {
19210
+ return fail("propose_decomposition requires boardId, taskId, and subtasks (2+).");
19211
+ }
19212
+ if (input.subtasks.length < 2) {
19213
+ return fail("propose_decomposition requires at least two subtasks.");
19214
+ }
19215
+ const invalid = input.subtasks.find(
19216
+ (subtask) => typeof subtask?.title !== "string" || !subtask.title.trim()
19217
+ );
19218
+ if (invalid) return fail("Every proposed subtask needs a non-blank title.");
19219
+ const result2 = await proposeTaskDecomposition(
19220
+ projectRoot,
19221
+ input.boardId,
19222
+ input.taskId,
19223
+ {
19224
+ subtasks: input.subtasks,
19225
+ ...input.note !== void 0 ? { rationale: input.note } : {},
19226
+ ...ctx.agentId !== void 0 ? { proposedBy: ctx.agentId } : {}
19227
+ },
19228
+ ctx.agentId !== void 0 ? { actor: ctx.agentId } : {}
19229
+ );
19230
+ if (!result2) return fail("Task not found.");
19231
+ const message = result2.proposal.status === "applied" ? `Decomposition applied: ${result2.proposal.appliedChildTaskIds?.length ?? 0} child tasks created (parent marked atomic).` : 'Decomposition proposal recorded \u2014 awaiting approval (board policy is "propose"). It can be approved from the WebUI or via update_task.';
19232
+ return okTask(result2.board, result2.task, message);
19233
+ }
19063
19234
  case "verify_completion": {
19064
19235
  if (!input.boardId || !input.taskId) {
19065
19236
  return fail("verify_completion requires boardId and taskId.");
@@ -19077,6 +19248,7 @@ var kanbanTool = {
19077
19248
  board: verResult.board
19078
19249
  };
19079
19250
  }
19251
+ recordKanbanVerificationEvidence(ctx, verResult.report);
19080
19252
  const freshTask = persistedBoard.tasks?.find((t) => t.id === input.taskId);
19081
19253
  const deterministicVerdicts = ["passed", "failed", "needs_human", "incomplete"];
19082
19254
  return {
@@ -19103,6 +19275,15 @@ var kanbanTool = {
19103
19275
  }
19104
19276
  }
19105
19277
  };
19278
+ function atomicityNudge(task) {
19279
+ if (task.atomicityAssessment?.verdict !== "needs_decomposition") return "";
19280
+ const reasons = task.atomicityAssessment.criteria.filter((entry) => entry.score < 1).map((entry) => entry.reason).join(" | ");
19281
+ return ` Atomicity: needs_decomposition (score ${task.atomicityAssessment.score}) \u2014 call propose_decomposition with 2+ subtasks before dispatch. Reasons: ${reasons}`;
19282
+ }
19283
+ function readEnvGateEnforcement() {
19284
+ const raw = process.env["WRONGSTACK_KANBAN_GATE"]?.trim().toLowerCase();
19285
+ return raw === "strict" || raw === "soft" || raw === "off" ? raw : void 0;
19286
+ }
19106
19287
  function fail(message) {
19107
19288
  return { ok: false, message };
19108
19289
  }
@@ -22324,22 +22505,10 @@ var toolUseTool = {
22324
22505
 
22325
22506
  // src/tree.ts
22326
22507
  init_util();
22327
- import { expectDefined as expectDefined10 } from "@wrongstack/core/utils";
22508
+ import { DEFAULT_WALK_IGNORE_DIRS as DEFAULT_WALK_IGNORE_DIRS4, expectDefined as expectDefined10 } from "@wrongstack/core/utils";
22328
22509
  import * as fs28 from "node:fs/promises";
22329
22510
  import * as path32 from "node:path";
22330
- var DEFAULT_IGNORE5 = [
22331
- "node_modules",
22332
- ".git",
22333
- "dist",
22334
- "build",
22335
- ".next",
22336
- "coverage",
22337
- "__pycache__",
22338
- ".wrongstack",
22339
- ".ssh",
22340
- ".gnupg",
22341
- ".aws"
22342
- ];
22511
+ var DEFAULT_IGNORE5 = [...DEFAULT_WALK_IGNORE_DIRS4, ".wrongstack", ".ssh", ".gnupg", ".aws"];
22343
22512
  var treeTool = {
22344
22513
  name: "tree",
22345
22514
  category: "Filesystem",
@@ -24208,6 +24377,8 @@ export {
24208
24377
  isIndexableFile,
24209
24378
  isIndexing,
24210
24379
  jsonTool,
24380
+ kanbanEvidenceKey,
24381
+ kanbanEvidencePointer,
24211
24382
  kanbanTool,
24212
24383
  languageInfoTool,
24213
24384
  languagePackageTool,
@@ -24234,6 +24405,7 @@ export {
24234
24405
  projectSessionTasksToKanban,
24235
24406
  projectSessionTodosToKanban,
24236
24407
  readTool,
24408
+ recordKanbanVerificationEvidence,
24237
24409
  redactBrowserText,
24238
24410
  registerBuiltinToolTier,
24239
24411
  relatedMemoryTool,