codewalk 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 (2) hide show
  1. package/dist/index.js +232 -276
  2. package/package.json +2 -3
package/dist/index.js CHANGED
@@ -1944,203 +1944,9 @@ var {
1944
1944
  Help
1945
1945
  } = import__.default;
1946
1946
 
1947
- // src/commands/init.ts
1948
- var import_picocolors = __toESM(require_picocolors(), 1);
1949
- import * as fs from "fs/promises";
1950
- import * as path from "path";
1951
- var SKILL_TEMPLATE = `# codewalk
1952
-
1953
- You are codewalk, an AI programming assistant built on top of Claude Code.
1954
-
1955
- Your purpose is to give the user more visibility into the changes you are making.
1956
-
1957
- The current functionality you follow is to make changes, asking for permission if needed as you go, and then you provide a brief summary of the changes made after you're done.
1958
-
1959
- In addition to your normal summary, you should also keep track of what you changed in a structured file.
1960
-
1961
- The purpose of the file is to walk the user through the code changes step-by-step so that they can understand the code changes you made, why you made them, and how they relate to other changes you made during that task. If the user follows up with further instructions or changes, you should update the file to track that. A full walkthrough can be found below.
1962
-
1963
- User prompts are surrounded by \`<USER>\` tags, your code changes are surrounded by \`<ASSISTANT>\` tags, example tracking files are surrounded by \`<TRACK>\` tags, and notes are surrounded in \`<NOTE>\` tags.
1964
-
1965
- ## Tracking File Schema
1966
-
1967
- \`\`\`typescript
1968
- type Changeset = {
1969
- // Schema version for forward compatibility
1970
- version: number;
1971
-
1972
- // Git commit SHA this changeset describes
1973
- commit: string;
1974
-
1975
- // Who made the changes (human name, "claude", etc.)
1976
- author: string;
1977
-
1978
- // List of logical changes, each with its own reasoning
1979
- changes: Change[];
1980
- };
1981
-
1982
- type Change = {
1983
- // Human-readable explanation of why this change was made.
1984
- // Should explain the intent, not just describe what changed.
1985
- reasoning: string;
1986
-
1987
- // Files affected by this logical change
1988
- files: FileChange[];
1989
- };
1990
-
1991
- type FileChange = {
1992
- // Path to the file, relative to repo root
1993
- path: string;
1994
-
1995
- // Which hunks from \`git show <commit>\` belong to this change.
1996
- // 1-indexed, in order of appearance in the diff.
1997
- // Example: [1, 3] means the first and third hunks in this file's diff.
1998
- hunks: number[];
1999
- };
2000
- \`\`\`
2001
-
2002
- ## Git Commands Reference
2003
-
2004
- - Get current commit hash: \`git rev-parse --short HEAD\`
2005
- - Get commit author: \`git log -1 --format="%an"\`
2006
- - View commit diff with hunks: \`git show <commit> --format=""\`
2007
- - List files changed: \`git show <commit> --name-only --format=""\`
2008
-
2009
- Hunks are numbered 1, 2, 3... in order of appearance. Each \`@@\` line in the diff starts a new hunk.
2010
-
2011
- ## Handling Refinements
2012
-
2013
- If the user requests changes to something you just did (e.g., "use different colors", "rename that function"):
2014
-
2015
- 1. If it's part of the same logical task, amend the commit: \`git add -A && git commit --amend --no-edit\`
2016
- 2. Update the existing tracking file to reflect the final state
2017
- 3. The reasoning should describe the final result, not the iteration history
2018
-
2019
- If it's a distinct new task, create a new commit and new tracking file.
2020
-
2021
- ## Instructions
2022
-
2023
- 1. Before committing, check if a git repo exists with \`git status\`. Only run \`git init\` if there isn't one.
2024
- 2. After completing a task, commit your changes and automatically create a tracking file at \`.codewalk/<commit-hash>.json\`
2025
- 3. Group hunks by their logical purpose\u2014one reasoning per logical change, even if it spans multiple hunks or multiple files. If a single user request results in changes across several files, those should typically be one change with one reasoning, not separate changes per file.
2026
- 4. Write reasoning that explains *why*, not just *what* (the diff already shows what)
2027
- 5. If a follow-up request refines previous work, amend the commit and update the tracking file
2028
- 6. If a follow-up request is a new task, create a new commit and new tracking file
2029
- 7. After writing the tracking file, validate it with: \`python3 -c "import json; json.load(open('.codewalk/<commit-hash>.json'))"\`
2030
-
2031
- Do not wait for the user to ask for the tracking file\u2014create it automatically as part of finishing each task.
2032
-
2033
- **Important:** Always complete the tracking step after every task, even in long conversations with multiple tasks. Do not skip this step regardless of conversation length or number of previous tasks.
2034
- `;
2035
- var CLAUDE_MD_CONTENT = `# codewalk
2036
-
2037
- **CRITICAL REQUIREMENT**: You MUST create git commits for your changes AND create tracking files after EVERY task. No exceptions.
2038
-
2039
- ## Workflow (MANDATORY):
2040
- 1. Make your code changes using Write/Edit tools
2041
- 2. **COMMIT the changes**: \`git add -A && git commit -m "descriptive message"\`
2042
- 3. Get the commit hash: \`git rev-parse --short HEAD\`
2043
- 4. **IMMEDIATELY** create \`.codewalk/<commit-hash>.json\` with the tracking data
2044
- 5. Commit the tracking file: \`git add .codewalk/<hash>.json && git commit -m "Add tracking file for <hash>"\`
2045
- 6. Only THEN respond to the user
2046
-
2047
- **DO NOT** skip commits. **DO NOT** respond to the user until both the code commit AND tracking file commit are done.
2048
-
2049
- See \`.claude/skills/codewalk.md\` for the complete schema and examples.
2050
- `;
2051
- var SETTINGS_CONTENT = {
2052
- hooks: {
2053
- Stop: [
2054
- {
2055
- hooks: [
2056
- {
2057
- type: "prompt",
2058
- prompt: "Check if the assistant made code changes in this session. If code changes were made, verify: 1) Changes were committed with git, 2) A tracking file was created at .codewalk/<commit-hash>.json, 3) The tracking file was also committed. If any of these are missing, block stopping and instruct to complete the codewalk workflow. If no code changes were made, or all steps are complete, approve stopping.",
2059
- timeout: 30
2060
- }
2061
- ]
2062
- }
2063
- ]
2064
- }
2065
- };
2066
- async function fileExists(filePath) {
2067
- try {
2068
- await fs.access(filePath);
2069
- return true;
2070
- } catch {
2071
- return false;
2072
- }
2073
- }
2074
- async function initCommand(options) {
2075
- const { cwd } = options;
2076
- console.log(import_picocolors.default.bold(`Initializing codewalk...
2077
- `));
2078
- const skillsDir = path.join(cwd, ".claude", "skills");
2079
- await fs.mkdir(skillsDir, { recursive: true });
2080
- const skillPath = path.join(skillsDir, "codewalk.md");
2081
- const skillExists = await fileExists(skillPath);
2082
- if (!skillExists) {
2083
- await fs.writeFile(skillPath, SKILL_TEMPLATE);
2084
- console.log(import_picocolors.default.green("\u2713") + " Created .claude/skills/codewalk.md");
2085
- } else {
2086
- console.log(import_picocolors.default.yellow("\u25CB") + " .claude/skills/codewalk.md already exists, skipping");
2087
- }
2088
- const claudePath = path.join(cwd, "CLAUDE.md");
2089
- let claudeContent = "";
2090
- try {
2091
- claudeContent = await fs.readFile(claudePath, "utf-8");
2092
- } catch {}
2093
- if (!claudeContent.includes(".claude/skills/codewalk.md")) {
2094
- const newContent = claudeContent ? claudeContent + `
2095
-
2096
- ` + CLAUDE_MD_CONTENT : CLAUDE_MD_CONTENT;
2097
- await fs.writeFile(claudePath, newContent);
2098
- console.log(import_picocolors.default.green("\u2713") + " Updated CLAUDE.md with codewalk instructions");
2099
- } else {
2100
- console.log(import_picocolors.default.yellow("\u25CB") + " CLAUDE.md already references codewalk, skipping");
2101
- }
2102
- const codewalkDir = path.join(cwd, ".codewalk");
2103
- const codewalkExists = await fileExists(codewalkDir);
2104
- await fs.mkdir(codewalkDir, { recursive: true });
2105
- if (!codewalkExists) {
2106
- console.log(import_picocolors.default.green("\u2713") + " Created .codewalk/ directory");
2107
- } else {
2108
- console.log(import_picocolors.default.yellow("\u25CB") + " .codewalk/ directory already exists");
2109
- }
2110
- const settingsPath = path.join(cwd, ".claude", "settings.local.json");
2111
- let existingSettings = {};
2112
- try {
2113
- const content = await fs.readFile(settingsPath, "utf-8");
2114
- existingSettings = JSON.parse(content);
2115
- } catch {}
2116
- const hasStopHook = existingSettings.hooks && typeof existingSettings.hooks === "object" && "Stop" in existingSettings.hooks;
2117
- if (!hasStopHook) {
2118
- const mergedSettings = {
2119
- ...existingSettings,
2120
- hooks: {
2121
- ...existingSettings.hooks || {},
2122
- ...SETTINGS_CONTENT.hooks
2123
- }
2124
- };
2125
- await fs.writeFile(settingsPath, JSON.stringify(mergedSettings, null, 2) + `
2126
- `);
2127
- console.log(import_picocolors.default.green("\u2713") + " Added Stop hook to .claude/settings.local.json");
2128
- } else {
2129
- console.log(import_picocolors.default.yellow("\u25CB") + " Stop hook already configured, skipping");
2130
- }
2131
- console.log(import_picocolors.default.bold(`
2132
- codewalk initialized successfully!`));
2133
- console.log(`
2134
- Next steps:`);
2135
- console.log(" 1. Start Claude Code in this directory");
2136
- console.log(" 2. Make changes - Claude will automatically track them");
2137
- console.log(" 3. Run " + import_picocolors.default.cyan("codewalk visualize") + " to browse changes");
2138
- }
2139
-
2140
1947
  // src/commands/visualize.ts
2141
- var import_picocolors2 = __toESM(require_picocolors(), 1);
2142
- import * as fs5 from "fs";
2143
- import * as path7 from "path";
1948
+ var import_picocolors = __toESM(require_picocolors(), 1);
1949
+ import * as path5 from "path";
2144
1950
 
2145
1951
  // ../../node_modules/@opentui/core/index-cr95zpf8.js
2146
1952
  import { Buffer as Buffer2 } from "buffer";
@@ -2186,9 +1992,9 @@ var tree_sitter_zig_default = "./tree-sitter-zig-e78zbjpm.wasm";
2186
1992
  // ../../node_modules/@opentui/core/index-cr95zpf8.js
2187
1993
  import { resolve as resolve2, isAbsolute, parse } from "path";
2188
1994
  import { existsSync } from "fs";
2189
- import { basename, join as join2 } from "path";
1995
+ import { basename, join } from "path";
2190
1996
  import os from "os";
2191
- import path2 from "path";
1997
+ import path from "path";
2192
1998
  import { EventEmitter as EventEmitter4 } from "events";
2193
1999
  import { dlopen, toArrayBuffer as toArrayBuffer4, JSCallback, ptr as ptr3 } from "bun:ffi";
2194
2000
  import { existsSync as existsSync2 } from "fs";
@@ -2200,7 +2006,7 @@ import { EventEmitter as EventEmitter6 } from "events";
2200
2006
  import util from "util";
2201
2007
  import { EventEmitter as EventEmitter8 } from "events";
2202
2008
  import { Console } from "console";
2203
- import fs2 from "fs";
2009
+ import fs from "fs";
2204
2010
  import path4 from "path";
2205
2011
  import util2 from "util";
2206
2012
  import { Writable } from "stream";
@@ -5916,14 +5722,14 @@ function getParsers() {
5916
5722
  }
5917
5723
  return _cachedParsers;
5918
5724
  }
5919
- function isBunfsPath(path3) {
5920
- return path3.includes("$bunfs") || /^B:[\\/]~BUN/i.test(path3);
5725
+ function isBunfsPath(path2) {
5726
+ return path2.includes("$bunfs") || /^B:[\\/]~BUN/i.test(path2);
5921
5727
  }
5922
5728
  function getBunfsRootPath() {
5923
5729
  return process.platform === "win32" ? "B:\\~BUN\\root" : "/$bunfs/root";
5924
5730
  }
5925
5731
  function normalizeBunfsPath(fileName) {
5926
- return join2(getBunfsRootPath(), basename(fileName));
5732
+ return join(getBunfsRootPath(), basename(fileName));
5927
5733
  }
5928
5734
  registerEnvVar({
5929
5735
  name: "OTUI_TREE_SITTER_WORKER_PATH",
@@ -5932,7 +5738,7 @@ registerEnvVar({
5932
5738
  default: ""
5933
5739
  });
5934
5740
  var DEFAULT_PARSERS = getParsers();
5935
- var isUrl = (path3) => path3.startsWith("http://") || path3.startsWith("https://");
5741
+ var isUrl = (path2) => path2.startsWith("http://") || path2.startsWith("https://");
5936
5742
 
5937
5743
  class TreeSitterClient extends EventEmitter3 {
5938
5744
  initialized = false;
@@ -6032,25 +5838,25 @@ class TreeSitterClient extends EventEmitter3 {
6032
5838
  this.addFiletypeParser(parser);
6033
5839
  }
6034
5840
  }
6035
- resolvePath(path3) {
6036
- if (isUrl(path3)) {
6037
- return path3;
5841
+ resolvePath(path2) {
5842
+ if (isUrl(path2)) {
5843
+ return path2;
6038
5844
  }
6039
- if (isBunfsPath(path3)) {
6040
- return normalizeBunfsPath(parse(path3).base);
5845
+ if (isBunfsPath(path2)) {
5846
+ return normalizeBunfsPath(parse(path2).base);
6041
5847
  }
6042
- if (!isAbsolute(path3)) {
6043
- return resolve2(path3);
5848
+ if (!isAbsolute(path2)) {
5849
+ return resolve2(path2);
6044
5850
  }
6045
- return path3;
5851
+ return path2;
6046
5852
  }
6047
5853
  addFiletypeParser(filetypeParser) {
6048
5854
  const resolvedParser = {
6049
5855
  ...filetypeParser,
6050
5856
  wasm: this.resolvePath(filetypeParser.wasm),
6051
5857
  queries: {
6052
- highlights: filetypeParser.queries.highlights.map((path3) => this.resolvePath(path3)),
6053
- injections: filetypeParser.queries.injections?.map((path3) => this.resolvePath(path3))
5858
+ highlights: filetypeParser.queries.highlights.map((path2) => this.resolvePath(path2)),
5859
+ injections: filetypeParser.queries.injections?.map((path2) => this.resolvePath(path2))
6054
5860
  }
6055
5861
  };
6056
5862
  this.worker?.postMessage({ type: "ADD_FILETYPE_PARSER", filetypeParser: resolvedParser });
@@ -6471,20 +6277,20 @@ class DataPathsManager extends EventEmitter4 {
6471
6277
  if (this._globalConfigPath === undefined) {
6472
6278
  const homeDir = os.homedir();
6473
6279
  const xdgConfigHome = env.XDG_CONFIG_HOME;
6474
- const baseConfigDir = xdgConfigHome || path2.join(homeDir, ".config");
6475
- this._globalConfigPath = path2.join(baseConfigDir, this._appName);
6280
+ const baseConfigDir = xdgConfigHome || path.join(homeDir, ".config");
6281
+ this._globalConfigPath = path.join(baseConfigDir, this._appName);
6476
6282
  }
6477
6283
  return this._globalConfigPath;
6478
6284
  }
6479
6285
  get globalConfigFile() {
6480
6286
  if (this._globalConfigFile === undefined) {
6481
- this._globalConfigFile = path2.join(this.globalConfigPath, "init.ts");
6287
+ this._globalConfigFile = path.join(this.globalConfigPath, "init.ts");
6482
6288
  }
6483
6289
  return this._globalConfigFile;
6484
6290
  }
6485
6291
  get localConfigFile() {
6486
6292
  if (this._localConfigFile === undefined) {
6487
- this._localConfigFile = path2.join(process.cwd(), `.${this._appName}.ts`);
6293
+ this._localConfigFile = path.join(process.cwd(), `.${this._appName}.ts`);
6488
6294
  }
6489
6295
  return this._localConfigFile;
6490
6296
  }
@@ -6492,8 +6298,8 @@ class DataPathsManager extends EventEmitter4 {
6492
6298
  if (this._globalDataPath === undefined) {
6493
6299
  const homeDir = os.homedir();
6494
6300
  const xdgDataHome = env.XDG_DATA_HOME;
6495
- const baseDataDir = xdgDataHome || path2.join(homeDir, ".local/share");
6496
- this._globalDataPath = path2.join(baseDataDir, this._appName);
6301
+ const baseDataDir = xdgDataHome || path.join(homeDir, ".local/share");
6302
+ this._globalDataPath = path.join(baseDataDir, this._appName);
6497
6303
  }
6498
6304
  return this._globalDataPath;
6499
6305
  }
@@ -13276,7 +13082,7 @@ class TerminalConsole extends EventEmitter8 {
13276
13082
  }
13277
13083
  const content = logLines.join(`
13278
13084
  `);
13279
- fs2.writeFileSync(filepath, content, "utf8");
13085
+ fs.writeFileSync(filepath, content, "utf8");
13280
13086
  console.info(`Console logs saved to: ${filename}`);
13281
13087
  } catch (error) {
13282
13088
  console.error(`Failed to save console logs:`, error);
@@ -17416,16 +17222,16 @@ class Diff {
17416
17222
  }
17417
17223
  }
17418
17224
  }
17419
- addToPath(path3, added, removed, oldPosInc, options) {
17420
- const last = path3.lastComponent;
17225
+ addToPath(path2, added, removed, oldPosInc, options) {
17226
+ const last = path2.lastComponent;
17421
17227
  if (last && !options.oneChangePerToken && last.added === added && last.removed === removed) {
17422
17228
  return {
17423
- oldPos: path3.oldPos + oldPosInc,
17229
+ oldPos: path2.oldPos + oldPosInc,
17424
17230
  lastComponent: { count: last.count + 1, added, removed, previousComponent: last.previousComponent }
17425
17231
  };
17426
17232
  } else {
17427
17233
  return {
17428
- oldPos: path3.oldPos + oldPosInc,
17234
+ oldPos: path2.oldPos + oldPosInc,
17429
17235
  lastComponent: { count: 1, added, removed, previousComponent: last }
17430
17236
  };
17431
17237
  }
@@ -20872,15 +20678,15 @@ function getCommitFileDiffs(cwd, commitSha) {
20872
20678
  }
20873
20679
 
20874
20680
  // src/utils/tracking.ts
20875
- import * as fs3 from "fs/promises";
20876
- import * as path3 from "path";
20681
+ import * as fs2 from "fs/promises";
20682
+ import * as path2 from "path";
20877
20683
  async function loadTrackingFiles(trackingDir, commits) {
20878
20684
  const result = [];
20879
20685
  for (const commit of commits) {
20880
- const trackingPath = path3.join(trackingDir, `${commit.shortSha}.json`);
20686
+ const trackingPath = path2.join(trackingDir, `${commit.shortSha}.json`);
20881
20687
  let tracking = null;
20882
20688
  try {
20883
- const content = await fs3.readFile(trackingPath, "utf-8");
20689
+ const content = await fs2.readFile(trackingPath, "utf-8");
20884
20690
  tracking = JSON.parse(content);
20885
20691
  } catch {}
20886
20692
  result.push({ commit, tracking });
@@ -20938,8 +20744,8 @@ function aggregateByReasoning(cwd, trackedCommits) {
20938
20744
  }
20939
20745
 
20940
20746
  // src/utils/settings.ts
20941
- import * as fs4 from "fs/promises";
20942
- import * as path5 from "path";
20747
+ import * as fs3 from "fs/promises";
20748
+ import * as path3 from "path";
20943
20749
  import * as os2 from "os";
20944
20750
  import { execSync as execSync2 } from "child_process";
20945
20751
  var DEFAULT_SETTINGS = {
@@ -20970,7 +20776,7 @@ function parseYamlFrontmatter(content) {
20970
20776
  }
20971
20777
  function expandTilde(filepath) {
20972
20778
  if (filepath.startsWith("~/")) {
20973
- return path5.join(os2.homedir(), filepath.slice(2));
20779
+ return path3.join(os2.homedir(), filepath.slice(2));
20974
20780
  }
20975
20781
  if (filepath === "~") {
20976
20782
  return os2.homedir();
@@ -20984,15 +20790,15 @@ function getRepoName(cwd) {
20984
20790
  encoding: "utf-8",
20985
20791
  stdio: ["pipe", "pipe", "pipe"]
20986
20792
  }).trim();
20987
- return path5.basename(repoRoot);
20793
+ return path3.basename(repoRoot);
20988
20794
  } catch {
20989
- return path5.basename(cwd);
20795
+ return path3.basename(cwd);
20990
20796
  }
20991
20797
  }
20992
20798
  async function loadSettings(cwd) {
20993
- const settingsPath = path5.join(cwd, ".claude", "codewalk.local.md");
20799
+ const settingsPath = path3.join(cwd, ".claude", "codewalk.local.md");
20994
20800
  try {
20995
- const content = await fs4.readFile(settingsPath, "utf-8");
20801
+ const content = await fs3.readFile(settingsPath, "utf-8");
20996
20802
  const parsed = parseYamlFrontmatter(content);
20997
20803
  if (!parsed) {
20998
20804
  return { ...DEFAULT_SETTINGS, globalDir: expandTilde(DEFAULT_SETTINGS.globalDir) };
@@ -21009,9 +20815,184 @@ async function loadSettings(cwd) {
21009
20815
  function getTrackingDirectory(cwd, settings) {
21010
20816
  if (settings.storage === "global") {
21011
20817
  const repoName = getRepoName(cwd);
21012
- return path5.join(settings.globalDir, repoName);
20818
+ return path3.join(settings.globalDir, repoName);
20819
+ }
20820
+ return path3.join(cwd, ".codewalk");
20821
+ }
20822
+
20823
+ // src/utils/file-watcher.ts
20824
+ import * as fs4 from "fs";
20825
+
20826
+ class FileWatcher {
20827
+ trackingWatcher = null;
20828
+ branchWatcher = null;
20829
+ trackingDebounceTimer = null;
20830
+ branchDebounceTimer = null;
20831
+ pollTimer = null;
20832
+ destroyed = false;
20833
+ lastBranchContent = null;
20834
+ lastTrackingFiles = new Set;
20835
+ trackingDir;
20836
+ gitHeadPath;
20837
+ repoRoot;
20838
+ onTrackingChange;
20839
+ onBranchChange;
20840
+ pollIntervalMs;
20841
+ constructor(options) {
20842
+ this.trackingDir = options.trackingDir;
20843
+ this.gitHeadPath = options.gitHeadPath;
20844
+ this.repoRoot = options.repoRoot;
20845
+ this.onTrackingChange = options.onTrackingChange;
20846
+ this.onBranchChange = options.onBranchChange;
20847
+ this.pollIntervalMs = options.pollIntervalMs ?? 1e4;
20848
+ this.initializeState();
20849
+ this.startWatchers();
20850
+ this.startPolling();
20851
+ }
20852
+ initializeState() {
20853
+ try {
20854
+ this.lastBranchContent = fs4.readFileSync(this.gitHeadPath, "utf-8");
20855
+ } catch {
20856
+ this.lastBranchContent = null;
20857
+ }
20858
+ try {
20859
+ const files = fs4.readdirSync(this.trackingDir);
20860
+ this.lastTrackingFiles = new Set(files.filter((f) => f.endsWith(".json")));
20861
+ } catch {
20862
+ this.lastTrackingFiles = new Set;
20863
+ }
20864
+ }
20865
+ startWatchers() {
20866
+ this.startTrackingWatcher();
20867
+ this.startBranchWatcher();
20868
+ }
20869
+ startTrackingWatcher() {
20870
+ if (this.destroyed)
20871
+ return;
20872
+ try {
20873
+ fs4.mkdirSync(this.trackingDir, { recursive: true });
20874
+ this.trackingWatcher = fs4.watch(this.trackingDir, (eventType, filename) => {
20875
+ if (filename && filename.endsWith(".json")) {
20876
+ this.scheduleTrackingChange();
20877
+ }
20878
+ });
20879
+ this.trackingWatcher.on("error", () => {
20880
+ this.restartTrackingWatcher();
20881
+ });
20882
+ } catch {}
20883
+ }
20884
+ startBranchWatcher() {
20885
+ if (this.destroyed)
20886
+ return;
20887
+ try {
20888
+ this.branchWatcher = fs4.watch(this.gitHeadPath, () => {
20889
+ this.scheduleBranchChange();
20890
+ });
20891
+ this.branchWatcher.on("error", () => {
20892
+ this.restartBranchWatcher();
20893
+ });
20894
+ } catch {}
20895
+ }
20896
+ restartTrackingWatcher() {
20897
+ if (this.destroyed)
20898
+ return;
20899
+ if (this.trackingWatcher) {
20900
+ try {
20901
+ this.trackingWatcher.close();
20902
+ } catch {}
20903
+ this.trackingWatcher = null;
20904
+ }
20905
+ setTimeout(() => {
20906
+ if (!this.destroyed) {
20907
+ this.startTrackingWatcher();
20908
+ }
20909
+ }, 1000);
20910
+ }
20911
+ restartBranchWatcher() {
20912
+ if (this.destroyed)
20913
+ return;
20914
+ if (this.branchWatcher) {
20915
+ try {
20916
+ this.branchWatcher.close();
20917
+ } catch {}
20918
+ this.branchWatcher = null;
20919
+ }
20920
+ setTimeout(() => {
20921
+ if (!this.destroyed) {
20922
+ this.startBranchWatcher();
20923
+ }
20924
+ }, 1000);
20925
+ }
20926
+ scheduleTrackingChange() {
20927
+ if (this.trackingDebounceTimer) {
20928
+ clearTimeout(this.trackingDebounceTimer);
20929
+ }
20930
+ this.trackingDebounceTimer = setTimeout(() => {
20931
+ this.trackingDebounceTimer = null;
20932
+ this.onTrackingChange();
20933
+ }, 100);
20934
+ }
20935
+ scheduleBranchChange() {
20936
+ if (this.branchDebounceTimer) {
20937
+ clearTimeout(this.branchDebounceTimer);
20938
+ }
20939
+ this.branchDebounceTimer = setTimeout(() => {
20940
+ this.branchDebounceTimer = null;
20941
+ this.onBranchChange();
20942
+ }, 100);
20943
+ }
20944
+ startPolling() {
20945
+ this.pollTimer = setInterval(() => {
20946
+ this.poll();
20947
+ }, this.pollIntervalMs);
20948
+ }
20949
+ poll() {
20950
+ if (this.destroyed)
20951
+ return;
20952
+ try {
20953
+ const currentBranchContent = fs4.readFileSync(this.gitHeadPath, "utf-8");
20954
+ if (currentBranchContent !== this.lastBranchContent) {
20955
+ this.lastBranchContent = currentBranchContent;
20956
+ this.scheduleBranchChange();
20957
+ }
20958
+ } catch {}
20959
+ try {
20960
+ const files = fs4.readdirSync(this.trackingDir);
20961
+ const currentFiles = new Set(files.filter((f) => f.endsWith(".json")));
20962
+ const hasChanges = currentFiles.size !== this.lastTrackingFiles.size || [...currentFiles].some((f) => !this.lastTrackingFiles.has(f));
20963
+ if (hasChanges) {
20964
+ this.lastTrackingFiles = currentFiles;
20965
+ this.scheduleTrackingChange();
20966
+ }
20967
+ } catch {}
20968
+ }
20969
+ destroy() {
20970
+ this.destroyed = true;
20971
+ if (this.trackingWatcher) {
20972
+ try {
20973
+ this.trackingWatcher.close();
20974
+ } catch {}
20975
+ this.trackingWatcher = null;
20976
+ }
20977
+ if (this.branchWatcher) {
20978
+ try {
20979
+ this.branchWatcher.close();
20980
+ } catch {}
20981
+ this.branchWatcher = null;
20982
+ }
20983
+ if (this.trackingDebounceTimer) {
20984
+ clearTimeout(this.trackingDebounceTimer);
20985
+ this.trackingDebounceTimer = null;
20986
+ }
20987
+ if (this.branchDebounceTimer) {
20988
+ clearTimeout(this.branchDebounceTimer);
20989
+ this.branchDebounceTimer = null;
20990
+ }
20991
+ if (this.pollTimer) {
20992
+ clearInterval(this.pollTimer);
20993
+ this.pollTimer = null;
20994
+ }
21013
20995
  }
21014
- return path5.join(cwd, ".codewalk");
21015
20996
  }
21016
20997
 
21017
20998
  // src/tui/app.ts
@@ -21547,15 +21528,15 @@ async function loadBranchData(cwd, trackingDir) {
21547
21528
  async function visualizeCommand(options) {
21548
21529
  const { cwd } = options;
21549
21530
  if (!isGitRepo(cwd)) {
21550
- console.error(import_picocolors2.default.red("Error: Not a git repository"));
21531
+ console.error(import_picocolors.default.red("Error: Not a git repository"));
21551
21532
  process.exit(1);
21552
21533
  }
21553
21534
  const repoRoot = getRepoRoot(cwd);
21554
21535
  const settings = await loadSettings(repoRoot);
21555
21536
  const trackingDir = getTrackingDirectory(repoRoot, settings);
21556
- console.log(import_picocolors2.default.dim("Loading tracking data..."));
21537
+ console.log(import_picocolors.default.dim("Loading tracking data..."));
21557
21538
  const { branch, reasoningGroups } = await loadBranchData(repoRoot, trackingDir);
21558
- console.log(import_picocolors2.default.dim("Starting visualizer..."));
21539
+ console.log(import_picocolors.default.dim("Starting visualizer..."));
21559
21540
  const renderer = await createCliRenderer({
21560
21541
  exitOnCtrlC: true,
21561
21542
  useAlternateScreen: true,
@@ -21565,10 +21546,7 @@ async function visualizeCommand(options) {
21565
21546
  const state = createAppState(branch, reasoningGroups, trackingDir);
21566
21547
  const treeView = new TreeView(renderer, state);
21567
21548
  let currentBranch = branch;
21568
- const gitHeadPath = path7.join(repoRoot, ".git", "HEAD");
21569
- let trackingWatcher = null;
21570
- let branchWatcher = null;
21571
- let debounceTimer = null;
21549
+ const gitHeadPath = path5.join(repoRoot, ".git", "HEAD");
21572
21550
  const reloadData = async (branchChanged = false) => {
21573
21551
  const { branch: newBranch, reasoningGroups: newGroups } = await loadBranchData(repoRoot, trackingDir);
21574
21552
  if (branchChanged || newBranch !== currentBranch) {
@@ -21578,33 +21556,19 @@ async function visualizeCommand(options) {
21578
21556
  treeView.updateData(newGroups);
21579
21557
  }
21580
21558
  };
21581
- try {
21582
- await fs5.promises.mkdir(trackingDir, { recursive: true });
21583
- trackingWatcher = fs5.watch(trackingDir, (eventType, filename) => {
21584
- if (filename && filename.endsWith(".json")) {
21585
- if (debounceTimer)
21586
- clearTimeout(debounceTimer);
21587
- debounceTimer = setTimeout(() => reloadData(), 100);
21588
- }
21589
- });
21590
- } catch {}
21591
- try {
21592
- branchWatcher = fs5.watch(gitHeadPath, () => {
21593
- if (debounceTimer)
21594
- clearTimeout(debounceTimer);
21595
- debounceTimer = setTimeout(() => reloadData(true), 100);
21596
- });
21597
- } catch {}
21559
+ const fileWatcher = new FileWatcher({
21560
+ trackingDir,
21561
+ gitHeadPath,
21562
+ repoRoot,
21563
+ onTrackingChange: () => reloadData(),
21564
+ onBranchChange: () => reloadData(true),
21565
+ pollIntervalMs: 1e4
21566
+ });
21598
21567
  renderer.keyInput.on("keypress", (event) => {
21599
21568
  const key = event.name;
21600
21569
  switch (key) {
21601
21570
  case "q":
21602
- if (trackingWatcher)
21603
- trackingWatcher.close();
21604
- if (branchWatcher)
21605
- branchWatcher.close();
21606
- if (debounceTimer)
21607
- clearTimeout(debounceTimer);
21571
+ fileWatcher.destroy();
21608
21572
  treeView.destroy();
21609
21573
  renderer.destroy();
21610
21574
  process.exit(0);
@@ -21636,15 +21600,7 @@ async function visualizeCommand(options) {
21636
21600
 
21637
21601
  // src/index.ts
21638
21602
  var program2 = new Command;
21639
- program2.name("codewalk").description("CLI tool for visualizing AI-assisted code changes").version("0.1.0");
21640
- program2.command("init").description("Initialize codewalk in the current project").action(async () => {
21641
- try {
21642
- await initCommand({ cwd: process.cwd() });
21643
- } catch (error) {
21644
- console.error("Error:", error instanceof Error ? error.message : error);
21645
- process.exit(1);
21646
- }
21647
- });
21603
+ program2.name("codewalk").description("CLI tool for visualizing AI-assisted code changes").version("0.1.3");
21648
21604
  program2.command("visualize").alias("viz").description("Open TUI to visualize tracked changes on the current branch").action(async () => {
21649
21605
  try {
21650
21606
  await visualizeCommand({ cwd: process.cwd() });
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "codewalk",
3
- "version": "0.1.1",
3
+ "version": "0.1.3",
4
4
  "type": "module",
5
5
  "description": "CLI tool for visualizing AI-assisted code changes",
6
6
  "bin": {
@@ -15,13 +15,12 @@
15
15
  "typecheck": "tsc --noEmit"
16
16
  },
17
17
  "dependencies": {
18
- "@codewalk/types": "*",
19
18
  "@opentui/core": "^0.1.69",
20
19
  "commander": "^12.0.0",
21
20
  "picocolors": "^1.0.0"
22
21
  },
23
22
  "devDependencies": {
24
- "@types/node": "^20.0.0",
23
+ "bun-types": "latest",
25
24
  "typescript": "^5.0.0"
26
25
  },
27
26
  "engines": {