@ulpi/cli 0.1.1 → 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.
Files changed (30) hide show
  1. package/dist/auth-KQCJ43U2.js +118 -0
  2. package/dist/{chunk-Q4HIY43N.js → chunk-2VYFVYJL.js} +67 -24
  3. package/dist/{chunk-DBMUNBNB.js → chunk-5J6NLQUN.js} +149 -19
  4. package/dist/{chunk-4KRVDKGB.js → chunk-F7OXF7Z3.js} +1 -1
  5. package/dist/chunk-G6SVZ4Q5.js +122 -0
  6. package/dist/{chunk-NNUWU6CV.js → chunk-JGBXM5NC.js} +42 -0
  7. package/dist/{chunk-6JCMYYBT.js → chunk-PDR55ZNW.js} +247 -112
  8. package/dist/{chunk-247GVVKK.js → chunk-ZLYRPD7I.js} +18 -16
  9. package/dist/ci-QM57ZCBW.js +367 -0
  10. package/dist/{codemap-RRJIDBQ5.js → codemap-RKSD4MIE.js} +49 -17
  11. package/dist/{dist-LZKZFPVX.js → dist-CB5D5LMO.js} +6 -3
  12. package/dist/{dist-7LHZ65GC.js → dist-CS2VKNYS.js} +5 -4
  13. package/dist/{dist-R5F4MX3I.js → dist-GJYT2OQV.js} +11 -4
  14. package/dist/{dist-RJGCUS3L.js → dist-QAU3LGJN.js} +3 -1
  15. package/dist/{dist-W7K4WPAF.js → dist-UKMCJBB2.js} +42 -14
  16. package/dist/{dist-R5ZJ4LX5.js → dist-YA2BWZB2.js} +1 -1
  17. package/dist/{history-Q2LDADFW.js → history-NFNA4HE5.js} +13 -7
  18. package/dist/index.js +50 -21
  19. package/dist/{init-AY5C2ZAS.js → init-6CH4HV5T.js} +2 -2
  20. package/dist/{memory-J3G24QHS.js → memory-Y6OZTXJ2.js} +231 -22
  21. package/dist/{server-MOYPE4SM-N7SE2AN7.js → server-USLHY6GH-AEOJC5ST.js} +2 -2
  22. package/dist/skills/ulpi-generate-guardian/SKILL.md +246 -7
  23. package/dist/skills/ulpi-generate-guardian/references/framework-rules.md +161 -4
  24. package/dist/skills/ulpi-generate-guardian/references/language-rules.md +13 -18
  25. package/dist/{ui-L7UAWXDY.js → ui-OWXZ3YSR.js} +3 -3
  26. package/dist/ui.html +112 -112
  27. package/dist/{update-DJ227LL3.js → update-WUITQX4Z.js} +1 -1
  28. package/dist/{version-checker-M37KI7DY.js → version-checker-SMAYSN7Y.js} +1 -1
  29. package/package.json +31 -28
  30. package/LICENSE +0 -21
@@ -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-3N5EEDFM.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-LZKZFPVX.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-R5F4MX3I.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,
@@ -2765,12 +2784,12 @@ async function memoryActionHandler(ctx) {
2765
2784
  return;
2766
2785
  }
2767
2786
  case "export": {
2768
- const result = engine.exportMemories(ctx.projectDir);
2787
+ const result = await engine.exportMemories(ctx.projectDir);
2769
2788
  jsonResponse(ctx.res, { ok: true, action, ...result }, 200, ctx.req);
2770
2789
  return;
2771
2790
  }
2772
2791
  case "import": {
2773
- const result = engine.importMemories(ctx.projectDir);
2792
+ const result = await engine.importMemories(ctx.projectDir);
2774
2793
  jsonResponse(ctx.res, { ok: true, action, ...result }, 200, ctx.req);
2775
2794
  return;
2776
2795
  }
@@ -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);
@@ -2855,7 +2900,7 @@ async function memoryConfigUpdateHandler(ctx) {
2855
2900
  }
2856
2901
  }
2857
2902
  async function getDepgraph() {
2858
- return await import("./dist-R5ZJ4LX5.js");
2903
+ return await import("./dist-YA2BWZB2.js");
2859
2904
  }
2860
2905
  async function getBranch(projectDir) {
2861
2906
  const { getCurrentBranch: getCurrentBranch2 } = await import("./dist-RKOGLK7R.js");
@@ -3014,11 +3059,7 @@ async function depgraphMetricsHandler(ctx) {
3014
3059
  jsonResponse(ctx.res, { error: error ?? "Invalid request body" }, 400, ctx.req);
3015
3060
  return;
3016
3061
  }
3017
- const modulePath = data.modulePath;
3018
- if (typeof modulePath !== "string" || !modulePath.trim()) {
3019
- jsonResponse(ctx.res, { error: "Missing or empty 'modulePath' field" }, 400, ctx.req);
3020
- return;
3021
- }
3062
+ const modulePath = typeof data.modulePath === "string" ? data.modulePath : "";
3022
3063
  try {
3023
3064
  const depgraph = await getDepgraph();
3024
3065
  const branch = await getBranch(ctx.projectDir);
@@ -3461,7 +3502,7 @@ async function historyInitHandler(ctx) {
3461
3502
  }
3462
3503
  let gitHooksResult = { installed: [], skipped: [] };
3463
3504
  try {
3464
- const { installGitHooks } = await import("./dist-RJGCUS3L.js");
3505
+ const { installGitHooks } = await import("./dist-QAU3LGJN.js");
3465
3506
  const binaryPath = getBinaryPath();
3466
3507
  gitHooksResult = installGitHooks(projectDir, binaryPath);
3467
3508
  } catch {
@@ -3499,8 +3540,10 @@ async function historyBackfillHandler(ctx) {
3499
3540
  } catch {
3500
3541
  }
3501
3542
  const limit = Math.min(options.limit ?? 20, 100);
3543
+ const branchOnly = options.branchOnly ?? false;
3502
3544
  const {
3503
3545
  listRecentCommits,
3546
+ listBranchOnlyCommits,
3504
3547
  getCommitMetadata,
3505
3548
  getCommitDiffStats,
3506
3549
  getCommitRawDiff,
@@ -3510,8 +3553,8 @@ async function historyBackfillHandler(ctx) {
3510
3553
  entryExists,
3511
3554
  writeHistoryEntry,
3512
3555
  DEFAULT_HISTORY_CONFIG
3513
- } = await import("./dist-RJGCUS3L.js");
3514
- const commits = listRecentCommits(projectDir, limit);
3556
+ } = await import("./dist-QAU3LGJN.js");
3557
+ const commits = branchOnly ? listBranchOnlyCommits(projectDir, limit) : listRecentCommits(projectDir, limit);
3515
3558
  if (commits.length === 0) {
3516
3559
  jsonResponse(res, { captured: 0, skipped: 0, total: 0 }, 200, req);
3517
3560
  return;
@@ -3834,7 +3877,7 @@ async function historyEntryTagsHandler(ctx) {
3834
3877
  return;
3835
3878
  }
3836
3879
  try {
3837
- const { updateEntryTags } = await import("./dist-RJGCUS3L.js");
3880
+ const { updateEntryTags } = await import("./dist-QAU3LGJN.js");
3838
3881
  await updateEntryTags(projectDir, sha, payload.tags);
3839
3882
  jsonResponse(res, { success: true, sha, tags: payload.tags }, 200, req);
3840
3883
  } catch (err) {
@@ -3850,7 +3893,7 @@ async function historyEntryTranscriptHandler(ctx) {
3850
3893
  jsonResponse(res, { error: "History branch not initialized" }, 400, req);
3851
3894
  return;
3852
3895
  }
3853
- const { readEntryTranscript } = await import("./dist-RJGCUS3L.js");
3896
+ const { readEntryTranscript } = await import("./dist-QAU3LGJN.js");
3854
3897
  const transcript = readEntryTranscript(projectDir, sha);
3855
3898
  if (!transcript) {
3856
3899
  jsonResponse(res, { error: "No transcript for this entry" }, 404, req);
@@ -4,10 +4,13 @@ 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";
11
+ import {
12
+ extractTagsFromTree
13
+ } from "./chunk-ZLYRPD7I.js";
11
14
  import {
12
15
  LOGS_DIR,
13
16
  codemapBranchDir,
@@ -527,6 +530,7 @@ function getLanguageId(filePath) {
527
530
  }
528
531
  var ParserCtor = null;
529
532
  var LanguageClass = null;
533
+ var QueryCtor = null;
530
534
  var initialized = false;
531
535
  var languageCache = /* @__PURE__ */ new Map();
532
536
  var require2 = createRequire(import.meta.url);
@@ -535,6 +539,7 @@ async function initTreeSitter() {
535
539
  const mod = await import("web-tree-sitter");
536
540
  ParserCtor = mod.Parser;
537
541
  LanguageClass = mod.Language;
542
+ QueryCtor = mod.Query;
538
543
  const wasmPath = require2.resolve("web-tree-sitter/web-tree-sitter.wasm");
539
544
  const wasmDir = path4.dirname(wasmPath) + "/";
540
545
  await ParserCtor.init({
@@ -556,6 +561,12 @@ async function loadLanguage(langConfig) {
556
561
  languageCache.set(cacheKey, language);
557
562
  return language;
558
563
  }
564
+ function createTreeSitterQuery(language, source) {
565
+ if (!QueryCtor) {
566
+ throw new Error("Tree-sitter not initialized. Call initTreeSitter() first.");
567
+ }
568
+ return new QueryCtor(language, source);
569
+ }
559
570
  async function parseSource(content, langConfig) {
560
571
  if (!ParserCtor) {
561
572
  throw new Error("Tree-sitter not initialized. Call initTreeSitter() first.");
@@ -1532,14 +1543,9 @@ async function runPipelineInner(projectDir, onProgress, branch) {
1532
1543
  total: scannedFiles.length,
1533
1544
  message: "Chunking files..."
1534
1545
  });
1535
- let extractTagsFromTree = null;
1536
- try {
1537
- const depgraph = await import("./dist-R5ZJ4LX5.js");
1538
- extractTagsFromTree = depgraph.extractTagsFromTree;
1539
- } catch {
1540
- }
1541
1546
  await initTreeSitter();
1542
1547
  const allTags = /* @__PURE__ */ new Map();
1548
+ let tagExtractionFailures = 0;
1543
1549
  const chunks = [];
1544
1550
  const filePaths = scannedFiles.map((f) => f.filePath);
1545
1551
  for (let i = 0; i < filePaths.length; i++) {
@@ -1561,15 +1567,19 @@ async function runPipelineInner(projectDir, onProgress, branch) {
1561
1567
  const fallbackChunks = await chunkFile(filePath, content, config.chunking);
1562
1568
  chunks.push(...fallbackChunks);
1563
1569
  }
1564
- if (extractTagsFromTree && result.tree && result.language && langId && langConfig.hasTagQuery) {
1570
+ if (result.tree && result.language && langId && langConfig.hasTagQuery) {
1565
1571
  try {
1566
1572
  const lang = result.language;
1567
- const createQuery = (source) => lang.query(source);
1573
+ const createQuery = (source) => createTreeSitterQuery(lang, source);
1568
1574
  const tags = extractTagsFromTree(createQuery, result.tree.rootNode, filePath, langId);
1569
1575
  if (tags.length > 0) {
1570
1576
  allTags.set(filePath, tags);
1571
1577
  }
1572
- } catch {
1578
+ } catch (tagErr) {
1579
+ tagExtractionFailures++;
1580
+ if (tagExtractionFailures <= 3) {
1581
+ log("warn", `Tag extraction failed for ${filePath}: ${tagErr instanceof Error ? tagErr.message : String(tagErr)}`);
1582
+ }
1573
1583
  }
1574
1584
  }
1575
1585
  if (result.tree) {
@@ -1594,6 +1604,13 @@ async function runPipelineInner(projectDir, onProgress, branch) {
1594
1604
  total: scannedFiles.length,
1595
1605
  message: `Created ${chunks.length} chunks`
1596
1606
  });
1607
+ log("info", `Tag extraction: ${allTags.size} files tagged, ${tagExtractionFailures} failures out of ${scannedFiles.length} files`);
1608
+ if (tagExtractionFailures > 3) {
1609
+ log("warn", `Tag extraction failed for ${tagExtractionFailures} files (only first 3 logged above)`);
1610
+ }
1611
+ if (allTags.size === 0) {
1612
+ log("warn", "No tags extracted \u2014 depgraph will be skipped. Check tree-sitter language support.");
1613
+ }
1597
1614
  assignChunkIds(chunks, config.chunking.version);
1598
1615
  onProgress?.({
1599
1616
  phase: "finalizing",
@@ -1646,20 +1663,20 @@ async function runPipelineInner(projectDir, onProgress, branch) {
1646
1663
  let depgraphResult;
1647
1664
  if (allTags.size > 0) {
1648
1665
  try {
1649
- const depgraph = await import("./dist-R5ZJ4LX5.js");
1666
+ const { buildReferenceGraph, computePageRank, savePageRank, computeMetrics, saveMetrics, saveGraph } = await import("./dist-YA2BWZB2.js");
1650
1667
  onProgress?.({
1651
1668
  phase: "graph",
1652
1669
  current: 0,
1653
1670
  total: 3,
1654
1671
  message: "Building dependency graph..."
1655
1672
  });
1656
- const graph = depgraph.buildReferenceGraph(allTags, filePaths);
1673
+ const graph = buildReferenceGraph(allTags, filePaths);
1657
1674
  for (const sf of scannedFiles) {
1658
1675
  if (graph.nodes[sf.filePath]) {
1659
1676
  graph.nodes[sf.filePath].sizeBytes = sf.sizeBytes;
1660
1677
  }
1661
1678
  }
1662
- depgraph.saveGraph(graph, projectDir, resolvedBranch);
1679
+ saveGraph(graph, projectDir, resolvedBranch);
1663
1680
  onProgress?.({
1664
1681
  phase: "graph",
1665
1682
  current: 1,
@@ -1672,8 +1689,8 @@ async function runPipelineInner(projectDir, onProgress, branch) {
1672
1689
  total: 3,
1673
1690
  message: "Computing PageRank..."
1674
1691
  });
1675
- const pageRank = depgraph.computePageRank(graph);
1676
- depgraph.savePageRank(pageRank, projectDir, resolvedBranch);
1692
+ const pageRank = computePageRank(graph);
1693
+ savePageRank(pageRank, projectDir, resolvedBranch);
1677
1694
  onProgress?.({
1678
1695
  phase: "ranking",
1679
1696
  current: 2,
@@ -1686,8 +1703,8 @@ async function runPipelineInner(projectDir, onProgress, branch) {
1686
1703
  total: 3,
1687
1704
  message: "Computing metrics..."
1688
1705
  });
1689
- const metrics = depgraph.computeMetrics(graph, pageRank);
1690
- depgraph.saveMetrics(metrics, projectDir, resolvedBranch);
1706
+ const metrics = computeMetrics(graph, pageRank);
1707
+ saveMetrics(metrics, projectDir, resolvedBranch);
1691
1708
  onProgress?.({
1692
1709
  phase: "metrics",
1693
1710
  current: 3,
@@ -1741,6 +1758,98 @@ async function runPipelineInner(projectDir, onProgress, branch) {
1741
1758
  depgraph: depgraphResult
1742
1759
  };
1743
1760
  }
1761
+ async function rebuildDepgraph(projectDir, onProgress, branch) {
1762
+ const resolvedBranch = branch ?? getCurrentBranch(projectDir);
1763
+ acquireCodemapLock(projectDir, resolvedBranch);
1764
+ try {
1765
+ return await rebuildDepgraphInner(projectDir, onProgress, resolvedBranch);
1766
+ } finally {
1767
+ releaseCodemapLock(projectDir, resolvedBranch);
1768
+ }
1769
+ }
1770
+ async function rebuildDepgraphInner(projectDir, onProgress, branch) {
1771
+ const startTime = Date.now();
1772
+ const config = loadCodemapConfig(projectDir);
1773
+ const resolvedBranch = branch ?? getCurrentBranch(projectDir);
1774
+ const codemapDir = codemapBranchDir(projectDir, resolvedBranch);
1775
+ fs9.mkdirSync(codemapDir, { recursive: true });
1776
+ onProgress?.({ phase: "scanning", current: 0, total: 0, message: "Scanning repository..." });
1777
+ const scannedFiles = scanRepository(projectDir, config);
1778
+ const filePaths = scannedFiles.map((f) => f.filePath);
1779
+ onProgress?.({ phase: "scanning", current: scannedFiles.length, total: scannedFiles.length, message: `Found ${scannedFiles.length} files` });
1780
+ if (scannedFiles.length === 0) {
1781
+ return { nodeCount: 0, edgeCount: 0, definitionCount: 0, referenceCount: 0, cycleCount: 0, durationMs: Date.now() - startTime, taggedFiles: 0, totalFiles: 0 };
1782
+ }
1783
+ await initTreeSitter();
1784
+ const allTags = /* @__PURE__ */ new Map();
1785
+ let tagFailures = 0;
1786
+ for (let i = 0; i < filePaths.length; i++) {
1787
+ const filePath = filePaths[i];
1788
+ const absPath = path11.join(projectDir, filePath);
1789
+ let content;
1790
+ try {
1791
+ content = fs9.readFileSync(absPath, "utf-8");
1792
+ } catch {
1793
+ continue;
1794
+ }
1795
+ const langConfig = getLanguageConfig(filePath);
1796
+ const langId = getLanguageId(filePath);
1797
+ if (langConfig && langConfig.hasTagQuery && langId) {
1798
+ const result = await parseAndChunkFile(filePath, content, config.chunking);
1799
+ if (result.tree && result.language) {
1800
+ try {
1801
+ const lang = result.language;
1802
+ const createQuery = (source) => createTreeSitterQuery(lang, source);
1803
+ const tags = extractTagsFromTree(createQuery, result.tree.rootNode, filePath, langId);
1804
+ if (tags.length > 0) {
1805
+ allTags.set(filePath, tags);
1806
+ }
1807
+ } catch {
1808
+ tagFailures++;
1809
+ }
1810
+ }
1811
+ if (result.tree) result.tree.delete();
1812
+ }
1813
+ if ((i + 1) % 100 === 0 || i === filePaths.length - 1) {
1814
+ onProgress?.({ phase: "chunking", current: i + 1, total: scannedFiles.length, message: `Parsed ${i + 1}/${scannedFiles.length} files (${allTags.size} tagged)` });
1815
+ }
1816
+ }
1817
+ log("info", `DepGraph rebuild: ${allTags.size} files tagged, ${tagFailures} failures out of ${scannedFiles.length} files`);
1818
+ if (allTags.size === 0) {
1819
+ log("warn", "No tags extracted \u2014 cannot build depgraph");
1820
+ return { nodeCount: 0, edgeCount: 0, definitionCount: 0, referenceCount: 0, cycleCount: 0, durationMs: Date.now() - startTime, taggedFiles: 0, totalFiles: scannedFiles.length };
1821
+ }
1822
+ const { buildReferenceGraph, computePageRank, savePageRank, computeMetrics, saveMetrics, saveGraph } = await import("./dist-YA2BWZB2.js");
1823
+ onProgress?.({ phase: "graph", current: 0, total: 3, message: "Building dependency graph..." });
1824
+ const graph = buildReferenceGraph(allTags, filePaths);
1825
+ for (const sf of scannedFiles) {
1826
+ if (graph.nodes[sf.filePath]) {
1827
+ graph.nodes[sf.filePath].sizeBytes = sf.sizeBytes;
1828
+ }
1829
+ }
1830
+ saveGraph(graph, projectDir, resolvedBranch);
1831
+ onProgress?.({ phase: "graph", current: 1, total: 3, message: `Graph: ${Object.keys(graph.nodes).length} nodes, ${graph.edges.length} edges` });
1832
+ onProgress?.({ phase: "ranking", current: 1, total: 3, message: "Computing PageRank..." });
1833
+ const pageRank = computePageRank(graph);
1834
+ savePageRank(pageRank, projectDir, resolvedBranch);
1835
+ onProgress?.({ phase: "ranking", current: 2, total: 3, message: `PageRank: ${pageRank.iterations} iterations, converged=${pageRank.converged}` });
1836
+ onProgress?.({ phase: "metrics", current: 2, total: 3, message: "Computing metrics..." });
1837
+ const metrics = computeMetrics(graph, pageRank);
1838
+ saveMetrics(metrics, projectDir, resolvedBranch);
1839
+ onProgress?.({ phase: "metrics", current: 3, total: 3, message: `Metrics: ${metrics.cycles.length} cycles, ${metrics.totalDefinitions} defs, ${metrics.totalReferences} refs` });
1840
+ const durationMs = Date.now() - startTime;
1841
+ log("info", `DepGraph rebuild: ${metrics.totalFiles} nodes, ${metrics.totalEdges} edges, ${metrics.totalDefinitions} defs, ${metrics.totalReferences} refs, ${metrics.cycles.length} cycles in ${(durationMs / 1e3).toFixed(1)}s`);
1842
+ return {
1843
+ nodeCount: metrics.totalFiles,
1844
+ edgeCount: metrics.totalEdges,
1845
+ definitionCount: metrics.totalDefinitions,
1846
+ referenceCount: metrics.totalReferences,
1847
+ cycleCount: metrics.cycles.length,
1848
+ durationMs,
1849
+ taggedFiles: allTags.size,
1850
+ totalFiles: scannedFiles.length
1851
+ };
1852
+ }
1744
1853
  var STORE_BATCH_SIZE = 5e3;
1745
1854
  async function embedAndStore(embedder, texts, chunks, store, batchConfig, onProgress) {
1746
1855
  if (texts.length === 0) return;
@@ -1981,7 +2090,7 @@ async function searchCode(projectDir, query, options = {}) {
1981
2090
  let graphRanks;
1982
2091
  if (weights.graphRank && weights.graphRank > 0) {
1983
2092
  try {
1984
- const { loadPageRankMap } = await import("./dist-R5ZJ4LX5.js");
2093
+ const { loadPageRankMap } = await import("./dist-YA2BWZB2.js");
1985
2094
  graphRanks = loadPageRankMap(projectDir, resolvedBranch) ?? void 0;
1986
2095
  } catch {
1987
2096
  }
@@ -2511,7 +2620,7 @@ var GITHUB_MAX_FILE_BYTES = 100 * 1024 * 1024;
2511
2620
  var GITHUB_WARN_FILE_BYTES = 50 * 1024 * 1024;
2512
2621
  function ensureBranch(projectDir, branchName) {
2513
2622
  if (historyBranchExists(projectDir, branchName)) return;
2514
- const emptyBlob = execFileSync2("git", ["hash-object", "-t", "blob", "--stdin"], {
2623
+ const emptyBlob = execFileSync2("git", ["hash-object", "-w", "-t", "blob", "--stdin"], {
2515
2624
  cwd: projectDir,
2516
2625
  input: "",
2517
2626
  encoding: "utf-8",
@@ -2591,6 +2700,26 @@ async function exportIndex(projectDir, branch) {
2591
2700
  };
2592
2701
  writeAndStage(worktreeDir, "export-meta.json", JSON.stringify(exportMeta, null, 2) + "\n");
2593
2702
  filesExported++;
2703
+ const readme = [
2704
+ "# ULPI CodeMap Index",
2705
+ "",
2706
+ "This branch stores the semantic code search index for this repository.",
2707
+ "It is maintained automatically by [ULPI](https://github.com/ulpi-io/ulpi).",
2708
+ "",
2709
+ "## Contents",
2710
+ "",
2711
+ "- `schema.json` \u2014 Index schema (embedding provider, model, dimensions)",
2712
+ "- `manifest.json` \u2014 File/chunk manifest for incremental updates",
2713
+ "- `config.json` \u2014 CodeMap configuration",
2714
+ "- `stats.json` \u2014 Index statistics",
2715
+ "- `index/lance/` \u2014 LanceDB vector index",
2716
+ "- `index/metadata/` \u2014 BM25 text index + symbol index",
2717
+ "- `export-meta.json` \u2014 Export metadata",
2718
+ "",
2719
+ `_Exported: ${exportMeta.exportedAt}_`
2720
+ ].join("\n") + "\n";
2721
+ writeAndStage(worktreeDir, "README.md", readme);
2722
+ filesExported++;
2594
2723
  return commitInWorktree(worktreeDir, `codemap: export index (${filesExported} files)`);
2595
2724
  });
2596
2725
  const commitMessage = `codemap: export index (${filesExported} files)`;
@@ -3027,6 +3156,7 @@ export {
3027
3156
  isCodemapLocked,
3028
3157
  buildEmbeddingTexts,
3029
3158
  runInitPipeline,
3159
+ rebuildDepgraph,
3030
3160
  DEFAULT_HYBRID_WEIGHTS,
3031
3161
  normalizeScores,
3032
3162
  computePathBoost,
@@ -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.1";
16
+ return "0.1.3";
17
17
  } catch {
18
18
  return "0.0.0";
19
19
  }