@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/builtin.js CHANGED
@@ -5067,6 +5067,7 @@ function emitStructuredLog(level, event, message, error) {
5067
5067
  }
5068
5068
  var HEARTBEAT_INTERVAL_MS = 5e3;
5069
5069
  var STALE_THRESHOLD_MS = 3e4;
5070
+ var LOCK_STALE_MS = 3e4;
5070
5071
  var LOCKFILE = ".process-registry.lock";
5071
5072
  function generateInstanceId() {
5072
5073
  const hostname2 = os2.hostname();
@@ -5095,21 +5096,26 @@ async function acquireLock(lockfilePath, timeoutMs = 5e3) {
5095
5096
  try {
5096
5097
  const content = await fs6.readFile(lockfilePath, "utf-8");
5097
5098
  const parts = content.split(":");
5098
- const lockPidStr = parts[0] ?? "0";
5099
- const lockPid = parseInt(lockPidStr, 10);
5100
- if (process.platform !== "win32") {
5099
+ const lockPid = parseInt(parts[0] ?? "0", 10);
5100
+ const lockTs = Number(parts[parts.length - 1]);
5101
+ const staleByAge = Number.isFinite(lockTs) && Date.now() - lockTs > LOCK_STALE_MS;
5102
+ let holderDead = false;
5103
+ if (process.platform !== "win32" && Number.isFinite(lockPid) && lockPid > 0) {
5101
5104
  try {
5102
5105
  process.kill(lockPid, 0);
5103
5106
  } catch {
5104
- await fs6.unlink(lockfilePath);
5105
- continue;
5107
+ holderDead = true;
5106
5108
  }
5107
5109
  }
5108
- } catch {
5109
- try {
5110
- await fs6.unlink(lockfilePath);
5111
- } catch {
5110
+ if (holderDead || staleByAge) {
5111
+ await fs6.unlink(lockfilePath).catch(() => {
5112
+ });
5113
+ continue;
5112
5114
  }
5115
+ } catch {
5116
+ await fs6.unlink(lockfilePath).catch(() => {
5117
+ });
5118
+ continue;
5113
5119
  }
5114
5120
  await new Promise((r) => setTimeout(r, 100));
5115
5121
  continue;
@@ -5119,25 +5125,35 @@ async function acquireLock(lockfilePath, timeoutMs = 5e3) {
5119
5125
  }
5120
5126
  throw new Error(`Failed to acquire lock after ${timeoutMs}ms`);
5121
5127
  }
5128
+ function freshRegistryData() {
5129
+ return {
5130
+ version: 1,
5131
+ instances: /* @__PURE__ */ new Map(),
5132
+ protectedPatterns: ["wrongstack", "node"],
5133
+ lastCleanup: Date.now()
5134
+ };
5135
+ }
5122
5136
  async function readRegistryFile(filePath) {
5137
+ let content;
5123
5138
  try {
5124
- const content = await fs6.readFile(filePath, "utf-8");
5125
- const parsed = JSON.parse(content);
5126
- if (parsed.instances && Array.isArray(parsed.instances)) {
5127
- parsed.instances = new Map(parsed.instances);
5128
- }
5129
- return parsed;
5139
+ content = await fs6.readFile(filePath, "utf-8");
5130
5140
  } catch (err) {
5131
- if (isNodeError(err) && err.code === "ENOENT") {
5132
- return {
5133
- version: 1,
5134
- instances: /* @__PURE__ */ new Map(),
5135
- protectedPatterns: ["wrongstack", "node"],
5136
- lastCleanup: Date.now()
5137
- };
5138
- }
5141
+ if (isNodeError(err) && err.code === "ENOENT") return freshRegistryData();
5139
5142
  throw err;
5140
5143
  }
5144
+ try {
5145
+ const parsed = JSON.parse(content);
5146
+ if (!parsed || typeof parsed !== "object") return freshRegistryData();
5147
+ const base = freshRegistryData();
5148
+ return {
5149
+ version: 1,
5150
+ instances: Array.isArray(parsed.instances) ? new Map(parsed.instances) : base.instances,
5151
+ protectedPatterns: Array.isArray(parsed.protectedPatterns) ? parsed.protectedPatterns : base.protectedPatterns,
5152
+ lastCleanup: typeof parsed.lastCleanup === "number" ? parsed.lastCleanup : base.lastCleanup
5153
+ };
5154
+ } catch {
5155
+ return freshRegistryData();
5156
+ }
5141
5157
  }
5142
5158
  async function writeRegistryFile(filePath, data) {
5143
5159
  const tmpPath = `${filePath}.tmp.${process.pid}`;
@@ -9130,11 +9146,12 @@ var IndexStore = class _IndexStore {
9130
9146
  }
9131
9147
  }
9132
9148
  const refRows = this.stmt(
9133
- `SELECT r.call_type, sf.file AS from_file, st.file AS to_file
9149
+ `SELECT r.call_type, sf.file AS from_file, st.file AS to_file, COUNT(*) AS n
9134
9150
  FROM refs r
9135
9151
  JOIN symbols sf ON sf.id = r.from_id
9136
9152
  JOIN symbols st ON st.id = r.to_id
9137
- WHERE r.to_id IS NOT NULL AND r.call_type != 'import'`
9153
+ WHERE r.to_id IS NOT NULL AND r.call_type != 'import'
9154
+ GROUP BY r.call_type, sf.file, st.file`
9138
9155
  ).all();
9139
9156
  const edgeMap = /* @__PURE__ */ new Map();
9140
9157
  for (const r of refRows) {
@@ -9147,14 +9164,16 @@ var IndexStore = class _IndexStore {
9147
9164
  e = { weight: 0, types: /* @__PURE__ */ new Map() };
9148
9165
  edgeMap.set(key, e);
9149
9166
  }
9150
- e.weight++;
9151
- e.types.set(r.call_type, (e.types.get(r.call_type) ?? 0) + 1);
9167
+ const n = Number(r.n) || 0;
9168
+ e.weight += n;
9169
+ e.types.set(r.call_type, (e.types.get(r.call_type) ?? 0) + n);
9152
9170
  }
9153
9171
  const importRows = this.stmt(
9154
- `SELECT r.to_name, s.file AS from_file
9172
+ `SELECT r.to_name, s.file AS from_file, COUNT(*) AS n
9155
9173
  FROM refs r
9156
9174
  JOIN symbols s ON s.id = r.from_id
9157
- WHERE r.call_type = 'import'`
9175
+ WHERE r.call_type = 'import'
9176
+ GROUP BY r.to_name, s.file`
9158
9177
  ).all();
9159
9178
  for (const r of importRows) {
9160
9179
  const fromPkg = fileToPkg.get(r.from_file) ?? _IndexStore.derivePackage(r.from_file) ?? "(root)";
@@ -9166,8 +9185,9 @@ var IndexStore = class _IndexStore {
9166
9185
  edge = { weight: 0, types: /* @__PURE__ */ new Map() };
9167
9186
  edgeMap.set(key, edge);
9168
9187
  }
9169
- edge.weight++;
9170
- edge.types.set("import", (edge.types.get("import") ?? 0) + 1);
9188
+ const n = Number(r.n) || 0;
9189
+ edge.weight += n;
9190
+ edge.types.set("import", (edge.types.get("import") ?? 0) + n);
9171
9191
  }
9172
9192
  const edges = [];
9173
9193
  for (const [key, e] of edgeMap) {
@@ -9232,11 +9252,12 @@ var IndexStore = class _IndexStore {
9232
9252
  }
9233
9253
  const indexedFiles = new Set(allFiles.map((f) => f.file));
9234
9254
  const refRows = this.stmt(
9235
- `SELECT r.from_id, r.to_id, r.call_type
9255
+ `SELECT r.from_id, r.to_id, r.call_type, COUNT(*) AS n
9236
9256
  FROM refs r
9237
9257
  WHERE (r.from_id IN (SELECT id FROM symbols WHERE file IN (${filePlaceholders}))
9238
9258
  OR r.to_id IN (SELECT id FROM symbols WHERE file IN (${filePlaceholders})))
9239
- AND r.to_id IS NOT NULL`
9259
+ AND r.to_id IS NOT NULL
9260
+ GROUP BY r.from_id, r.to_id, r.call_type`
9240
9261
  ).all(...pkgFilePaths, ...pkgFilePaths);
9241
9262
  const knownSymIds = new Set(pkgSyms.map((s) => s.id));
9242
9263
  const crossRefIds = /* @__PURE__ */ new Set();
@@ -9271,14 +9292,16 @@ var IndexStore = class _IndexStore {
9271
9292
  e = { weight: 0, types: /* @__PURE__ */ new Map() };
9272
9293
  edgeMap.set(key, e);
9273
9294
  }
9274
- e.weight++;
9275
- e.types.set(r.call_type, (e.types.get(r.call_type) ?? 0) + 1);
9295
+ const n = Number(r.n) || 0;
9296
+ e.weight += n;
9297
+ e.types.set(r.call_type, (e.types.get(r.call_type) ?? 0) + n);
9276
9298
  }
9277
9299
  const importRows = this.stmt(
9278
- `SELECT r.from_id, r.to_name
9300
+ `SELECT r.from_id, r.to_name, COUNT(*) AS n
9279
9301
  FROM refs r
9280
9302
  WHERE r.call_type = 'import'
9281
- AND r.from_id IN (SELECT id FROM symbols WHERE file IN (${filePlaceholders}))`
9303
+ AND r.from_id IN (SELECT id FROM symbols WHERE file IN (${filePlaceholders}))
9304
+ GROUP BY r.from_id, r.to_name`
9282
9305
  ).all(...pkgFilePaths);
9283
9306
  for (const r of importRows) {
9284
9307
  const fromFile = symToFile.get(r.from_id);
@@ -9293,8 +9316,9 @@ var IndexStore = class _IndexStore {
9293
9316
  edge = { weight: 0, types: /* @__PURE__ */ new Map() };
9294
9317
  edgeMap.set(key, edge);
9295
9318
  }
9296
- edge.weight++;
9297
- edge.types.set("import", (edge.types.get("import") ?? 0) + 1);
9319
+ const n = Number(r.n) || 0;
9320
+ edge.weight += n;
9321
+ edge.types.set("import", (edge.types.get("import") ?? 0) + n);
9298
9322
  }
9299
9323
  const edges = [];
9300
9324
  for (const [key, e] of edgeMap) {
@@ -9341,22 +9365,25 @@ var IndexStore = class _IndexStore {
9341
9365
  scope: s.scope,
9342
9366
  external: s.file !== fileFilter
9343
9367
  });
9344
- const symIds = new Set(syms.map((s) => s.id));
9345
9368
  const refRows = this.stmt(
9346
- `SELECT r.from_id, r.to_id, r.to_name, r.call_type, r.line
9347
- FROM refs r
9348
- JOIN symbols s ON s.id = r.from_id
9349
- WHERE s.file = ?
9350
- UNION
9351
- SELECT r.from_id, r.to_id, r.to_name, r.call_type, r.line
9352
- FROM refs r
9353
- JOIN symbols s ON s.id = r.to_id
9354
- WHERE s.file = ?`
9369
+ `SELECT from_id, to_id, call_type, COUNT(*) AS n
9370
+ FROM (
9371
+ SELECT r.from_id, r.to_id, r.to_name, r.call_type, r.line
9372
+ FROM refs r
9373
+ JOIN symbols s ON s.id = r.from_id
9374
+ WHERE s.file = ?
9375
+ UNION
9376
+ SELECT r.from_id, r.to_id, r.to_name, r.call_type, r.line
9377
+ FROM refs r
9378
+ JOIN symbols s ON s.id = r.to_id
9379
+ WHERE s.file = ?
9380
+ )
9381
+ WHERE to_id IS NOT NULL
9382
+ GROUP BY from_id, to_id, call_type`
9355
9383
  ).all(fileFilter, fileFilter);
9356
9384
  const edgeMap = /* @__PURE__ */ new Map();
9357
9385
  for (const r of refRows) {
9358
- if (r.to_id === null) continue;
9359
- if (!symIds.has(r.from_id) && !symIds.has(r.to_id)) continue;
9386
+ if (r.to_id == null) continue;
9360
9387
  relatedIds.add(r.from_id);
9361
9388
  relatedIds.add(r.to_id);
9362
9389
  const key = `${r.from_id}\0${r.to_id}`;
@@ -9365,8 +9392,9 @@ var IndexStore = class _IndexStore {
9365
9392
  e = { weight: 0, types: /* @__PURE__ */ new Map() };
9366
9393
  edgeMap.set(key, e);
9367
9394
  }
9368
- e.weight++;
9369
- e.types.set(r.call_type, (e.types.get(r.call_type) ?? 0) + 1);
9395
+ const n = Number(r.n) || 0;
9396
+ e.weight += n;
9397
+ e.types.set(r.call_type, (e.types.get(r.call_type) ?? 0) + n);
9370
9398
  }
9371
9399
  const edges = [];
9372
9400
  for (const [key, e] of edgeMap) {
@@ -9423,7 +9451,7 @@ import { expectDefined as expectDefined6 } from "@wrongstack/core/utils";
9423
9451
  import * as fs14 from "node:fs/promises";
9424
9452
  import * as path20 from "node:path";
9425
9453
  import { availableParallelism } from "node:os";
9426
- import { indexParallelBatchSize, isFrugalPerf } from "@wrongstack/core/utils";
9454
+ import { DEFAULT_WALK_IGNORE_DIRS, indexParallelBatchSize, isFrugalPerf } from "@wrongstack/core/utils";
9427
9455
 
9428
9456
  // src/codebase-index/ts-parser.ts
9429
9457
  import * as ts from "@typescript/typescript6";
@@ -10078,7 +10106,7 @@ import * as path16 from "node:path";
10078
10106
  var C_LIKE = [
10079
10107
  { re: /\b(?:class|struct|enum|interface|union)\s+([A-Za-z_]\w*)/g, kind: "class" },
10080
10108
  {
10081
- 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,
10109
+ 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,
10082
10110
  kind: "function"
10083
10111
  },
10084
10112
  { re: /\b(?:namespace)\s+([A-Za-z_]\w*)/g, kind: "namespace" }
@@ -10109,7 +10137,7 @@ var LANG_PATTERNS = {
10109
10137
  java: [
10110
10138
  { re: /\b(?:class|interface|enum|record)\s+([A-Za-z_]\w*)/g, kind: "class" },
10111
10139
  {
10112
- re: /\b(?:public|private|protected|static|final|abstract|synchronized|native|default|\s)+\s*[\w.<>,\[\]\s]+\s+([A-Za-z_]\w*)\s*\(/g,
10140
+ re: /\b(?:public|private|protected|static|final|abstract|synchronized|native|default|\s)+\s*[\w.<>,[\]\s]+\s+([A-Za-z_]\w*)\s*\(/g,
10113
10141
  kind: "method"
10114
10142
  }
10115
10143
  ],
@@ -10117,7 +10145,7 @@ var LANG_PATTERNS = {
10117
10145
  { re: /\b(?:class|interface|struct|enum|record)\s+([A-Za-z_]\w*)/g, kind: "class" },
10118
10146
  { re: /\bnamespace\s+([A-Za-z_.\w]+)/g, kind: "namespace" },
10119
10147
  {
10120
- re: /\b(?:public|private|protected|internal|static|async|override|virtual|\s)+\s*[\w.<>,\[\]\s]+\s+([A-Za-z_]\w*)\s*\(/g,
10148
+ re: /\b(?:public|private|protected|internal|static|async|override|virtual|\s)+\s*[\w.<>,[\]\s]+\s+([A-Za-z_]\w*)\s*\(/g,
10121
10149
  kind: "method"
10122
10150
  }
10123
10151
  ],
@@ -10322,8 +10350,7 @@ function parseGeneric2(opts) {
10322
10350
  for (const pattern of patterns) {
10323
10351
  const re = new RegExp(pattern.re.source, pattern.re.flags.includes("g") ? pattern.re.flags : `${pattern.re.flags}g`);
10324
10352
  re.lastIndex = 0;
10325
- let match;
10326
- while ((match = re.exec(content)) !== null) {
10353
+ for (const match of content.matchAll(re)) {
10327
10354
  if (symbols.length >= maxSymbols) break;
10328
10355
  let name = (match[1] ?? match[2] ?? "").trim();
10329
10356
  if (lang === "md" && match[2]) name = match[2].trim();
@@ -11279,17 +11306,7 @@ function throwIfAborted(signal) {
11279
11306
  function isAbortError(err) {
11280
11307
  return err instanceof DOMException && err.name === "AbortError";
11281
11308
  }
11282
- var DEFAULT_IGNORE = [
11283
- "node_modules",
11284
- ".git",
11285
- "dist",
11286
- "build",
11287
- ".next",
11288
- "coverage",
11289
- ".turbo",
11290
- "__snapshots__",
11291
- ".nyc_output"
11292
- ];
11309
+ var DEFAULT_IGNORE = DEFAULT_WALK_IGNORE_DIRS;
11293
11310
  var DEFAULT_IGNORE_FILES = /* @__PURE__ */ new Set(["package-lock.json", "pnpm-lock.yaml", "pnpm-lock.yml"]);
11294
11311
  var MAX_INDEX_FILE_BYTES = 5 * 1024 * 1024;
11295
11312
  function isWithinProject(projectRoot, file) {
@@ -16053,7 +16070,7 @@ function runGit2(args, cwd, signal) {
16053
16070
  // src/glob.ts
16054
16071
  import * as fs20 from "node:fs/promises";
16055
16072
  import * as path26 from "node:path";
16056
- import { compileGlob as compileGlob2 } from "@wrongstack/core/utils";
16073
+ import { compileGlob as compileGlob2, DEFAULT_WALK_IGNORE_DIRS as DEFAULT_WALK_IGNORE_DIRS2 } from "@wrongstack/core/utils";
16057
16074
 
16058
16075
  // src/_concurrency.ts
16059
16076
  async function mapWithConcurrency(items, limit, fn) {
@@ -16074,7 +16091,7 @@ async function mapWithConcurrency(items, limit, fn) {
16074
16091
 
16075
16092
  // src/glob.ts
16076
16093
  init_util();
16077
- var DEFAULT_IGNORE2 = ["node_modules", ".git", "dist", "build", ".next", "coverage", ".turbo"];
16094
+ var DEFAULT_IGNORE2 = DEFAULT_WALK_IGNORE_DIRS2;
16078
16095
  var WALK_CONCURRENCY = 16;
16079
16096
  var globTool = {
16080
16097
  name: "glob",
@@ -16114,7 +16131,7 @@ var globTool = {
16114
16131
  const signal = opts?.signal;
16115
16132
  const base = input.path ? await safeResolveReal(input.path, ctx) : ctx.cwd;
16116
16133
  const limit = Math.max(1, Math.min(input.limit ?? 1e3, 5e3));
16117
- const ignored = await readGitignore(base);
16134
+ const isGitIgnored = await loadGitignoreMatcher(base);
16118
16135
  const re = compileGlob2(input.pattern);
16119
16136
  const results = [];
16120
16137
  let truncated = false;
@@ -16154,12 +16171,13 @@ var globTool = {
16154
16171
  for (const e of entries) {
16155
16172
  const name = e.name;
16156
16173
  if (DEFAULT_IGNORE2.includes(name)) continue;
16157
- if (ignored.includes(name)) continue;
16158
16174
  const rel = relPrefix ? `${relPrefix}/${name}` : name;
16159
16175
  const full = path26.join(dir, name);
16160
16176
  if (e.isDirectory()) {
16177
+ if (isGitIgnored(rel, true)) continue;
16161
16178
  subdirs.push({ full, rel });
16162
16179
  } else if (e.isFile()) {
16180
+ if (isGitIgnored(rel, false)) continue;
16163
16181
  re.lastIndex = 0;
16164
16182
  const relMatch = re.test(rel);
16165
16183
  re.lastIndex = 0;
@@ -16171,10 +16189,12 @@ var globTool = {
16171
16189
  try {
16172
16190
  const st = await fs20.stat(full);
16173
16191
  if (st.isDirectory()) {
16192
+ if (isGitIgnored(rel, true)) continue;
16174
16193
  const real = await fs20.realpath(full);
16175
16194
  await assertRealInsideRoot(real, ctx);
16176
16195
  subdirs.push({ full, rel });
16177
16196
  } else if (st.isFile()) {
16197
+ if (isGitIgnored(rel, false)) continue;
16178
16198
  const real = await fs20.realpath(full);
16179
16199
  await assertRealInsideRoot(real, ctx);
16180
16200
  re.lastIndex = 0;
@@ -16198,14 +16218,6 @@ var globTool = {
16198
16218
  return { files: results.map((r) => r.rel), truncated };
16199
16219
  }
16200
16220
  };
16201
- async function readGitignore(dir) {
16202
- try {
16203
- const raw = await fs20.readFile(path26.join(dir, ".gitignore"), "utf8");
16204
- return raw.split("\n").map((l) => l.trim()).filter((l) => l && !l.startsWith("#"));
16205
- } catch {
16206
- return [];
16207
- }
16208
- }
16209
16221
 
16210
16222
  // src/grep.ts
16211
16223
  import { expectDefined as expectDefined7 } from "@wrongstack/core/utils";
@@ -16213,7 +16225,7 @@ import { spawn as spawn10 } from "node:child_process";
16213
16225
  import * as fs21 from "node:fs/promises";
16214
16226
  import * as path27 from "node:path";
16215
16227
  import { ToolValidationError as ToolValidationError4 } from "@wrongstack/core/types";
16216
- import { buildChildEnv as buildChildEnv5, compileGlob as compileGlob3 } from "@wrongstack/core/utils";
16228
+ import { buildChildEnv as buildChildEnv5, compileGlob as compileGlob3, DEFAULT_WALK_IGNORE_DIRS as DEFAULT_WALK_IGNORE_DIRS3 } from "@wrongstack/core/utils";
16217
16229
 
16218
16230
  // src/_regex.ts
16219
16231
  var MAX_PATTERN_LEN = 256;
@@ -16262,7 +16274,7 @@ function capSubject(line) {
16262
16274
 
16263
16275
  // src/grep.ts
16264
16276
  init_util();
16265
- var DEFAULT_IGNORE3 = ["node_modules", ".git", "dist", "build", ".next", "coverage"];
16277
+ var DEFAULT_IGNORE3 = DEFAULT_WALK_IGNORE_DIRS3;
16266
16278
  var NATIVE_SCAN_CONCURRENCY = 32;
16267
16279
  var NATIVE_READ_CHUNK_BYTES = 64 * 1024;
16268
16280
  var NATIVE_MAX_FILE_BYTES = 1e6;
@@ -16379,6 +16391,10 @@ async function* runRgStream(input, base, mode, limit, signal) {
16379
16391
  for (const ignored of DEFAULT_IGNORE3) {
16380
16392
  args.push("--glob", `!${ignored}/**`, "--glob", `!**/${ignored}/**`);
16381
16393
  }
16394
+ const gitignorePath = path27.join(base, ".gitignore");
16395
+ if (await fs21.access(gitignorePath).then(() => true, () => false)) {
16396
+ args.push("--ignore-file", gitignorePath);
16397
+ }
16382
16398
  if (input.glob) args.push("--glob", input.glob);
16383
16399
  args.push("--", input.pattern, base);
16384
16400
  const matches = [];
@@ -16504,6 +16520,7 @@ async function runNative(input, base, mode, limit, signal) {
16504
16520
  }
16505
16521
  const re = compiled.regex;
16506
16522
  const globRe = input.glob ? compileGlob3(input.glob) : null;
16523
+ const isGitIgnored = await loadGitignoreMatcher(base);
16507
16524
  const matches = [];
16508
16525
  const countOnlyFirstHit = mode === "count" && limit === 1;
16509
16526
  const maxBytes = mode === "content" ? NATIVE_MAX_FILE_BYTES : Math.min(NATIVE_MAX_FILE_BYTES, 256 * 1024);
@@ -16591,7 +16608,7 @@ async function runNative(input, base, mode, limit, signal) {
16591
16608
  } catch {
16592
16609
  }
16593
16610
  };
16594
- const walk = async (dir) => {
16611
+ const walk = async (dir, relPrefix) => {
16595
16612
  if (stopped || signal.aborted) return;
16596
16613
  let entries;
16597
16614
  try {
@@ -16605,17 +16622,24 @@ async function runNative(input, base, mode, limit, signal) {
16605
16622
  if (stopped) return;
16606
16623
  if (DEFAULT_IGNORE3.includes(e.name)) continue;
16607
16624
  if (e.isSymbolicLink()) continue;
16625
+ const rel = relPrefix ? `${relPrefix}/${e.name}` : e.name;
16608
16626
  const full = path27.join(dir, e.name);
16609
16627
  if (e.isDirectory()) {
16610
- subdirs.push(full);
16628
+ if (isGitIgnored(rel, true)) continue;
16629
+ subdirs.push({ full, rel });
16611
16630
  } else if (e.isFile()) {
16631
+ if (isGitIgnored(rel, false)) continue;
16612
16632
  files.push({ full, name: e.name });
16613
16633
  }
16614
16634
  }
16615
16635
  await mapWithConcurrency(files, NATIVE_SCAN_CONCURRENCY, ({ full, name }) => scanFile(full, name));
16616
- await mapWithConcurrency(subdirs, Math.min(16, NATIVE_SCAN_CONCURRENCY), walk);
16636
+ await mapWithConcurrency(
16637
+ subdirs,
16638
+ Math.min(16, NATIVE_SCAN_CONCURRENCY),
16639
+ ({ full, rel }) => walk(full, rel)
16640
+ );
16617
16641
  };
16618
- await walk(base);
16642
+ await walk(base, "");
16619
16643
  return {
16620
16644
  matches,
16621
16645
  count: total,
@@ -17345,9 +17369,35 @@ import {
17345
17369
  updateGoalMetricOnTask,
17346
17370
  updateTask,
17347
17371
  updateTaskAssignment,
17348
- verifyTaskCompletion
17372
+ verifyTaskCompletion,
17373
+ finalizeTaskCompletion,
17374
+ assessTaskAtomicity,
17375
+ proposeTaskDecomposition
17349
17376
  } from "@wrongstack/kanban";
17350
17377
 
17378
+ // src/kanban-evidence-bridge.ts
17379
+ import { recordCompletedWorkEvidence } from "@wrongstack/core/utils";
17380
+ function kanbanEvidenceKey(boardId, taskId) {
17381
+ return `kanban:${boardId}:${taskId}`;
17382
+ }
17383
+ function kanbanEvidencePointer(boardId, taskId) {
17384
+ return `kanban://${boardId}/${taskId}#verificationReport`;
17385
+ }
17386
+ function recordKanbanVerificationEvidence(ctx, report) {
17387
+ try {
17388
+ const passed = report.checks.filter((check) => check.status === "passed").length;
17389
+ const completedAt = Date.parse(report.completedAt);
17390
+ recordCompletedWorkEvidence(ctx, {
17391
+ key: kanbanEvidenceKey(report.boardId, report.taskId),
17392
+ source: "verification",
17393
+ summary: `${report.taskTitle} \u2014 verification ${report.verdict} (${passed}/${report.checks.length} checks)`,
17394
+ ...Number.isFinite(completedAt) ? { completedAt } : {},
17395
+ evidence: kanbanEvidencePointer(report.boardId, report.taskId)
17396
+ });
17397
+ } catch {
17398
+ }
17399
+ }
17400
+
17351
17401
  // src/session-kanban.ts
17352
17402
  import { GlobalMailbox } from "@wrongstack/core/coordination";
17353
17403
  import {
@@ -17636,7 +17686,9 @@ var kanbanTool = {
17636
17686
  "add_note",
17637
17687
  "add_link",
17638
17688
  "verify_completion",
17639
- "split_atomic"
17689
+ "split_atomic",
17690
+ "assess_atomicity",
17691
+ "propose_decomposition"
17640
17692
  ]
17641
17693
  },
17642
17694
  boardId: { type: "string" },
@@ -17772,7 +17824,24 @@ var kanbanTool = {
17772
17824
  includeCompletedTasks: { type: "boolean" },
17773
17825
  preserveAssignment: { type: "boolean" },
17774
17826
  preserveDependencies: { type: "boolean" },
17775
- moveTasksToColumnId: { type: "string" }
17827
+ moveTasksToColumnId: { type: "string" },
17828
+ atomicityMode: { type: "string", enum: ["off", "assess", "enforce"] },
17829
+ atomicityDecomposition: { type: "string", enum: ["auto", "propose"] },
17830
+ gateEnforcement: { type: "string", enum: ["strict", "soft", "off"] },
17831
+ subtasks: {
17832
+ type: "array",
17833
+ minItems: 2,
17834
+ items: {
17835
+ type: "object",
17836
+ properties: {
17837
+ title: { type: "string" },
17838
+ description: { type: "string" },
17839
+ successCriteria: { type: "array", items: { type: "string" } },
17840
+ dependsOnIndex: { type: "array", items: { type: "number" } }
17841
+ },
17842
+ required: ["title"]
17843
+ }
17844
+ }
17776
17845
  },
17777
17846
  required: ["action"]
17778
17847
  },
@@ -17812,7 +17881,14 @@ var kanbanTool = {
17812
17881
  title: input.title,
17813
17882
  ...input.description !== void 0 ? { description: input.description } : {},
17814
17883
  ...input.tags !== void 0 ? { tags: input.tags } : {},
17815
- ...input.generatedBy !== void 0 ? { generatedBy: input.generatedBy } : {}
17884
+ ...input.generatedBy !== void 0 ? { generatedBy: input.generatedBy } : {},
17885
+ ...input.atomicityMode !== void 0 ? {
17886
+ atomicity: {
17887
+ mode: input.atomicityMode,
17888
+ decomposition: input.atomicityDecomposition ?? "propose"
17889
+ }
17890
+ } : {},
17891
+ ...input.gateEnforcement !== void 0 ? { completionGate: { enforcement: input.gateEnforcement } } : {}
17816
17892
  });
17817
17893
  return { ok: true, message: `Board created: ${board.title}`, board };
17818
17894
  }
@@ -17821,7 +17897,14 @@ var kanbanTool = {
17821
17897
  const board = await updateBoard2(projectRoot, input.boardId, {
17822
17898
  ...input.title !== void 0 ? { title: input.title } : {},
17823
17899
  ...input.description !== void 0 ? { description: input.description } : {},
17824
- ...input.tags !== void 0 ? { tags: input.tags } : {}
17900
+ ...input.tags !== void 0 ? { tags: input.tags } : {},
17901
+ ...input.atomicityMode !== void 0 ? {
17902
+ atomicity: {
17903
+ mode: input.atomicityMode,
17904
+ decomposition: input.atomicityDecomposition ?? "propose"
17905
+ }
17906
+ } : {},
17907
+ ...input.gateEnforcement !== void 0 ? { completionGate: { enforcement: input.gateEnforcement } } : {}
17825
17908
  });
17826
17909
  return board ? okBoard(board, "Board updated.") : fail("Board not found.");
17827
17910
  }
@@ -18029,7 +18112,12 @@ var kanbanTool = {
18029
18112
  case "add_task": {
18030
18113
  if (!input.boardId || !input.title) return fail("add_task requires boardId and title.");
18031
18114
  const result2 = await addTask(projectRoot, input.boardId, taskInput(input));
18032
- return result2 ? okTask(result2.board, result2.task, "Task added.") : fail("Board not found.");
18115
+ if (!result2) return fail("Board not found.");
18116
+ return okTask(
18117
+ result2.board,
18118
+ result2.task,
18119
+ `Task added.${atomicityNudge(result2.task)}`
18120
+ );
18033
18121
  }
18034
18122
  case "split_task": {
18035
18123
  if (!input.boardId || !input.taskId || !input.childTitles?.length) {
@@ -18109,6 +18197,19 @@ var kanbanTool = {
18109
18197
  "transition_task requires boardId, taskId, lifecycleStage, author, and transitionComment."
18110
18198
  );
18111
18199
  }
18200
+ if (input.lifecycleStage === "done") {
18201
+ const boardBefore = await getBoard2(projectRoot, input.boardId);
18202
+ const taskBefore = boardBefore ? await getTask(projectRoot, input.boardId, input.taskId) : null;
18203
+ if (boardBefore && taskBefore && !taskBefore.verificationReport && (taskBefore.atomic || Boolean(taskBefore.successCriteria?.length))) {
18204
+ const preGate = await verifyTaskCompletion(projectRoot, input.boardId, taskBefore.id, {
18205
+ persist: false
18206
+ });
18207
+ await updateTask(projectRoot, input.boardId, taskBefore.id, {
18208
+ verificationReport: preGate.report,
18209
+ successCriteria: preGate.task.successCriteria
18210
+ });
18211
+ }
18212
+ }
18112
18213
  const result2 = await transitionTask(projectRoot, input.boardId, input.taskId, {
18113
18214
  to: input.lifecycleStage,
18114
18215
  actor: input.author,
@@ -18123,6 +18224,9 @@ var kanbanTool = {
18123
18224
  } : {},
18124
18225
  patch: taskPatch(input)
18125
18226
  });
18227
+ if (result2 && input.lifecycleStage === "done" && result2.task.verificationReport) {
18228
+ recordKanbanVerificationEvidence(ctx, result2.task.verificationReport);
18229
+ }
18126
18230
  return result2 ? okTask(result2.board, result2.task, `Task advanced to ${result2.transition.to}.`) : fail("Board or task not found.");
18127
18231
  }
18128
18232
  case "move_task": {
@@ -18236,7 +18340,31 @@ var kanbanTool = {
18236
18340
  // is atomic inside updateTaskAssignment's mutateBoard lock.
18237
18341
  input.expectedLeaseId !== void 0 ? { expectedLeaseId: input.expectedLeaseId } : {}
18238
18342
  );
18239
- return board ? okBoard(board, "Assignment updated.") : fail("Task not found.");
18343
+ if (!board) return fail("Task not found.");
18344
+ if (assignmentStatus === "completed") {
18345
+ const envGate = readEnvGateEnforcement();
18346
+ const finalized = await finalizeTaskCompletion(projectRoot, board.id, input.taskId, {
18347
+ ...board.completionGate === void 0 && envGate !== void 0 ? { enforcement: envGate } : {},
18348
+ ...ctx.agentId !== void 0 ? { eventContext: { actor: ctx.agentId } } : {}
18349
+ });
18350
+ if (finalized) {
18351
+ if (finalized.gate.report) {
18352
+ recordKanbanVerificationEvidence(ctx, finalized.gate.report);
18353
+ }
18354
+ const gateSummary = {
18355
+ enforcement: finalized.gate.enforcement,
18356
+ allowed: finalized.gate.allowed,
18357
+ verdict: finalized.gate.verdict,
18358
+ issues: finalized.gate.issues.map((issue) => issue.message)
18359
+ };
18360
+ 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(" | ")}`;
18361
+ return {
18362
+ ...okTask(finalized.board, finalized.task, `Assignment updated. ${gateMessage}`),
18363
+ gate: gateSummary
18364
+ };
18365
+ }
18366
+ }
18367
+ return okBoard(board, "Assignment updated.");
18240
18368
  }
18241
18369
  case "heartbeat_assignment": {
18242
18370
  if (!input.boardId || !input.taskId) {
@@ -18400,6 +18528,49 @@ var kanbanTool = {
18400
18528
  });
18401
18529
  return board ? okBoard(board, "Link added.") : fail("Task not found.");
18402
18530
  }
18531
+ case "assess_atomicity": {
18532
+ if (!input.boardId || !input.taskId) {
18533
+ return fail("assess_atomicity requires boardId and taskId.");
18534
+ }
18535
+ const result2 = await assessTaskAtomicity(projectRoot, input.boardId, input.taskId, {
18536
+ assessedBy: "agent",
18537
+ ...ctx.agentId !== void 0 ? { eventContext: { actor: ctx.agentId } } : {}
18538
+ });
18539
+ if (!result2) return fail("Task not found.");
18540
+ const failing = result2.assessment.criteria.filter((entry) => entry.score < 1).map((entry) => entry.reason);
18541
+ 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." : "";
18542
+ return okTask(
18543
+ result2.board,
18544
+ result2.task,
18545
+ `Atomicity verdict: ${result2.assessment.verdict} (score ${result2.assessment.score}).${guidance}`
18546
+ );
18547
+ }
18548
+ case "propose_decomposition": {
18549
+ if (!input.boardId || !input.taskId || !input.subtasks?.length) {
18550
+ return fail("propose_decomposition requires boardId, taskId, and subtasks (2+).");
18551
+ }
18552
+ if (input.subtasks.length < 2) {
18553
+ return fail("propose_decomposition requires at least two subtasks.");
18554
+ }
18555
+ const invalid = input.subtasks.find(
18556
+ (subtask) => typeof subtask?.title !== "string" || !subtask.title.trim()
18557
+ );
18558
+ if (invalid) return fail("Every proposed subtask needs a non-blank title.");
18559
+ const result2 = await proposeTaskDecomposition(
18560
+ projectRoot,
18561
+ input.boardId,
18562
+ input.taskId,
18563
+ {
18564
+ subtasks: input.subtasks,
18565
+ ...input.note !== void 0 ? { rationale: input.note } : {},
18566
+ ...ctx.agentId !== void 0 ? { proposedBy: ctx.agentId } : {}
18567
+ },
18568
+ ctx.agentId !== void 0 ? { actor: ctx.agentId } : {}
18569
+ );
18570
+ if (!result2) return fail("Task not found.");
18571
+ 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.';
18572
+ return okTask(result2.board, result2.task, message);
18573
+ }
18403
18574
  case "verify_completion": {
18404
18575
  if (!input.boardId || !input.taskId) {
18405
18576
  return fail("verify_completion requires boardId and taskId.");
@@ -18417,6 +18588,7 @@ var kanbanTool = {
18417
18588
  board: verResult.board
18418
18589
  };
18419
18590
  }
18591
+ recordKanbanVerificationEvidence(ctx, verResult.report);
18420
18592
  const freshTask = persistedBoard.tasks?.find((t) => t.id === input.taskId);
18421
18593
  const deterministicVerdicts = ["passed", "failed", "needs_human", "incomplete"];
18422
18594
  return {
@@ -18443,6 +18615,15 @@ var kanbanTool = {
18443
18615
  }
18444
18616
  }
18445
18617
  };
18618
+ function atomicityNudge(task) {
18619
+ if (task.atomicityAssessment?.verdict !== "needs_decomposition") return "";
18620
+ const reasons = task.atomicityAssessment.criteria.filter((entry) => entry.score < 1).map((entry) => entry.reason).join(" | ");
18621
+ return ` Atomicity: needs_decomposition (score ${task.atomicityAssessment.score}) \u2014 call propose_decomposition with 2+ subtasks before dispatch. Reasons: ${reasons}`;
18622
+ }
18623
+ function readEnvGateEnforcement() {
18624
+ const raw = process.env["WRONGSTACK_KANBAN_GATE"]?.trim().toLowerCase();
18625
+ return raw === "strict" || raw === "soft" || raw === "off" ? raw : void 0;
18626
+ }
18446
18627
  function fail(message) {
18447
18628
  return { ok: false, message };
18448
18629
  }
@@ -21664,22 +21845,10 @@ var toolUseTool = {
21664
21845
 
21665
21846
  // src/tree.ts
21666
21847
  init_util();
21667
- import { expectDefined as expectDefined10 } from "@wrongstack/core/utils";
21848
+ import { DEFAULT_WALK_IGNORE_DIRS as DEFAULT_WALK_IGNORE_DIRS4, expectDefined as expectDefined10 } from "@wrongstack/core/utils";
21668
21849
  import * as fs28 from "node:fs/promises";
21669
21850
  import * as path32 from "node:path";
21670
- var DEFAULT_IGNORE5 = [
21671
- "node_modules",
21672
- ".git",
21673
- "dist",
21674
- "build",
21675
- ".next",
21676
- "coverage",
21677
- "__pycache__",
21678
- ".wrongstack",
21679
- ".ssh",
21680
- ".gnupg",
21681
- ".aws"
21682
- ];
21851
+ var DEFAULT_IGNORE5 = [...DEFAULT_WALK_IGNORE_DIRS4, ".wrongstack", ".ssh", ".gnupg", ".aws"];
21683
21852
  var treeTool = {
21684
21853
  name: "tree",
21685
21854
  category: "Filesystem",