@preapexis/piexis 0.2.2 → 0.2.4

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.
Files changed (149) hide show
  1. package/AGENTS.md +21 -41
  2. package/README.md +73 -139
  3. package/extensions/agents.ts +205 -0
  4. package/extensions/ask-question.ts +54 -95
  5. package/extensions/recommended-tools.ts +62 -0
  6. package/extensions/ui.ts +204 -58
  7. package/lib/agent-config.ts +55 -0
  8. package/lib/agents.ts +208 -0
  9. package/lib/question-model.ts +98 -0
  10. package/lib/question-ui.ts +214 -0
  11. package/lib/recommended-tools.ts +119 -0
  12. package/lib/subagent-runner.ts +146 -0
  13. package/lib/ui-tips.json +41 -0
  14. package/package.json +5 -13
  15. package/prompts/init.md +10 -2
  16. package/prompts/summary.md +7 -6
  17. package/schemas/agent.schema.json +48 -0
  18. package/skills/{claude-api/LICENSE.txt → caveman/LICENSE} +2 -2
  19. package/skills/caveman/LICENSE-MIT +21 -0
  20. package/skills/caveman/NOTICE +10 -0
  21. package/skills/caveman/SKILL.md +89 -0
  22. package/skills/find-skills/SKILL.md +141 -0
  23. package/skills/ponytail/LICENSE +21 -0
  24. package/skills/ponytail/NOTICE +9 -0
  25. package/skills/ponytail/SKILL.md +52 -0
  26. package/skills-lock.json +6 -0
  27. package/tests/agents.test.mjs +605 -0
  28. package/tests/ask-question-pty.test.mjs +16 -0
  29. package/tests/ask-question.test.mjs +269 -0
  30. package/tests/fixtures/agents-provider.ts +55 -0
  31. package/tests/fixtures/ask-question-pty.py +91 -0
  32. package/tests/fixtures/ask-question-terminal.mjs +69 -0
  33. package/tests/fixtures/recommended-tools-terminal.mjs +103 -0
  34. package/tests/init-workflow.test.mjs +31 -0
  35. package/tests/recommended-tools-pty.test.mjs +16 -0
  36. package/tests/recommended-tools.test.mjs +423 -0
  37. package/tests/resources.test.mjs +66 -0
  38. package/tests/skills-routing.test.mjs +7 -4
  39. package/tests/ui-tip-reload.test.mjs +70 -0
  40. package/tests/ui.test.mjs +450 -0
  41. package/tsconfig.json +1 -0
  42. package/eval/README.md +0 -72
  43. package/eval/permission-cases.json +0 -28
  44. package/eval/permissions.mjs +0 -103
  45. package/extensions/background-tasks.ts +0 -433
  46. package/extensions/background-worker.ts +0 -50
  47. package/extensions/glob.ts +0 -32
  48. package/extensions/mode.ts +0 -397
  49. package/lib/bash-approvals.ts +0 -113
  50. package/lib/bash-policy.ts +0 -164
  51. package/lib/mode-policy.ts +0 -174
  52. package/lib/path-policy.ts +0 -182
  53. package/lib/permission-classifier.ts +0 -128
  54. package/lib/permission-config.ts +0 -91
  55. package/lib/permission-denials.ts +0 -32
  56. package/lib/permission-intent.ts +0 -128
  57. package/lib/permission-taxonomy.ts +0 -15
  58. package/lib/task-broker.ts +0 -65
  59. package/lib/task-checkout.ts +0 -39
  60. package/lib/task-git.ts +0 -44
  61. package/lib/task-patch.ts +0 -106
  62. package/lib/task-permissions.ts +0 -51
  63. package/lib/task-report.ts +0 -62
  64. package/lib/task-worktree-policy.ts +0 -56
  65. package/skills/claude-api/SKILL.md +0 -572
  66. package/skills/claude-api/csharp/claude-api/README.md +0 -362
  67. package/skills/claude-api/csharp/claude-api/batches.md +0 -14
  68. package/skills/claude-api/csharp/claude-api/files-api.md +0 -25
  69. package/skills/claude-api/csharp/claude-api/streaming.md +0 -28
  70. package/skills/claude-api/csharp/claude-api/tool-use.md +0 -164
  71. package/skills/claude-api/curl/examples.md +0 -256
  72. package/skills/claude-api/curl/managed-agents.md +0 -371
  73. package/skills/claude-api/go/claude-api/README.md +0 -188
  74. package/skills/claude-api/go/claude-api/files-api.md +0 -23
  75. package/skills/claude-api/go/claude-api/streaming.md +0 -43
  76. package/skills/claude-api/go/claude-api/tool-use.md +0 -220
  77. package/skills/claude-api/go/managed-agents/README.md +0 -564
  78. package/skills/claude-api/java/claude-api/README.md +0 -238
  79. package/skills/claude-api/java/claude-api/files-api.md +0 -27
  80. package/skills/claude-api/java/claude-api/streaming.md +0 -24
  81. package/skills/claude-api/java/claude-api/tool-use.md +0 -228
  82. package/skills/claude-api/java/managed-agents/README.md +0 -443
  83. package/skills/claude-api/php/claude-api/README.md +0 -174
  84. package/skills/claude-api/php/claude-api/batches.md +0 -15
  85. package/skills/claude-api/php/claude-api/files-api.md +0 -13
  86. package/skills/claude-api/php/claude-api/streaming.md +0 -27
  87. package/skills/claude-api/php/claude-api/tool-use.md +0 -253
  88. package/skills/claude-api/php/managed-agents/README.md +0 -443
  89. package/skills/claude-api/python/claude-api/README.md +0 -565
  90. package/skills/claude-api/python/claude-api/batches.md +0 -198
  91. package/skills/claude-api/python/claude-api/files-api.md +0 -170
  92. package/skills/claude-api/python/claude-api/sdk-upgrade.md +0 -286
  93. package/skills/claude-api/python/claude-api/streaming.md +0 -179
  94. package/skills/claude-api/python/claude-api/tool-use.md +0 -627
  95. package/skills/claude-api/python/managed-agents/README.md +0 -335
  96. package/skills/claude-api/ruby/claude-api/README.md +0 -134
  97. package/skills/claude-api/ruby/claude-api/streaming.md +0 -16
  98. package/skills/claude-api/ruby/claude-api/tool-use.md +0 -41
  99. package/skills/claude-api/ruby/managed-agents/README.md +0 -394
  100. package/skills/claude-api/shared/admin-api.md +0 -179
  101. package/skills/claude-api/shared/agent-design.md +0 -101
  102. package/skills/claude-api/shared/anthropic-cli.md +0 -269
  103. package/skills/claude-api/shared/claude-platform-on-aws.md +0 -59
  104. package/skills/claude-api/shared/cost-optimization.md +0 -233
  105. package/skills/claude-api/shared/error-codes.md +0 -262
  106. package/skills/claude-api/shared/live-sources.md +0 -166
  107. package/skills/claude-api/shared/managed-agents-api-reference.md +0 -455
  108. package/skills/claude-api/shared/managed-agents-client-patterns.md +0 -220
  109. package/skills/claude-api/shared/managed-agents-core.md +0 -359
  110. package/skills/claude-api/shared/managed-agents-environments.md +0 -226
  111. package/skills/claude-api/shared/managed-agents-events.md +0 -290
  112. package/skills/claude-api/shared/managed-agents-memory.md +0 -201
  113. package/skills/claude-api/shared/managed-agents-multiagent.md +0 -260
  114. package/skills/claude-api/shared/managed-agents-onboarding.md +0 -82
  115. package/skills/claude-api/shared/managed-agents-outcomes.md +0 -108
  116. package/skills/claude-api/shared/managed-agents-overview.md +0 -77
  117. package/skills/claude-api/shared/managed-agents-scheduled-deployments.md +0 -156
  118. package/skills/claude-api/shared/managed-agents-self-hosted-sandboxes.md +0 -282
  119. package/skills/claude-api/shared/managed-agents-tools.md +0 -504
  120. package/skills/claude-api/shared/managed-agents-webhooks.md +0 -143
  121. package/skills/claude-api/shared/model-migration.md +0 -1872
  122. package/skills/claude-api/shared/models.md +0 -142
  123. package/skills/claude-api/shared/platform-availability.md +0 -53
  124. package/skills/claude-api/shared/prompt-audit.md +0 -223
  125. package/skills/claude-api/shared/prompt-caching.md +0 -292
  126. package/skills/claude-api/shared/token-counting.md +0 -56
  127. package/skills/claude-api/shared/tool-use-concepts.md +0 -526
  128. package/skills/claude-api/typescript/claude-api/README.md +0 -405
  129. package/skills/claude-api/typescript/claude-api/batches.md +0 -106
  130. package/skills/claude-api/typescript/claude-api/files-api.md +0 -98
  131. package/skills/claude-api/typescript/claude-api/streaming.md +0 -178
  132. package/skills/claude-api/typescript/claude-api/tool-use.md +0 -586
  133. package/skills/claude-api/typescript/managed-agents/README.md +0 -359
  134. package/tests/auto-permissions.test.mjs +0 -315
  135. package/tests/background-tasks.test.mjs +0 -27
  136. package/tests/background-worker.test.mjs +0 -60
  137. package/tests/bash-permissions.test.mjs +0 -276
  138. package/tests/glob.test.mjs +0 -31
  139. package/tests/mode.test.mjs +0 -62
  140. package/tests/permission-evaluation.test.mjs +0 -70
  141. package/tests/permission-foundation.test.mjs +0 -194
  142. package/tests/permission-policy.test.mjs +0 -158
  143. package/tests/runtime/README.md +0 -51
  144. package/tests/runtime/harness.mjs +0 -190
  145. package/tests/runtime/permissions.test.mjs +0 -192
  146. package/tests/runtime/tui.test.mjs +0 -60
  147. package/tests/task-integration.test.mjs +0 -324
  148. package/tests/task-permissions.test.mjs +0 -178
  149. package/tests/task-worktree-policy.test.mjs +0 -53
@@ -1,178 +0,0 @@
1
- import assert from "node:assert/strict";
2
- import test from "node:test";
3
- import { mkdtemp, mkdir, writeFile, readFile, readdir, rm, symlink } from "node:fs/promises";
4
- import { tmpdir } from "node:os";
5
- import { join } from "node:path";
6
- import { randomUUID, createHash } from "node:crypto";
7
- import { evaluatePermission } from "../lib/mode-policy.ts";
8
- import { validateTaskScope, taskPermissionService } from "../lib/task-permissions.ts";
9
- import { requestWorkerPermission, readBrokerJson, publishBrokerJson, validBrokerRequest } from "../lib/task-broker.ts";
10
- import { taskGit } from "../lib/task-git.ts";
11
- import { inspectTaskPatch, taskPatchPreview } from "../lib/task-patch.ts";
12
- import { checkoutTaskFiles } from "../lib/task-checkout.ts";
13
- import { TaskReport, verificationSummary } from "../lib/task-report.ts";
14
-
15
- async function directory(t, prefix = "piexis-permission-task-") {
16
- const root = await mkdtemp(join(tmpdir(), prefix));
17
- t.after(() => rm(root, { recursive: true, force: true }));
18
- return root;
19
- }
20
- async function repository(t) {
21
- const root = await directory(t);
22
- await taskGit(["init"], root);
23
- await taskGit(["config", "user.name", "Fixture"], root);
24
- await taskGit(["config", "user.email", "fixture@example.invalid"], root);
25
- await mkdir(join(root, "src"));
26
- await writeFile(join(root, "src", "main.txt"), "before\n");
27
- await writeFile(join(root, "remove.txt"), "delete me\n");
28
- await taskGit(["add", "."], root);
29
- await taskGit(["commit", "-m", "fixture"], root);
30
- return { root, base: (await taskGit(["rev-parse", "HEAD"], root)).trim() };
31
- }
32
-
33
- test("task tools are individually classified, including Plan cancellation and explicit mutation handlers", async (t) => {
34
- const root = await directory(t);
35
- const modes = ["default", "acceptEdits", "plan", "auto", "dontAsk", "bypassPermissions"];
36
- for (const mode of modes) {
37
- const check = (tool, input = {}) => evaluatePermission({ mode, tool, input, cwd: root, workspaceRoot: root });
38
- for (const tool of ["task_status", "task_cancel", "task_diff"]) assert.equal((await check(tool)).decision, "allow");
39
- for (const tool of ["task_apply", "task_clean"]) assert.equal((await check(tool, { id: "one" })).decision, ["plan", "dontAsk"].includes(mode) ? "deny" : "allow");
40
- const creation = await check("background_task", { task: "Inspect src", paths: ["src"], bash: false });
41
- assert.equal(creation.decision, mode === "bypassPermissions" ? "allow" : mode === "auto" ? "require-model-review" : ["plan", "dontAsk"].includes(mode) ? "deny" : "require-approval");
42
- }
43
- assert.equal((await evaluatePermission({ mode: "auto", tool: "arbitrary_plugin", input: {}, cwd: root, workspaceRoot: root })).decision, "deny");
44
- });
45
-
46
- test("worker policy intersects parent mode with canonical delegated paths and capabilities", async (t) => {
47
- const root = await directory(t);
48
- const outside = await directory(t);
49
- await mkdir(join(root, "src"));
50
- await symlink(outside, join(root, "escape"));
51
- const scope = validateTaskScope(["src"], false);
52
- const check = (mode, tool, input, selected = scope) => evaluatePermission({ mode, tool, input, cwd: root, workspaceRoot: root, actor: { kind: "worker", scope: selected }, authorization: { approvals: [{ tool, scope: "tool" }] } });
53
- assert.equal((await check("default", "read", { path: "src/new.txt" })).decision, "allow");
54
- assert.equal((await check("default", "write", { path: "src/new.txt" })).decision, "require-approval"); // Foreground grants are not inherited.
55
- assert.equal((await check("acceptEdits", "write", { path: "src/new.txt" })).decision, "allow");
56
- assert.equal((await check("auto", "write", { path: "src/new.txt" })).decision, "require-model-review");
57
- assert.equal((await check("plan", "write", { path: "src/new.txt" })).decision, "deny");
58
- for (const mode of ["default", "acceptEdits", "plan", "auto", "dontAsk", "bypassPermissions"]) {
59
- for (const path of ["other.txt", "../outside.txt", "escape/file", "src/.env", "src/.git/config"])
60
- assert.equal((await check(mode, "write", { path })).decision, "deny", `${mode}: ${path}`);
61
- assert.equal((await check(mode, "background_task", { task: "recurse" })).decision, "deny");
62
- assert.equal((await check(mode, "bash", { command: "pwd" })).decision, "deny");
63
- assert.equal((await check(mode, "bash", { command: "cat ../external.txt" }, validateTaskScope(undefined, true))).decision, "deny");
64
- }
65
- assert.equal((await check("auto", "bash", { command: "pwd" }, validateTaskScope(undefined, true))).decision, "allow");
66
- for (const paths of [[], ["../src"], ["/tmp"], ["src/.git"], ["src\\.."], Array(33).fill("src")]) assert.throws(() => validateTaskScope(paths, false));
67
- assert.throws(() => validateTaskScope(["src"], true), /whole-worktree/);
68
- assert.throws(() => taskPermissionService({ events: { emit() {} } }), /require/);
69
- });
70
-
71
- test("broker requests are bounded, authenticated to a task, expiring, cancellable, and fail closed", async (t) => {
72
- const root = await directory(t);
73
- const token = randomUUID();
74
- await writeFile(join(root, "heartbeat"), "");
75
- const pending = requestWorkerPermission(root, token, "read", { path: "src/a" }, undefined, { timeoutMs: 1000, pollMs: 5 });
76
- let file;
77
- while (!(file = (await readdir(root)).find((name) => name.endsWith(".request.json")))) await new Promise(setImmediate);
78
- const request = await readBrokerJson(join(root, file));
79
- assert.equal(validBrokerRequest(request, file, token), true);
80
- assert.equal(validBrokerRequest(request, file, "wrong"), false);
81
- assert.equal(validBrokerRequest({ ...request, id: "../escape" }, file, token), false);
82
- await publishBrokerJson(join(root, `${request.id}.response.json`), { id: request.id, outcome: "allowed" });
83
- assert.equal((await pending).outcome, "allowed");
84
- assert.deepEqual(await readdir(root), ["heartbeat"]);
85
- assert.equal((await requestWorkerPermission(root, token, "read", {}, undefined, { timeoutMs: 10, pollMs: 2 })).outcome, "expired");
86
- const controller = new AbortController();
87
- const cancelled = requestWorkerPermission(root, token, "read", {}, controller.signal, { pollMs: 2 });
88
- controller.abort();
89
- assert.equal((await cancelled).outcome, "cancelled");
90
- assert.equal((await requestWorkerPermission(root, token, "write", { content: "x".repeat(1024 * 1024) })).outcome, "denied");
91
- await rm(join(root, "heartbeat"));
92
- assert.equal((await requestWorkerPermission(root, token, "read", {})).outcome, "unavailable");
93
- assert.deepEqual(await readdir(root), []);
94
- });
95
-
96
- test("patch inspection includes untracked/deleted/binary files without index/object mutation or filters", async (t) => {
97
- const { root, base } = await repository(t);
98
- const marker = join(root, "filter-ran");
99
- await writeFile(join(root, ".gitattributes"), "*.txt filter=fixture diff=fixture\n");
100
- await taskGit(["config", "filter.fixture.clean", `touch '${marker}'; cat`], root);
101
- await taskGit(["config", "diff.fixture.command", `touch '${marker}'`], root);
102
- await writeFile(join(root, "src", "main.txt"), "after\n");
103
- await rm(join(root, "remove.txt"));
104
- await writeFile(join(root, "new space.txt"), "new\n");
105
- await writeFile(join(root, "binary.dat"), Buffer.from([0, 255, 0, 123]));
106
- const index = await readFile(join(root, ".git", "index"));
107
- const objects = (await readdir(join(root, ".git", "objects"), { recursive: true })).sort();
108
- const snapshot = await inspectTaskPatch(root, root, base);
109
- assert.deepEqual(snapshot.changes.map((item) => item.path).sort(), [".gitattributes", "binary.dat", "new space.txt", "remove.txt", "src/main.txt"]);
110
- assert.match(snapshot.patch, /GIT binary patch/);
111
- assert.match(snapshot.patch, /after/);
112
- assert.equal(snapshot.digest, createHash("sha256").update(snapshot.patch).digest("hex"));
113
- assert.deepEqual(await readFile(join(root, ".git", "index")), index);
114
- assert.deepEqual((await readdir(join(root, ".git", "objects"), { recursive: true })).sort(), objects);
115
- await assert.rejects(readFile(marker), { code: "ENOENT" });
116
- assert.match(taskPatchPreview(snapshot), /withheld/);
117
- });
118
-
119
- test("sensitive/configuration changes and external symlinks are flagged from actual patch paths", async (t) => {
120
- const { root, base } = await repository(t);
121
- await mkdir(join(root, ".pi"));
122
- await writeFile(join(root, ".pi", "settings.json"), '{"sensitive":"not-for-preview"}\n');
123
- await writeFile(join(root, ".env"), "PRIVATE_FIXTURE=not-for-preview\n");
124
- await symlink("/outside/fixture", join(root, "link"));
125
- const snapshot = await inspectTaskPatch(root, root, base);
126
- assert.ok(snapshot.changes.find((item) => item.path === ".env").warnings.includes("sensitive-path"));
127
- assert.ok(snapshot.changes.find((item) => item.path === ".pi/settings.json").warnings.includes("protected-config"));
128
- assert.ok(snapshot.changes.find((item) => item.path === "link").warnings.includes("symlink-change"));
129
- assert.doesNotMatch(taskPatchPreview(snapshot), /not-for-preview/);
130
- });
131
-
132
- test("raw task checkout never executes smudge filters", async (t) => {
133
- const { root, base } = await repository(t);
134
- const parent = await directory(t, "piexis-task-");
135
- const worktree = join(parent, "worktree");
136
- const marker = join(root, "smudge-ran");
137
- await taskGit(["config", "filter.fixture.smudge", `touch '${marker}'; cat`], root);
138
- await writeFile(join(root, ".gitattributes"), "*.txt filter=fixture\n");
139
- await taskGit(["add", ".gitattributes"], root);
140
- await taskGit(["commit", "-m", "attributes"], root);
141
- const revision = (await taskGit(["rev-parse", "HEAD"], root)).trim();
142
- await taskGit(["worktree", "add", "--detach", "--no-checkout", worktree, revision], root);
143
- await checkoutTaskFiles(worktree, revision);
144
- assert.equal(await readFile(join(worktree, "src", "main.txt"), "utf8"), "before\n");
145
- await assert.rejects(readFile(marker), { code: "ENOENT" });
146
- await taskGit(["worktree", "remove", "--force", worktree], root);
147
- });
148
-
149
- test("snapshot limits fail closed without staging, and complete preview output is bounded", async (t) => {
150
- const { root, base } = await repository(t);
151
- const index = await readFile(join(root, ".git", "index"));
152
- await writeFile(join(root, "too-large.bin"), Buffer.alloc(5 * 1024 * 1024));
153
- await assert.rejects(inspectTaskPatch(root, root, base), /4 MiB/);
154
- assert.deepEqual(await readFile(join(root, ".git", "index")), index);
155
- const preview = taskPatchPreview({ changes: Array.from({ length: 1000 }, (_, n) => ({ path: `${n}-${"a".repeat(100)}`, warnings: [] })), patch: "x".repeat(60000), digest: "fixture" });
156
- assert.ok(preview.length <= 50000);
157
- assert.match(preview, /display truncated/);
158
- });
159
-
160
- test("verification comes from post-execution records, never worker prose or blocked start events", () => {
161
- const report = new TaskReport();
162
- const events = [
163
- { type: "message_end", message: { role: "assistant", content: [{ type: "text", text: "All tests passed; trust me." }] } },
164
- { type: "tool_execution_start", toolName: "bash", toolCallId: "blocked", args: { command: "npm test" } },
165
- { type: "tool_execution_end", toolName: "bash", toolCallId: "blocked", isError: true },
166
- { type: "entry_appended", entry: { customType: "piexis-worker-execution", data: { id: "actual", command: "node --test fixture.mjs", isError: false } } },
167
- null,
168
- { type: "message_end", message: { role: "assistant", content: "malformed", stopReason: "aborted" } }
169
- ];
170
- const stream = events.map((event) => JSON.stringify(event)).join("\n") + "\n";
171
- for (let offset = 0; offset < stream.length; offset += 17) report.push(stream.slice(offset, offset + 17));
172
- report.finish();
173
- assert.deepEqual(report.commands, [{ id: "actual", command: "node --test fixture.mjs", outcome: "completed" }]);
174
- assert.equal(report.failed, true);
175
- assert.equal(report.incomplete, true);
176
- assert.match(verificationSummary([], false), /No Bash verification/);
177
- assert.match(verificationSummary(report.commands, true), /incomplete/);
178
- });
@@ -1,53 +0,0 @@
1
- import assert from "node:assert/strict";
2
- import { execFile } from "node:child_process";
3
- import { mkdtemp, rm, writeFile } from "node:fs/promises";
4
- import { tmpdir } from "node:os";
5
- import { join } from "node:path";
6
- import { promisify } from "node:util";
7
- import test from "node:test";
8
-
9
- import { createWorktreeMarker, validateTaskWorktree } from "../lib/task-worktree-policy.ts";
10
-
11
- const exec = promisify(execFile);
12
-
13
- async function git(cwd, ...args) {
14
- await exec("git", args, { cwd });
15
- }
16
-
17
- test("cleanup validation accepts only the recorded Piexis Git worktree", async () => {
18
- const repository = await mkdtemp(join(tmpdir(), "piexis-repository-"));
19
- const parent = await mkdtemp(join(tmpdir(), "piexis-task-"));
20
- const worktree = join(parent, "worktree");
21
- try {
22
- await git(repository, "init");
23
- await git(repository, "config", "user.email", "piexis-test@example.invalid");
24
- await git(repository, "config", "user.name", "Piexis Test");
25
- await writeFile(join(repository, "README.md"), "fixture\n");
26
- await git(repository, "add", "README.md");
27
- await git(repository, "commit", "-m", "fixture");
28
- await createWorktreeMarker(parent, { id: "1", repository, worktree });
29
- await git(repository, "worktree", "add", "--detach", worktree, "HEAD");
30
- const result = await validateTaskWorktree({ id: "1", repository, worktree });
31
- assert.equal(result.worktree, worktree);
32
- assert.equal(result.parent, parent);
33
- assert.equal(result.dirty, false);
34
- } finally {
35
- await git(repository, "worktree", "remove", "--force", worktree).catch(() => undefined);
36
- await Promise.all([rm(parent, { recursive: true, force: true }), rm(repository, { recursive: true, force: true })]);
37
- }
38
- });
39
-
40
- test("cleanup validation rejects traversal and arbitrary recorded worktree paths", async () => {
41
- const repository = await mkdtemp(join(tmpdir(), "piexis-repository-"));
42
- for (const worktree of ["/", "..", "/home/user", repository, join(repository, "worktree")]) {
43
- await assert.rejects(validateTaskWorktree({ id: "1", repository, worktree }), /Refusing|Task has/);
44
- }
45
- });
46
-
47
- test("cleanup validation rejects a Piexis-looking directory without a marker", async () => {
48
- const parent = await mkdtemp(join(tmpdir(), "piexis-task-corrupt-"));
49
- await assert.rejects(
50
- validateTaskWorktree({ id: "1", repository: parent, worktree: join(parent, "worktree") }),
51
- /valid Piexis marker|ENOENT/
52
- );
53
- });