@ulpi/cli 0.1.2 → 0.1.3

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.
@@ -0,0 +1,118 @@
1
+ import {
2
+ extractCredentials,
3
+ getCredentialExpiry,
4
+ validateCredentials
5
+ } from "./chunk-G6SVZ4Q5.js";
6
+ import "./chunk-4VNS5WPM.js";
7
+
8
+ // src/commands/auth.ts
9
+ import * as fs from "fs";
10
+ import chalk from "chalk";
11
+ async function runAuth(args) {
12
+ const subcommand = args[0];
13
+ switch (subcommand) {
14
+ case "setup":
15
+ return authSetup();
16
+ case "check":
17
+ return authCheck(args.slice(1));
18
+ case "refresh":
19
+ return authSetup();
20
+ // Same flow as setup
21
+ default:
22
+ console.log(`
23
+ ${chalk.bold("ulpi auth")} \u2014 Manage Claude Code credentials for CI workers
24
+
25
+ Usage: ulpi auth <command>
26
+
27
+ Commands:
28
+ setup Extract Claude Code credentials for CI use (interactive)
29
+ check Validate stored credentials
30
+ refresh Re-authenticate and output new credentials
31
+
32
+ How it works:
33
+ 1. Run 'ulpi auth setup' on a machine with a browser
34
+ 2. It extracts your Claude Code session credentials
35
+ 3. Outputs a base64 blob you can store as a Docker secret
36
+ 4. Set ULPI_CLAUDE_CREDENTIALS in your orchestrator config
37
+ `.trim());
38
+ }
39
+ }
40
+ async function authSetup() {
41
+ console.log(chalk.blue("Extracting Claude Code credentials..."));
42
+ console.log();
43
+ const claudeDir = process.env.CLAUDE_CONFIG_DIR ?? `${process.env.HOME}/.claude`;
44
+ if (!fs.existsSync(claudeDir)) {
45
+ console.log(chalk.yellow("No Claude Code configuration found."));
46
+ console.log();
47
+ console.log("Please run 'claude login' first to authenticate with Anthropic.");
48
+ console.log("Then run 'ulpi auth setup' again.");
49
+ process.exit(1);
50
+ }
51
+ try {
52
+ const blob = extractCredentials(claudeDir);
53
+ const expiry = getCredentialExpiry(blob);
54
+ console.log(chalk.green("Credentials extracted successfully."));
55
+ console.log();
56
+ if (expiry) {
57
+ console.log(` Expires: ${expiry}`);
58
+ console.log();
59
+ }
60
+ console.log(chalk.bold("Your credentials blob:"));
61
+ console.log();
62
+ console.log(blob);
63
+ console.log();
64
+ console.log(chalk.bold("To use in your orchestrator:"));
65
+ console.log();
66
+ console.log(" 1. Set as environment variable:");
67
+ console.log(chalk.cyan(` export ULPI_CLAUDE_CREDENTIALS="${blob.slice(0, 20)}..."`));
68
+ console.log();
69
+ console.log(" 2. Or add to docker-compose.yml:");
70
+ console.log(chalk.cyan(" environment:"));
71
+ console.log(chalk.cyan(" - ULPI_CLAUDE_CREDENTIALS=${ULPI_CLAUDE_CREDENTIALS}"));
72
+ console.log();
73
+ console.log(" 3. Or store as a Docker secret:");
74
+ console.log(chalk.cyan(` echo "${blob.slice(0, 20)}..." | docker secret create ulpi-claude-creds -`));
75
+ console.log();
76
+ console.log(chalk.yellow("Keep this blob safe \u2014 it contains your Claude Code session credentials."));
77
+ } catch (err) {
78
+ const message = err instanceof Error ? err.message : String(err);
79
+ console.error(chalk.red(`Error: ${message}`));
80
+ process.exit(1);
81
+ }
82
+ }
83
+ async function authCheck(args) {
84
+ const blob = args[0] ?? process.env.ULPI_CLAUDE_CREDENTIALS;
85
+ if (!blob) {
86
+ console.error(chalk.red("Error: No credentials provided."));
87
+ console.log(" Provide as argument: ulpi auth check <blob>");
88
+ console.log(" Or set ULPI_CLAUDE_CREDENTIALS env var");
89
+ process.exit(1);
90
+ }
91
+ console.log(chalk.blue("Validating credentials..."));
92
+ const valid = validateCredentials(blob);
93
+ const expiry = getCredentialExpiry(blob);
94
+ if (valid) {
95
+ console.log(chalk.green("Credentials are valid."));
96
+ if (expiry) {
97
+ const expiryDate = new Date(expiry);
98
+ const now = /* @__PURE__ */ new Date();
99
+ const hoursLeft = (expiryDate.getTime() - now.getTime()) / (60 * 60 * 1e3);
100
+ if (hoursLeft < 0) {
101
+ console.log(chalk.red(` Expired: ${expiry}`));
102
+ console.log(chalk.yellow(" Run 'ulpi auth refresh' to re-authenticate."));
103
+ } else if (hoursLeft < 24) {
104
+ console.log(chalk.yellow(` Expires soon: ${expiry} (${Math.floor(hoursLeft)}h remaining)`));
105
+ console.log(chalk.yellow(" Consider running 'ulpi auth refresh' soon."));
106
+ } else {
107
+ console.log(chalk.green(` Expires: ${expiry}`));
108
+ }
109
+ }
110
+ } else {
111
+ console.log(chalk.red("Credentials are invalid or corrupted."));
112
+ console.log("Run 'ulpi auth setup' to generate new credentials.");
113
+ process.exit(1);
114
+ }
115
+ }
116
+ export {
117
+ runAuth
118
+ };
@@ -49,7 +49,7 @@ import {
49
49
  updateEntryEnrichment,
50
50
  withWorktree,
51
51
  writeAndStage
52
- } from "./chunk-NNUWU6CV.js";
52
+ } from "./chunk-JGBXM5NC.js";
53
53
  import {
54
54
  JsonSessionStore,
55
55
  readEvents
@@ -74,7 +74,7 @@ import {
74
74
  saveUlpiSettings
75
75
  } from "./chunk-7LXY5UVC.js";
76
76
 
77
- // ../api/dist/chunk-77FAT4LI.js
77
+ // ../api/dist/chunk-P4BERD2G.js
78
78
  import * as http from "http";
79
79
  import * as fs12 from "fs";
80
80
  import * as path9 from "path";
@@ -394,33 +394,51 @@ async function putUsernameSettings(ctx) {
394
394
  resolvedHistoryBranch: getHistoryBranch()
395
395
  }, 200, ctx.req);
396
396
  }
397
+ function yamlEscapeString(s) {
398
+ return s.replace(/\\/g, "\\\\").replace(/"/g, '\\"');
399
+ }
397
400
  function writeYamlFields(obj, lines, indent) {
398
401
  const pad = " ".repeat(indent);
399
402
  for (const [k, v] of Object.entries(obj)) {
400
403
  if (k === "id" || k === "type" || k === "source") continue;
401
404
  if (typeof v === "string") {
402
- lines.push(`${pad}${k}: "${v}"`);
405
+ lines.push(`${pad}${k}: "${yamlEscapeString(v)}"`);
403
406
  } else if (typeof v === "boolean" || typeof v === "number") {
404
407
  lines.push(`${pad}${k}: ${v}`);
405
408
  } else if (Array.isArray(v)) {
406
409
  lines.push(`${pad}${k}:`);
407
410
  for (const item of v) {
408
411
  if (typeof item === "string") {
409
- lines.push(`${pad} - "${item}"`);
412
+ lines.push(`${pad} - "${yamlEscapeString(item)}"`);
413
+ } else if (item && typeof item === "object") {
414
+ const entries = Object.entries(item);
415
+ if (entries.length > 0) {
416
+ const [firstKey, firstVal] = entries[0];
417
+ lines.push(`${pad} - ${firstKey}: ${formatScalar(firstVal)}`);
418
+ for (let i = 1; i < entries.length; i++) {
419
+ const [ek, ev] = entries[i];
420
+ lines.push(`${pad} ${ek}: ${formatScalar(ev)}`);
421
+ }
422
+ }
410
423
  }
411
424
  }
412
425
  }
413
426
  }
414
427
  }
428
+ function formatScalar(v) {
429
+ if (typeof v === "string") return `"${yamlEscapeString(v)}"`;
430
+ if (typeof v === "boolean" || typeof v === "number") return String(v);
431
+ return `"${String(v)}"`;
432
+ }
415
433
  function writeRulesConfig(rulesPath, config) {
416
434
  const lines = [
417
435
  "# ULPI \u2014 Rules Configuration",
418
436
  "# Generated via Web UI",
419
437
  "",
420
438
  "project:",
421
- ` name: "${config.project.name}"`,
422
- ` runtime: "${config.project.runtime}"`,
423
- ` package_manager: "${config.project.package_manager}"`,
439
+ ` name: "${yamlEscapeString(config.project.name)}"`,
440
+ ` runtime: "${yamlEscapeString(config.project.runtime)}"`,
441
+ ` package_manager: "${yamlEscapeString(config.project.package_manager)}"`,
424
442
  ""
425
443
  ];
426
444
  const sections = [
@@ -2245,7 +2263,7 @@ async function exportObsidian(ctx) {
2245
2263
  jsonResponse(ctx.res, { format: "obsidian", content: md }, 200, ctx.req);
2246
2264
  }
2247
2265
  async function getEngine() {
2248
- return await import("./dist-MFFX7TZW.js");
2266
+ return await import("./dist-CB5D5LMO.js");
2249
2267
  }
2250
2268
  var runningPipelines = /* @__PURE__ */ new Map();
2251
2269
  var activeWatchers = /* @__PURE__ */ new Map();
@@ -2551,7 +2569,7 @@ async function codemapActionHandler(ctx) {
2551
2569
  }
2552
2570
  }
2553
2571
  async function getEngine2() {
2554
- return await import("./dist-57UMTPGR.js");
2572
+ return await import("./dist-GJYT2OQV.js");
2555
2573
  }
2556
2574
  async function memoryStatusHandler(ctx) {
2557
2575
  try {
@@ -2559,7 +2577,8 @@ async function memoryStatusHandler(ctx) {
2559
2577
  const stats = await engine.getMemoryStats(ctx.projectDir);
2560
2578
  const config = engine.loadMemoryConfig(ctx.projectDir);
2561
2579
  const initialized = engine.isMemoryInitialized(ctx.projectDir);
2562
- jsonResponse(ctx.res, { stats, config, initialized }, 200, ctx.req);
2580
+ const classifyProgress = engine.readClassifyBatchProgress(ctx.projectDir);
2581
+ jsonResponse(ctx.res, { stats, config, initialized, classifyProgress }, 200, ctx.req);
2563
2582
  } catch (err) {
2564
2583
  const message = err instanceof Error ? err.message : "Failed to get memory status";
2565
2584
  jsonResponse(ctx.res, { error: message }, 500, ctx.req);
@@ -2739,7 +2758,7 @@ async function memoryActionHandler(ctx) {
2739
2758
  return;
2740
2759
  }
2741
2760
  const action = data.action;
2742
- const validActions = ["init", "export", "import", "enable", "disable", "classify", "reindex"];
2761
+ const validActions = ["init", "export", "import", "enable", "disable", "classify", "classify-all", "classify-clear", "reindex"];
2743
2762
  if (!validActions.includes(action)) {
2744
2763
  jsonResponse(
2745
2764
  ctx.res,
@@ -2796,6 +2815,32 @@ async function memoryActionHandler(ctx) {
2796
2815
  jsonResponse(ctx.res, { ok: true, action, ...result }, 200, ctx.req);
2797
2816
  return;
2798
2817
  }
2818
+ case "classify-all": {
2819
+ const { spawn: spawn4 } = await import("child_process");
2820
+ const ulpiBin = process.argv[1];
2821
+ const args = ["memory", "classify", "-p", ctx.projectDir];
2822
+ if (typeof data.branch === "string" && data.branch) {
2823
+ args.push("--branch", data.branch);
2824
+ }
2825
+ if (data.export) args.push("--export");
2826
+ const child = spawn4(process.execPath, [ulpiBin, ...args], {
2827
+ detached: true,
2828
+ stdio: "ignore",
2829
+ env: { ...process.env, ULPI_BG_CLASSIFY: "1" }
2830
+ });
2831
+ child.unref();
2832
+ jsonResponse(ctx.res, {
2833
+ ok: true,
2834
+ action,
2835
+ message: "Classification started"
2836
+ }, 200, ctx.req);
2837
+ return;
2838
+ }
2839
+ case "classify-clear": {
2840
+ engine.clearClassifyBatchProgress(ctx.projectDir);
2841
+ jsonResponse(ctx.res, { ok: true, action, message: "Classification progress cleared" }, 200, ctx.req);
2842
+ return;
2843
+ }
2799
2844
  case "reindex": {
2800
2845
  const result = await engine.reindexMemories(ctx.projectDir);
2801
2846
  jsonResponse(ctx.res, { ok: true, action, ...result }, 200, ctx.req);
@@ -3457,7 +3502,7 @@ async function historyInitHandler(ctx) {
3457
3502
  }
3458
3503
  let gitHooksResult = { installed: [], skipped: [] };
3459
3504
  try {
3460
- const { installGitHooks } = await import("./dist-RJGCUS3L.js");
3505
+ const { installGitHooks } = await import("./dist-QAU3LGJN.js");
3461
3506
  const binaryPath = getBinaryPath();
3462
3507
  gitHooksResult = installGitHooks(projectDir, binaryPath);
3463
3508
  } catch {
@@ -3495,8 +3540,10 @@ async function historyBackfillHandler(ctx) {
3495
3540
  } catch {
3496
3541
  }
3497
3542
  const limit = Math.min(options.limit ?? 20, 100);
3543
+ const branchOnly = options.branchOnly ?? false;
3498
3544
  const {
3499
3545
  listRecentCommits,
3546
+ listBranchOnlyCommits,
3500
3547
  getCommitMetadata,
3501
3548
  getCommitDiffStats,
3502
3549
  getCommitRawDiff,
@@ -3506,8 +3553,8 @@ async function historyBackfillHandler(ctx) {
3506
3553
  entryExists,
3507
3554
  writeHistoryEntry,
3508
3555
  DEFAULT_HISTORY_CONFIG
3509
- } = await import("./dist-RJGCUS3L.js");
3510
- const commits = listRecentCommits(projectDir, limit);
3556
+ } = await import("./dist-QAU3LGJN.js");
3557
+ const commits = branchOnly ? listBranchOnlyCommits(projectDir, limit) : listRecentCommits(projectDir, limit);
3511
3558
  if (commits.length === 0) {
3512
3559
  jsonResponse(res, { captured: 0, skipped: 0, total: 0 }, 200, req);
3513
3560
  return;
@@ -3830,7 +3877,7 @@ async function historyEntryTagsHandler(ctx) {
3830
3877
  return;
3831
3878
  }
3832
3879
  try {
3833
- const { updateEntryTags } = await import("./dist-RJGCUS3L.js");
3880
+ const { updateEntryTags } = await import("./dist-QAU3LGJN.js");
3834
3881
  await updateEntryTags(projectDir, sha, payload.tags);
3835
3882
  jsonResponse(res, { success: true, sha, tags: payload.tags }, 200, req);
3836
3883
  } catch (err) {
@@ -3846,7 +3893,7 @@ async function historyEntryTranscriptHandler(ctx) {
3846
3893
  jsonResponse(res, { error: "History branch not initialized" }, 400, req);
3847
3894
  return;
3848
3895
  }
3849
- const { readEntryTranscript } = await import("./dist-RJGCUS3L.js");
3896
+ const { readEntryTranscript } = await import("./dist-QAU3LGJN.js");
3850
3897
  const transcript = readEntryTranscript(projectDir, sha);
3851
3898
  if (!transcript) {
3852
3899
  jsonResponse(res, { error: "No transcript for this entry" }, 404, req);
@@ -4,7 +4,7 @@ import {
4
4
  historyBranchExists,
5
5
  withWorktree,
6
6
  writeAndStage
7
- } from "./chunk-NNUWU6CV.js";
7
+ } from "./chunk-JGBXM5NC.js";
8
8
  import {
9
9
  CodemapConfigSchema
10
10
  } from "./chunk-74WVVWJ4.js";
@@ -13,7 +13,7 @@ var REGISTRY_URL = CLI_REGISTRY_URL;
13
13
  var FETCH_TIMEOUT_MS = 5e3;
14
14
  function getCurrentVersion() {
15
15
  try {
16
- return "0.1.2";
16
+ return "0.1.3";
17
17
  } catch {
18
18
  return "0.0.0";
19
19
  }
@@ -0,0 +1,122 @@
1
+ // ../../packages/ci-engine/dist/index.js
2
+ import * as fs4 from "fs";
3
+ import * as path3 from "path";
4
+ import { execFileSync as execFileSync2 } from "child_process";
5
+ function buildPrompt(config, context) {
6
+ const sections = [];
7
+ sections.push("# Task");
8
+ sections.push("");
9
+ sections.push(config.instruction);
10
+ sections.push("");
11
+ if (context.prTitle || context.prBody) {
12
+ sections.push("# Pull Request Context");
13
+ sections.push("");
14
+ if (context.prTitle) sections.push(`**Title:** ${context.prTitle}`);
15
+ if (context.prBody) {
16
+ sections.push("");
17
+ sections.push(context.prBody);
18
+ }
19
+ sections.push("");
20
+ }
21
+ sections.push("# Branch Info");
22
+ sections.push("");
23
+ sections.push(`- **Working branch:** ${config.ref}`);
24
+ sections.push(`- **Base branch:** ${config.baseBranch}`);
25
+ sections.push(`- **Repository:** ${config.repoFullName}`);
26
+ sections.push("");
27
+ if (config.jiraContext || context.jiraContext) {
28
+ sections.push("# Jira Context");
29
+ sections.push("");
30
+ sections.push(config.jiraContext ?? context.jiraContext ?? "");
31
+ sections.push("");
32
+ }
33
+ sections.push("# Important Instructions");
34
+ sections.push("");
35
+ sections.push(
36
+ "- You are running in CI mode inside a worker container."
37
+ );
38
+ sections.push(
39
+ "- Work on the existing branch \u2014 do NOT create new branches."
40
+ );
41
+ sections.push(
42
+ "- Commit your changes with clear, descriptive commit messages."
43
+ );
44
+ sections.push("- Run tests if a test runner is configured.");
45
+ sections.push(
46
+ "- Do NOT push to the remote \u2014 the orchestrator will handle pushing."
47
+ );
48
+ sections.push("");
49
+ return sections.join("\n");
50
+ }
51
+ function extractCredentials(claudeConfigDir) {
52
+ const configDir = claudeConfigDir ?? path3.join(process.env.HOME ?? "", ".claude");
53
+ if (!fs4.existsSync(configDir)) {
54
+ throw new Error(
55
+ `Claude config directory not found: ${configDir}`
56
+ );
57
+ }
58
+ const tarOutput = execFileSync2(
59
+ "tar",
60
+ ["-czf", "-", "-C", configDir, "."],
61
+ {
62
+ encoding: "buffer",
63
+ timeout: 3e4,
64
+ maxBuffer: 50 * 1024 * 1024
65
+ // 50MB limit
66
+ }
67
+ );
68
+ return tarOutput.toString("base64");
69
+ }
70
+ function writeCredentials(blob, targetDir) {
71
+ fs4.mkdirSync(targetDir, { recursive: true });
72
+ const tarBuffer = Buffer.from(blob, "base64");
73
+ execFileSync2("tar", ["-xzf", "-", "-C", targetDir], {
74
+ input: tarBuffer,
75
+ timeout: 3e4
76
+ });
77
+ }
78
+ function validateCredentials(blob) {
79
+ const tmpDir = fs4.mkdtempSync("/tmp/ulpi-claude-check-");
80
+ try {
81
+ writeCredentials(blob, tmpDir);
82
+ const files = fs4.readdirSync(tmpDir);
83
+ return files.length > 0;
84
+ } catch {
85
+ return false;
86
+ } finally {
87
+ try {
88
+ fs4.rmSync(tmpDir, { recursive: true, force: true });
89
+ } catch {
90
+ }
91
+ }
92
+ }
93
+ function getCredentialExpiry(blob) {
94
+ const tmpDir = fs4.mkdtempSync("/tmp/ulpi-claude-expiry-");
95
+ try {
96
+ writeCredentials(blob, tmpDir);
97
+ const authFile = path3.join(tmpDir, ".credentials.json");
98
+ if (fs4.existsSync(authFile)) {
99
+ try {
100
+ const data = JSON.parse(
101
+ fs4.readFileSync(authFile, "utf-8")
102
+ );
103
+ if (data.expiresAt) return data.expiresAt;
104
+ if (data.expires_at) return data.expires_at;
105
+ } catch {
106
+ }
107
+ }
108
+ return void 0;
109
+ } finally {
110
+ try {
111
+ fs4.rmSync(tmpDir, { recursive: true, force: true });
112
+ } catch {
113
+ }
114
+ }
115
+ }
116
+
117
+ export {
118
+ buildPrompt,
119
+ extractCredentials,
120
+ validateCredentials,
121
+ getCredentialExpiry
122
+ };
@@ -345,6 +345,47 @@ function listRecentCommits(projectDir, maxCount = 20) {
345
345
  return [];
346
346
  }
347
347
  }
348
+ function listBranchOnlyCommits(projectDir, maxCount = 50) {
349
+ try {
350
+ let currentBranch;
351
+ try {
352
+ currentBranch = gitExec(projectDir, ["rev-parse", "--abbrev-ref", "HEAD"]);
353
+ } catch {
354
+ return listRecentCommits(projectDir, maxCount);
355
+ }
356
+ let defaultBranch = "main";
357
+ try {
358
+ const ref = gitExec(projectDir, ["symbolic-ref", "refs/remotes/origin/HEAD"]);
359
+ defaultBranch = ref.replace("refs/remotes/origin/", "");
360
+ } catch {
361
+ try {
362
+ gitExec(projectDir, ["rev-parse", "--verify", "refs/heads/main"]);
363
+ defaultBranch = "main";
364
+ } catch {
365
+ try {
366
+ gitExec(projectDir, ["rev-parse", "--verify", "refs/heads/master"]);
367
+ defaultBranch = "master";
368
+ } catch {
369
+ return listRecentCommits(projectDir, maxCount);
370
+ }
371
+ }
372
+ }
373
+ if (currentBranch === defaultBranch) {
374
+ return listRecentCommits(projectDir, maxCount);
375
+ }
376
+ const raw = gitExec(projectDir, [
377
+ "rev-list",
378
+ "--reverse",
379
+ `--max-count=${maxCount}`,
380
+ "HEAD",
381
+ "--not",
382
+ defaultBranch
383
+ ]);
384
+ return raw ? raw.split("\n").filter(Boolean) : [];
385
+ } catch {
386
+ return listRecentCommits(projectDir, maxCount);
387
+ }
388
+ }
348
389
  function readFromBranch(projectDir, filePath, branchName) {
349
390
  branchName ??= getHistoryBranch();
350
391
  validateBranchPath(filePath);
@@ -1575,6 +1616,7 @@ export {
1575
1616
  getCurrentHead,
1576
1617
  listCommitsBetween,
1577
1618
  listRecentCommits,
1619
+ listBranchOnlyCommits,
1578
1620
  readFromBranch,
1579
1621
  listBranchDir,
1580
1622
  getWorktreeId,
@@ -1,13 +1,15 @@
1
1
  import {
2
- createEmbedder
3
- } from "./chunk-VGZLMUNO.js";
2
+ createEmbedder,
3
+ loadCodemapIgnore,
4
+ matchesDenyPattern
5
+ } from "./chunk-5J6NLQUN.js";
4
6
  import {
5
7
  commitInWorktree,
6
8
  copyAndStage,
7
9
  historyBranchExists,
8
10
  withWorktree,
9
11
  writeAndStage
10
- } from "./chunk-NNUWU6CV.js";
12
+ } from "./chunk-JGBXM5NC.js";
11
13
  import {
12
14
  readEvents
13
15
  } from "./chunk-YM2HV4IA.js";
@@ -53,6 +55,8 @@ import { execFileSync as execFileSync2 } from "child_process";
53
55
  import * as fs9 from "fs";
54
56
  import * as os3 from "os";
55
57
  import * as path9 from "path";
58
+ import { readFileSync as readFileSync8, writeFileSync as writeFileSync8, unlinkSync as unlinkSync3, existsSync as existsSync10 } from "fs";
59
+ import { join as join8 } from "path";
56
60
  var DEFAULT_MEMORY_CONFIG = MemoryConfigSchema.parse({});
57
61
  function loadMemoryConfig(projectDir) {
58
62
  const configPath = memoryConfigFile(projectDir);
@@ -411,11 +415,16 @@ var TRIVIAL_COMMANDS = /* @__PURE__ */ new Set([
411
415
  "date",
412
416
  "wc"
413
417
  ]);
414
- function filterSignificantEvents(events) {
418
+ function filterSignificantEvents(events, projectDir) {
419
+ const ignorePatterns = projectDir ? loadCodemapIgnore(projectDir) : [];
415
420
  return events.filter((ev) => {
416
421
  if (ev.event === "tool_blocked" || ev.event === "permission_denied" || ev.event === "postcondition_failed") {
417
422
  return true;
418
423
  }
424
+ if (ev.filePath && ignorePatterns.length > 0 && projectDir) {
425
+ const rel = ev.filePath.startsWith(projectDir) ? ev.filePath.slice(projectDir.length + 1) : ev.filePath;
426
+ if (matchesDenyPattern(rel, ignorePatterns)) return false;
427
+ }
419
428
  if (ev.event === "tool_used" && ev.toolName === "Read") {
420
429
  return false;
421
430
  }
@@ -1031,7 +1040,7 @@ async function classifySession(projectDir, sessionId, onProgress) {
1031
1040
  };
1032
1041
  }
1033
1042
  const unprocessedEvents = allEvents.slice(startIdx);
1034
- const filteredEvents = config.classifier.filterNoiseEvents ? filterSignificantEvents(unprocessedEvents) : unprocessedEvents;
1043
+ const filteredEvents = config.classifier.filterNoiseEvents ? filterSignificantEvents(unprocessedEvents, projectDir) : unprocessedEvents;
1035
1044
  const meta = loadSessionMeta(projectDir, sessionId);
1036
1045
  if (!isSessionSignificant(meta, filteredEvents, config.classifier.minSignificantEvents)) {
1037
1046
  saveWatermark(projectDir, {
@@ -1578,6 +1587,45 @@ function copyDirRecursiveImport(srcDir, destDir) {
1578
1587
  }
1579
1588
  return { fileCount, totalBytes };
1580
1589
  }
1590
+ function progressFile(projectDir) {
1591
+ return join8(projectMemoryDir(projectDir), "classify-progress.json");
1592
+ }
1593
+ var STALE_THRESHOLD_MS = 2 * 60 * 1e3;
1594
+ function writeClassifyBatchProgress(projectDir, progress) {
1595
+ try {
1596
+ const p = { ...progress, updatedAt: (/* @__PURE__ */ new Date()).toISOString() };
1597
+ writeFileSync8(progressFile(projectDir), JSON.stringify(p, null, 2));
1598
+ } catch {
1599
+ }
1600
+ }
1601
+ function readClassifyBatchProgress(projectDir) {
1602
+ const file = progressFile(projectDir);
1603
+ if (!existsSync10(file)) return null;
1604
+ try {
1605
+ const raw = readFileSync8(file, "utf-8");
1606
+ const data = JSON.parse(raw);
1607
+ if (data.status === "classifying" && data.updatedAt) {
1608
+ const age = Date.now() - new Date(data.updatedAt).getTime();
1609
+ if (age > STALE_THRESHOLD_MS) {
1610
+ return {
1611
+ ...data,
1612
+ status: "error",
1613
+ error: "Classification process appears to have stopped unexpectedly."
1614
+ };
1615
+ }
1616
+ }
1617
+ return data;
1618
+ } catch {
1619
+ return null;
1620
+ }
1621
+ }
1622
+ function clearClassifyBatchProgress(projectDir) {
1623
+ try {
1624
+ const file = progressFile(projectDir);
1625
+ if (existsSync10(file)) unlinkSync3(file);
1626
+ } catch {
1627
+ }
1628
+ }
1581
1629
 
1582
1630
  export {
1583
1631
  DEFAULT_MEMORY_CONFIG,
@@ -1626,5 +1674,8 @@ export {
1626
1674
  memoryBranchExists,
1627
1675
  initMemoryBranch,
1628
1676
  exportMemories,
1629
- importMemories
1677
+ importMemories,
1678
+ writeClassifyBatchProgress,
1679
+ readClassifyBatchProgress,
1680
+ clearClassifyBatchProgress
1630
1681
  };