pi-code 1.0.42 → 1.0.43
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.
|
@@ -121,6 +121,10 @@ function findMarkdownFiles(dir: string, basePath = '', visited = new Set<string>
|
|
|
121
121
|
} catch {
|
|
122
122
|
return results // a missing or unreadable directory must not take down session start
|
|
123
123
|
}
|
|
124
|
+
// Raw readdir order is filesystem-dependent (hash order on ext4, sorted on
|
|
125
|
+
// APFS), so unsorted iteration injects rules into the prompt in a different
|
|
126
|
+
// order per machine. Pinned-locale sort makes the prompt reproducible.
|
|
127
|
+
entries.sort((a, b) => a.name.localeCompare(b.name, 'en'))
|
|
124
128
|
for (const entry of entries) {
|
|
125
129
|
const relativePath = basePath ? `${basePath}/${entry.name}` : entry.name
|
|
126
130
|
const fullPath = path.join(dir, entry.name)
|
|
@@ -299,7 +299,9 @@ export function additionalDirContextFiles(dir: string, includeLocal: boolean): A
|
|
|
299
299
|
const names = fs
|
|
300
300
|
.readdirSync(rulesDir)
|
|
301
301
|
.filter((name) => name.endsWith('.md'))
|
|
302
|
-
|
|
302
|
+
// Pinned locale: the default collator follows the host locale, which
|
|
303
|
+
// reorders names like ch/ci/h and makes prompt content machine-dependent.
|
|
304
|
+
.sort((a, b) => a.localeCompare(b, 'en'))
|
|
303
305
|
candidates.push(...names.map((name) => path.join(rulesDir, name)))
|
|
304
306
|
} catch {
|
|
305
307
|
// no rules directory in this additional dir
|
|
@@ -262,11 +262,15 @@ export default function gitCheckpointExtension(pi: ExtensionAPI) {
|
|
|
262
262
|
runNeedsSnapshot = true
|
|
263
263
|
await ensureShadow(ctx)
|
|
264
264
|
checkpoints.clear()
|
|
265
|
+
const stored: Checkpoint[] = []
|
|
265
266
|
for (const entry of ctx.sessionManager.getEntries()) {
|
|
266
267
|
if (entry.type !== 'custom' || entry.customType !== CUSTOM_TYPE) continue
|
|
267
268
|
const checkpoint = entry.data as Checkpoint | undefined
|
|
268
|
-
if (checkpoint?.entryId)
|
|
269
|
+
if (checkpoint?.entryId) stored.push(checkpoint)
|
|
269
270
|
}
|
|
271
|
+
// The same cap the append path enforces: a resumed long session must not
|
|
272
|
+
// rebuild a rewind list beyond the per-session limit.
|
|
273
|
+
for (const checkpoint of capCheckpoints(stored)) checkpoints.set(checkpoint.entryId, checkpoint)
|
|
270
274
|
})
|
|
271
275
|
|
|
272
276
|
// A new agent loop starts a run: the next turn_start snapshots the pre-run tree.
|
|
@@ -531,13 +531,6 @@ function fenceBlocks(body: string): FenceBlock[] {
|
|
|
531
531
|
return blocks
|
|
532
532
|
}
|
|
533
533
|
|
|
534
|
-
/** Spans of a body inside a protective fenced code block. */
|
|
535
|
-
function fencedRanges(body: string): Array<[number, number]> {
|
|
536
|
-
return fenceBlocks(body)
|
|
537
|
-
.filter((block) => !block.exec)
|
|
538
|
-
.map((block) => [block.start, block.end])
|
|
539
|
-
}
|
|
540
|
-
|
|
541
534
|
/** Exit 1 is a normal result for Claude's documented search and comparison commands
|
|
542
535
|
* (no matches, files differ); exit 2 and up fails even for these. The PowerShell
|
|
543
536
|
* shell uses a different set, which "includes grep and git diff but not find or
|
|
@@ -12,6 +12,7 @@
|
|
|
12
12
|
* ~/.claude/plugins/data/<id>, id being the qualified name folded to dashes.
|
|
13
13
|
*/
|
|
14
14
|
|
|
15
|
+
import * as crypto from 'node:crypto'
|
|
15
16
|
import * as fs from 'node:fs'
|
|
16
17
|
import * as path from 'node:path'
|
|
17
18
|
|
|
@@ -49,9 +50,33 @@ function listDirs(dir: string): string[] {
|
|
|
49
50
|
}
|
|
50
51
|
}
|
|
51
52
|
|
|
52
|
-
/**
|
|
53
|
+
/** One version string split for comparison: optional v prefix dropped, numeric
|
|
54
|
+
* base segments, and whatever follows a dash as the prerelease tag. */
|
|
55
|
+
function parseVersion(version: string): { base: number[]; pre: string | undefined } {
|
|
56
|
+
const stripped = version.replace(/^v/i, '')
|
|
57
|
+
const dash = stripped.indexOf('-')
|
|
58
|
+
const base = (dash === -1 ? stripped : stripped.slice(0, dash)).split('.').map((segment) => Number.parseInt(segment, 10) || 0)
|
|
59
|
+
return { base, pre: dash === -1 ? undefined : stripped.slice(dash + 1) }
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
/** Semver ordering to the depth plugin cache dirs need: 1.10.0 beats 1.9.0,
|
|
63
|
+
* 10.0.0 beats v2.0.0, and a release outranks its own prerelease (a plain
|
|
64
|
+
* string sort got both of the latter wrong). */
|
|
65
|
+
function compareVersions(a: string, b: string): number {
|
|
66
|
+
const left = parseVersion(a)
|
|
67
|
+
const right = parseVersion(b)
|
|
68
|
+
for (let i = 0; i < Math.max(left.base.length, right.base.length); i++) {
|
|
69
|
+
const diff = (left.base[i] ?? 0) - (right.base[i] ?? 0)
|
|
70
|
+
if (diff !== 0) return diff
|
|
71
|
+
}
|
|
72
|
+
if (left.pre === right.pre) return 0
|
|
73
|
+
if (left.pre === undefined) return 1
|
|
74
|
+
if (right.pre === undefined) return -1
|
|
75
|
+
return left.pre.localeCompare(right.pre, 'en', { numeric: true })
|
|
76
|
+
}
|
|
77
|
+
|
|
53
78
|
function newestVersion(versions: string[]): string | undefined {
|
|
54
|
-
return [...versions].sort(
|
|
79
|
+
return [...versions].sort(compareVersions).at(-1)
|
|
55
80
|
}
|
|
56
81
|
|
|
57
82
|
/** The enablement map, later files winning per key, as settings scopes merge. */
|
|
@@ -101,7 +126,8 @@ export function resetInstalledPluginsCache(): void {
|
|
|
101
126
|
pluginCache.clear()
|
|
102
127
|
}
|
|
103
128
|
|
|
104
|
-
/** mtime plus size,
|
|
129
|
+
/** mtime plus size; cheap, but blind to a same-size rewrite within one
|
|
130
|
+
* timestamp tick, so only directory-tree entries use it. */
|
|
105
131
|
function statToken(target: string): string {
|
|
106
132
|
try {
|
|
107
133
|
const stat = fs.statSync(target)
|
|
@@ -111,15 +137,25 @@ function statToken(target: string): string {
|
|
|
111
137
|
}
|
|
112
138
|
}
|
|
113
139
|
|
|
140
|
+
/** Content hash for the small settings files: a same-size rewrite within one
|
|
141
|
+
* mtime tick (the settings-watch flake class) must still invalidate the cache. */
|
|
142
|
+
function contentToken(target: string): string {
|
|
143
|
+
try {
|
|
144
|
+
return crypto.createHash('sha256').update(fs.readFileSync(target)).digest('hex')
|
|
145
|
+
} catch {
|
|
146
|
+
return 'missing'
|
|
147
|
+
}
|
|
148
|
+
}
|
|
149
|
+
|
|
114
150
|
/**
|
|
115
|
-
* A cheap change signature for one home's plugin config: the settings files'
|
|
116
|
-
*
|
|
151
|
+
* A cheap change signature for one home's plugin config: the settings files'
|
|
152
|
+
* content hashes plus the cache tree's directory names and mtimes down through each plugin's
|
|
117
153
|
* version directories, and the stat token of the resolved (newest) version's manifest
|
|
118
154
|
* so an in-place edit of it invalidates the cache. Costs a few stats where the full
|
|
119
155
|
* walk reads and parses the settings and every manifest.
|
|
120
156
|
*/
|
|
121
157
|
function pluginFingerprint(cacheDir: string, settingsFiles: string[]): string {
|
|
122
|
-
const parts = settingsFiles.map(
|
|
158
|
+
const parts = settingsFiles.map(contentToken)
|
|
123
159
|
for (const marketplace of listDirs(cacheDir)) {
|
|
124
160
|
const marketplaceDir = path.join(cacheDir, marketplace)
|
|
125
161
|
parts.push(`${marketplace}:${statToken(marketplaceDir)}`)
|
|
@@ -105,7 +105,9 @@ async function writePromptToTempFile(agentName: string, prompt: string): Promise
|
|
|
105
105
|
return { dir: tmpDir, filePath }
|
|
106
106
|
}
|
|
107
107
|
|
|
108
|
-
|
|
108
|
+
/** Exported as a test seam: the fallbacks only fire in packaged distributions
|
|
109
|
+
* (bun single-file, compiled binary), which no CI run reaches naturally. */
|
|
110
|
+
export function getPiInvocation(args: string[]): { command: string; args: string[] } {
|
|
109
111
|
const currentScript = process.argv[1]
|
|
110
112
|
const isBunVirtualScript = currentScript?.startsWith('/$bunfs/root/')
|
|
111
113
|
if (currentScript && !isBunVirtualScript && fs.existsSync(currentScript)) {
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "pi-code",
|
|
3
|
-
"version": "1.0.
|
|
3
|
+
"version": "1.0.43",
|
|
4
4
|
"description": "Claude Code experience for the pi coding agent: reads your .claude config (rules, commands, skills, hooks, output styles, MCP servers, agents) and adds todo, checkpoints, memory, web, and subagents",
|
|
5
5
|
"keywords": [
|
|
6
6
|
"pi",
|