@pi-archimedes/core 2.11.2 → 2.13.0

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 CHANGED
@@ -34,6 +34,6 @@ After installing Pi, choose one installation command above, then `cd` into your
34
34
 
35
35
  ## Part of the suite
36
36
 
37
- In [pi-archimedes](https://github.com/danielcherubini/pi-archimedes), core is always registered — it isn't one of the `/plugins` toggles — and it underpins what the other components share: the channel vocabulary that carries subagent todos to the task board and subagent questions to the ask UI, and the bridge that reaches the Archimedes Client. The diff renderer is standalone and does not depend on core's chrome — it only shares the suite when it loads.
37
+ In [pi-archimedes](https://github.com/archimedes-codes/pi-archimedes), core is always registered — it isn't one of the `/plugins` toggles — and it underpins what the other components share: the channel vocabulary that carries subagent todos to the task board and subagent questions to the ask UI, and the bridge that reaches the Archimedes Client. The diff renderer is standalone and does not depend on core's chrome — it only shares the suite when it loads.
38
38
 
39
- ← [Back to pi-archimedes](https://github.com/danielcherubini/pi-archimedes)
39
+ ← [Back to pi-archimedes](https://github.com/archimedes-codes/pi-archimedes)
package/package.json CHANGED
@@ -1,9 +1,9 @@
1
1
  {
2
2
  "name": "@pi-archimedes/core",
3
- "version": "2.11.2",
3
+ "version": "2.13.0",
4
4
  "repository": {
5
5
  "type": "git",
6
- "url": "https://github.com/danielcherubini/pi-archimedes.git"
6
+ "url": "https://github.com/archimedes-codes/pi-archimedes.git"
7
7
  },
8
8
  "type": "module",
9
9
  "keywords": [
@@ -36,8 +36,8 @@
36
36
  "@earendil-works/pi-tui": ">=0.85.0"
37
37
  },
38
38
  "devDependencies": {
39
- "@earendil-works/pi-coding-agent": "1.2.0",
40
- "@earendil-works/pi-tui": "1.2.0",
39
+ "@earendil-works/pi-coding-agent": "1.4.0",
40
+ "@earendil-works/pi-tui": "1.4.0",
41
41
  "typescript": "^6.0.0"
42
42
  },
43
43
  "pi": {
@@ -199,6 +199,44 @@ describe("loadConfig", () => {
199
199
  const result = loadConfig("test.ns", { foo: "bar", count: 42 });
200
200
  expect(result).toEqual({ foo: "bar", count: 42 });
201
201
  });
202
+
203
+ it("handles leading UTF-8 BOM in settings.json", () => {
204
+ const settingsPath = join(tempDir, "settings.json");
205
+ fs.writeFileSync(
206
+ settingsPath,
207
+ "\uFEFF" + JSON.stringify({ "test.ns": { foo: "overridden" } }),
208
+ "utf-8",
209
+ );
210
+ const result = loadConfig("test.ns", { foo: "bar", count: 42 });
211
+ expect(result).toEqual({ foo: "overridden", count: 42 });
212
+ });
213
+
214
+ it("prevents clobbering sibling namespaces when settings.json has UTF-8 BOM", () => {
215
+ const settingsPath = join(tempDir, "settings.json");
216
+ fs.writeFileSync(
217
+ settingsPath,
218
+ "\uFEFF" + JSON.stringify({ "test.ns": { foo: "overridden" } }),
219
+ "utf-8",
220
+ );
221
+ loadConfig("test.ns", { foo: "bar", count: 42 });
222
+ saveConfig("other.ns", { val: 123 });
223
+ const raw = fs.readFileSync(settingsPath, "utf-8");
224
+ expect(raw.startsWith("\uFEFF")).toBe(false);
225
+ const data = JSON.parse(raw);
226
+ expect(data["test.ns"]).toEqual({ foo: "overridden" });
227
+ expect(data["other.ns"]).toEqual({ val: 123 });
228
+ });
229
+
230
+ it("readEffectiveSettings returns parsed object from BOM-prefixed settings.json (file fallback)", () => {
231
+ const settingsPath = join(tempDir, "settings.json");
232
+ fs.writeFileSync(
233
+ settingsPath,
234
+ "\uFEFF" + JSON.stringify({ "test.ns": { foo: "overridden" } }),
235
+ "utf-8",
236
+ );
237
+ const result = readEffectiveSettings();
238
+ expect(result).toEqual({ "test.ns": { foo: "overridden" } });
239
+ });
202
240
  });
203
241
 
204
242
  describe("saveConfig", () => {
@@ -22,8 +22,9 @@ function readRawSettings(): string | null {
22
22
  /** Parse a raw settings string, returning empty object on null/corrupt input. */
23
23
  function parseSettings(raw: string | null): Record<string, unknown> {
24
24
  if (raw === null) return {};
25
+ const sanitized = raw.startsWith("\uFEFF") ? raw.slice(1) : raw;
25
26
  try {
26
- return JSON.parse(raw) as Record<string, unknown>;
27
+ return JSON.parse(sanitized) as Record<string, unknown>;
27
28
  } catch {
28
29
  return {};
29
30
  }