codewalk 0.1.0 → 0.1.2
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 +342 -121
- package/package.json +1 -1
- package/dist/commands/init.d.ts +0 -5
- package/dist/commands/init.d.ts.map +0 -1
- package/dist/commands/init.js +0 -163
- package/dist/commands/visualize.d.ts +0 -5
- package/dist/commands/visualize.d.ts.map +0 -1
- package/dist/commands/visualize.js +0 -79
- package/dist/index.d.ts +0 -3
- package/dist/index.d.ts.map +0 -1
- package/dist/tui/app.d.ts +0 -13
- package/dist/tui/app.d.ts.map +0 -1
- package/dist/tui/app.js +0 -11
- package/dist/tui/tree-view.d.ts +0 -24
- package/dist/tui/tree-view.d.ts.map +0 -1
- package/dist/tui/tree-view.js +0 -274
- package/dist/types/codewalker.d.ts +0 -15
- package/dist/types/codewalker.d.ts.map +0 -1
- package/dist/types/codewalker.js +0 -1
- package/dist/utils/git.d.ts +0 -24
- package/dist/utils/git.d.ts.map +0 -1
- package/dist/utils/git.js +0 -132
- package/dist/utils/tracking.d.ts +0 -23
- package/dist/utils/tracking.d.ts.map +0 -1
- package/dist/utils/tracking.js +0 -85
package/package.json
CHANGED
package/dist/commands/init.d.ts
DELETED
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"file":"init.d.ts","sourceRoot":"","sources":["../../src/commands/init.ts"],"names":[],"mappings":"AAmHA,MAAM,WAAW,WAAW;IAC1B,GAAG,EAAE,MAAM,CAAC;CACb;AAED,wBAAsB,WAAW,CAAC,OAAO,EAAE,WAAW,GAAG,OAAO,CAAC,IAAI,CAAC,CAwDrE"}
|
package/dist/commands/init.js
DELETED
|
@@ -1,163 +0,0 @@
|
|
|
1
|
-
import * as fs from 'fs/promises';
|
|
2
|
-
import * as path from 'path';
|
|
3
|
-
import pc from 'picocolors';
|
|
4
|
-
const SKILL_TEMPLATE = `# Code Walker
|
|
5
|
-
|
|
6
|
-
You are Code Walker, an AI programming assistant built on top of Claude Code.
|
|
7
|
-
|
|
8
|
-
Your purpose is to give the user more visibility into the changes you are making.
|
|
9
|
-
|
|
10
|
-
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.
|
|
11
|
-
|
|
12
|
-
In addition to your normal summary, you should also keep track of what you changed in a structured file.
|
|
13
|
-
|
|
14
|
-
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.
|
|
15
|
-
|
|
16
|
-
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.
|
|
17
|
-
|
|
18
|
-
## Tracking File Schema
|
|
19
|
-
|
|
20
|
-
\`\`\`typescript
|
|
21
|
-
type Changeset = {
|
|
22
|
-
// Schema version for forward compatibility
|
|
23
|
-
version: number;
|
|
24
|
-
|
|
25
|
-
// Git commit SHA this changeset describes
|
|
26
|
-
commit: string;
|
|
27
|
-
|
|
28
|
-
// Who made the changes (human name, "claude", etc.)
|
|
29
|
-
author: string;
|
|
30
|
-
|
|
31
|
-
// List of logical changes, each with its own reasoning
|
|
32
|
-
changes: Change[];
|
|
33
|
-
};
|
|
34
|
-
|
|
35
|
-
type Change = {
|
|
36
|
-
// Human-readable explanation of why this change was made.
|
|
37
|
-
// Should explain the intent, not just describe what changed.
|
|
38
|
-
reasoning: string;
|
|
39
|
-
|
|
40
|
-
// Files affected by this logical change
|
|
41
|
-
files: FileChange[];
|
|
42
|
-
};
|
|
43
|
-
|
|
44
|
-
type FileChange = {
|
|
45
|
-
// Path to the file, relative to repo root
|
|
46
|
-
path: string;
|
|
47
|
-
|
|
48
|
-
// Which hunks from \`git show <commit>\` belong to this change.
|
|
49
|
-
// 1-indexed, in order of appearance in the diff.
|
|
50
|
-
// Example: [1, 3] means the first and third hunks in this file's diff.
|
|
51
|
-
hunks: number[];
|
|
52
|
-
};
|
|
53
|
-
\`\`\`
|
|
54
|
-
|
|
55
|
-
## Git Commands Reference
|
|
56
|
-
|
|
57
|
-
- Get current commit hash: \`git rev-parse --short HEAD\`
|
|
58
|
-
- Get commit author: \`git log -1 --format="%an"\`
|
|
59
|
-
- View commit diff with hunks: \`git show <commit> --format=""\`
|
|
60
|
-
- List files changed: \`git show <commit> --name-only --format=""\`
|
|
61
|
-
|
|
62
|
-
Hunks are numbered 1, 2, 3... in order of appearance. Each \`@@\` line in the diff starts a new hunk.
|
|
63
|
-
|
|
64
|
-
## Handling Refinements
|
|
65
|
-
|
|
66
|
-
If the user requests changes to something you just did (e.g., "use different colors", "rename that function"):
|
|
67
|
-
|
|
68
|
-
1. If it's part of the same logical task, amend the commit: \`git add -A && git commit --amend --no-edit\`
|
|
69
|
-
2. Update the existing tracking file to reflect the final state
|
|
70
|
-
3. The reasoning should describe the final result, not the iteration history
|
|
71
|
-
|
|
72
|
-
If it's a distinct new task, create a new commit and new tracking file.
|
|
73
|
-
|
|
74
|
-
## Instructions
|
|
75
|
-
|
|
76
|
-
1. Before committing, check if a git repo exists with \`git status\`. Only run \`git init\` if there isn't one.
|
|
77
|
-
2. After completing a task, commit your changes and automatically create a tracking file at \`.codewalker/<commit-hash>.json\`
|
|
78
|
-
3. Group hunks by their logical purpose—one 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.
|
|
79
|
-
4. Write reasoning that explains *why*, not just *what* (the diff already shows what)
|
|
80
|
-
5. If a follow-up request refines previous work, amend the commit and update the tracking file
|
|
81
|
-
6. If a follow-up request is a new task, create a new commit and new tracking file
|
|
82
|
-
7. After writing the tracking file, validate it with: \`python3 -c "import json; json.load(open('.codewalker/<commit-hash>.json'))"\`
|
|
83
|
-
|
|
84
|
-
Do not wait for the user to ask for the tracking file—create it automatically as part of finishing each task.
|
|
85
|
-
|
|
86
|
-
**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.
|
|
87
|
-
`;
|
|
88
|
-
const CLAUDE_MD_CONTENT = `# Code Walker
|
|
89
|
-
|
|
90
|
-
**CRITICAL REQUIREMENT**: You MUST create git commits for your changes AND create tracking files after EVERY task. No exceptions.
|
|
91
|
-
|
|
92
|
-
## Workflow (MANDATORY):
|
|
93
|
-
1. Make your code changes using Write/Edit tools
|
|
94
|
-
2. **COMMIT the changes**: \`git add -A && git commit -m "descriptive message"\`
|
|
95
|
-
3. Get the commit hash: \`git rev-parse --short HEAD\`
|
|
96
|
-
4. **IMMEDIATELY** create \`.codewalker/<commit-hash>.json\` with the tracking data
|
|
97
|
-
5. Commit the tracking file: \`git add .codewalker/<hash>.json && git commit -m "Add tracking file for <hash>"\`
|
|
98
|
-
6. Only THEN respond to the user
|
|
99
|
-
|
|
100
|
-
**DO NOT** skip commits. **DO NOT** respond to the user until both the code commit AND tracking file commit are done.
|
|
101
|
-
|
|
102
|
-
See \`.claude/skills/codewalker.md\` for the complete schema and examples.
|
|
103
|
-
`;
|
|
104
|
-
async function fileExists(filePath) {
|
|
105
|
-
try {
|
|
106
|
-
await fs.access(filePath);
|
|
107
|
-
return true;
|
|
108
|
-
}
|
|
109
|
-
catch {
|
|
110
|
-
return false;
|
|
111
|
-
}
|
|
112
|
-
}
|
|
113
|
-
export async function initCommand(options) {
|
|
114
|
-
const { cwd } = options;
|
|
115
|
-
console.log(pc.bold('Initializing CodeWalker...\n'));
|
|
116
|
-
// 1. Create .claude/skills directory
|
|
117
|
-
const skillsDir = path.join(cwd, '.claude', 'skills');
|
|
118
|
-
await fs.mkdir(skillsDir, { recursive: true });
|
|
119
|
-
// 2. Create skill file (idempotent)
|
|
120
|
-
const skillPath = path.join(skillsDir, 'codewalker.md');
|
|
121
|
-
const skillExists = await fileExists(skillPath);
|
|
122
|
-
if (!skillExists) {
|
|
123
|
-
await fs.writeFile(skillPath, SKILL_TEMPLATE);
|
|
124
|
-
console.log(pc.green('✓') + ' Created .claude/skills/codewalker.md');
|
|
125
|
-
}
|
|
126
|
-
else {
|
|
127
|
-
console.log(pc.yellow('○') + ' .claude/skills/codewalker.md already exists, skipping');
|
|
128
|
-
}
|
|
129
|
-
// 3. Update CLAUDE.md (idempotent)
|
|
130
|
-
const claudePath = path.join(cwd, 'CLAUDE.md');
|
|
131
|
-
let claudeContent = '';
|
|
132
|
-
try {
|
|
133
|
-
claudeContent = await fs.readFile(claudePath, 'utf-8');
|
|
134
|
-
}
|
|
135
|
-
catch {
|
|
136
|
-
// File doesn't exist, will create
|
|
137
|
-
}
|
|
138
|
-
if (!claudeContent.includes('.claude/skills/codewalker.md')) {
|
|
139
|
-
const newContent = claudeContent
|
|
140
|
-
? claudeContent + '\n\n' + CLAUDE_MD_CONTENT
|
|
141
|
-
: CLAUDE_MD_CONTENT;
|
|
142
|
-
await fs.writeFile(claudePath, newContent);
|
|
143
|
-
console.log(pc.green('✓') + ' Updated CLAUDE.md with CodeWalker instructions');
|
|
144
|
-
}
|
|
145
|
-
else {
|
|
146
|
-
console.log(pc.yellow('○') + ' CLAUDE.md already references CodeWalker, skipping');
|
|
147
|
-
}
|
|
148
|
-
// 4. Create .codewalker directory
|
|
149
|
-
const codewalkerDir = path.join(cwd, '.codewalker');
|
|
150
|
-
const codewalkerExists = await fileExists(codewalkerDir);
|
|
151
|
-
await fs.mkdir(codewalkerDir, { recursive: true });
|
|
152
|
-
if (!codewalkerExists) {
|
|
153
|
-
console.log(pc.green('✓') + ' Created .codewalker/ directory');
|
|
154
|
-
}
|
|
155
|
-
else {
|
|
156
|
-
console.log(pc.yellow('○') + ' .codewalker/ directory already exists');
|
|
157
|
-
}
|
|
158
|
-
console.log(pc.bold('\nCodeWalker initialized successfully!'));
|
|
159
|
-
console.log('\nNext steps:');
|
|
160
|
-
console.log(' 1. Start Claude Code in this directory');
|
|
161
|
-
console.log(' 2. Make changes - Claude will automatically track them');
|
|
162
|
-
console.log(' 3. Run ' + pc.cyan('codewalker visualize') + ' to browse changes');
|
|
163
|
-
}
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"file":"visualize.d.ts","sourceRoot":"","sources":["../../src/commands/visualize.ts"],"names":[],"mappings":"AAOA,MAAM,WAAW,gBAAgB;IAC/B,GAAG,EAAE,MAAM,CAAC;CACb;AAED,wBAAsB,gBAAgB,CAAC,OAAO,EAAE,gBAAgB,GAAG,OAAO,CAAC,IAAI,CAAC,CA2F/E"}
|
|
@@ -1,79 +0,0 @@
|
|
|
1
|
-
import pc from 'picocolors';
|
|
2
|
-
import { createCliRenderer } from '@opentui/core';
|
|
3
|
-
import { getCurrentBranch, getCommitList, isGitRepo } from '../utils/git.js';
|
|
4
|
-
import { loadTrackingFiles, getTrackedCommits, aggregateByReasoning } from '../utils/tracking.js';
|
|
5
|
-
import { createAppState } from '../tui/app.js';
|
|
6
|
-
import { TreeView } from '../tui/tree-view.js';
|
|
7
|
-
export async function visualizeCommand(options) {
|
|
8
|
-
const { cwd } = options;
|
|
9
|
-
// Check if in git repo
|
|
10
|
-
if (!isGitRepo(cwd)) {
|
|
11
|
-
console.error(pc.red('Error: Not a git repository'));
|
|
12
|
-
process.exit(1);
|
|
13
|
-
}
|
|
14
|
-
// Load data
|
|
15
|
-
console.log(pc.dim('Loading tracking data...'));
|
|
16
|
-
const branch = getCurrentBranch(cwd);
|
|
17
|
-
const commits = getCommitList(cwd);
|
|
18
|
-
const allTrackedCommits = await loadTrackingFiles(cwd, commits);
|
|
19
|
-
const trackedCommits = getTrackedCommits(allTrackedCommits);
|
|
20
|
-
if (trackedCommits.length === 0) {
|
|
21
|
-
console.log(pc.yellow('No tracked commits found in .codewalker/'));
|
|
22
|
-
console.log(pc.dim('Run some tasks with Claude Code to generate tracking data.'));
|
|
23
|
-
return;
|
|
24
|
-
}
|
|
25
|
-
console.log(pc.dim('Aggregating changes by reasoning...'));
|
|
26
|
-
// Aggregate changes by reasoning (like the PR "By Reasoning" view)
|
|
27
|
-
const reasoningGroups = aggregateByReasoning(cwd, trackedCommits);
|
|
28
|
-
if (reasoningGroups.length === 0) {
|
|
29
|
-
console.log(pc.yellow('No changes with diffs found.'));
|
|
30
|
-
return;
|
|
31
|
-
}
|
|
32
|
-
// Create OpenTUI renderer
|
|
33
|
-
console.log(pc.dim('Starting visualizer...'));
|
|
34
|
-
const renderer = await createCliRenderer({
|
|
35
|
-
exitOnCtrlC: true,
|
|
36
|
-
useAlternateScreen: true,
|
|
37
|
-
useMouse: true,
|
|
38
|
-
backgroundColor: '#0f0f1a',
|
|
39
|
-
});
|
|
40
|
-
// Create app state
|
|
41
|
-
const state = createAppState(branch, reasoningGroups);
|
|
42
|
-
// Create tree view
|
|
43
|
-
const treeView = new TreeView(renderer, state);
|
|
44
|
-
// Handle keyboard input
|
|
45
|
-
renderer.keyInput.on('keypress', (event) => {
|
|
46
|
-
const key = event.name;
|
|
47
|
-
switch (key) {
|
|
48
|
-
case 'q':
|
|
49
|
-
treeView.destroy();
|
|
50
|
-
renderer.destroy();
|
|
51
|
-
process.exit(0);
|
|
52
|
-
break;
|
|
53
|
-
case 'j':
|
|
54
|
-
case 'ArrowDown':
|
|
55
|
-
treeView.moveSelection(1);
|
|
56
|
-
break;
|
|
57
|
-
case 'k':
|
|
58
|
-
case 'ArrowUp':
|
|
59
|
-
treeView.moveSelection(-1);
|
|
60
|
-
break;
|
|
61
|
-
case 'Enter':
|
|
62
|
-
case ' ':
|
|
63
|
-
treeView.toggleExpand();
|
|
64
|
-
break;
|
|
65
|
-
case 'g':
|
|
66
|
-
// Go to top
|
|
67
|
-
state.selectedIndex = 0;
|
|
68
|
-
treeView.refresh();
|
|
69
|
-
break;
|
|
70
|
-
case 'G':
|
|
71
|
-
// Go to bottom
|
|
72
|
-
state.selectedIndex = Math.max(0, treeView.getItemCount() - 1);
|
|
73
|
-
treeView.refresh();
|
|
74
|
-
break;
|
|
75
|
-
}
|
|
76
|
-
});
|
|
77
|
-
// Start rendering
|
|
78
|
-
renderer.start();
|
|
79
|
-
}
|
package/dist/index.d.ts
DELETED
package/dist/index.d.ts.map
DELETED
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":""}
|
package/dist/tui/app.d.ts
DELETED
|
@@ -1,13 +0,0 @@
|
|
|
1
|
-
import { createCliRenderer, type CliRenderer, BoxRenderable, TextRenderable, ScrollBoxRenderable, DiffRenderable } from '@opentui/core';
|
|
2
|
-
import type { ReasoningGroup } from '../utils/tracking.js';
|
|
3
|
-
export interface AppState {
|
|
4
|
-
branch: string;
|
|
5
|
-
reasoningGroups: ReasoningGroup[];
|
|
6
|
-
selectedIndex: number;
|
|
7
|
-
expandedReasonings: Set<number>;
|
|
8
|
-
expandedFiles: Set<string>;
|
|
9
|
-
}
|
|
10
|
-
export declare function createAppState(branch: string, reasoningGroups: ReasoningGroup[]): AppState;
|
|
11
|
-
export { createCliRenderer, BoxRenderable, TextRenderable, ScrollBoxRenderable, DiffRenderable };
|
|
12
|
-
export type { CliRenderer };
|
|
13
|
-
//# sourceMappingURL=app.d.ts.map
|
package/dist/tui/app.d.ts.map
DELETED
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"file":"app.d.ts","sourceRoot":"","sources":["../../src/tui/app.ts"],"names":[],"mappings":"AAAA,OAAO,EACL,iBAAiB,EACjB,KAAK,WAAW,EAChB,aAAa,EACb,cAAc,EACd,mBAAmB,EACnB,cAAc,EACf,MAAM,eAAe,CAAC;AACvB,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,sBAAsB,CAAC;AAE3D,MAAM,WAAW,QAAQ;IACvB,MAAM,EAAE,MAAM,CAAC;IACf,eAAe,EAAE,cAAc,EAAE,CAAC;IAClC,aAAa,EAAE,MAAM,CAAC;IACtB,kBAAkB,EAAE,GAAG,CAAC,MAAM,CAAC,CAAC;IAChC,aAAa,EAAE,GAAG,CAAC,MAAM,CAAC,CAAC;CAC5B;AAED,wBAAgB,cAAc,CAC5B,MAAM,EAAE,MAAM,EACd,eAAe,EAAE,cAAc,EAAE,GAChC,QAAQ,CAQV;AAED,OAAO,EAAE,iBAAiB,EAAE,aAAa,EAAE,cAAc,EAAE,mBAAmB,EAAE,cAAc,EAAE,CAAC;AACjG,YAAY,EAAE,WAAW,EAAE,CAAC"}
|
package/dist/tui/app.js
DELETED
|
@@ -1,11 +0,0 @@
|
|
|
1
|
-
import { createCliRenderer, BoxRenderable, TextRenderable, ScrollBoxRenderable, DiffRenderable, } from '@opentui/core';
|
|
2
|
-
export function createAppState(branch, reasoningGroups) {
|
|
3
|
-
return {
|
|
4
|
-
branch,
|
|
5
|
-
reasoningGroups,
|
|
6
|
-
selectedIndex: 0,
|
|
7
|
-
expandedReasonings: new Set(),
|
|
8
|
-
expandedFiles: new Set(),
|
|
9
|
-
};
|
|
10
|
-
}
|
|
11
|
-
export { createCliRenderer, BoxRenderable, TextRenderable, ScrollBoxRenderable, DiffRenderable };
|
package/dist/tui/tree-view.d.ts
DELETED
|
@@ -1,24 +0,0 @@
|
|
|
1
|
-
import { type CliRenderer } from '@opentui/core';
|
|
2
|
-
import type { AppState } from './app.js';
|
|
3
|
-
export declare class TreeView {
|
|
4
|
-
private renderer;
|
|
5
|
-
private state;
|
|
6
|
-
private rootBox;
|
|
7
|
-
private headerBox;
|
|
8
|
-
private scrollBox;
|
|
9
|
-
private footerBox;
|
|
10
|
-
private selectableItems;
|
|
11
|
-
constructor(renderer: CliRenderer, state: AppState);
|
|
12
|
-
private buildUI;
|
|
13
|
-
private buildHeader;
|
|
14
|
-
private buildContent;
|
|
15
|
-
private buildFooter;
|
|
16
|
-
private isReasoningSelected;
|
|
17
|
-
private isFileSelected;
|
|
18
|
-
getItemCount(): number;
|
|
19
|
-
toggleExpand(): void;
|
|
20
|
-
moveSelection(delta: number): void;
|
|
21
|
-
refresh(): void;
|
|
22
|
-
destroy(): void;
|
|
23
|
-
}
|
|
24
|
-
//# sourceMappingURL=tree-view.d.ts.map
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"file":"tree-view.d.ts","sourceRoot":"","sources":["../../src/tui/tree-view.ts"],"names":[],"mappings":"AAAA,OAAO,EAKL,KAAK,WAAW,EACjB,MAAM,eAAe,CAAC;AACvB,OAAO,KAAK,EAAE,QAAQ,EAAE,MAAM,UAAU,CAAC;AAgDzC,qBAAa,QAAQ;IACnB,OAAO,CAAC,QAAQ,CAAc;IAC9B,OAAO,CAAC,KAAK,CAAW;IACxB,OAAO,CAAC,OAAO,CAAgB;IAC/B,OAAO,CAAC,SAAS,CAAgB;IACjC,OAAO,CAAC,SAAS,CAAsB;IACvC,OAAO,CAAC,SAAS,CAAgB;IACjC,OAAO,CAAC,eAAe,CAAwB;gBAEnC,QAAQ,EAAE,WAAW,EAAE,KAAK,EAAE,QAAQ;IAiDlD,OAAO,CAAC,OAAO;IAoBf,OAAO,CAAC,WAAW;IAiBnB,OAAO,CAAC,YAAY;IAwGpB,OAAO,CAAC,WAAW;IAiBnB,OAAO,CAAC,mBAAmB;IAK3B,OAAO,CAAC,cAAc;IAKf,YAAY,IAAI,MAAM;IAItB,YAAY,IAAI,IAAI;IA4BpB,aAAa,CAAC,KAAK,EAAE,MAAM,GAAG,IAAI;IAQlC,OAAO,IAAI,IAAI;IAIf,OAAO,IAAI,IAAI;CAGvB"}
|
package/dist/tui/tree-view.js
DELETED
|
@@ -1,274 +0,0 @@
|
|
|
1
|
-
import { BoxRenderable, TextRenderable, ScrollBoxRenderable, DiffRenderable, } from '@opentui/core';
|
|
2
|
-
// Convert hunks to unified diff format for DiffRenderable
|
|
3
|
-
function hunksToUnifiedDiff(filePath, hunks) {
|
|
4
|
-
let diff = `--- a/${filePath}\n+++ b/${filePath}\n`;
|
|
5
|
-
for (const hunk of hunks) {
|
|
6
|
-
diff += hunk.header + '\n';
|
|
7
|
-
diff += hunk.content;
|
|
8
|
-
}
|
|
9
|
-
return diff;
|
|
10
|
-
}
|
|
11
|
-
// Get file extension for syntax highlighting
|
|
12
|
-
function getFileType(filePath) {
|
|
13
|
-
const ext = filePath.split('.').pop()?.toLowerCase();
|
|
14
|
-
const typeMap = {
|
|
15
|
-
ts: 'typescript',
|
|
16
|
-
tsx: 'tsx',
|
|
17
|
-
js: 'javascript',
|
|
18
|
-
jsx: 'jsx',
|
|
19
|
-
py: 'python',
|
|
20
|
-
rb: 'ruby',
|
|
21
|
-
go: 'go',
|
|
22
|
-
rs: 'rust',
|
|
23
|
-
java: 'java',
|
|
24
|
-
c: 'c',
|
|
25
|
-
cpp: 'cpp',
|
|
26
|
-
h: 'c',
|
|
27
|
-
hpp: 'cpp',
|
|
28
|
-
css: 'css',
|
|
29
|
-
scss: 'scss',
|
|
30
|
-
html: 'html',
|
|
31
|
-
json: 'json',
|
|
32
|
-
md: 'markdown',
|
|
33
|
-
yaml: 'yaml',
|
|
34
|
-
yml: 'yaml',
|
|
35
|
-
};
|
|
36
|
-
return ext ? typeMap[ext] : undefined;
|
|
37
|
-
}
|
|
38
|
-
export class TreeView {
|
|
39
|
-
renderer;
|
|
40
|
-
state;
|
|
41
|
-
rootBox;
|
|
42
|
-
headerBox;
|
|
43
|
-
scrollBox;
|
|
44
|
-
footerBox;
|
|
45
|
-
selectableItems = [];
|
|
46
|
-
constructor(renderer, state) {
|
|
47
|
-
this.renderer = renderer;
|
|
48
|
-
this.state = state;
|
|
49
|
-
// Root container - full screen, vertical flex
|
|
50
|
-
this.rootBox = new BoxRenderable(renderer, {
|
|
51
|
-
width: '100%',
|
|
52
|
-
height: '100%',
|
|
53
|
-
flexDirection: 'column',
|
|
54
|
-
});
|
|
55
|
-
// Header - fixed at top
|
|
56
|
-
this.headerBox = new BoxRenderable(renderer, {
|
|
57
|
-
width: '100%',
|
|
58
|
-
height: 3,
|
|
59
|
-
border: true,
|
|
60
|
-
borderStyle: 'single',
|
|
61
|
-
borderColor: '#555555',
|
|
62
|
-
backgroundColor: '#1a1a2e',
|
|
63
|
-
});
|
|
64
|
-
// Scrollable content area
|
|
65
|
-
this.scrollBox = new ScrollBoxRenderable(renderer, {
|
|
66
|
-
width: '100%',
|
|
67
|
-
flexGrow: 1,
|
|
68
|
-
scrollY: true,
|
|
69
|
-
backgroundColor: '#0f0f1a',
|
|
70
|
-
});
|
|
71
|
-
// Footer - fixed at bottom
|
|
72
|
-
this.footerBox = new BoxRenderable(renderer, {
|
|
73
|
-
width: '100%',
|
|
74
|
-
height: 2,
|
|
75
|
-
border: true,
|
|
76
|
-
borderStyle: 'single',
|
|
77
|
-
borderColor: '#555555',
|
|
78
|
-
backgroundColor: '#1a1a2e',
|
|
79
|
-
paddingLeft: 1,
|
|
80
|
-
});
|
|
81
|
-
this.rootBox.add(this.headerBox);
|
|
82
|
-
this.rootBox.add(this.scrollBox);
|
|
83
|
-
this.rootBox.add(this.footerBox);
|
|
84
|
-
renderer.root.add(this.rootBox);
|
|
85
|
-
this.buildUI();
|
|
86
|
-
}
|
|
87
|
-
buildUI() {
|
|
88
|
-
// Clear previous content
|
|
89
|
-
this.selectableItems = [];
|
|
90
|
-
// Clear scrollbox content
|
|
91
|
-
const children = this.scrollBox.getChildren();
|
|
92
|
-
for (const child of children) {
|
|
93
|
-
this.scrollBox.remove(child.id);
|
|
94
|
-
}
|
|
95
|
-
// Build header
|
|
96
|
-
this.buildHeader();
|
|
97
|
-
// Build content
|
|
98
|
-
this.buildContent();
|
|
99
|
-
// Build footer
|
|
100
|
-
this.buildFooter();
|
|
101
|
-
}
|
|
102
|
-
buildHeader() {
|
|
103
|
-
// Clear header
|
|
104
|
-
const headerChildren = this.headerBox.getChildren();
|
|
105
|
-
for (const child of headerChildren) {
|
|
106
|
-
this.headerBox.remove(child.id);
|
|
107
|
-
}
|
|
108
|
-
const totalChanges = this.state.reasoningGroups.length;
|
|
109
|
-
const headerText = new TextRenderable(this.renderer, {
|
|
110
|
-
content: ` CodeWalker - ${this.state.branch} (${totalChanges} logical changes)`,
|
|
111
|
-
fg: '#88ccff',
|
|
112
|
-
paddingTop: 0,
|
|
113
|
-
paddingLeft: 1,
|
|
114
|
-
});
|
|
115
|
-
this.headerBox.add(headerText);
|
|
116
|
-
}
|
|
117
|
-
buildContent() {
|
|
118
|
-
this.state.reasoningGroups.forEach((group, reasoningIdx) => {
|
|
119
|
-
const isExpanded = this.state.expandedReasonings.has(reasoningIdx);
|
|
120
|
-
const isSelected = this.isReasoningSelected(reasoningIdx);
|
|
121
|
-
// Reasoning container
|
|
122
|
-
const reasoningBox = new BoxRenderable(this.renderer, {
|
|
123
|
-
width: '100%',
|
|
124
|
-
flexDirection: 'column',
|
|
125
|
-
paddingLeft: 1,
|
|
126
|
-
paddingTop: 1,
|
|
127
|
-
backgroundColor: isSelected ? '#2a2a4e' : undefined,
|
|
128
|
-
});
|
|
129
|
-
// Reasoning header with arrow and text
|
|
130
|
-
const arrow = isExpanded ? '▼' : '▶';
|
|
131
|
-
const fileCount = group.files.length;
|
|
132
|
-
const reasoningHeader = new TextRenderable(this.renderer, {
|
|
133
|
-
content: `${arrow} ${group.reasoning} (${fileCount} file${fileCount !== 1 ? 's' : ''})`,
|
|
134
|
-
fg: isSelected ? '#ffffff' : '#cccccc',
|
|
135
|
-
width: '100%',
|
|
136
|
-
});
|
|
137
|
-
reasoningBox.add(reasoningHeader);
|
|
138
|
-
this.selectableItems.push({
|
|
139
|
-
type: 'reasoning',
|
|
140
|
-
reasoningIdx,
|
|
141
|
-
renderable: reasoningBox,
|
|
142
|
-
});
|
|
143
|
-
// If expanded, show files
|
|
144
|
-
if (isExpanded) {
|
|
145
|
-
group.files.forEach((file) => {
|
|
146
|
-
const fileKey = `${reasoningIdx}|${file.path}`;
|
|
147
|
-
const isFileExpanded = this.state.expandedFiles.has(fileKey);
|
|
148
|
-
const isFileSelected = this.isFileSelected(reasoningIdx, file.path);
|
|
149
|
-
// File container
|
|
150
|
-
const fileBox = new BoxRenderable(this.renderer, {
|
|
151
|
-
width: '100%',
|
|
152
|
-
flexDirection: 'column',
|
|
153
|
-
paddingLeft: 3,
|
|
154
|
-
paddingTop: 1,
|
|
155
|
-
backgroundColor: isFileSelected ? '#2a2a4e' : undefined,
|
|
156
|
-
});
|
|
157
|
-
// File header
|
|
158
|
-
const fileArrow = isFileExpanded ? '▼' : '▶';
|
|
159
|
-
const fileHeader = new TextRenderable(this.renderer, {
|
|
160
|
-
content: `${fileArrow} ${file.path}`,
|
|
161
|
-
fg: isFileSelected ? '#88ccff' : '#6699cc',
|
|
162
|
-
});
|
|
163
|
-
fileBox.add(fileHeader);
|
|
164
|
-
this.selectableItems.push({
|
|
165
|
-
type: 'file',
|
|
166
|
-
reasoningIdx,
|
|
167
|
-
filePath: file.path,
|
|
168
|
-
renderable: fileBox,
|
|
169
|
-
});
|
|
170
|
-
// If file is expanded, show diff
|
|
171
|
-
if (isFileExpanded && file.hunks.length > 0) {
|
|
172
|
-
const diffContent = hunksToUnifiedDiff(file.path, file.hunks);
|
|
173
|
-
const fileType = getFileType(file.path);
|
|
174
|
-
const diffBox = new BoxRenderable(this.renderer, {
|
|
175
|
-
width: '100%',
|
|
176
|
-
border: true,
|
|
177
|
-
borderStyle: 'single',
|
|
178
|
-
borderColor: '#444444',
|
|
179
|
-
title: file.path,
|
|
180
|
-
titleAlignment: 'left',
|
|
181
|
-
marginLeft: 2,
|
|
182
|
-
marginTop: 1,
|
|
183
|
-
marginBottom: 1,
|
|
184
|
-
});
|
|
185
|
-
const diffRenderable = new DiffRenderable(this.renderer, {
|
|
186
|
-
diff: diffContent,
|
|
187
|
-
view: 'unified',
|
|
188
|
-
showLineNumbers: true,
|
|
189
|
-
filetype: fileType,
|
|
190
|
-
addedBg: '#1a3d1a',
|
|
191
|
-
removedBg: '#3d1a1a',
|
|
192
|
-
contextBg: '#1a1a2e',
|
|
193
|
-
addedSignColor: '#22cc22',
|
|
194
|
-
removedSignColor: '#cc2222',
|
|
195
|
-
lineNumberFg: '#666666',
|
|
196
|
-
width: '100%',
|
|
197
|
-
});
|
|
198
|
-
diffBox.add(diffRenderable);
|
|
199
|
-
fileBox.add(diffBox);
|
|
200
|
-
}
|
|
201
|
-
reasoningBox.add(fileBox);
|
|
202
|
-
});
|
|
203
|
-
}
|
|
204
|
-
this.scrollBox.add(reasoningBox);
|
|
205
|
-
});
|
|
206
|
-
}
|
|
207
|
-
buildFooter() {
|
|
208
|
-
// Clear footer
|
|
209
|
-
const footerChildren = this.footerBox.getChildren();
|
|
210
|
-
for (const child of footerChildren) {
|
|
211
|
-
this.footerBox.remove(child.id);
|
|
212
|
-
}
|
|
213
|
-
const totalItems = this.selectableItems.length;
|
|
214
|
-
const currentPos = this.state.selectedIndex + 1;
|
|
215
|
-
const footerText = new TextRenderable(this.renderer, {
|
|
216
|
-
content: `j/k: navigate │ Enter: expand/collapse │ q: quit [${currentPos}/${totalItems}]`,
|
|
217
|
-
fg: '#888888',
|
|
218
|
-
});
|
|
219
|
-
this.footerBox.add(footerText);
|
|
220
|
-
}
|
|
221
|
-
isReasoningSelected(reasoningIdx) {
|
|
222
|
-
const item = this.selectableItems[this.state.selectedIndex];
|
|
223
|
-
return item?.type === 'reasoning' && item.reasoningIdx === reasoningIdx;
|
|
224
|
-
}
|
|
225
|
-
isFileSelected(reasoningIdx, filePath) {
|
|
226
|
-
const item = this.selectableItems[this.state.selectedIndex];
|
|
227
|
-
return item?.type === 'file' && item.reasoningIdx === reasoningIdx && item.filePath === filePath;
|
|
228
|
-
}
|
|
229
|
-
getItemCount() {
|
|
230
|
-
return this.selectableItems.length;
|
|
231
|
-
}
|
|
232
|
-
toggleExpand() {
|
|
233
|
-
const item = this.selectableItems[this.state.selectedIndex];
|
|
234
|
-
if (!item)
|
|
235
|
-
return;
|
|
236
|
-
if (item.type === 'reasoning') {
|
|
237
|
-
if (this.state.expandedReasonings.has(item.reasoningIdx)) {
|
|
238
|
-
this.state.expandedReasonings.delete(item.reasoningIdx);
|
|
239
|
-
// Collapse all files in this reasoning
|
|
240
|
-
for (const key of this.state.expandedFiles) {
|
|
241
|
-
if (key.startsWith(`${item.reasoningIdx}|`)) {
|
|
242
|
-
this.state.expandedFiles.delete(key);
|
|
243
|
-
}
|
|
244
|
-
}
|
|
245
|
-
}
|
|
246
|
-
else {
|
|
247
|
-
this.state.expandedReasonings.add(item.reasoningIdx);
|
|
248
|
-
}
|
|
249
|
-
}
|
|
250
|
-
else if (item.type === 'file' && item.filePath) {
|
|
251
|
-
const fileKey = `${item.reasoningIdx}|${item.filePath}`;
|
|
252
|
-
if (this.state.expandedFiles.has(fileKey)) {
|
|
253
|
-
this.state.expandedFiles.delete(fileKey);
|
|
254
|
-
}
|
|
255
|
-
else {
|
|
256
|
-
this.state.expandedFiles.add(fileKey);
|
|
257
|
-
}
|
|
258
|
-
}
|
|
259
|
-
this.buildUI();
|
|
260
|
-
}
|
|
261
|
-
moveSelection(delta) {
|
|
262
|
-
const newIndex = this.state.selectedIndex + delta;
|
|
263
|
-
if (newIndex >= 0 && newIndex < this.selectableItems.length) {
|
|
264
|
-
this.state.selectedIndex = newIndex;
|
|
265
|
-
this.buildUI();
|
|
266
|
-
}
|
|
267
|
-
}
|
|
268
|
-
refresh() {
|
|
269
|
-
this.buildUI();
|
|
270
|
-
}
|
|
271
|
-
destroy() {
|
|
272
|
-
this.renderer.root.remove(this.rootBox.id);
|
|
273
|
-
}
|
|
274
|
-
}
|
|
@@ -1,15 +0,0 @@
|
|
|
1
|
-
export type Changeset = {
|
|
2
|
-
version: number;
|
|
3
|
-
commit: string;
|
|
4
|
-
author: string;
|
|
5
|
-
changes: Change[];
|
|
6
|
-
};
|
|
7
|
-
export type Change = {
|
|
8
|
-
reasoning: string;
|
|
9
|
-
files: FileChange[];
|
|
10
|
-
};
|
|
11
|
-
export type FileChange = {
|
|
12
|
-
path: string;
|
|
13
|
-
hunks: number[];
|
|
14
|
-
};
|
|
15
|
-
//# sourceMappingURL=codewalker.d.ts.map
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"file":"codewalker.d.ts","sourceRoot":"","sources":["../../src/types/codewalker.ts"],"names":[],"mappings":"AAAA,MAAM,MAAM,SAAS,GAAG;IAEtB,OAAO,EAAE,MAAM,CAAC;IAGhB,MAAM,EAAE,MAAM,CAAC;IAGf,MAAM,EAAE,MAAM,CAAC;IAGf,OAAO,EAAE,MAAM,EAAE,CAAC;CACnB,CAAC;AAEF,MAAM,MAAM,MAAM,GAAG;IAGnB,SAAS,EAAE,MAAM,CAAC;IAGlB,KAAK,EAAE,UAAU,EAAE,CAAC;CACrB,CAAC;AAEF,MAAM,MAAM,UAAU,GAAG;IAEvB,IAAI,EAAE,MAAM,CAAC;IAKb,KAAK,EAAE,MAAM,EAAE,CAAC;CACjB,CAAC"}
|
package/dist/types/codewalker.js
DELETED
|
@@ -1 +0,0 @@
|
|
|
1
|
-
export {};
|
package/dist/utils/git.d.ts
DELETED
|
@@ -1,24 +0,0 @@
|
|
|
1
|
-
export interface CommitInfo {
|
|
2
|
-
sha: string;
|
|
3
|
-
shortSha: string;
|
|
4
|
-
author: string;
|
|
5
|
-
message: string;
|
|
6
|
-
}
|
|
7
|
-
export interface ParsedHunk {
|
|
8
|
-
hunkNumber: number;
|
|
9
|
-
header: string;
|
|
10
|
-
content: string;
|
|
11
|
-
}
|
|
12
|
-
export interface FileDiff {
|
|
13
|
-
path: string;
|
|
14
|
-
hunks: ParsedHunk[];
|
|
15
|
-
}
|
|
16
|
-
export declare function getCurrentBranch(cwd: string): string;
|
|
17
|
-
export declare function getCommitList(cwd: string): CommitInfo[];
|
|
18
|
-
export declare function isGitRepo(cwd: string): boolean;
|
|
19
|
-
export declare function getMainBranch(cwd: string): string;
|
|
20
|
-
export declare function getMergeBase(cwd: string, branch1: string, branch2: string): string;
|
|
21
|
-
export declare function getCommitDiff(cwd: string, commitSha: string): string;
|
|
22
|
-
export declare function parseDiffIntoFiles(diffOutput: string): FileDiff[];
|
|
23
|
-
export declare function getCommitFileDiffs(cwd: string, commitSha: string): FileDiff[];
|
|
24
|
-
//# sourceMappingURL=git.d.ts.map
|
package/dist/utils/git.d.ts.map
DELETED
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"file":"git.d.ts","sourceRoot":"","sources":["../../src/utils/git.ts"],"names":[],"mappings":"AAEA,MAAM,WAAW,UAAU;IACzB,GAAG,EAAE,MAAM,CAAC;IACZ,QAAQ,EAAE,MAAM,CAAC;IACjB,MAAM,EAAE,MAAM,CAAC;IACf,OAAO,EAAE,MAAM,CAAC;CACjB;AAED,MAAM,WAAW,UAAU;IACzB,UAAU,EAAE,MAAM,CAAC;IACnB,MAAM,EAAE,MAAM,CAAC;IACf,OAAO,EAAE,MAAM,CAAC;CACjB;AAED,MAAM,WAAW,QAAQ;IACvB,IAAI,EAAE,MAAM,CAAC;IACb,KAAK,EAAE,UAAU,EAAE,CAAC;CACrB;AAED,wBAAgB,gBAAgB,CAAC,GAAG,EAAE,MAAM,GAAG,MAAM,CASpD;AAED,wBAAgB,aAAa,CAAC,GAAG,EAAE,MAAM,GAAG,UAAU,EAAE,CAuBvD;AAED,wBAAgB,SAAS,CAAC,GAAG,EAAE,MAAM,GAAG,OAAO,CAO9C;AAED,wBAAgB,aAAa,CAAC,GAAG,EAAE,MAAM,GAAG,MAAM,CAWjD;AAED,wBAAgB,YAAY,CAAC,GAAG,EAAE,MAAM,EAAE,OAAO,EAAE,MAAM,EAAE,OAAO,EAAE,MAAM,GAAG,MAAM,CASlF;AAED,wBAAgB,aAAa,CAAC,GAAG,EAAE,MAAM,EAAE,SAAS,EAAE,MAAM,GAAG,MAAM,CAUpE;AAED,wBAAgB,kBAAkB,CAAC,UAAU,EAAE,MAAM,GAAG,QAAQ,EAAE,CAsDjE;AAED,wBAAgB,kBAAkB,CAAC,GAAG,EAAE,MAAM,EAAE,SAAS,EAAE,MAAM,GAAG,QAAQ,EAAE,CAG7E"}
|