pi-cc-extensions 0.1.1 → 0.1.3
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +156 -141
- package/assets/readme/welcome.webp +0 -0
- package/extensions/ccstyle-powerline/fixed-editor/terminal-split.ts +1318 -1317
- package/extensions/ccstyle-powerline/git-status.ts +97 -32
- package/extensions/ccstyle-powerline/powerline.ts +3150 -3150
- package/extensions/ccstyle-powerline/presets.ts +109 -109
- package/extensions/ccstyle-powerline/segments.ts +11 -1
- package/extensions/ccstyle-powerline/types.ts +3 -0
- package/extensions/ccstyle-powerline/working-vibes.ts +1 -1
- package/extensions/claude-code-style.ts +26 -4
- package/extensions/session-reference/README.md +1 -1
- package/extensions/session-reference/index.ts +411 -379
- package/package.json +9 -2
- package/extensions/session-reference/core.test.ts +0 -74
|
@@ -5,6 +5,8 @@ interface CachedGitStatus {
|
|
|
5
5
|
staged: number;
|
|
6
6
|
unstaged: number;
|
|
7
7
|
untracked: number;
|
|
8
|
+
added: number;
|
|
9
|
+
deleted: number;
|
|
8
10
|
timestamp: number;
|
|
9
11
|
}
|
|
10
12
|
|
|
@@ -19,19 +21,25 @@ const CACHE_TTL_MS = 1000; // 1 second for file status
|
|
|
19
21
|
const BRANCH_TTL_MS = 500; // Shorter TTL so branch updates quickly after invalidation
|
|
20
22
|
let cachedStatus: CachedGitStatus | null = null;
|
|
21
23
|
let cachedBranch: CachedBranch | null = null;
|
|
24
|
+
let statusCwd: string | null = null;
|
|
25
|
+
let branchCwd: string | null = null;
|
|
22
26
|
let pendingFetch: Promise<void> | null = null;
|
|
23
27
|
let pendingBranchFetch: Promise<void> | null = null;
|
|
28
|
+
let fetchCwd: string | null = null;
|
|
29
|
+
let branchFetchCwd: string | null = null;
|
|
24
30
|
let invalidationCounter = 0; // Track invalidations to prevent stale updates
|
|
25
31
|
let branchInvalidationCounter = 0;
|
|
26
32
|
|
|
27
33
|
/**
|
|
28
|
-
* Parse git status --porcelain output
|
|
29
|
-
*
|
|
34
|
+
* Parse git status --porcelain output.
|
|
35
|
+
*
|
|
30
36
|
* Format: XY filename
|
|
31
37
|
* X = index status, Y = working tree status
|
|
32
38
|
* ?? = untracked
|
|
33
|
-
*
|
|
34
|
-
*
|
|
39
|
+
*
|
|
40
|
+
* Each file is counted exactly once:
|
|
41
|
+
* - Y ≠ ' ' → unstaged (working tree has changes; if X also shows staged, Y wins)
|
|
42
|
+
* - X ≠ ' ' && X ≠ '?' → staged only (index has changes, working tree is clean)
|
|
35
43
|
*/
|
|
36
44
|
function parseGitStatusOutput(output: string): { staged: number; unstaged: number; untracked: number } {
|
|
37
45
|
let staged = 0;
|
|
@@ -48,24 +56,24 @@ function parseGitStatusOutput(output: string): { staged: number; unstaged: numbe
|
|
|
48
56
|
continue;
|
|
49
57
|
}
|
|
50
58
|
|
|
51
|
-
//
|
|
52
|
-
|
|
53
|
-
staged++;
|
|
54
|
-
}
|
|
55
|
-
|
|
56
|
-
// Y position (working tree/unstaged)
|
|
59
|
+
// Y position (working tree) takes priority — if the file has working-tree
|
|
60
|
+
// changes at all, count it as unstaged.
|
|
57
61
|
if (y && y !== " ") {
|
|
58
62
|
unstaged++;
|
|
63
|
+
} else if (x && x !== " " && x !== "?") {
|
|
64
|
+
// X position (index) only counted when working tree is clean.
|
|
65
|
+
staged++;
|
|
59
66
|
}
|
|
60
67
|
}
|
|
61
68
|
|
|
62
69
|
return { staged, unstaged, untracked };
|
|
63
70
|
}
|
|
64
71
|
|
|
65
|
-
function runGit(args: string[], timeoutMs = 200): Promise<string | null> {
|
|
72
|
+
function runGit(args: string[], cwd: string, timeoutMs = 200): Promise<string | null> {
|
|
66
73
|
return new Promise((resolve) => {
|
|
67
74
|
const proc = spawn("git", args, {
|
|
68
75
|
stdio: ["ignore", "pipe", "pipe"],
|
|
76
|
+
cwd,
|
|
69
77
|
});
|
|
70
78
|
|
|
71
79
|
let stdout = "";
|
|
@@ -83,7 +91,7 @@ function runGit(args: string[], timeoutMs = 200): Promise<string | null> {
|
|
|
83
91
|
});
|
|
84
92
|
|
|
85
93
|
proc.on("close", (code) => {
|
|
86
|
-
finish(code === 0 ? stdout.
|
|
94
|
+
finish(code === 0 ? stdout.replace(/\n+$/, "") : null);
|
|
87
95
|
});
|
|
88
96
|
|
|
89
97
|
proc.on("error", () => {
|
|
@@ -101,20 +109,42 @@ function runGit(args: string[], timeoutMs = 200): Promise<string | null> {
|
|
|
101
109
|
* Fetch current git branch asynchronously.
|
|
102
110
|
* For detached HEAD, returns the short commit SHA (matches provider's "detached" behavior).
|
|
103
111
|
*/
|
|
104
|
-
async function fetchGitBranch(): Promise<string | null> {
|
|
105
|
-
const branch = await runGit(["branch", "--show-current"]);
|
|
112
|
+
async function fetchGitBranch(cwd: string): Promise<string | null> {
|
|
113
|
+
const branch = await runGit(["branch", "--show-current"], cwd);
|
|
106
114
|
if (branch === null) return null;
|
|
107
115
|
if (branch) return branch;
|
|
108
116
|
|
|
109
|
-
const sha = await runGit(["rev-parse", "--short", "HEAD"]);
|
|
117
|
+
const sha = await runGit(["rev-parse", "--short", "HEAD"], cwd);
|
|
110
118
|
return sha ? `${sha} (detached)` : "detached";
|
|
111
119
|
}
|
|
112
120
|
|
|
113
121
|
/**
|
|
114
|
-
*
|
|
122
|
+
* Parse git diff --shortstat HEAD output.
|
|
123
|
+
* Format: " N files changed, M insertions(+), K deletions(-)"
|
|
115
124
|
*/
|
|
116
|
-
|
|
117
|
-
const
|
|
125
|
+
function parseDiffStat(output: string): { added: number; deleted: number } {
|
|
126
|
+
const added = (output.match(/(\d+) insertion/) ?? [])[1];
|
|
127
|
+
const deleted = (output.match(/(\d+) deletion/) ?? [])[1];
|
|
128
|
+
return {
|
|
129
|
+
added: added ? Number(added) : 0,
|
|
130
|
+
deleted: deleted ? Number(deleted) : 0,
|
|
131
|
+
};
|
|
132
|
+
}
|
|
133
|
+
|
|
134
|
+
/**
|
|
135
|
+
* Fetch line-level diff stats (additions / deletions) from HEAD.
|
|
136
|
+
*/
|
|
137
|
+
async function fetchDiffStat(cwd: string): Promise<{ added: number; deleted: number } | null> {
|
|
138
|
+
const output = await runGit(["diff", "--shortstat", "HEAD"], cwd, 1000);
|
|
139
|
+
if (output === null) return null;
|
|
140
|
+
return parseDiffStat(output);
|
|
141
|
+
}
|
|
142
|
+
|
|
143
|
+
/**
|
|
144
|
+
* Fetch git status asynchronously.
|
|
145
|
+
*/
|
|
146
|
+
async function fetchGitStatus(cwd: string): Promise<{ staged: number; unstaged: number; untracked: number } | null> {
|
|
147
|
+
const output = await runGit(["status", "--porcelain"], cwd, 500);
|
|
118
148
|
if (output === null) return null;
|
|
119
149
|
return parseGitStatusOutput(output);
|
|
120
150
|
}
|
|
@@ -123,9 +153,15 @@ async function fetchGitStatus(): Promise<{ staged: number; unstaged: number; unt
|
|
|
123
153
|
* Get the current git branch with caching.
|
|
124
154
|
* Falls back to provider branch if our cache is empty.
|
|
125
155
|
*/
|
|
126
|
-
export function getCurrentBranch(providerBranch: string | null): string | null {
|
|
156
|
+
export function getCurrentBranch(providerBranch: string | null, cwd: string): string | null {
|
|
127
157
|
const now = Date.now();
|
|
128
158
|
|
|
159
|
+
// Invalidate cache if cwd changed (different repo)
|
|
160
|
+
if (branchCwd !== cwd) {
|
|
161
|
+
cachedBranch = null;
|
|
162
|
+
branchCwd = cwd;
|
|
163
|
+
}
|
|
164
|
+
|
|
129
165
|
// Return cached if fresh
|
|
130
166
|
if (cachedBranch && now - cachedBranch.timestamp < BRANCH_TTL_MS) {
|
|
131
167
|
return cachedBranch.branch;
|
|
@@ -134,9 +170,10 @@ export function getCurrentBranch(providerBranch: string | null): string | null {
|
|
|
134
170
|
// Trigger background fetch if not already pending
|
|
135
171
|
if (!pendingBranchFetch) {
|
|
136
172
|
const fetchId = branchInvalidationCounter;
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
if
|
|
173
|
+
branchFetchCwd = cwd;
|
|
174
|
+
pendingBranchFetch = fetchGitBranch(cwd).then((result) => {
|
|
175
|
+
// Cache result if no invalidation happened AND cwd hasn't changed
|
|
176
|
+
if (fetchId === branchInvalidationCounter && branchFetchCwd === cwd) {
|
|
140
177
|
cachedBranch = {
|
|
141
178
|
branch: result,
|
|
142
179
|
timestamp: Date.now(),
|
|
@@ -156,12 +193,18 @@ export function getCurrentBranch(providerBranch: string | null): string | null {
|
|
|
156
193
|
* This is designed for synchronous render() calls - returns last known value
|
|
157
194
|
* while refreshing in background.
|
|
158
195
|
*/
|
|
159
|
-
export function getGitStatus(providerBranch: string | null, pollingMode: GitPollingMode = "full"): GitStatus {
|
|
196
|
+
export function getGitStatus(providerBranch: string | null, cwd: string, pollingMode: GitPollingMode = "full"): GitStatus {
|
|
160
197
|
const now = Date.now();
|
|
161
|
-
const branch = pollingMode === "off" ? providerBranch : getCurrentBranch(providerBranch);
|
|
198
|
+
const branch = pollingMode === "off" ? providerBranch : getCurrentBranch(providerBranch, cwd);
|
|
162
199
|
|
|
163
200
|
if (pollingMode !== "full") {
|
|
164
|
-
return { branch, staged: 0, unstaged: 0, untracked: 0 };
|
|
201
|
+
return { branch, staged: 0, unstaged: 0, untracked: 0, added: 0, deleted: 0 };
|
|
202
|
+
}
|
|
203
|
+
|
|
204
|
+
// Invalidate cache if cwd changed (different repo)
|
|
205
|
+
if (statusCwd !== cwd) {
|
|
206
|
+
cachedStatus = null;
|
|
207
|
+
statusCwd = cwd;
|
|
165
208
|
}
|
|
166
209
|
|
|
167
210
|
// Return cached if fresh
|
|
@@ -171,18 +214,38 @@ export function getGitStatus(providerBranch: string | null, pollingMode: GitPoll
|
|
|
171
214
|
staged: cachedStatus.staged,
|
|
172
215
|
unstaged: cachedStatus.unstaged,
|
|
173
216
|
untracked: cachedStatus.untracked,
|
|
217
|
+
added: cachedStatus.added,
|
|
218
|
+
deleted: cachedStatus.deleted,
|
|
174
219
|
};
|
|
175
220
|
}
|
|
176
221
|
|
|
177
222
|
// Trigger background fetch if not already pending
|
|
178
223
|
if (!pendingFetch) {
|
|
179
224
|
const fetchId = invalidationCounter; // Capture current counter
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
225
|
+
fetchCwd = cwd;
|
|
226
|
+
pendingFetch = Promise.all([
|
|
227
|
+
fetchGitStatus(cwd),
|
|
228
|
+
fetchDiffStat(cwd),
|
|
229
|
+
]).then(([statusResult, diffResult]) => {
|
|
230
|
+
// Cache result if no invalidation happened AND cwd hasn't changed
|
|
231
|
+
if (fetchId === invalidationCounter && fetchCwd === cwd) {
|
|
232
|
+
cachedStatus = statusResult
|
|
233
|
+
? {
|
|
234
|
+
staged: statusResult.staged,
|
|
235
|
+
unstaged: statusResult.unstaged,
|
|
236
|
+
untracked: statusResult.untracked,
|
|
237
|
+
added: diffResult?.added ?? 0,
|
|
238
|
+
deleted: diffResult?.deleted ?? 0,
|
|
239
|
+
timestamp: Date.now(),
|
|
240
|
+
}
|
|
241
|
+
: {
|
|
242
|
+
staged: 0,
|
|
243
|
+
unstaged: 0,
|
|
244
|
+
untracked: 0,
|
|
245
|
+
added: 0,
|
|
246
|
+
deleted: 0,
|
|
247
|
+
timestamp: Date.now(),
|
|
248
|
+
};
|
|
186
249
|
}
|
|
187
250
|
pendingFetch = null;
|
|
188
251
|
});
|
|
@@ -195,10 +258,12 @@ export function getGitStatus(providerBranch: string | null, pollingMode: GitPoll
|
|
|
195
258
|
staged: cachedStatus.staged,
|
|
196
259
|
unstaged: cachedStatus.unstaged,
|
|
197
260
|
untracked: cachedStatus.untracked,
|
|
261
|
+
added: cachedStatus.added,
|
|
262
|
+
deleted: cachedStatus.deleted,
|
|
198
263
|
};
|
|
199
264
|
}
|
|
200
265
|
|
|
201
|
-
return { branch, staged: 0, unstaged: 0, untracked: 0 };
|
|
266
|
+
return { branch, staged: 0, unstaged: 0, untracked: 0, added: 0, deleted: 0 };
|
|
202
267
|
}
|
|
203
268
|
|
|
204
269
|
/**
|