pi-cohort 5.3.0 → 5.3.2

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 CHANGED
@@ -1,5 +1,18 @@
1
1
  # Changelog
2
2
 
3
+ ## [5.3.2] - 2026-09-06
4
+
5
+ ### Fixed
6
+
7
+ - Extension startup now retries transient `EPERM` failures while creating completed-result and async-run temporary directories, with a strict limit of three attempts and two one-second waits. ([#10](https://github.com/jjuraszek/pi-cohort/issues/10))
8
+
9
+ ## [5.3.1] - 2026-09-01
10
+
11
+ ### Fixed
12
+
13
+ - Async (detached) subagent runs no longer crash at boot with `Cannot find module 'typebox/compile'` on consumer installs: `typebox` is now a real runtime dependency (`^1.3.11`). This deliberately re-reverses 2.0.0's move of typebox to peerDependencies - that decision's "match pi's bundled packages" rationale only holds for code running under pi's jiti aliases, and the detached runner runs outside them (same reasoning as `jiti` itself being a real dependency). ([#9](https://github.com/jjuraszek/pi-cohort/issues/9))
14
+ - Acceptance evidence checks now treat present-but-empty `changedFiles`, `testsAddedOrUpdated`, `commandsRun`, and `validationOutput` arrays as reported evidence ("ran and touched nothing") instead of rejecting the run with `evidence missing`; an absent field still fails structurally. No-op async worker runs with all criteria passing now reach `checked` status. ([#9](https://github.com/jjuraszek/pi-cohort/issues/9))
15
+
3
16
  ## [5.3.0] - 2026-09-01
4
17
 
5
18
  ### Changed
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "pi-cohort",
3
- "version": "5.3.0",
3
+ "version": "5.3.2",
4
4
  "description": "Delegate Pi work to focused child agents: code review, scouting, implementation, parallel audits, saved chains, and background jobs.",
5
5
  "author": "Jacek Juraszek",
6
6
  "license": "MIT",
@@ -60,8 +60,7 @@
60
60
  "@earendil-works/pi-agent-core": "*",
61
61
  "@earendil-works/pi-ai": "*",
62
62
  "@earendil-works/pi-coding-agent": "*",
63
- "@earendil-works/pi-tui": "*",
64
- "typebox": "*"
63
+ "@earendil-works/pi-tui": "*"
65
64
  },
66
65
  "peerDependenciesMeta": {
67
66
  "@earendil-works/pi-agent-core": {
@@ -75,19 +74,16 @@
75
74
  },
76
75
  "@earendil-works/pi-tui": {
77
76
  "optional": true
78
- },
79
- "typebox": {
80
- "optional": true
81
77
  }
82
78
  },
83
79
  "dependencies": {
84
- "jiti": "^2.7.0"
80
+ "jiti": "^2.7.0",
81
+ "typebox": "^1.3.11"
85
82
  },
86
83
  "devDependencies": {
87
84
  "@earendil-works/pi-agent-core": "^0.74.0",
88
85
  "@earendil-works/pi-ai": "^0.74.0",
89
86
  "@earendil-works/pi-coding-agent": "^0.74.0",
90
- "@earendil-works/pi-tui": "^0.74.0",
91
- "typebox": "^1.1.24"
87
+ "@earendil-works/pi-tui": "^0.74.0"
92
88
  }
93
89
  }
@@ -24,6 +24,7 @@ import { resolveCurrentSessionId } from "../shared/session-identity.ts";
24
24
  import { cleanupOldChainDirs } from "../shared/settings.ts";
25
25
  import { clearLegacyResultAnimationTimer, renderWidget, renderSubagentResult } from "../tui/render.ts";
26
26
  import { SubagentParams } from "./schemas.ts";
27
+ import { mkdirWithEpermRetry } from "./mkdir-with-retry.ts";
27
28
  import { createSubagentExecutor, type SubagentParamsLike } from "../runs/foreground/subagent-executor.ts";
28
29
  import { createAsyncJobTracker } from "../runs/background/async-job-tracker.ts";
29
30
  import { createResultWatcher } from "../runs/background/result-watcher.ts";
@@ -82,15 +83,9 @@ function expandTilde(p: string): string {
82
83
  return p.startsWith("~/") ? path.join(os.homedir(), p.slice(2)) : p;
83
84
  }
84
85
 
85
- /**
86
- * Create a directory and verify it is actually accessible.
87
- * On Windows with Azure AD/Entra ID, directories created shortly after
88
- * wake-from-sleep can end up with broken NTFS ACLs (null DACL) when the
89
- * cloud SID cannot be resolved without network connectivity. This leaves
90
- * the directory completely inaccessible to the creating user.
91
- */
86
+ /** Create a directory and verify it is actually accessible. */
92
87
  function ensureAccessibleDir(dirPath: string): void {
93
- fs.mkdirSync(dirPath, { recursive: true });
88
+ mkdirWithEpermRetry(dirPath);
94
89
  try {
95
90
  fs.accessSync(dirPath, fs.constants.R_OK | fs.constants.W_OK);
96
91
  } catch {
@@ -99,7 +94,7 @@ function ensureAccessibleDir(dirPath: string): void {
99
94
  } catch {
100
95
  // Best effort: retry mkdir/access even if cleanup fails.
101
96
  }
102
- fs.mkdirSync(dirPath, { recursive: true });
97
+ mkdirWithEpermRetry(dirPath);
103
98
  fs.accessSync(dirPath, fs.constants.R_OK | fs.constants.W_OK);
104
99
  }
105
100
  }
@@ -0,0 +1,34 @@
1
+ import { mkdirSync } from "node:fs";
2
+
3
+ type MkdirWithEpermRetryDeps = {
4
+ mkdir?: (path: string) => void;
5
+ wait?: (milliseconds: number) => void;
6
+ };
7
+
8
+ const RETRY_DELAY_MS = 1000;
9
+ const MAX_ATTEMPTS = 3;
10
+ const waitArray = new Int32Array(new SharedArrayBuffer(4));
11
+
12
+ function sleep(delayMs: number): void {
13
+ Atomics.wait(waitArray, 0, 0, delayMs);
14
+ }
15
+
16
+ export function mkdirWithEpermRetry(dirPath: string, deps: MkdirWithEpermRetryDeps = {}): void {
17
+ const mkdir = deps.mkdir ?? ((path) => mkdirSync(path, { recursive: true }));
18
+ const wait = deps.wait ?? sleep;
19
+
20
+ for (let attempt = 1; attempt <= MAX_ATTEMPTS; attempt += 1) {
21
+ try {
22
+ mkdir(dirPath);
23
+ return;
24
+ } catch (error) {
25
+ if (
26
+ !(typeof error === "object" && error !== null && "code" in error && error.code === "EPERM") ||
27
+ attempt === MAX_ATTEMPTS
28
+ ) {
29
+ throw error;
30
+ }
31
+ wait(RETRY_DELAY_MS);
32
+ }
33
+ }
34
+ }
@@ -387,10 +387,10 @@ function checkCriteriaSatisfied(criteria: ResolvedAcceptanceGate[], report: Acce
387
387
 
388
388
  function reportEvidencePresent(report: AcceptanceReport, kind: AcceptanceEvidenceKind): boolean {
389
389
  switch (kind) {
390
- case "changed-files": return isStringArray(report.changedFiles) && report.changedFiles.length > 0;
391
- case "tests-added": return isStringArray(report.testsAddedOrUpdated) && report.testsAddedOrUpdated.length > 0;
392
- case "commands-run": return Array.isArray(report.commandsRun) && report.commandsRun.length > 0;
393
- case "validation-output": return isStringArray(report.validationOutput) && report.validationOutput.length > 0;
390
+ case "changed-files": return isStringArray(report.changedFiles);
391
+ case "tests-added": return isStringArray(report.testsAddedOrUpdated);
392
+ case "commands-run": return Array.isArray(report.commandsRun);
393
+ case "validation-output": return isStringArray(report.validationOutput);
394
394
  case "residual-risks": return isStringArray(report.residualRisks);
395
395
  case "no-staged-files": return report.noStagedFiles === true;
396
396
  case "diff-summary": return typeof report.diffSummary === "string" && report.diffSummary.trim().length > 0;