@maxiaochao/pi-toolkit 0.9.3 → 0.9.6
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 +20 -0
- package/README.md +40 -90
- package/extensions/{btw.ts → btw/index.ts} +1 -1
- package/extensions/{tests/btw-history.mjs → btw/tests/history.mjs} +1 -1
- package/package.json +19 -10
- package/packages/cache-export/CHANGELOG.md +5 -0
- package/packages/cache-export/README.md +70 -0
- package/packages/cache-export/TESTING.md +17 -0
- package/packages/cache-export/docs/demo.gif +0 -0
- package/packages/cache-export/docs/screenshot.png +0 -0
- package/{extensions/cache-export/index.ts → packages/cache-export/extensions/cache-export.ts} +1 -1
- package/packages/cache-export/package.json +22 -0
- package/{extensions/cache-export → packages/cache-export/src}/render.ts +1 -1
- package/{extensions → packages}/cache-export/tests/e2e-browser.mjs +2 -2
- package/{extensions → packages}/cache-export/tests/run-tests.mjs +2 -2
- package/packages/codex-edit/CHANGELOG.md +47 -0
- package/packages/codex-edit/README.md +130 -0
- package/packages/codex-edit/TESTING.md +51 -0
- package/packages/codex-edit/benchmark/explicit-edit-100.json +2824 -0
- package/packages/codex-edit/benchmark/export-evidence.mjs +77 -0
- package/packages/codex-edit/benchmark/incident-cases.mjs +37 -0
- package/packages/codex-edit/benchmark/prepare.mjs +181 -0
- package/packages/codex-edit/benchmark/results/explicit-edit-100-sol-low-v3.json +4562 -0
- package/packages/codex-edit/benchmark/run-with-budget.mjs +77 -0
- package/packages/codex-edit/benchmark/schedule.mjs +19 -0
- package/packages/codex-edit/benchmark/select-tasks.mjs +167 -0
- package/{extensions → packages}/codex-edit/codex-edit-explainer.html +43 -22
- package/packages/codex-edit/docs/demo.gif +0 -0
- package/packages/codex-edit/docs/screenshot.png +0 -0
- package/packages/codex-edit/docs/test-report.html +181 -0
- package/packages/codex-edit/docs/test-report.md +242 -0
- package/packages/codex-edit/docs/test-summary.md +9 -0
- package/{extensions/codex-edit/index.ts → packages/codex-edit/extensions/codex-edit.ts} +7 -5
- package/packages/codex-edit/package.json +54 -0
- package/{extensions/codex-edit → packages/codex-edit/src}/parser.ts +27 -5
- package/{extensions → packages}/codex-edit/summary.md +1 -1
- package/packages/codex-edit/tests/benchmark.test.mjs +81 -0
- package/packages/codex-edit/tests/comparison-cases.mjs +396 -0
- package/packages/codex-edit/tests/comparison-harness.mjs +44 -0
- package/packages/codex-edit/tests/generate-test-report.mjs +589 -0
- package/packages/codex-edit/tests/legacy-parser-v0.1.7.mjs +128 -0
- package/packages/codex-edit/tests/native-vs-codex.ts +109 -0
- package/packages/codex-edit/tests/package.test.mjs +51 -0
- package/packages/codex-edit/tests/parser.test.mjs +308 -0
- package/packages/codex-edit/tests/released-vs-local.test.mjs +31 -0
- package/packages/tiny-subagent/CHANGELOG.md +12 -0
- package/packages/tiny-subagent/README.md +128 -0
- package/packages/tiny-subagent/TESTING.md +20 -0
- package/packages/tiny-subagent/agents/worker.md +9 -0
- package/packages/tiny-subagent/docs/demo.gif +0 -0
- package/packages/tiny-subagent/docs/screenshot.png +0 -0
- package/packages/tiny-subagent/extensions/tiny-subagent.ts +228 -0
- package/packages/tiny-subagent/package.json +44 -0
- package/packages/tiny-subagent/src/personas.ts +110 -0
- package/packages/tiny-subagent/src/runner.ts +319 -0
- package/packages/tiny-subagent/tests/run-tests.mjs +111 -0
- package/packages/todo/CHANGELOG.md +5 -0
- package/packages/todo/README.md +63 -0
- package/packages/todo/TESTING.md +20 -0
- package/packages/todo/docs/demo.gif +0 -0
- package/packages/todo/docs/screenshot.png +0 -0
- package/{extensions → packages/todo/extensions}/todo.ts +1 -1
- package/packages/todo/package.json +25 -0
- package/{extensions → packages/todo}/tests/todo-state.mjs +1 -1
- package/packages/worktree/CHANGELOG.md +5 -0
- package/packages/worktree/README.md +60 -0
- package/packages/worktree/TESTING.md +21 -0
- package/packages/worktree/docs/demo.gif +0 -0
- package/packages/worktree/docs/screenshot.png +0 -0
- package/packages/worktree/package.json +20 -0
- package/packages/worktree/scripts/install.mjs +26 -0
- package/packages/worktree/tests/install.test.mjs +25 -0
- package/packages/worktree/tests/run-tests.sh +46 -0
- package/scripts/install-agents.mjs +8 -18
- package/extensions/codex-edit/README.md +0 -53
- package/extensions/codex-edit/tests/package.test.mjs +0 -38
- package/extensions/codex-edit/tests/parser.test.mjs +0 -89
- /package/extensions/{btw-scroll.ts → btw/scroll.ts} +0 -0
- /package/{extensions/cache-export → packages/cache-export/src}/tau-assets.ts +0 -0
- /package/{extensions → packages}/codex-edit/config.json +0 -0
- /package/{extensions/codex-edit → packages/codex-edit/src}/model-routing.ts +0 -0
- /package/{extensions → packages/todo/src}/todo-state.ts +0 -0
- /package/{bin → packages/worktree/bin}/pi-worktree +0 -0
- /package/{bin → packages/worktree/bin}/pi-worktree-completion.bash +0 -0
- /package/{skills → packages/worktree/skills}/pi-worktree/SKILL.md +0 -0
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@maxiaochao/pi-worktree",
|
|
3
|
+
"version": "0.1.0",
|
|
4
|
+
"description": "Git worktree CLI and Pi skill for isolated coding tasks.",
|
|
5
|
+
"type": "module",
|
|
6
|
+
"keywords": ["pi-package", "pi-skill", "git", "worktree"],
|
|
7
|
+
"files": ["bin", "skills", "scripts", "docs", "README.md", "TESTING.md", "CHANGELOG.md"],
|
|
8
|
+
"repository": {
|
|
9
|
+
"type": "git",
|
|
10
|
+
"url": "git+https://github.com/chaochaoxiaochao/pi-toolkit.git",
|
|
11
|
+
"directory": "packages/worktree"
|
|
12
|
+
},
|
|
13
|
+
"bin": { "pi-worktree": "bin/pi-worktree" },
|
|
14
|
+
"pi": { "skills": ["./skills"] },
|
|
15
|
+
"scripts": {
|
|
16
|
+
"test": "node --test tests/install.test.mjs && bash tests/run-tests.sh",
|
|
17
|
+
"postinstall": "node scripts/install.mjs"
|
|
18
|
+
},
|
|
19
|
+
"publishConfig": { "access": "public" }
|
|
20
|
+
}
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
import { chmodSync, copyFileSync, mkdirSync } from "node:fs";
|
|
2
|
+
import { homedir, platform } from "node:os";
|
|
3
|
+
import { dirname, join } from "node:path";
|
|
4
|
+
import { fileURLToPath, pathToFileURL } from "node:url";
|
|
5
|
+
|
|
6
|
+
const packageRoot = join(dirname(fileURLToPath(import.meta.url)), "..");
|
|
7
|
+
|
|
8
|
+
export function installWorktree({ root = packageRoot, home = homedir() } = {}) {
|
|
9
|
+
if (platform() === "win32") return [];
|
|
10
|
+
const installed = [];
|
|
11
|
+
const binTarget = join(home, ".local", "bin", "pi-worktree");
|
|
12
|
+
mkdirSync(dirname(binTarget), { recursive: true });
|
|
13
|
+
copyFileSync(join(root, "bin", "pi-worktree"), binTarget);
|
|
14
|
+
chmodSync(binTarget, 0o755);
|
|
15
|
+
installed.push(binTarget);
|
|
16
|
+
|
|
17
|
+
const completionTarget = join(home, ".local", "share", "bash-completion", "completions", "pi-worktree");
|
|
18
|
+
mkdirSync(dirname(completionTarget), { recursive: true });
|
|
19
|
+
copyFileSync(join(root, "bin", "pi-worktree-completion.bash"), completionTarget);
|
|
20
|
+
installed.push(completionTarget);
|
|
21
|
+
return installed;
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
if (process.argv[1] && import.meta.url === pathToFileURL(process.argv[1]).href) {
|
|
25
|
+
for (const path of installWorktree()) console.log(`[pi-worktree] installed -> ${path}`);
|
|
26
|
+
}
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
import assert from "node:assert/strict";
|
|
2
|
+
import { access, mkdtemp, readFile, rm } from "node:fs/promises";
|
|
3
|
+
import { constants } from "node:fs";
|
|
4
|
+
import { tmpdir } from "node:os";
|
|
5
|
+
import { dirname, join } from "node:path";
|
|
6
|
+
import { fileURLToPath } from "node:url";
|
|
7
|
+
import test from "node:test";
|
|
8
|
+
import { installWorktree } from "../scripts/install.mjs";
|
|
9
|
+
|
|
10
|
+
const packageRoot = dirname(dirname(fileURLToPath(import.meta.url)));
|
|
11
|
+
|
|
12
|
+
test("installs the CLI and completion into an isolated home", async () => {
|
|
13
|
+
const home = await mkdtemp(join(tmpdir(), "pi-worktree-install-"));
|
|
14
|
+
try {
|
|
15
|
+
const installed = installWorktree({ root: packageRoot, home });
|
|
16
|
+
const cli = join(home, ".local", "bin", "pi-worktree");
|
|
17
|
+
const completion = join(home, ".local", "share", "bash-completion", "completions", "pi-worktree");
|
|
18
|
+
assert.deepEqual(installed, [cli, completion]);
|
|
19
|
+
await access(cli, constants.X_OK);
|
|
20
|
+
assert.match(await readFile(cli, "utf8"), /git worktree/);
|
|
21
|
+
assert.match(await readFile(completion, "utf8"), /complete -F _pi_worktree/);
|
|
22
|
+
} finally {
|
|
23
|
+
await rm(home, { recursive: true, force: true });
|
|
24
|
+
}
|
|
25
|
+
});
|
|
@@ -0,0 +1,46 @@
|
|
|
1
|
+
#!/usr/bin/env bash
|
|
2
|
+
set -euo pipefail
|
|
3
|
+
|
|
4
|
+
PACKAGE_DIR=$(cd "$(dirname "$0")/.." && pwd)
|
|
5
|
+
ROOT=$(mktemp -d)
|
|
6
|
+
trap 'rm -rf "$ROOT"' EXIT
|
|
7
|
+
|
|
8
|
+
mkdir -p "$ROOT/bin"
|
|
9
|
+
cat > "$ROOT/bin/pi" <<'EOF'
|
|
10
|
+
#!/usr/bin/env bash
|
|
11
|
+
printf '%s\n' "$PWD" > "${PI_WORKTREE_TEST_OUTPUT}.cwd"
|
|
12
|
+
printf '%s\n' "$@" > "${PI_WORKTREE_TEST_OUTPUT}.args"
|
|
13
|
+
EOF
|
|
14
|
+
chmod +x "$ROOT/bin/pi"
|
|
15
|
+
|
|
16
|
+
mkdir "$ROOT/repo"
|
|
17
|
+
cd "$ROOT/repo"
|
|
18
|
+
git init -q -b main
|
|
19
|
+
git config user.email test@example.com
|
|
20
|
+
git config user.name Test
|
|
21
|
+
printf '.worktrees/\n' > .gitignore
|
|
22
|
+
printf 'base\n' > tracked.txt
|
|
23
|
+
git add .gitignore tracked.txt
|
|
24
|
+
git commit -qm initial
|
|
25
|
+
|
|
26
|
+
export PATH="$ROOT/bin:$PATH"
|
|
27
|
+
export PI_WORKTREE_TEST_OUTPUT="$ROOT/pi"
|
|
28
|
+
bash "$PACKAGE_DIR/bin/pi-worktree" start feature --model fake/model
|
|
29
|
+
|
|
30
|
+
test -d .worktrees/feature
|
|
31
|
+
test "$(git -C .worktrees/feature branch --show-current)" = feature
|
|
32
|
+
test "$(cat "$ROOT/pi.cwd")" = "$ROOT/repo/.worktrees/feature"
|
|
33
|
+
grep -qx -- '--model' "$ROOT/pi.args"
|
|
34
|
+
grep -qx -- 'fake/model' "$ROOT/pi.args"
|
|
35
|
+
|
|
36
|
+
bash "$PACKAGE_DIR/bin/pi-worktree" list > "$ROOT/list.txt"
|
|
37
|
+
grep -q 'main checkout' "$ROOT/list.txt"
|
|
38
|
+
grep -q 'feature' "$ROOT/list.txt"
|
|
39
|
+
|
|
40
|
+
bash "$PACKAGE_DIR/bin/pi-worktree" info feature > "$ROOT/info.txt"
|
|
41
|
+
grep -q 'status: clean' "$ROOT/info.txt"
|
|
42
|
+
grep -q 'branch: feature' "$ROOT/info.txt"
|
|
43
|
+
grep -q 'based on:' "$ROOT/info.txt"
|
|
44
|
+
|
|
45
|
+
bash "$PACKAGE_DIR/bin/pi-worktree" --help | grep -q 'pi-worktree start'
|
|
46
|
+
printf 'PASS worktree CLI integration cases\n'
|
|
@@ -1,9 +1,10 @@
|
|
|
1
1
|
// postinstall hook: sync the bundled global AGENTS.md and seed the default theme
|
|
2
2
|
// without overriding a theme the user has already selected.
|
|
3
|
-
import { readFileSync, writeFileSync, mkdirSync, existsSync
|
|
4
|
-
import { homedir
|
|
3
|
+
import { readFileSync, writeFileSync, mkdirSync, existsSync } from "node:fs";
|
|
4
|
+
import { homedir } from "node:os";
|
|
5
5
|
import { dirname, join } from "node:path";
|
|
6
6
|
import { fileURLToPath } from "node:url";
|
|
7
|
+
import { installWorktree } from "../packages/worktree/scripts/install.mjs";
|
|
7
8
|
|
|
8
9
|
const packageRoot = join(dirname(fileURLToPath(import.meta.url)), "..");
|
|
9
10
|
|
|
@@ -44,21 +45,10 @@ if (!existsSync(settingsTarget)) {
|
|
|
44
45
|
}
|
|
45
46
|
}
|
|
46
47
|
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
try {
|
|
51
|
-
const binTarget = join(homedir(), ".local", "bin", "pi-worktree");
|
|
52
|
-
mkdirSync(dirname(binTarget), { recursive: true });
|
|
53
|
-
copyFileSync(join(packageRoot, "bin", "pi-worktree"), binTarget);
|
|
54
|
-
chmodSync(binTarget, 0o755);
|
|
55
|
-
console.log("[pi-toolkit] pi-worktree installed ->", binTarget);
|
|
56
|
-
|
|
57
|
-
const compTarget = join(homedir(), ".local", "share", "bash-completion", "completions", "pi-worktree");
|
|
58
|
-
mkdirSync(dirname(compTarget), { recursive: true });
|
|
59
|
-
copyFileSync(join(packageRoot, "bin", "pi-worktree-completion.bash"), compTarget);
|
|
60
|
-
console.log("[pi-toolkit] pi-worktree bash completion installed ->", compTarget);
|
|
61
|
-
} catch (error) {
|
|
62
|
-
console.warn("[pi-toolkit] could not install pi-worktree CLI:", error instanceof Error ? error.message : String(error));
|
|
48
|
+
try {
|
|
49
|
+
for (const path of installWorktree({ root: join(packageRoot, "packages", "worktree") })) {
|
|
50
|
+
console.log("[pi-toolkit] pi-worktree installed ->", path);
|
|
63
51
|
}
|
|
52
|
+
} catch (error) {
|
|
53
|
+
console.warn("[pi-toolkit] could not install pi-worktree CLI:", error instanceof Error ? error.message : String(error));
|
|
64
54
|
}
|
|
@@ -1,53 +0,0 @@
|
|
|
1
|
-
# Codex Edit (apply_patch)
|
|
2
|
-
|
|
3
|
-
A Pi extension bundled with `@maxiaochao/pi-toolkit` that exposes Codex-style `apply_patch` for GPT/Codex models and keeps Pi's native `edit` tool for other models.
|
|
4
|
-
|
|
5
|
-
## Install
|
|
6
|
-
|
|
7
|
-
Bundled with the toolkit; install or update the root package and the extension comes along:
|
|
8
|
-
|
|
9
|
-
```bash
|
|
10
|
-
pi install npm:@maxiaochao/pi-toolkit
|
|
11
|
-
pi update npm:@maxiaochao/pi-toolkit
|
|
12
|
-
```
|
|
13
|
-
|
|
14
|
-
The extension registers `apply_patch` and selects the editing tool from the resolved model configuration:
|
|
15
|
-
|
|
16
|
-
- Allowlisted GPT/Codex model: `apply_patch`
|
|
17
|
-
- Other models: Pi's native `edit`
|
|
18
|
-
|
|
19
|
-
The default allowlist enables all `gpt-5.6-*` models regardless of provider. Other model families can be added after separate validation by editing `config.json`.
|
|
20
|
-
|
|
21
|
-
The extension does not add a permanent AGENTS rule telling models not to use `apply_patch`. Only the selected editor tool is active, so the model receives one editing protocol.
|
|
22
|
-
|
|
23
|
-
## What `apply_patch` does
|
|
24
|
-
|
|
25
|
-
The tool accepts raw Codex patch text between `*** Begin Patch` and `*** End Patch`. It supports:
|
|
26
|
-
|
|
27
|
-
- `Add File`
|
|
28
|
-
- `Delete File`
|
|
29
|
-
- `Update File`
|
|
30
|
-
- `Move to`
|
|
31
|
-
- multiple update hunks with `@@` context
|
|
32
|
-
- EOF-oriented insertions
|
|
33
|
-
|
|
34
|
-
Before writing, it parses the complete patch, resolves paths, rejects workspace escapes and unsafe symlink/parent paths, reads all source files, and computes every update in memory. Only after preflight succeeds does it mutate files. It returns per-file diff details and reports committed paths if a later filesystem operation fails.
|
|
35
|
-
|
|
36
|
-
## Configuration
|
|
37
|
-
|
|
38
|
-
The extension defaults to a model-ID allowlist in `config.json`: `"gpt-5.6-*"`. Provider does not affect routing. Each entry is a `*` glob, so this matches every `gpt-5.6-` variant. The route additionally requires:
|
|
39
|
-
|
|
40
|
-
```text
|
|
41
|
-
model.api = openai-responses or openai-codex-responses
|
|
42
|
-
```
|
|
43
|
-
|
|
44
|
-
Grammar support is not part of the route. When a provider does not emit Lark grammar tools, Pi falls back to a normal function tool, so `apply_patch` still runs there — only the grammar-enforced output shape is lost.
|
|
45
|
-
|
|
46
|
-
Edit `extensions/codex-edit/config.json` inside the installed `@maxiaochao/pi-toolkit` package only if you need to change the default allowlist. The implementation keeps both tools registered for session replay and changes only the active tool set on `session_start` and `model_select`.
|
|
47
|
-
|
|
48
|
-
## Documentation
|
|
49
|
-
|
|
50
|
-
- `summary.md`: benchmark summary and decision record.
|
|
51
|
-
- `codex-edit-explainer.html`: self-contained interactive architecture and configuration explainer.
|
|
52
|
-
|
|
53
|
-
The benchmark found equal final correctness in the fair 20-case comparison, with first editor-call success improving from 85% to 95%. Overall tool-call count and cache-inclusive token use were effectively unchanged, so the route stays limited to the GPT/Codex allowlist rather than replacing `edit` universally.
|
|
@@ -1,38 +0,0 @@
|
|
|
1
|
-
import assert from "node:assert/strict";
|
|
2
|
-
import { readFile } from "node:fs/promises";
|
|
3
|
-
import { dirname, join } from "node:path";
|
|
4
|
-
import test from "node:test";
|
|
5
|
-
import { modelIsAllowed } from "../model-routing.ts";
|
|
6
|
-
|
|
7
|
-
const extensionDir = dirname(dirname(new URL(import.meta.url).pathname));
|
|
8
|
-
|
|
9
|
-
test("config is next to the extension entry file", async () => {
|
|
10
|
-
const source = await readFile(join(extensionDir, "index.ts"), "utf8");
|
|
11
|
-
assert.match(source, /new URL\("\.\/config\.json", import\.meta\.url\)/);
|
|
12
|
-
assert.match(source, /model-routing\.ts/);
|
|
13
|
-
const config = JSON.parse(await readFile(join(extensionDir, "config.json"), "utf8"));
|
|
14
|
-
assert.equal(config.enabled, true);
|
|
15
|
-
assert.deepEqual(config.models, ["gpt-5.6-*"]);
|
|
16
|
-
});
|
|
17
|
-
|
|
18
|
-
test("matches allowed model IDs with globs independently of provider and grammar support", () => {
|
|
19
|
-
const settings = { enabled: true, models: ["gpt-5.6-*"] };
|
|
20
|
-
const baseModel = {
|
|
21
|
-
id: "gpt-5.6-terra",
|
|
22
|
-
api: "openai-codex-responses",
|
|
23
|
-
compat: { supportsOpenAIGrammarTools: true },
|
|
24
|
-
};
|
|
25
|
-
assert.equal(modelIsAllowed({ ...baseModel, provider: "openai-codex" }, settings), true);
|
|
26
|
-
assert.equal(modelIsAllowed({ ...baseModel, provider: "another-provider" }, settings), true);
|
|
27
|
-
assert.equal(modelIsAllowed({ ...baseModel, compat: { supportsOpenAIGrammarTools: false } }, settings), true);
|
|
28
|
-
assert.equal(
|
|
29
|
-
modelIsAllowed(
|
|
30
|
-
{ ...baseModel, api: "openai-responses", compat: { sessionAffinityFormat: "openai-nosession" } },
|
|
31
|
-
settings,
|
|
32
|
-
),
|
|
33
|
-
true,
|
|
34
|
-
);
|
|
35
|
-
assert.equal(modelIsAllowed({ ...baseModel, api: "openai-completions" }, settings), false);
|
|
36
|
-
assert.equal(modelIsAllowed({ ...baseModel, id: "gpt-5.5" }, settings), false);
|
|
37
|
-
assert.equal(modelIsAllowed(baseModel, { enabled: false, models: ["gpt-5.6-*"] }), false);
|
|
38
|
-
});
|
|
@@ -1,89 +0,0 @@
|
|
|
1
|
-
import assert from "node:assert/strict";
|
|
2
|
-
import { mkdtemp, readFile, rm, writeFile } from "node:fs/promises";
|
|
3
|
-
import { tmpdir } from "node:os";
|
|
4
|
-
import { join } from "node:path";
|
|
5
|
-
import test from "node:test";
|
|
6
|
-
import { applyUpdate, parsePatch, resolveWorkspacePath } from "../parser.ts";
|
|
7
|
-
|
|
8
|
-
test("parses add, delete, update, and move operations", () => {
|
|
9
|
-
const operations = parsePatch(`*** Begin Patch
|
|
10
|
-
*** Add File: added.txt
|
|
11
|
-
+hello
|
|
12
|
-
*** Delete File: removed.txt
|
|
13
|
-
*** Update File: old.txt
|
|
14
|
-
*** Move to: new.txt
|
|
15
|
-
@@
|
|
16
|
-
-old
|
|
17
|
-
+new
|
|
18
|
-
*** End Patch`);
|
|
19
|
-
assert.deepEqual(operations.map((operation) => operation.action), ["add", "delete", "update"]);
|
|
20
|
-
assert.equal(operations[0].content, "hello\n");
|
|
21
|
-
assert.equal(operations[2].moveTo, "new.txt");
|
|
22
|
-
assert.deepEqual(operations[2].chunks[0].oldLines, ["old"]);
|
|
23
|
-
assert.deepEqual(operations[2].chunks[0].newLines, ["new"]);
|
|
24
|
-
});
|
|
25
|
-
|
|
26
|
-
test("rejects malformed patches and duplicate targets", () => {
|
|
27
|
-
assert.throws(() => parsePatch("bad"), /first line/);
|
|
28
|
-
assert.throws(() => parsePatch(`*** Begin Patch
|
|
29
|
-
*** Add File: same.txt
|
|
30
|
-
+x
|
|
31
|
-
*** Add File: same.txt
|
|
32
|
-
+y
|
|
33
|
-
*** End Patch`), /same path/);
|
|
34
|
-
});
|
|
35
|
-
|
|
36
|
-
test("applies multiple chunks using context and preserves BOM and CRLF", () => {
|
|
37
|
-
const before = "\ufefffunction run() {\r\n const value = 1;\r\n return value;\r\n}\r\n";
|
|
38
|
-
const operations = parsePatch(`*** Begin Patch
|
|
39
|
-
*** Update File: src/run.js
|
|
40
|
-
@@ function run() {
|
|
41
|
-
- const value = 1;
|
|
42
|
-
+ const value = 2;
|
|
43
|
-
@@
|
|
44
|
-
- return value;
|
|
45
|
-
+ return value + 1;
|
|
46
|
-
*** End Patch`);
|
|
47
|
-
const after = applyUpdate(before, operations[0].chunks, "src/run.js");
|
|
48
|
-
assert.equal(after, "\ufefffunction run() {\r\n const value = 2;\r\n return value + 1;\r\n}\r\n");
|
|
49
|
-
});
|
|
50
|
-
|
|
51
|
-
test("supports insertion chunks and EOF matching", () => {
|
|
52
|
-
const operations = parsePatch(`*** Begin Patch
|
|
53
|
-
*** Update File: file.txt
|
|
54
|
-
@@
|
|
55
|
-
+first
|
|
56
|
-
*** End of File
|
|
57
|
-
*** End Patch`);
|
|
58
|
-
assert.equal(applyUpdate("", operations[0].chunks, "file.txt"), "first\n");
|
|
59
|
-
});
|
|
60
|
-
|
|
61
|
-
test("rejects ambiguous matches and workspace escapes", () => {
|
|
62
|
-
const operations = parsePatch(`*** Begin Patch
|
|
63
|
-
*** Update File: file.txt
|
|
64
|
-
@@
|
|
65
|
-
-value
|
|
66
|
-
+changed
|
|
67
|
-
*** End Patch`);
|
|
68
|
-
assert.throws(() => applyUpdate("value\nvalue\n", operations[0].chunks, "file.txt"), /Ambiguous/);
|
|
69
|
-
assert.equal(resolveWorkspacePath("/tmp/project", "/tmp/project/src/a.js"), "/tmp/project/src/a.js");
|
|
70
|
-
assert.throws(() => resolveWorkspacePath("/tmp/project", "/tmp/other/a.js"), /escapes/);
|
|
71
|
-
});
|
|
72
|
-
|
|
73
|
-
test("round-trips a temporary file operation fixture", async () => {
|
|
74
|
-
const workspace = await mkdtemp(join(tmpdir(), "codex-edit-test-"));
|
|
75
|
-
try {
|
|
76
|
-
const path = join(workspace, "file.txt");
|
|
77
|
-
await writeFile(path, "before\n", "utf8");
|
|
78
|
-
const operations = parsePatch(`*** Begin Patch
|
|
79
|
-
*** Update File: file.txt
|
|
80
|
-
@@
|
|
81
|
-
-before
|
|
82
|
-
+after
|
|
83
|
-
*** End Patch`);
|
|
84
|
-
await writeFile(path, applyUpdate(await readFile(path, "utf8"), operations[0].chunks, "file.txt"), "utf8");
|
|
85
|
-
assert.equal(await readFile(path, "utf8"), "after\n");
|
|
86
|
-
} finally {
|
|
87
|
-
await rm(workspace, { recursive: true, force: true });
|
|
88
|
-
}
|
|
89
|
-
});
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|