pi-code 1.0.21 → 1.0.22

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.
@@ -13,19 +13,21 @@
13
13
  * approval-gated on purpose: a checked-out repository's env can redirect providers
14
14
  * (ANTHROPIC_BASE_URL and friends), so an untrusted repo must not reach process.env.
15
15
  *
16
- * Precedence is per key, managed > user > project, matching Claude's merge: a scope
17
- * only supplies keys it names and never wipes another scope's keys. Values must be
18
- * strings; a number or boolean is coerced via String, anything else is skipped.
16
+ * Precedence is per key, managed > project (settings.local.json overlaying
17
+ * settings.json inside the project scope) > user, matching Claude's settings
18
+ * precedence: a scope only supplies keys it names and never wipes another scope's
19
+ * keys. Values must be strings; a number or boolean is coerced via String,
20
+ * anything else is skipped.
19
21
  *
20
- * A variable already present in the real environment that this module did not set is
21
- * left untouched for the user and project scopes: a shell `export` outranks them. The
22
- * managed scope is the exception: an org policy env must win over an ambient export, so
23
- * a managed key overwrites a preexisting shell value. The keys this module sets are
24
- * recorded so a later refresh can update them without clobbering unrelated env, and a
25
- * key an earlier apply set that the current merge no longer defines is unset (deleted
26
- * from process.env), so an approved project's env cannot leak into a later session or
27
- * project that does not define it. A managed overwrite of a shell var is owned like any
28
- * other set key; its original shell value cannot be restored, so on unset it is deleted.
22
+ * A settings value replaces a value inherited from the shell, as Claude documents
23
+ * ("Claude Code writes each env entry into the process environment, replacing the
24
+ * value inherited from the shell"), and an empty string is the documented way to
25
+ * override an export that cannot be unset. The original value of each key is
26
+ * recorded so a later apply that no longer defines the key restores the shell's
27
+ * value (or deletes a key the shell never had), so an approved project's env
28
+ * cannot leak into a later session or project that does not define it. The keys a
29
+ * repository must not control are dropped from the project scope before any of
30
+ * this (see sanitizeProjectEnv).
29
31
  *
30
32
  * Docs: https://code.claude.com/docs/en/settings.md
31
33
  */
@@ -57,35 +59,31 @@ export function envFromSettings(settings: unknown): Record<string, string> {
57
59
  return out
58
60
  }
59
61
 
60
- /** Merge the three env scopes with Claude's per-key precedence managed > user >
61
- * project: lower scopes are laid down first and higher ones overlay, so each key
62
- * takes its highest-precedence value and no scope wipes another's keys. */
62
+ /** Merge the three env scopes with Claude's per-key settings precedence managed >
63
+ * project > user: lower scopes are laid down first and higher ones overlay, so each
64
+ * key takes its highest-precedence value and no scope wipes another's keys. */
63
65
  export function mergeEnvScopes(managed: Record<string, string>, user: Record<string, string>, project: Record<string, string>): Record<string, string> {
64
- return { ...project, ...user, ...managed }
66
+ return { ...user, ...project, ...managed }
65
67
  }
66
68
 
67
- /** Assign the merged env into `env`, recording each key set into `owned`. A key already
68
- * present that this module did not set (a shell export) is left untouched for user and
69
- * project keys; a `managedKeys` entry overwrites it (an org policy outranks the shell).
70
- * A key this module set before is updated. A previously-owned key that the current
71
- * `merged` no longer defines is unset (deleted from `env` and `owned`), so an approved
72
- * project's env cannot leak into a later apply that does not define it. */
73
- export function applyEnvSettings(merged: Record<string, string>, env: NodeJS.ProcessEnv, owned: Set<string>, managedKeys: ReadonlySet<string> = new Set()): void {
74
- // Unset any key an earlier apply set that the current merge dropped. Iterate a copy
75
- // since `owned` is mutated. A shell export this module never owned is left in place.
76
- for (const key of Array.from(owned)) {
77
- if (!(key in merged)) {
78
- delete env[key]
79
- owned.delete(key)
80
- }
69
+ /** Assign the merged env into `env`. Every settings value applies, replacing a
70
+ * shell-inherited value, as Claude documents; an empty string is the documented
71
+ * override for an export that cannot be unset. `owned` records each key's original
72
+ * value at first ownership, so a later apply that drops the key restores the
73
+ * shell's value (or deletes a key the shell never had) rather than leaking a stale
74
+ * setting into the rest of the process. */
75
+ export function applyEnvSettings(merged: Record<string, string>, env: NodeJS.ProcessEnv, owned: Map<string, string | undefined>): void {
76
+ // Restore any key an earlier apply set that the current merge dropped. Iterate a
77
+ // copy since `owned` is mutated.
78
+ for (const [key, original] of Array.from(owned.entries())) {
79
+ if (key in merged) continue
80
+ if (original === undefined) delete env[key]
81
+ else env[key] = original
82
+ owned.delete(key)
81
83
  }
82
84
  for (const [key, value] of Object.entries(merged)) {
83
- // A shell export outranks user/project (skip), but a managed key outranks even the
84
- // shell. Once owned, updates always apply. A managed overwrite is owned like any
85
- // set key: its shell value is gone and cannot be restored, so on unset it is deleted.
86
- if (key in env && !owned.has(key) && !managedKeys.has(key)) continue
85
+ if (!owned.has(key)) owned.set(key, env[key])
87
86
  env[key] = value
88
- owned.add(key)
89
87
  }
90
88
  }
91
89
 
@@ -148,11 +146,10 @@ function projectEnv(cwd: string): Record<string, string> {
148
146
  }
149
147
 
150
148
  export default function envSettingsExtension(pi: ExtensionAPI) {
151
- const owned = new Set<string>()
149
+ const owned = new Map<string, string | undefined>()
152
150
 
153
151
  const apply = (home: string, project: Record<string, string>): void => {
154
- const managed = envFromSettings(readManagedSettings())
155
- applyEnvSettings(mergeEnvScopes(managed, userEnv(home), project), process.env, owned, new Set(Object.keys(managed)))
152
+ applyEnvSettings(mergeEnvScopes(envFromSettings(readManagedSettings()), userEnv(home), project), process.env, owned)
156
153
  }
157
154
 
158
155
  // Factory time: managed + user only. Approval needs the session ctx, so the project
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "pi-code",
3
- "version": "1.0.21",
3
+ "version": "1.0.22",
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",