@signalridge/pi-goal 1.4.0 → 1.4.1
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/CHANGELOG.md +34 -0
- package/package.json +1 -1
- package/src/persistence.ts +21 -4
package/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,39 @@
|
|
|
1
1
|
# @signalridge/pi-goal
|
|
2
2
|
|
|
3
|
+
## 1.4.1
|
|
4
|
+
### Patch Changes
|
|
5
|
+
|
|
6
|
+
- c1b1741: Durability and containment fixes found in a repository-wide scan.
|
|
7
|
+
|
|
8
|
+
`pi-gpt-fast` rewrote pi's own global `settings.json` with a plain
|
|
9
|
+
`writeFileSync`. That file is shared with pi and every other extension, and this
|
|
10
|
+
one is the only writer of it; a torn write would have left the user with no pi
|
|
11
|
+
configuration at all rather than just no gpt-fast setting. It now writes a temp
|
|
12
|
+
file and renames, matching every other settings writer in the repository.
|
|
13
|
+
`pi-goal` gets the same treatment for its cross-project goal state file, which
|
|
14
|
+
its own settings module already did.
|
|
15
|
+
|
|
16
|
+
`pi-ralph-wiggum` resolved `/ralph start <path>` straight against the session
|
|
17
|
+
cwd, so `../../notes.md` would create directories and a file outside the project
|
|
18
|
+
the user opened — silently, before the loop started. This extension drives long
|
|
19
|
+
unattended loops, so that command is as likely to come from a model as from a
|
|
20
|
+
person. Task-file paths are now required to stay inside the workspace, on the
|
|
21
|
+
command, on the `ralph_start` tool, and on the paths read back out of persisted
|
|
22
|
+
state, so a state file written earlier or edited by hand cannot pull a file in
|
|
23
|
+
from outside either.
|
|
24
|
+
|
|
25
|
+
The containment test canonicalizes both sides before comparing, because a purely
|
|
26
|
+
lexical one is defeated by a symlink: every segment of `linked/plan.md` reads as
|
|
27
|
+
inside the workspace when `linked` is a door out of it. `realpathSync` throws on
|
|
28
|
+
a path that does not exist yet — the ordinary case, since `start` is usually
|
|
29
|
+
creating the file — so it resolves the deepest existing ancestor and re-attaches
|
|
30
|
+
the not-yet-created suffix, which cannot itself be a link. The traversal test
|
|
31
|
+
matches a `..` segment rather than a `..` prefix; the prefix form also rejected
|
|
32
|
+
`..notes.md`, an ordinary filename sitting in the workspace.
|
|
33
|
+
|
|
34
|
+
Every remaining bare `catch {}` now says why the error is safe to drop, matching
|
|
35
|
+
the convention the rest of the repository already follows.
|
|
36
|
+
|
|
3
37
|
## 1.4.0
|
|
4
38
|
### Minor Changes
|
|
5
39
|
|
package/package.json
CHANGED
package/src/persistence.ts
CHANGED
|
@@ -1,5 +1,6 @@
|
|
|
1
|
-
import {
|
|
2
|
-
import {
|
|
1
|
+
import { randomUUID } from "node:crypto";
|
|
2
|
+
import { existsSync, mkdirSync, readFileSync, renameSync, rmSync, writeFileSync } from "node:fs";
|
|
3
|
+
import { basename, dirname, join } from "node:path";
|
|
3
4
|
import { getAgentDir } from "@earendil-works/pi-coding-agent";
|
|
4
5
|
import { isNonNegativeFiniteNumber, nonNegativeFiniteNumber, normalizeTokenBudget } from "./accounting.js";
|
|
5
6
|
import type { GoalStatus } from "./prompts.js";
|
|
@@ -238,12 +239,28 @@ function normalizeSafetyPauseCause(value: unknown): SafetyPauseCause | undefined
|
|
|
238
239
|
return value === "continuation_limit" || value === "no_progress" ? value : undefined;
|
|
239
240
|
}
|
|
240
241
|
|
|
242
|
+
/**
|
|
243
|
+
* Atomic for the same reason `saveGoalSettings` is: this file holds every
|
|
244
|
+
* project's goal state, so a torn write while clearing one project's entry
|
|
245
|
+
* would take the rest with it.
|
|
246
|
+
*/
|
|
241
247
|
export function clearLegacyPersistedGoal(cwd: string) {
|
|
242
248
|
if (!existsSync(STATE_FILE)) return;
|
|
243
249
|
const goals = readState();
|
|
244
250
|
delete goals[cwd];
|
|
245
|
-
|
|
246
|
-
|
|
251
|
+
const document = `${JSON.stringify(goals, null, 2)}\n`;
|
|
252
|
+
const temporaryPath = join(dirname(STATE_FILE), `.${basename(STATE_FILE)}.${randomUUID()}.tmp`);
|
|
253
|
+
try {
|
|
254
|
+
mkdirSync(dirname(STATE_FILE), { recursive: true });
|
|
255
|
+
writeFileSync(temporaryPath, document, { encoding: "utf8", flag: "wx" });
|
|
256
|
+
renameSync(temporaryPath, STATE_FILE);
|
|
257
|
+
} finally {
|
|
258
|
+
try {
|
|
259
|
+
rmSync(temporaryPath, { force: true });
|
|
260
|
+
} catch {
|
|
261
|
+
// Best-effort cleanup must not replace the clear result.
|
|
262
|
+
}
|
|
263
|
+
}
|
|
247
264
|
}
|
|
248
265
|
|
|
249
266
|
function readState(): Record<string, unknown> {
|