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/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "codewalk",
3
- "version": "0.1.0",
3
+ "version": "0.1.2",
4
4
  "type": "module",
5
5
  "description": "CLI tool for visualizing AI-assisted code changes",
6
6
  "bin": {
@@ -1,5 +0,0 @@
1
- export interface InitOptions {
2
- cwd: string;
3
- }
4
- export declare function initCommand(options: InitOptions): Promise<void>;
5
- //# sourceMappingURL=init.d.ts.map
@@ -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"}
@@ -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,5 +0,0 @@
1
- export interface VisualizeOptions {
2
- cwd: string;
3
- }
4
- export declare function visualizeCommand(options: VisualizeOptions): Promise<void>;
5
- //# sourceMappingURL=visualize.d.ts.map
@@ -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
@@ -1,3 +0,0 @@
1
- #!/usr/bin/env bun
2
- export {};
3
- //# sourceMappingURL=index.d.ts.map
@@ -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
@@ -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 };
@@ -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"}
@@ -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"}
@@ -1 +0,0 @@
1
- export {};
@@ -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
@@ -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"}