codewalk 0.1.2 → 0.1.4
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 +232 -276
- package/package.json +2 -2
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
|
|
2142
|
-
import * as
|
|
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
|
|
1995
|
+
import { basename, join } from "path";
|
|
2190
1996
|
import os from "os";
|
|
2191
|
-
import
|
|
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
|
|
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(
|
|
5920
|
-
return
|
|
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
|
|
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 = (
|
|
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(
|
|
6036
|
-
if (isUrl(
|
|
6037
|
-
return
|
|
5841
|
+
resolvePath(path2) {
|
|
5842
|
+
if (isUrl(path2)) {
|
|
5843
|
+
return path2;
|
|
6038
5844
|
}
|
|
6039
|
-
if (isBunfsPath(
|
|
6040
|
-
return normalizeBunfsPath(parse(
|
|
5845
|
+
if (isBunfsPath(path2)) {
|
|
5846
|
+
return normalizeBunfsPath(parse(path2).base);
|
|
6041
5847
|
}
|
|
6042
|
-
if (!isAbsolute(
|
|
6043
|
-
return resolve2(
|
|
5848
|
+
if (!isAbsolute(path2)) {
|
|
5849
|
+
return resolve2(path2);
|
|
6044
5850
|
}
|
|
6045
|
-
return
|
|
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((
|
|
6053
|
-
injections: filetypeParser.queries.injections?.map((
|
|
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 ||
|
|
6475
|
-
this._globalConfigPath =
|
|
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 =
|
|
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 =
|
|
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 ||
|
|
6496
|
-
this._globalDataPath =
|
|
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
|
-
|
|
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(
|
|
17420
|
-
const last =
|
|
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:
|
|
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:
|
|
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
|
|
20876
|
-
import * as
|
|
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 =
|
|
20686
|
+
const trackingPath = path2.join(trackingDir, `${commit.shortSha}.json`);
|
|
20881
20687
|
let tracking = null;
|
|
20882
20688
|
try {
|
|
20883
|
-
const content = await
|
|
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
|
|
20942
|
-
import * as
|
|
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
|
|
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
|
|
20793
|
+
return path3.basename(repoRoot);
|
|
20988
20794
|
} catch {
|
|
20989
|
-
return
|
|
20795
|
+
return path3.basename(cwd);
|
|
20990
20796
|
}
|
|
20991
20797
|
}
|
|
20992
20798
|
async function loadSettings(cwd) {
|
|
20993
|
-
const settingsPath =
|
|
20799
|
+
const settingsPath = path3.join(cwd, ".claude", "codewalk.local.md");
|
|
20994
20800
|
try {
|
|
20995
|
-
const content = await
|
|
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
|
|
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(
|
|
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(
|
|
21537
|
+
console.log(import_picocolors.default.dim("Loading tracking data..."));
|
|
21557
21538
|
const { branch, reasoningGroups } = await loadBranchData(repoRoot, trackingDir);
|
|
21558
|
-
console.log(
|
|
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 =
|
|
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
|
-
|
|
21582
|
-
|
|
21583
|
-
|
|
21584
|
-
|
|
21585
|
-
|
|
21586
|
-
|
|
21587
|
-
|
|
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
|
-
|
|
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.
|
|
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.
|
|
3
|
+
"version": "0.1.4",
|
|
4
4
|
"type": "module",
|
|
5
5
|
"description": "CLI tool for visualizing AI-assisted code changes",
|
|
6
6
|
"bin": {
|
|
@@ -20,7 +20,7 @@
|
|
|
20
20
|
"picocolors": "^1.0.0"
|
|
21
21
|
},
|
|
22
22
|
"devDependencies": {
|
|
23
|
-
"
|
|
23
|
+
"bun-types": "latest",
|
|
24
24
|
"typescript": "^5.0.0"
|
|
25
25
|
},
|
|
26
26
|
"engines": {
|