pi-code 1.0.53 → 1.0.54

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.
@@ -174,10 +174,19 @@ export function shellExecutionDisabled(cwd: string, home: string, trusted: boole
174
174
  if (readManagedSettings().disableSkillShellExecution === true) return true
175
175
  const files = claudeSettingsChain(cwd, home, trusted)
176
176
  return files.some((file) => {
177
+ let raw: string
177
178
  try {
178
- return (JSON.parse(fs.readFileSync(file, 'utf-8')) as Record<string, unknown>).disableSkillShellExecution === true
179
+ raw = fs.readFileSync(file, 'utf-8')
179
180
  } catch {
180
- return false // missing or invalid file: not a policy statement
181
+ return false // no such file: genuinely not a policy statement
182
+ }
183
+ try {
184
+ return (JSON.parse(raw) as Record<string, unknown>).disableSkillShellExecution === true
185
+ } catch {
186
+ // Present but unreadable: the user may believe this file disables skill shells, so
187
+ // say so rather than silently leaving them enabled.
188
+ console.warn(`pi-code: ignoring ${file}: not valid JSON; skill shell execution stays as the other settings leave it`)
189
+ return false
181
190
  }
182
191
  })
183
192
  }
@@ -210,7 +210,7 @@ export default function gitCheckpointExtension(pi: ExtensionAPI) {
210
210
  }
211
211
  // Written on every start, so repos that predate the sidecar pick it up too.
212
212
  rememberWorkTree(shadowDir, ctx.cwd)
213
- await mirrorLocalExcludes(ctx.cwd)
213
+ await mirrorLocalExcludes(ctx)
214
214
  }
215
215
 
216
216
  /** git reads ignore rules from the tree's .gitignore files, the user's global excludes,
@@ -219,7 +219,8 @@ export default function gitCheckpointExtension(pi: ExtensionAPI) {
219
219
  * would be snapshotted and restored. Mirror it into the shadow on every start; the
220
220
  * global excludes stay untouched (core.excludesFile is single-valued, so pointing it
221
221
  * at the repo file would replace them). */
222
- async function mirrorLocalExcludes(cwd: string): Promise<void> {
222
+ async function mirrorLocalExcludes(ctx: ExtensionContext): Promise<void> {
223
+ const cwd = ctx.cwd
223
224
  if (!shadowDir) return
224
225
  // Resolved through git so a linked worktree maps to its common dir; outside a repo
225
226
  // git exits 128 and there is nothing to mirror.
@@ -233,8 +234,10 @@ export default function gitCheckpointExtension(pi: ExtensionAPI) {
233
234
  } else {
234
235
  fs.rmSync(target, { force: true })
235
236
  }
236
- } catch {
237
- // A failed mirror only means local excludes are not honored this session.
237
+ } catch (error) {
238
+ // Without the mirror, files the user excluded locally are snapshotted into the
239
+ // checkpoint store and restored by /rewind, so this is not a silent fallback.
240
+ ctx.ui.notify(`Checkpoints cannot honor this repository's .git/info/exclude: ${error instanceof Error ? error.message : String(error)}`, 'warning')
238
241
  }
239
242
  }
240
243
 
@@ -28,6 +28,12 @@ function asOAuthRequiredError(name: string, error: unknown): OAuthRequiredError
28
28
  * (stored-token) connects do not pass through here and stay fully parallel. */
29
29
  let oauthQueue: Promise<unknown> = Promise.resolve()
30
30
 
31
+ /** Test seam: the queue is module state that outlives a test, so one login left pending
32
+ * would serialize every later login in the same file behind it. */
33
+ export function resetOAuthQueue(): void {
34
+ oauthQueue = Promise.resolve()
35
+ }
36
+
31
37
  export function serializeInteractiveOAuth<T>(run: () => Promise<T>): Promise<T> {
32
38
  const result = oauthQueue.then(run, run)
33
39
  oauthQueue = result.then(
@@ -97,11 +97,19 @@ function parsePolicyEntry(entry: unknown): McpPolicyEntry | undefined {
97
97
  * honored scope sets the key; an empty list is an explicit lockdown (deny all). */
98
98
  export function mcpAllowDeny(scopeFiles: string[] = [], managedFile: string = managedSettingsFile()): McpPolicy {
99
99
  const read = (file: string): Record<string, unknown> => {
100
+ let raw: string
100
101
  try {
101
- const parsed = JSON.parse(fs.readFileSync(file, 'utf-8'))
102
+ raw = fs.readFileSync(file, 'utf-8')
103
+ } catch {
104
+ return {} // no such file: genuinely no policy
105
+ }
106
+ try {
107
+ const parsed = JSON.parse(raw)
102
108
  return parsed && typeof parsed === 'object' ? parsed : {}
103
109
  } catch {
104
- // Missing or invalid file contributes no policy.
110
+ // Present but unreadable: its allow and deny lists are not in force, and a deny list
111
+ // silently dropped leaves every server allowed.
112
+ console.warn(`pi-code-mcp: ignoring ${file}: not valid JSON; its MCP allow and deny lists are not applied`)
105
113
  return {}
106
114
  }
107
115
  }
@@ -336,9 +336,11 @@ export async function connectWithRetries(name: string, config: ServerConfig, aut
336
336
  * environment helperEnv built. It runs through the platform shell (/bin/sh; Git Bash
337
337
  * or PowerShell on Windows). A failure, a 10s timeout, or a machine with no shell
338
338
  * yields no extra headers rather than blocking the connection. */
339
- function runHeadersHelper(command: string, env: NodeJS.ProcessEnv): Promise<Record<string, string>> {
339
+ export function runHeadersHelper(command: string, env: NodeJS.ProcessEnv, resolve_ = resolveShell): Promise<Record<string, string>> {
340
340
  return new Promise((resolve) => {
341
- const shell = resolveShell(undefined)
341
+ // The resolver is a parameter so a test can drive both outcomes on any platform:
342
+ // stubbing the platform cannot produce a shell-less host on a machine that has one.
343
+ const shell = resolve_(undefined)
342
344
  if (!shell) {
343
345
  resolve({})
344
346
  return
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "pi-code",
3
- "version": "1.0.53",
3
+ "version": "1.0.54",
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, subagents, and goals",
5
5
  "keywords": [
6
6
  "pi",