codewalk 0.1.0 → 0.1.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.
package/dist/index.js CHANGED
@@ -1948,9 +1948,9 @@ var {
1948
1948
  var import_picocolors = __toESM(require_picocolors(), 1);
1949
1949
  import * as fs from "fs/promises";
1950
1950
  import * as path from "path";
1951
- var SKILL_TEMPLATE = `# Code Walker
1951
+ var SKILL_TEMPLATE = `# codewalk
1952
1952
 
1953
- You are Code Walker, an AI programming assistant built on top of Claude Code.
1953
+ You are codewalk, an AI programming assistant built on top of Claude Code.
1954
1954
 
1955
1955
  Your purpose is to give the user more visibility into the changes you are making.
1956
1956
 
@@ -2021,18 +2021,18 @@ If it's a distinct new task, create a new commit and new tracking file.
2021
2021
  ## Instructions
2022
2022
 
2023
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 \`.codewalker/<commit-hash>.json\`
2024
+ 2. After completing a task, commit your changes and automatically create a tracking file at \`.codewalk/<commit-hash>.json\`
2025
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
2026
  4. Write reasoning that explains *why*, not just *what* (the diff already shows what)
2027
2027
  5. If a follow-up request refines previous work, amend the commit and update the tracking file
2028
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('.codewalker/<commit-hash>.json'))"\`
2029
+ 7. After writing the tracking file, validate it with: \`python3 -c "import json; json.load(open('.codewalk/<commit-hash>.json'))"\`
2030
2030
 
2031
2031
  Do not wait for the user to ask for the tracking file\u2014create it automatically as part of finishing each task.
2032
2032
 
2033
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
2034
  `;
2035
- var CLAUDE_MD_CONTENT = `# Code Walker
2035
+ var CLAUDE_MD_CONTENT = `# codewalk
2036
2036
 
2037
2037
  **CRITICAL REQUIREMENT**: You MUST create git commits for your changes AND create tracking files after EVERY task. No exceptions.
2038
2038
 
@@ -2040,13 +2040,13 @@ var CLAUDE_MD_CONTENT = `# Code Walker
2040
2040
  1. Make your code changes using Write/Edit tools
2041
2041
  2. **COMMIT the changes**: \`git add -A && git commit -m "descriptive message"\`
2042
2042
  3. Get the commit hash: \`git rev-parse --short HEAD\`
2043
- 4. **IMMEDIATELY** create \`.codewalker/<commit-hash>.json\` with the tracking data
2044
- 5. Commit the tracking file: \`git add .codewalker/<hash>.json && git commit -m "Add tracking file for <hash>"\`
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
2045
  6. Only THEN respond to the user
2046
2046
 
2047
2047
  **DO NOT** skip commits. **DO NOT** respond to the user until both the code commit AND tracking file commit are done.
2048
2048
 
2049
- See \`.claude/skills/codewalker.md\` for the complete schema and examples.
2049
+ See \`.claude/skills/codewalk.md\` for the complete schema and examples.
2050
2050
  `;
2051
2051
  var SETTINGS_CONTENT = {
2052
2052
  hooks: {
@@ -2055,7 +2055,7 @@ var SETTINGS_CONTENT = {
2055
2055
  hooks: [
2056
2056
  {
2057
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 .codewalker/<commit-hash>.json, 3) The tracking file was also committed. If any of these are missing, block stopping and instruct to complete the codewalker workflow. If no code changes were made, or all steps are complete, approve stopping.",
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
2059
  timeout: 30
2060
2060
  }
2061
2061
  ]
@@ -2073,39 +2073,39 @@ async function fileExists(filePath) {
2073
2073
  }
2074
2074
  async function initCommand(options) {
2075
2075
  const { cwd } = options;
2076
- console.log(import_picocolors.default.bold(`Initializing CodeWalker...
2076
+ console.log(import_picocolors.default.bold(`Initializing codewalk...
2077
2077
  `));
2078
2078
  const skillsDir = path.join(cwd, ".claude", "skills");
2079
2079
  await fs.mkdir(skillsDir, { recursive: true });
2080
- const skillPath = path.join(skillsDir, "codewalker.md");
2080
+ const skillPath = path.join(skillsDir, "codewalk.md");
2081
2081
  const skillExists = await fileExists(skillPath);
2082
2082
  if (!skillExists) {
2083
2083
  await fs.writeFile(skillPath, SKILL_TEMPLATE);
2084
- console.log(import_picocolors.default.green("\u2713") + " Created .claude/skills/codewalker.md");
2084
+ console.log(import_picocolors.default.green("\u2713") + " Created .claude/skills/codewalk.md");
2085
2085
  } else {
2086
- console.log(import_picocolors.default.yellow("\u25CB") + " .claude/skills/codewalker.md already exists, skipping");
2086
+ console.log(import_picocolors.default.yellow("\u25CB") + " .claude/skills/codewalk.md already exists, skipping");
2087
2087
  }
2088
2088
  const claudePath = path.join(cwd, "CLAUDE.md");
2089
2089
  let claudeContent = "";
2090
2090
  try {
2091
2091
  claudeContent = await fs.readFile(claudePath, "utf-8");
2092
2092
  } catch {}
2093
- if (!claudeContent.includes(".claude/skills/codewalker.md")) {
2093
+ if (!claudeContent.includes(".claude/skills/codewalk.md")) {
2094
2094
  const newContent = claudeContent ? claudeContent + `
2095
2095
 
2096
2096
  ` + CLAUDE_MD_CONTENT : CLAUDE_MD_CONTENT;
2097
2097
  await fs.writeFile(claudePath, newContent);
2098
- console.log(import_picocolors.default.green("\u2713") + " Updated CLAUDE.md with CodeWalker instructions");
2098
+ console.log(import_picocolors.default.green("\u2713") + " Updated CLAUDE.md with codewalk instructions");
2099
2099
  } else {
2100
- console.log(import_picocolors.default.yellow("\u25CB") + " CLAUDE.md already references CodeWalker, skipping");
2100
+ console.log(import_picocolors.default.yellow("\u25CB") + " CLAUDE.md already references codewalk, skipping");
2101
2101
  }
2102
- const codewalkerDir = path.join(cwd, ".codewalker");
2103
- const codewalkerExists = await fileExists(codewalkerDir);
2104
- await fs.mkdir(codewalkerDir, { recursive: true });
2105
- if (!codewalkerExists) {
2106
- console.log(import_picocolors.default.green("\u2713") + " Created .codewalker/ directory");
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
2107
  } else {
2108
- console.log(import_picocolors.default.yellow("\u25CB") + " .codewalker/ directory already exists");
2108
+ console.log(import_picocolors.default.yellow("\u25CB") + " .codewalk/ directory already exists");
2109
2109
  }
2110
2110
  const settingsPath = path.join(cwd, ".claude", "settings.local.json");
2111
2111
  let existingSettings = {};
@@ -2129,20 +2129,20 @@ async function initCommand(options) {
2129
2129
  console.log(import_picocolors.default.yellow("\u25CB") + " Stop hook already configured, skipping");
2130
2130
  }
2131
2131
  console.log(import_picocolors.default.bold(`
2132
- CodeWalker initialized successfully!`));
2132
+ codewalk initialized successfully!`));
2133
2133
  console.log(`
2134
2134
  Next steps:`);
2135
2135
  console.log(" 1. Start Claude Code in this directory");
2136
2136
  console.log(" 2. Make changes - Claude will automatically track them");
2137
- console.log(" 3. Run " + import_picocolors.default.cyan("codewalker visualize") + " to browse changes");
2137
+ console.log(" 3. Run " + import_picocolors.default.cyan("codewalk visualize") + " to browse changes");
2138
2138
  }
2139
2139
 
2140
2140
  // src/commands/visualize.ts
2141
2141
  var import_picocolors2 = __toESM(require_picocolors(), 1);
2142
- import * as fs4 from "fs";
2143
- import * as path5 from "path";
2142
+ import * as fs5 from "fs";
2143
+ import * as path7 from "path";
2144
2144
 
2145
- // ../../node_modules/@opentui/core/index-zj0wwh9d.js
2145
+ // ../../node_modules/@opentui/core/index-cr95zpf8.js
2146
2146
  import { Buffer as Buffer2 } from "buffer";
2147
2147
  import { EventEmitter } from "events";
2148
2148
  import { EventEmitter as EventEmitter2 } from "events";
@@ -2183,7 +2183,7 @@ var highlights_default5 = "./highlights-hk7bwhj4.scm";
2183
2183
  // ../../node_modules/@opentui/core/assets/zig/tree-sitter-zig.wasm
2184
2184
  var tree_sitter_zig_default = "./tree-sitter-zig-e78zbjpm.wasm";
2185
2185
 
2186
- // ../../node_modules/@opentui/core/index-zj0wwh9d.js
2186
+ // ../../node_modules/@opentui/core/index-cr95zpf8.js
2187
2187
  import { resolve as resolve2, isAbsolute, parse } from "path";
2188
2188
  import { existsSync } from "fs";
2189
2189
  import { basename, join as join2 } from "path";
@@ -4525,6 +4525,7 @@ class KeyEvent {
4525
4525
  baseCode;
4526
4526
  repeated;
4527
4527
  _defaultPrevented = false;
4528
+ _propagationStopped = false;
4528
4529
  constructor(key) {
4529
4530
  this.name = key.name;
4530
4531
  this.ctrl = key.ctrl;
@@ -4547,23 +4548,36 @@ class KeyEvent {
4547
4548
  get defaultPrevented() {
4548
4549
  return this._defaultPrevented;
4549
4550
  }
4551
+ get propagationStopped() {
4552
+ return this._propagationStopped;
4553
+ }
4550
4554
  preventDefault() {
4551
4555
  this._defaultPrevented = true;
4552
4556
  }
4557
+ stopPropagation() {
4558
+ this._propagationStopped = true;
4559
+ }
4553
4560
  }
4554
4561
 
4555
4562
  class PasteEvent {
4556
4563
  text;
4557
4564
  _defaultPrevented = false;
4565
+ _propagationStopped = false;
4558
4566
  constructor(text) {
4559
4567
  this.text = text;
4560
4568
  }
4561
4569
  get defaultPrevented() {
4562
4570
  return this._defaultPrevented;
4563
4571
  }
4572
+ get propagationStopped() {
4573
+ return this._propagationStopped;
4574
+ }
4564
4575
  preventDefault() {
4565
4576
  this._defaultPrevented = true;
4566
4577
  }
4578
+ stopPropagation() {
4579
+ this._propagationStopped = true;
4580
+ }
4567
4581
  }
4568
4582
 
4569
4583
  class KeyHandler extends EventEmitter {
@@ -4615,10 +4629,22 @@ class InternalKeyHandler extends KeyHandler {
4615
4629
  }
4616
4630
  emitWithPriority(event, ...args) {
4617
4631
  let hasGlobalListeners = false;
4618
- try {
4619
- hasGlobalListeners = super.emit(event, ...args);
4620
- } catch (error) {
4621
- console.error(`[KeyHandler] Error in global ${event} handler:`, error);
4632
+ const globalListeners = this.listeners(event);
4633
+ if (globalListeners.length > 0) {
4634
+ hasGlobalListeners = true;
4635
+ for (const listener of globalListeners) {
4636
+ try {
4637
+ listener(...args);
4638
+ } catch (error) {
4639
+ console.error(`[KeyHandler] Error in global ${event} handler:`, error);
4640
+ }
4641
+ if (event === "keypress" || event === "keyrelease" || event === "paste") {
4642
+ const keyEvent = args[0];
4643
+ if (keyEvent.propagationStopped) {
4644
+ return hasGlobalListeners;
4645
+ }
4646
+ }
4647
+ }
4622
4648
  }
4623
4649
  const renderableSet = this.renderableHandlers.get(event);
4624
4650
  const renderableHandlers = renderableSet && renderableSet.size > 0 ? [...renderableSet] : [];
@@ -4629,6 +4655,8 @@ class InternalKeyHandler extends KeyHandler {
4629
4655
  const keyEvent = args[0];
4630
4656
  if (keyEvent.defaultPrevented)
4631
4657
  return hasGlobalListeners || hasRenderableListeners;
4658
+ if (keyEvent.propagationStopped)
4659
+ return hasGlobalListeners || hasRenderableListeners;
4632
4660
  }
4633
4661
  for (const handler of renderableHandlers) {
4634
4662
  try {
@@ -4636,6 +4664,12 @@ class InternalKeyHandler extends KeyHandler {
4636
4664
  } catch (error) {
4637
4665
  console.error(`[KeyHandler] Error in renderable ${event} handler:`, error);
4638
4666
  }
4667
+ if (event === "keypress" || event === "keyrelease" || event === "paste") {
4668
+ const keyEvent = args[0];
4669
+ if (keyEvent.propagationStopped) {
4670
+ return hasGlobalListeners || hasRenderableListeners;
4671
+ }
4672
+ }
4639
4673
  }
4640
4674
  }
4641
4675
  return hasGlobalListeners || hasRenderableListeners;
@@ -8658,6 +8692,26 @@ function getOpenTUILib(libPath) {
8658
8692
  args: ["ptr", "i32", "i32", "u32", "u32", "u32"],
8659
8693
  returns: "void"
8660
8694
  },
8695
+ clearCurrentHitGrid: {
8696
+ args: ["ptr"],
8697
+ returns: "void"
8698
+ },
8699
+ hitGridPushScissorRect: {
8700
+ args: ["ptr", "i32", "i32", "u32", "u32"],
8701
+ returns: "void"
8702
+ },
8703
+ hitGridPopScissorRect: {
8704
+ args: ["ptr"],
8705
+ returns: "void"
8706
+ },
8707
+ hitGridClearScissorRects: {
8708
+ args: ["ptr"],
8709
+ returns: "void"
8710
+ },
8711
+ addToCurrentHitGridClipped: {
8712
+ args: ["ptr", "i32", "i32", "u32", "u32", "u32"],
8713
+ returns: "void"
8714
+ },
8661
8715
  checkHit: {
8662
8716
  args: ["ptr", "u32", "u32"],
8663
8717
  returns: "u32"
@@ -9754,6 +9808,21 @@ class FFIRenderLib {
9754
9808
  addToHitGrid(renderer, x, y, width, height, id) {
9755
9809
  this.opentui.symbols.addToHitGrid(renderer, x, y, width, height, id);
9756
9810
  }
9811
+ clearCurrentHitGrid(renderer) {
9812
+ this.opentui.symbols.clearCurrentHitGrid(renderer);
9813
+ }
9814
+ hitGridPushScissorRect(renderer, x, y, width, height) {
9815
+ this.opentui.symbols.hitGridPushScissorRect(renderer, x, y, width, height);
9816
+ }
9817
+ hitGridPopScissorRect(renderer) {
9818
+ this.opentui.symbols.hitGridPopScissorRect(renderer);
9819
+ }
9820
+ hitGridClearScissorRects(renderer) {
9821
+ this.opentui.symbols.hitGridClearScissorRects(renderer);
9822
+ }
9823
+ addToCurrentHitGridClipped(renderer, x, y, width, height, id) {
9824
+ this.opentui.symbols.addToCurrentHitGridClipped(renderer, x, y, width, height, id);
9825
+ }
9757
9826
  checkHit(renderer, x, y) {
9758
9827
  return this.opentui.symbols.checkHit(renderer, x, y);
9759
9828
  }
@@ -11134,9 +11203,9 @@ class Renderable extends BaseRenderable {
11134
11203
  if (this._translateX === value)
11135
11204
  return;
11136
11205
  this._translateX = value;
11137
- this.requestRender();
11138
11206
  if (this.parent)
11139
11207
  this.parent.childrenPrimarySortDirty = true;
11208
+ this.requestRender();
11140
11209
  }
11141
11210
  get translateY() {
11142
11211
  return this._translateY;
@@ -11145,9 +11214,9 @@ class Renderable extends BaseRenderable {
11145
11214
  if (this._translateY === value)
11146
11215
  return;
11147
11216
  this._translateY = value;
11148
- this.requestRender();
11149
11217
  if (this.parent)
11150
11218
  this.parent.childrenPrimarySortDirty = true;
11219
+ this.requestRender();
11151
11220
  }
11152
11221
  get x() {
11153
11222
  if (this.parent) {
@@ -11780,7 +11849,9 @@ class Renderable extends BaseRenderable {
11780
11849
  x: scissorRect.x,
11781
11850
  y: scissorRect.y,
11782
11851
  width: scissorRect.width,
11783
- height: scissorRect.height
11852
+ height: scissorRect.height,
11853
+ screenX: this.x,
11854
+ screenY: this.y
11784
11855
  });
11785
11856
  }
11786
11857
  const visibleChildren = this._getVisibleChildren();
@@ -11996,6 +12067,7 @@ class RootRenderable extends Renderable {
11996
12067
  }
11997
12068
  this.renderList.length = 0;
11998
12069
  this.updateLayout(deltaTime, this.renderList);
12070
+ this._ctx.clearHitGridScissorRects();
11999
12071
  for (let i = 1;i < this.renderList.length; i++) {
12000
12072
  const command = this.renderList[i];
12001
12073
  switch (command.action) {
@@ -12006,9 +12078,11 @@ class RootRenderable extends Renderable {
12006
12078
  break;
12007
12079
  case "pushScissorRect":
12008
12080
  buffer.pushScissorRect(command.x, command.y, command.width, command.height);
12081
+ this._ctx.pushHitGridScissorRect(command.screenX, command.screenY, command.width, command.height);
12009
12082
  break;
12010
12083
  case "popScissorRect":
12011
12084
  buffer.popScissorRect();
12085
+ this._ctx.popHitGridScissorRect();
12012
12086
  break;
12013
12087
  case "pushOpacity":
12014
12088
  buffer.pushOpacity(command.opacity);
@@ -13524,6 +13598,8 @@ class CliRenderer extends EventEmitter9 {
13524
13598
  exitSignals;
13525
13599
  _exitListenersAdded = false;
13526
13600
  _isDestroyed = false;
13601
+ _destroyPending = false;
13602
+ _destroyFinalized = false;
13527
13603
  nextRenderBuffer;
13528
13604
  currentRenderBuffer;
13529
13605
  _isRunning = false;
@@ -13609,6 +13685,7 @@ class CliRenderer extends EventEmitter9 {
13609
13685
  }).bind(this);
13610
13686
  _capabilities = null;
13611
13687
  _latestPointer = { x: 0, y: 0 };
13688
+ _hasPointer = false;
13612
13689
  _currentFocusedRenderable = null;
13613
13690
  lifecyclePasses = new Set;
13614
13691
  _openConsoleOnError = true;
@@ -13790,11 +13867,26 @@ Captured output:
13790
13867
  }
13791
13868
  this._currentFocusedRenderable = renderable;
13792
13869
  }
13870
+ setCapturedRenderable(renderable) {
13871
+ if (this.capturedRenderable === renderable) {
13872
+ return;
13873
+ }
13874
+ this.capturedRenderable = renderable;
13875
+ }
13793
13876
  addToHitGrid(x, y, width, height, id) {
13794
13877
  if (id !== this.capturedRenderable?.num) {
13795
13878
  this.lib.addToHitGrid(this.rendererPtr, x, y, width, height, id);
13796
13879
  }
13797
13880
  }
13881
+ pushHitGridScissorRect(x, y, width, height) {
13882
+ this.lib.hitGridPushScissorRect(this.rendererPtr, x, y, width, height);
13883
+ }
13884
+ popHitGridScissorRect() {
13885
+ this.lib.hitGridPopScissorRect(this.rendererPtr);
13886
+ }
13887
+ clearHitGridScissorRects() {
13888
+ this.lib.hitGridClearScissorRects(this.rendererPtr);
13889
+ }
13798
13890
  get widthMethod() {
13799
13891
  const caps = this.capabilities;
13800
13892
  return caps?.unicode === "wcwidth" ? "wcwidth" : "unicode";
@@ -13989,7 +14081,11 @@ Captured output:
13989
14081
  const backgroundColor = this.backgroundColor.toInts();
13990
14082
  const newlines = " ".repeat(this.width) + `
13991
14083
  `.repeat(space);
13992
- clear = ANSI.setRgbBackground(backgroundColor[0], backgroundColor[1], backgroundColor[2]) + newlines + ANSI.resetBackground;
14084
+ if (backgroundColor[3] === 0) {
14085
+ clear = newlines;
14086
+ } else {
14087
+ clear = ANSI.setRgbBackground(backgroundColor[0], backgroundColor[1], backgroundColor[2]) + newlines + ANSI.resetBackground;
14088
+ }
13993
14089
  }
13994
14090
  this.writeOut(flush + move + output + clear);
13995
14091
  return true;
@@ -14000,7 +14096,7 @@ Captured output:
14000
14096
  }
14001
14097
  disableMouse() {
14002
14098
  this._useMouse = false;
14003
- this.capturedRenderable = undefined;
14099
+ this.setCapturedRenderable(undefined);
14004
14100
  this.mouseParser.reset();
14005
14101
  this.lib.disableMouse(this.rendererPtr);
14006
14102
  }
@@ -14118,6 +14214,7 @@ Captured output:
14118
14214
  }
14119
14215
  this._latestPointer.x = mouseEvent.x;
14120
14216
  this._latestPointer.y = mouseEvent.y;
14217
+ this._hasPointer = true;
14121
14218
  if (this._console.visible) {
14122
14219
  const consoleBounds = this._console.bounds;
14123
14220
  if (mouseEvent.x >= consoleBounds.x && mouseEvent.x < consoleBounds.x + consoleBounds.width && mouseEvent.y >= consoleBounds.y && mouseEvent.y < consoleBounds.y + consoleBounds.height) {
@@ -14128,7 +14225,7 @@ Captured output:
14128
14225
  }
14129
14226
  }
14130
14227
  if (mouseEvent.type === "scroll") {
14131
- const maybeRenderableId2 = this.lib.checkHit(this.rendererPtr, mouseEvent.x, mouseEvent.y);
14228
+ const maybeRenderableId2 = this.hitTest(mouseEvent.x, mouseEvent.y);
14132
14229
  const maybeRenderable2 = Renderable.renderablesByNumber.get(maybeRenderableId2);
14133
14230
  if (maybeRenderable2) {
14134
14231
  const event2 = new MouseEvent(maybeRenderable2, mouseEvent);
@@ -14136,7 +14233,7 @@ Captured output:
14136
14233
  }
14137
14234
  return true;
14138
14235
  }
14139
- const maybeRenderableId = this.lib.checkHit(this.rendererPtr, mouseEvent.x, mouseEvent.y);
14236
+ const maybeRenderableId = this.hitTest(mouseEvent.x, mouseEvent.y);
14140
14237
  const sameElement = maybeRenderableId === this.lastOverRenderableNum;
14141
14238
  this.lastOverRenderableNum = maybeRenderableId;
14142
14239
  const maybeRenderable = Renderable.renderablesByNumber.get(maybeRenderableId);
@@ -14205,20 +14302,20 @@ Captured output:
14205
14302
  }
14206
14303
  this.lastOverRenderable = this.capturedRenderable;
14207
14304
  this.lastOverRenderableNum = this.capturedRenderable.num;
14208
- this.capturedRenderable = undefined;
14305
+ this.setCapturedRenderable(undefined);
14209
14306
  this.requestRender();
14210
14307
  }
14211
14308
  let event = undefined;
14212
14309
  if (maybeRenderable) {
14213
14310
  if (mouseEvent.type === "drag" && mouseEvent.button === 0) {
14214
- this.capturedRenderable = maybeRenderable;
14311
+ this.setCapturedRenderable(maybeRenderable);
14215
14312
  } else {
14216
- this.capturedRenderable = undefined;
14313
+ this.setCapturedRenderable(undefined);
14217
14314
  }
14218
14315
  event = new MouseEvent(maybeRenderable, mouseEvent);
14219
14316
  maybeRenderable.processMouseEvent(event);
14220
14317
  } else {
14221
- this.capturedRenderable = undefined;
14318
+ this.setCapturedRenderable(undefined);
14222
14319
  this.lastOverRenderable = undefined;
14223
14320
  }
14224
14321
  if (!event?.defaultPrevented && mouseEvent.type === "down" && this.currentSelection) {
@@ -14228,6 +14325,9 @@ Captured output:
14228
14325
  }
14229
14326
  return false;
14230
14327
  }
14328
+ hitTest(x, y) {
14329
+ return this.lib.checkHit(this.rendererPtr, x, y);
14330
+ }
14231
14331
  takeMemorySnapshot() {
14232
14332
  if (this._isDestroyed)
14233
14333
  return;
@@ -14288,7 +14388,7 @@ Captured output:
14288
14388
  this._terminalWidth = width;
14289
14389
  this._terminalHeight = height;
14290
14390
  this.queryPixelResolution();
14291
- this.capturedRenderable = undefined;
14391
+ this.setCapturedRenderable(undefined);
14292
14392
  this.mouseParser.reset();
14293
14393
  if (this._splitHeight > 0) {
14294
14394
  if (width < prevWidth) {
@@ -14500,6 +14600,17 @@ Captured output:
14500
14600
  if (this._isDestroyed)
14501
14601
  return;
14502
14602
  this._isDestroyed = true;
14603
+ this._destroyPending = true;
14604
+ if (this.rendering) {
14605
+ return;
14606
+ }
14607
+ this.finalizeDestroy();
14608
+ }
14609
+ finalizeDestroy() {
14610
+ if (this._destroyFinalized)
14611
+ return;
14612
+ this._destroyFinalized = true;
14613
+ this._destroyPending = false;
14503
14614
  process.removeListener("SIGWINCH", this.sigwinchHandler);
14504
14615
  process.removeListener("uncaughtException", this.handleError);
14505
14616
  process.removeListener("unhandledRejection", this.handleError);
@@ -14531,7 +14642,7 @@ Captured output:
14531
14642
  }
14532
14643
  this._isRunning = false;
14533
14644
  this.waitingForPixelResolution = false;
14534
- this.capturedRenderable = undefined;
14645
+ this.setCapturedRenderable(undefined);
14535
14646
  try {
14536
14647
  this.root.destroyRecursively();
14537
14648
  } catch (e) {
@@ -14576,65 +14687,71 @@ Captured output:
14576
14687
  clearTimeout(this.renderTimeout);
14577
14688
  this.renderTimeout = null;
14578
14689
  }
14579
- const now = Date.now();
14580
- const elapsed = now - this.lastTime;
14581
- const deltaTime = elapsed;
14582
- this.lastTime = now;
14583
- this.frameCount++;
14584
- if (now - this.lastFpsTime >= 1000) {
14585
- this.currentFps = this.frameCount;
14586
- this.frameCount = 0;
14587
- this.lastFpsTime = now;
14588
- }
14589
- this.renderStats.frameCount++;
14590
- this.renderStats.fps = this.currentFps;
14591
- const overallStart = performance.now();
14592
- const frameRequests = Array.from(this.animationRequest.values());
14593
- this.animationRequest.clear();
14594
- const animationRequestStart = performance.now();
14595
- frameRequests.forEach((callback) => {
14596
- callback(deltaTime);
14597
- this.dropLive();
14598
- });
14599
- const animationRequestEnd = performance.now();
14600
- const animationRequestTime = animationRequestEnd - animationRequestStart;
14601
- const start = performance.now();
14602
- for (const frameCallback of this.frameCallbacks) {
14603
- try {
14604
- await frameCallback(deltaTime);
14605
- } catch (error) {
14606
- console.error("Error in frame callback:", error);
14607
- }
14608
- }
14609
- const end = performance.now();
14610
- this.renderStats.frameCallbackTime = end - start;
14611
- this.root.render(this.nextRenderBuffer, deltaTime);
14612
- for (const postProcessFn of this.postProcessFns) {
14613
- postProcessFn(this.nextRenderBuffer, deltaTime);
14614
- }
14615
- this._console.renderToBuffer(this.nextRenderBuffer);
14616
- if (!this._isDestroyed) {
14617
- this.renderNative();
14618
- const overallFrameTime = performance.now() - overallStart;
14619
- this.lib.updateStats(this.rendererPtr, overallFrameTime, this.renderStats.fps, this.renderStats.frameCallbackTime);
14620
- if (this.gatherStats) {
14621
- this.collectStatSample(overallFrameTime);
14622
- }
14623
- if (this._isRunning || this.immediateRerenderRequested) {
14624
- const targetFrameTime = this.immediateRerenderRequested ? this.minTargetFrameTime : this.targetFrameTime;
14625
- const delay = Math.max(1, targetFrameTime - Math.floor(overallFrameTime));
14626
- this.immediateRerenderRequested = false;
14627
- this.renderTimeout = setTimeout(() => {
14690
+ try {
14691
+ const now = Date.now();
14692
+ const elapsed = now - this.lastTime;
14693
+ const deltaTime = elapsed;
14694
+ this.lastTime = now;
14695
+ this.frameCount++;
14696
+ if (now - this.lastFpsTime >= 1000) {
14697
+ this.currentFps = this.frameCount;
14698
+ this.frameCount = 0;
14699
+ this.lastFpsTime = now;
14700
+ }
14701
+ this.renderStats.frameCount++;
14702
+ this.renderStats.fps = this.currentFps;
14703
+ const overallStart = performance.now();
14704
+ const frameRequests = Array.from(this.animationRequest.values());
14705
+ this.animationRequest.clear();
14706
+ const animationRequestStart = performance.now();
14707
+ for (const callback of frameRequests) {
14708
+ callback(deltaTime);
14709
+ this.dropLive();
14710
+ }
14711
+ const animationRequestEnd = performance.now();
14712
+ const animationRequestTime = animationRequestEnd - animationRequestStart;
14713
+ const start = performance.now();
14714
+ for (const frameCallback of this.frameCallbacks) {
14715
+ try {
14716
+ await frameCallback(deltaTime);
14717
+ } catch (error) {
14718
+ console.error("Error in frame callback:", error);
14719
+ }
14720
+ }
14721
+ const end = performance.now();
14722
+ this.renderStats.frameCallbackTime = end - start;
14723
+ this.root.render(this.nextRenderBuffer, deltaTime);
14724
+ for (const postProcessFn of this.postProcessFns) {
14725
+ postProcessFn(this.nextRenderBuffer, deltaTime);
14726
+ }
14727
+ this._console.renderToBuffer(this.nextRenderBuffer);
14728
+ if (!this._isDestroyed) {
14729
+ this.renderNative();
14730
+ const overallFrameTime = performance.now() - overallStart;
14731
+ this.lib.updateStats(this.rendererPtr, overallFrameTime, this.renderStats.fps, this.renderStats.frameCallbackTime);
14732
+ if (this.gatherStats) {
14733
+ this.collectStatSample(overallFrameTime);
14734
+ }
14735
+ if (this._isRunning || this.immediateRerenderRequested) {
14736
+ const targetFrameTime = this.immediateRerenderRequested ? this.minTargetFrameTime : this.targetFrameTime;
14737
+ const delay = Math.max(1, targetFrameTime - Math.floor(overallFrameTime));
14738
+ this.immediateRerenderRequested = false;
14739
+ this.renderTimeout = setTimeout(() => {
14740
+ this.renderTimeout = null;
14741
+ this.loop();
14742
+ }, delay);
14743
+ } else {
14744
+ clearTimeout(this.renderTimeout);
14628
14745
  this.renderTimeout = null;
14629
- this.loop();
14630
- }, delay);
14631
- } else {
14632
- clearTimeout(this.renderTimeout);
14633
- this.renderTimeout = null;
14746
+ }
14634
14747
  }
14748
+ } finally {
14749
+ this.rendering = false;
14750
+ if (this._destroyPending) {
14751
+ this.finalizeDestroy();
14752
+ }
14753
+ this.resolveIdleIfNeeded();
14635
14754
  }
14636
- this.rendering = false;
14637
- this.resolveIdleIfNeeded();
14638
14755
  }
14639
14756
  intermediateRender() {
14640
14757
  this.immediateRerenderRequested = true;
@@ -14739,7 +14856,7 @@ Captured output:
14739
14856
  requestSelectionUpdate() {
14740
14857
  if (this.currentSelection?.isSelecting) {
14741
14858
  const pointer = this._latestPointer;
14742
- const maybeRenderableId = this.lib.checkHit(this.rendererPtr, pointer.x, pointer.y);
14859
+ const maybeRenderableId = this.hitTest(pointer.x, pointer.y);
14743
14860
  const maybeRenderable = Renderable.renderablesByNumber.get(maybeRenderableId);
14744
14861
  this.updateSelection(maybeRenderable, pointer.x, pointer.y);
14745
14862
  }
@@ -17950,7 +18067,8 @@ class TextRenderable extends TextBufferRenderable {
17950
18067
  const chunks = this.rootTextNode.gatherWithInheritedStyle({
17951
18068
  fg: this._defaultFg,
17952
18069
  bg: this._defaultBg,
17953
- attributes: this._defaultAttributes
18070
+ attributes: this._defaultAttributes,
18071
+ link: undefined
17954
18072
  });
17955
18073
  this.textBuffer.setStyledText(new StyledText(chunks));
17956
18074
  this.refreshLocalSelection();
@@ -20664,6 +20782,16 @@ function isGitRepo(cwd) {
20664
20782
  return false;
20665
20783
  }
20666
20784
  }
20785
+ function getRepoRoot(cwd) {
20786
+ try {
20787
+ return execSync("git rev-parse --show-toplevel", {
20788
+ cwd,
20789
+ encoding: "utf-8"
20790
+ }).trim();
20791
+ } catch {
20792
+ throw new Error("Not a git repository");
20793
+ }
20794
+ }
20667
20795
  function getMainBranch(cwd) {
20668
20796
  try {
20669
20797
  const result = execSync('git symbolic-ref refs/remotes/origin/HEAD 2>/dev/null || echo "refs/heads/main"', {
@@ -20746,10 +20874,10 @@ function getCommitFileDiffs(cwd, commitSha) {
20746
20874
  // src/utils/tracking.ts
20747
20875
  import * as fs3 from "fs/promises";
20748
20876
  import * as path3 from "path";
20749
- async function loadTrackingFiles(cwd, commits) {
20877
+ async function loadTrackingFiles(trackingDir, commits) {
20750
20878
  const result = [];
20751
20879
  for (const commit of commits) {
20752
- const trackingPath = path3.join(cwd, ".codewalker", `${commit.shortSha}.json`);
20880
+ const trackingPath = path3.join(trackingDir, `${commit.shortSha}.json`);
20753
20881
  let tracking = null;
20754
20882
  try {
20755
20883
  const content = await fs3.readFile(trackingPath, "utf-8");
@@ -20809,11 +20937,89 @@ function aggregateByReasoning(cwd, trackedCommits) {
20809
20937
  return Array.from(reasoningMap.values()).filter((group) => group.files.length > 0).sort((a, b) => b.files.length - a.files.length);
20810
20938
  }
20811
20939
 
20940
+ // src/utils/settings.ts
20941
+ import * as fs4 from "fs/promises";
20942
+ import * as path5 from "path";
20943
+ import * as os2 from "os";
20944
+ import { execSync as execSync2 } from "child_process";
20945
+ var DEFAULT_SETTINGS = {
20946
+ storage: "local",
20947
+ autoCommit: true,
20948
+ globalDir: "~/.codewalk"
20949
+ };
20950
+ function parseYamlFrontmatter(content) {
20951
+ const match = content.match(/^---\r?\n([\s\S]*?)\r?\n---/);
20952
+ if (!match)
20953
+ return null;
20954
+ const yaml = match[1];
20955
+ const result = {};
20956
+ for (const line of yaml.split(`
20957
+ `)) {
20958
+ const colonIndex = line.indexOf(":");
20959
+ if (colonIndex === -1)
20960
+ continue;
20961
+ const key = line.slice(0, colonIndex).trim();
20962
+ let value = line.slice(colonIndex + 1).trim();
20963
+ if (value === "true")
20964
+ value = true;
20965
+ else if (value === "false")
20966
+ value = false;
20967
+ result[key] = value;
20968
+ }
20969
+ return result;
20970
+ }
20971
+ function expandTilde(filepath) {
20972
+ if (filepath.startsWith("~/")) {
20973
+ return path5.join(os2.homedir(), filepath.slice(2));
20974
+ }
20975
+ if (filepath === "~") {
20976
+ return os2.homedir();
20977
+ }
20978
+ return filepath;
20979
+ }
20980
+ function getRepoName(cwd) {
20981
+ try {
20982
+ const repoRoot = execSync2("git rev-parse --show-toplevel", {
20983
+ cwd,
20984
+ encoding: "utf-8",
20985
+ stdio: ["pipe", "pipe", "pipe"]
20986
+ }).trim();
20987
+ return path5.basename(repoRoot);
20988
+ } catch {
20989
+ return path5.basename(cwd);
20990
+ }
20991
+ }
20992
+ async function loadSettings(cwd) {
20993
+ const settingsPath = path5.join(cwd, ".claude", "codewalk.local.md");
20994
+ try {
20995
+ const content = await fs4.readFile(settingsPath, "utf-8");
20996
+ const parsed = parseYamlFrontmatter(content);
20997
+ if (!parsed) {
20998
+ return { ...DEFAULT_SETTINGS, globalDir: expandTilde(DEFAULT_SETTINGS.globalDir) };
20999
+ }
21000
+ return {
21001
+ storage: parsed.storage === "global" ? "global" : "local",
21002
+ autoCommit: parsed.autoCommit !== false,
21003
+ globalDir: expandTilde(String(parsed.globalDir || DEFAULT_SETTINGS.globalDir))
21004
+ };
21005
+ } catch {
21006
+ return { ...DEFAULT_SETTINGS, globalDir: expandTilde(DEFAULT_SETTINGS.globalDir) };
21007
+ }
21008
+ }
21009
+ function getTrackingDirectory(cwd, settings) {
21010
+ if (settings.storage === "global") {
21011
+ const repoName = getRepoName(cwd);
21012
+ return path5.join(settings.globalDir, repoName);
21013
+ }
21014
+ return path5.join(cwd, ".codewalk");
21015
+ }
21016
+
20812
21017
  // src/tui/app.ts
20813
- function createAppState(branch, reasoningGroups) {
21018
+ function createAppState(branch, reasoningGroups, trackingDir) {
20814
21019
  return {
20815
21020
  branch,
20816
21021
  reasoningGroups,
21022
+ trackingDir,
20817
21023
  selectedIndex: 0,
20818
21024
  expandedReasonings: new Set,
20819
21025
  expandedFiles: new Set
@@ -21018,14 +21224,26 @@ class TreeView {
21018
21224
  for (const child of headerChildren) {
21019
21225
  this.headerBox.remove(child.id);
21020
21226
  }
21227
+ const headerContainer = new BoxRenderable(this.renderer, {
21228
+ width: "100%",
21229
+ flexDirection: "row",
21230
+ paddingLeft: 1,
21231
+ paddingRight: 1
21232
+ });
21021
21233
  const totalChanges = this.state.reasoningGroups.length;
21022
21234
  const headerText = new TextRenderable(this.renderer, {
21023
- content: ` CodeWalker - ${this.state.branch} (${totalChanges} logical changes)`,
21235
+ content: ` codewalk - ${this.state.branch} (${totalChanges} logical changes)`,
21024
21236
  fg: "#88ccff",
21025
- paddingTop: 0,
21026
- paddingLeft: 1
21237
+ flexGrow: 1
21238
+ });
21239
+ headerContainer.add(headerText);
21240
+ const trackingPath = this.state.trackingDir.replace(process.env.HOME || "", "~");
21241
+ const trackingText = new TextRenderable(this.renderer, {
21242
+ content: trackingPath,
21243
+ fg: "#666666"
21027
21244
  });
21028
- this.headerBox.add(headerText);
21245
+ headerContainer.add(trackingText);
21246
+ this.headerBox.add(headerContainer);
21029
21247
  }
21030
21248
  buildContent() {
21031
21249
  if (this.state.reasoningGroups.length === 0) {
@@ -21312,13 +21530,13 @@ class TreeView {
21312
21530
  }
21313
21531
 
21314
21532
  // src/commands/visualize.ts
21315
- async function loadBranchData(cwd) {
21533
+ async function loadBranchData(cwd, trackingDir) {
21316
21534
  const branch = getCurrentBranch(cwd);
21317
21535
  const commits = getBranchCommits(cwd);
21318
21536
  if (commits.length === 0) {
21319
21537
  return { branch, reasoningGroups: [] };
21320
21538
  }
21321
- const allTrackedCommits = await loadTrackingFiles(cwd, commits);
21539
+ const allTrackedCommits = await loadTrackingFiles(trackingDir, commits);
21322
21540
  const trackedCommits = getTrackedCommits(allTrackedCommits);
21323
21541
  if (trackedCommits.length === 0) {
21324
21542
  return { branch, reasoningGroups: [] };
@@ -21332,8 +21550,11 @@ async function visualizeCommand(options) {
21332
21550
  console.error(import_picocolors2.default.red("Error: Not a git repository"));
21333
21551
  process.exit(1);
21334
21552
  }
21553
+ const repoRoot = getRepoRoot(cwd);
21554
+ const settings = await loadSettings(repoRoot);
21555
+ const trackingDir = getTrackingDirectory(repoRoot, settings);
21335
21556
  console.log(import_picocolors2.default.dim("Loading tracking data..."));
21336
- const { branch, reasoningGroups } = await loadBranchData(cwd);
21557
+ const { branch, reasoningGroups } = await loadBranchData(repoRoot, trackingDir);
21337
21558
  console.log(import_picocolors2.default.dim("Starting visualizer..."));
21338
21559
  const renderer = await createCliRenderer({
21339
21560
  exitOnCtrlC: true,
@@ -21341,16 +21562,15 @@ async function visualizeCommand(options) {
21341
21562
  useMouse: true,
21342
21563
  backgroundColor: "#0f0f1a"
21343
21564
  });
21344
- const state = createAppState(branch, reasoningGroups);
21565
+ const state = createAppState(branch, reasoningGroups, trackingDir);
21345
21566
  const treeView = new TreeView(renderer, state);
21346
21567
  let currentBranch = branch;
21347
- const codewalkerDir = path5.join(cwd, ".codewalker");
21348
- const gitHeadPath = path5.join(cwd, ".git", "HEAD");
21568
+ const gitHeadPath = path7.join(repoRoot, ".git", "HEAD");
21349
21569
  let trackingWatcher = null;
21350
21570
  let branchWatcher = null;
21351
21571
  let debounceTimer = null;
21352
21572
  const reloadData = async (branchChanged = false) => {
21353
- const { branch: newBranch, reasoningGroups: newGroups } = await loadBranchData(cwd);
21573
+ const { branch: newBranch, reasoningGroups: newGroups } = await loadBranchData(repoRoot, trackingDir);
21354
21574
  if (branchChanged || newBranch !== currentBranch) {
21355
21575
  currentBranch = newBranch;
21356
21576
  treeView.updateData(newGroups, newBranch);
@@ -21359,7 +21579,8 @@ async function visualizeCommand(options) {
21359
21579
  }
21360
21580
  };
21361
21581
  try {
21362
- trackingWatcher = fs4.watch(codewalkerDir, (eventType, filename) => {
21582
+ await fs5.promises.mkdir(trackingDir, { recursive: true });
21583
+ trackingWatcher = fs5.watch(trackingDir, (eventType, filename) => {
21363
21584
  if (filename && filename.endsWith(".json")) {
21364
21585
  if (debounceTimer)
21365
21586
  clearTimeout(debounceTimer);
@@ -21368,7 +21589,7 @@ async function visualizeCommand(options) {
21368
21589
  });
21369
21590
  } catch {}
21370
21591
  try {
21371
- branchWatcher = fs4.watch(gitHeadPath, () => {
21592
+ branchWatcher = fs5.watch(gitHeadPath, () => {
21372
21593
  if (debounceTimer)
21373
21594
  clearTimeout(debounceTimer);
21374
21595
  debounceTimer = setTimeout(() => reloadData(true), 100);
@@ -21415,8 +21636,8 @@ async function visualizeCommand(options) {
21415
21636
 
21416
21637
  // src/index.ts
21417
21638
  var program2 = new Command;
21418
- program2.name("codewalker").description("CLI tool for visualizing AI-assisted code changes").version("0.1.0");
21419
- program2.command("init").description("Initialize CodeWalker in the current project").action(async () => {
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 () => {
21420
21641
  try {
21421
21642
  await initCommand({ cwd: process.cwd() });
21422
21643
  } catch (error) {