@aria-cli/tools 1.0.9 → 1.0.11
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/package.json +9 -5
- package/src/__tests__/web-fetch-download.test.ts +0 -433
- package/src/__tests__/web-tools.test.ts +0 -619
- package/src/ask-user-interaction.ts +0 -33
- package/src/cache/web-cache.ts +0 -110
- package/src/definitions/arion.ts +0 -118
- package/src/definitions/browser/browser.ts +0 -502
- package/src/definitions/browser/index.ts +0 -5
- package/src/definitions/browser/pw-downloads.ts +0 -142
- package/src/definitions/browser/pw-interactions.ts +0 -282
- package/src/definitions/browser/pw-responses.ts +0 -98
- package/src/definitions/browser/pw-session.ts +0 -405
- package/src/definitions/browser/pw-shared.ts +0 -85
- package/src/definitions/browser/pw-snapshot.ts +0 -383
- package/src/definitions/browser/pw-state.ts +0 -101
- package/src/definitions/browser/types.ts +0 -203
- package/src/definitions/code-intelligence.ts +0 -526
- package/src/definitions/core.ts +0 -118
- package/src/definitions/delegation.ts +0 -567
- package/src/definitions/deploy.ts +0 -73
- package/src/definitions/filesystem.ts +0 -217
- package/src/definitions/frg.ts +0 -67
- package/src/definitions/index.ts +0 -28
- package/src/definitions/memory.ts +0 -150
- package/src/definitions/messaging.ts +0 -734
- package/src/definitions/meta.ts +0 -392
- package/src/definitions/network.ts +0 -179
- package/src/definitions/outlook.ts +0 -318
- package/src/definitions/patch/apply-patch.ts +0 -235
- package/src/definitions/patch/fuzzy-match.ts +0 -217
- package/src/definitions/patch/index.ts +0 -1
- package/src/definitions/patch/patch-parser.ts +0 -297
- package/src/definitions/patch/sandbox-paths.ts +0 -129
- package/src/definitions/process/index.ts +0 -5
- package/src/definitions/process/process-registry.ts +0 -303
- package/src/definitions/process/process.ts +0 -456
- package/src/definitions/process/pty-keys.ts +0 -298
- package/src/definitions/process/session-slug.ts +0 -147
- package/src/definitions/quip.ts +0 -225
- package/src/definitions/search.ts +0 -67
- package/src/definitions/session-history.ts +0 -79
- package/src/definitions/shell.ts +0 -202
- package/src/definitions/slack.ts +0 -211
- package/src/definitions/web.ts +0 -119
- package/src/executors/apply-patch.ts +0 -1035
- package/src/executors/arion.ts +0 -199
- package/src/executors/code-intelligence.ts +0 -1179
- package/src/executors/deploy.ts +0 -1066
- package/src/executors/filesystem.ts +0 -1428
- package/src/executors/frg-freshness.ts +0 -743
- package/src/executors/frg.ts +0 -394
- package/src/executors/index.ts +0 -280
- package/src/executors/learning-meta.ts +0 -1367
- package/src/executors/lsp-client.ts +0 -355
- package/src/executors/memory.ts +0 -978
- package/src/executors/meta.ts +0 -293
- package/src/executors/process-registry.ts +0 -570
- package/src/executors/pty-session-store.ts +0 -43
- package/src/executors/pty.ts +0 -342
- package/src/executors/restart.ts +0 -133
- package/src/executors/search-freshness.ts +0 -249
- package/src/executors/search-types.ts +0 -98
- package/src/executors/search.ts +0 -89
- package/src/executors/self-diagnose.ts +0 -552
- package/src/executors/session-history.ts +0 -435
- package/src/executors/shell-safety.ts +0 -519
- package/src/executors/shell.ts +0 -1243
- package/src/executors/utils.ts +0 -40
- package/src/executors/web.ts +0 -786
- package/src/extraction/content-extraction.ts +0 -281
- package/src/extraction/index.ts +0 -5
- package/src/headless-control-contract.ts +0 -1149
- package/src/index.ts +0 -788
- package/src/local-control-http-auth.ts +0 -2
- package/src/mcp/client.ts +0 -218
- package/src/mcp/connection.ts +0 -568
- package/src/mcp/index.ts +0 -11
- package/src/mcp/jsonrpc.ts +0 -195
- package/src/mcp/types.ts +0 -199
- package/src/network-control-adapter.ts +0 -88
- package/src/network-runtime/address-types.ts +0 -218
- package/src/network-runtime/db-owner-fencing.ts +0 -91
- package/src/network-runtime/delivery-receipts.ts +0 -372
- package/src/network-runtime/direct-endpoint-authority.ts +0 -35
- package/src/network-runtime/index.ts +0 -316
- package/src/network-runtime/local-control-contract.ts +0 -784
- package/src/network-runtime/node-store-contract.ts +0 -46
- package/src/network-runtime/pair-route-contract.ts +0 -97
- package/src/network-runtime/peer-capabilities.ts +0 -48
- package/src/network-runtime/peer-principal-ref.ts +0 -20
- package/src/network-runtime/peer-state-machine.ts +0 -160
- package/src/network-runtime/protocol-schemas.ts +0 -265
- package/src/network-runtime/runtime-bootstrap-contract.ts +0 -83
- package/src/outlook/desktop-session.ts +0 -409
- package/src/policy.ts +0 -171
- package/src/providers/brave.ts +0 -80
- package/src/providers/duckduckgo.ts +0 -199
- package/src/providers/exa.ts +0 -85
- package/src/providers/firecrawl.ts +0 -77
- package/src/providers/index.ts +0 -8
- package/src/providers/jina.ts +0 -70
- package/src/providers/router.ts +0 -121
- package/src/providers/search-provider.ts +0 -74
- package/src/providers/tavily.ts +0 -74
- package/src/quip/desktop-session.ts +0 -435
- package/src/registry/index.ts +0 -1
- package/src/registry/registry.ts +0 -905
- package/src/runtime-socket-local-control-client.ts +0 -632
- package/src/security/dns-normalization.ts +0 -34
- package/src/security/dns-pinning.ts +0 -138
- package/src/security/external-content.ts +0 -129
- package/src/security/ssrf.ts +0 -207
- package/src/slack/desktop-session.ts +0 -493
- package/src/tool-factory.ts +0 -91
- package/src/types.ts +0 -1341
- package/src/utils/retry.ts +0 -163
- package/src/utils/safe-parse-json.ts +0 -176
- package/src/utils/url.ts +0 -20
- package/tests/benchmarks/registry.bench.ts +0 -57
- package/tests/cache/web-cache.test.ts +0 -147
- package/tests/critical-integration.test.ts +0 -1465
- package/tests/definitions/apply-patch.test.ts +0 -586
- package/tests/definitions/browser.test.ts +0 -495
- package/tests/definitions/delegation-pause-resume.test.ts +0 -758
- package/tests/definitions/execution.test.ts +0 -671
- package/tests/definitions/messaging-inbox-scope.test.ts +0 -229
- package/tests/definitions/messaging.test.ts +0 -1468
- package/tests/definitions/outlook.test.ts +0 -30
- package/tests/definitions/process.test.ts +0 -469
- package/tests/definitions/slack.test.ts +0 -28
- package/tests/definitions/tool-inventory.test.ts +0 -218
- package/tests/e2e/delegation-quest-orchestration.e2e.test.ts +0 -433
- package/tests/e2e/memory-tool-discovery-contract.e2e.test.ts +0 -81
- package/tests/executors/apply-patch.test.ts +0 -538
- package/tests/executors/arion.test.ts +0 -309
- package/tests/executors/conversation-primitives.test.ts +0 -250
- package/tests/executors/deploy.test.ts +0 -746
- package/tests/executors/filesystem-tools.test.ts +0 -357
- package/tests/executors/filesystem.test.ts +0 -959
- package/tests/executors/frg-freshness.test.ts +0 -136
- package/tests/executors/frg-merge.test.ts +0 -70
- package/tests/executors/frg-session-content.test.ts +0 -40
- package/tests/executors/frg.test.ts +0 -56
- package/tests/executors/memory-bugfixes.test.ts +0 -257
- package/tests/executors/memory-real-memoria.integration.test.ts +0 -316
- package/tests/executors/memory.test.ts +0 -853
- package/tests/executors/meta-tools.test.ts +0 -411
- package/tests/executors/meta.test.ts +0 -683
- package/tests/executors/path-containment.test.ts +0 -51
- package/tests/executors/process-registry.test.ts +0 -505
- package/tests/executors/pty.test.ts +0 -664
- package/tests/executors/quest-security.test.ts +0 -249
- package/tests/executors/read-file-media.test.ts +0 -230
- package/tests/executors/recall-knowledge-schema.test.ts +0 -209
- package/tests/executors/recall-tags.test.ts +0 -278
- package/tests/executors/remember-null-safety.contract.test.ts +0 -41
- package/tests/executors/restart.test.ts +0 -67
- package/tests/executors/search-unified.test.ts +0 -381
- package/tests/executors/session-history.test.ts +0 -340
- package/tests/executors/session-transcript.test.ts +0 -561
- package/tests/executors/shell-abort.test.ts +0 -416
- package/tests/executors/shell-env-blocklist.test.ts +0 -648
- package/tests/executors/shell-env-process.test.ts +0 -245
- package/tests/executors/shell-process-registry.test.ts +0 -334
- package/tests/executors/shell-tools.test.ts +0 -393
- package/tests/executors/shell.test.ts +0 -690
- package/tests/executors/web-abort-vs-timeout.test.ts +0 -213
- package/tests/executors/web-integration.test.ts +0 -633
- package/tests/executors/web-symlink.test.ts +0 -18
- package/tests/executors/web.test.ts +0 -1400
- package/tests/executors/write-stdin.test.ts +0 -145
- package/tests/extraction/content-extraction.test.ts +0 -153
- package/tests/guards/tools-default-test-lane.integration.test.ts +0 -21
- package/tests/guards/tools-package-test-commands.e2e.test.ts +0 -43
- package/tests/guards/tools-test-lane-manifest.contract.test.ts +0 -76
- package/tests/guards/tools-vitest-workspace-alias.contract.test.ts +0 -63
- package/tests/helpers/async-waits.ts +0 -53
- package/tests/integration/headless-control-contract.integration.test.ts +0 -153
- package/tests/integration/memory-tool-schema-parity.integration.test.ts +0 -67
- package/tests/integration/meta-tools-round-trip.integration.test.ts +0 -506
- package/tests/integration/quest-round-trip.test.ts +0 -303
- package/tests/integration/registry-executor-flow.test.ts +0 -85
- package/tests/integration.test.ts +0 -177
- package/tests/loading-tier.test.ts +0 -126
- package/tests/mcp/client-reconnect.test.ts +0 -267
- package/tests/mcp/connection.test.ts +0 -846
- package/tests/mcp/injectable-logger.test.ts +0 -83
- package/tests/mcp/jsonrpc.test.ts +0 -109
- package/tests/mcp/lifecycle.test.ts +0 -879
- package/tests/network-runtime/address-types.contract.test.ts +0 -143
- package/tests/network-runtime/continuity-bind-schema.contract.test.ts +0 -203
- package/tests/network-runtime/local-control-contract.test.ts +0 -869
- package/tests/network-runtime/local-control-invite-token.contract.test.ts +0 -146
- package/tests/network-runtime/node-store-contract.test.ts +0 -11
- package/tests/network-runtime/pair-protocol-nodeid.contract.test.ts +0 -15
- package/tests/network-runtime/peer-state-machine.contract.test.ts +0 -148
- package/tests/network-runtime/protocol-schemas.contract.test.ts +0 -512
- package/tests/network-runtime/relay-pending-nodeid.contract.test.ts +0 -62
- package/tests/network-runtime/runtime-bootstrap-contract.test.ts +0 -227
- package/tests/network-runtime/runtime-socket-local-control-client.test.ts +0 -621
- package/tests/network-runtime/wait-for-message-script.test.ts +0 -288
- package/tests/parallel.test.ts +0 -71
- package/tests/policy.test.ts +0 -184
- package/tests/print-default-test-lane.ts +0 -14
- package/tests/print-test-lane-manifest.ts +0 -22
- package/tests/providers/brave.test.ts +0 -159
- package/tests/providers/duckduckgo.test.ts +0 -207
- package/tests/providers/exa.test.ts +0 -175
- package/tests/providers/firecrawl.test.ts +0 -168
- package/tests/providers/jina.test.ts +0 -144
- package/tests/providers/router.test.ts +0 -328
- package/tests/providers/tavily.test.ts +0 -165
- package/tests/registry/discovery.test.ts +0 -154
- package/tests/registry/injectable-logger.test.ts +0 -230
- package/tests/registry/input-validation.test.ts +0 -361
- package/tests/registry/interface-completeness.test.ts +0 -85
- package/tests/registry/mcp-integration.test.ts +0 -103
- package/tests/registry/mcp-read-only-hint.test.ts +0 -60
- package/tests/registry/memoria-discovery.test.ts +0 -390
- package/tests/registry/nested-validation.test.ts +0 -283
- package/tests/registry/pseudo-tool-filtering.test.ts +0 -258
- package/tests/registry/registration-lifecycle.test.ts +0 -133
- package/tests/registry-validation.test.ts +0 -424
- package/tests/registry.test.ts +0 -460
- package/tests/security/dns-pinning.test.ts +0 -162
- package/tests/security/external-content.test.ts +0 -144
- package/tests/security/ssrf.test.ts +0 -118
- package/tests/shell-safety-integration.test.ts +0 -32
- package/tests/shell-safety.test.ts +0 -365
- package/tests/slack/desktop-session.test.ts +0 -50
- package/tests/test-lane-manifest.ts +0 -440
- package/tests/test-utils.ts +0 -27
- package/tests/tool-factory.test.ts +0 -188
- package/tests/utils/retry.test.ts +0 -231
- package/tests/utils/url.test.ts +0 -63
- package/tsconfig.cjs.json +0 -24
- package/tsconfig.json +0 -12
- package/vitest.config.ts +0 -55
- package/vitest.e2e.config.ts +0 -24
- package/vitest.integration.config.ts +0 -24
- package/vitest.native.config.ts +0 -24
|
@@ -1,586 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* @aria/tools - apply_patch unit tests
|
|
3
|
-
*
|
|
4
|
-
* Tests for the patch parser, fuzzy matching, and sandbox path validation.
|
|
5
|
-
* These are the core subsystems of the extended apply_patch tool.
|
|
6
|
-
*/
|
|
7
|
-
|
|
8
|
-
import { describe, it, expect } from "vitest";
|
|
9
|
-
import {
|
|
10
|
-
parsePatchText,
|
|
11
|
-
normalizeUnicodeSpaces,
|
|
12
|
-
checkPatchBoundariesLenient,
|
|
13
|
-
parseOneHunk,
|
|
14
|
-
BEGIN_PATCH_MARKER,
|
|
15
|
-
END_PATCH_MARKER,
|
|
16
|
-
} from "../../src/definitions/patch/patch-parser.js";
|
|
17
|
-
import { applyUpdateHunk } from "../../src/definitions/patch/fuzzy-match.js";
|
|
18
|
-
import {
|
|
19
|
-
resolveSandboxPath,
|
|
20
|
-
assertSandboxPath,
|
|
21
|
-
} from "../../src/definitions/patch/sandbox-paths.js";
|
|
22
|
-
|
|
23
|
-
// ── Patch Parser ──────────────────────────────────────────────────────
|
|
24
|
-
|
|
25
|
-
describe("apply_patch", () => {
|
|
26
|
-
describe("patch-parser", () => {
|
|
27
|
-
it("parses Add File hunk", () => {
|
|
28
|
-
const input = [
|
|
29
|
-
"*** Begin Patch",
|
|
30
|
-
"*** Add File: src/hello.ts",
|
|
31
|
-
'+export const hello = "world";',
|
|
32
|
-
"*** End Patch",
|
|
33
|
-
].join("\n");
|
|
34
|
-
|
|
35
|
-
const { hunks } = parsePatchText(input);
|
|
36
|
-
expect(hunks).toHaveLength(1);
|
|
37
|
-
expect(hunks[0]!.kind).toBe("add");
|
|
38
|
-
if (hunks[0]!.kind === "add") {
|
|
39
|
-
expect(hunks[0]!.path).toBe("src/hello.ts");
|
|
40
|
-
expect(hunks[0]!.contents).toBe('export const hello = "world";\n');
|
|
41
|
-
}
|
|
42
|
-
});
|
|
43
|
-
|
|
44
|
-
it("parses Add File with multiple lines", () => {
|
|
45
|
-
const input = [
|
|
46
|
-
"*** Begin Patch",
|
|
47
|
-
"*** Add File: README.md",
|
|
48
|
-
"+# Title",
|
|
49
|
-
"+",
|
|
50
|
-
"+Some content",
|
|
51
|
-
"*** End Patch",
|
|
52
|
-
].join("\n");
|
|
53
|
-
|
|
54
|
-
const { hunks } = parsePatchText(input);
|
|
55
|
-
expect(hunks).toHaveLength(1);
|
|
56
|
-
if (hunks[0]!.kind === "add") {
|
|
57
|
-
expect(hunks[0]!.contents).toBe("# Title\n\nSome content\n");
|
|
58
|
-
}
|
|
59
|
-
});
|
|
60
|
-
|
|
61
|
-
it("parses Delete File hunk", () => {
|
|
62
|
-
const input = ["*** Begin Patch", "*** Delete File: old/deprecated.ts", "*** End Patch"].join(
|
|
63
|
-
"\n",
|
|
64
|
-
);
|
|
65
|
-
|
|
66
|
-
const { hunks } = parsePatchText(input);
|
|
67
|
-
expect(hunks).toHaveLength(1);
|
|
68
|
-
expect(hunks[0]!.kind).toBe("delete");
|
|
69
|
-
if (hunks[0]!.kind === "delete") {
|
|
70
|
-
expect(hunks[0]!.path).toBe("old/deprecated.ts");
|
|
71
|
-
}
|
|
72
|
-
});
|
|
73
|
-
|
|
74
|
-
it("parses Update File hunk with @@ chunks", () => {
|
|
75
|
-
const input = [
|
|
76
|
-
"*** Begin Patch",
|
|
77
|
-
"*** Update File: src/main.ts",
|
|
78
|
-
"@@ export function main() {",
|
|
79
|
-
'- console.log("old");',
|
|
80
|
-
'+ console.log("new");',
|
|
81
|
-
"*** End Patch",
|
|
82
|
-
].join("\n");
|
|
83
|
-
|
|
84
|
-
const { hunks } = parsePatchText(input);
|
|
85
|
-
expect(hunks).toHaveLength(1);
|
|
86
|
-
expect(hunks[0]!.kind).toBe("update");
|
|
87
|
-
if (hunks[0]!.kind === "update") {
|
|
88
|
-
expect(hunks[0]!.path).toBe("src/main.ts");
|
|
89
|
-
expect(hunks[0]!.chunks).toHaveLength(1);
|
|
90
|
-
expect(hunks[0]!.chunks[0]!.changeContext).toBe("export function main() {");
|
|
91
|
-
expect(hunks[0]!.chunks[0]!.oldLines).toEqual([' console.log("old");']);
|
|
92
|
-
expect(hunks[0]!.chunks[0]!.newLines).toEqual([' console.log("new");']);
|
|
93
|
-
}
|
|
94
|
-
});
|
|
95
|
-
|
|
96
|
-
it("parses Update File with context lines (space prefix)", () => {
|
|
97
|
-
const input = [
|
|
98
|
-
"*** Begin Patch",
|
|
99
|
-
"*** Update File: src/config.ts",
|
|
100
|
-
"@@ const config = {",
|
|
101
|
-
" debug: false,",
|
|
102
|
-
"- port: 3000,",
|
|
103
|
-
"+ port: 8080,",
|
|
104
|
-
' host: "localhost",',
|
|
105
|
-
"*** End Patch",
|
|
106
|
-
].join("\n");
|
|
107
|
-
|
|
108
|
-
const { hunks } = parsePatchText(input);
|
|
109
|
-
expect(hunks).toHaveLength(1);
|
|
110
|
-
if (hunks[0]!.kind === "update") {
|
|
111
|
-
const chunk = hunks[0]!.chunks[0]!;
|
|
112
|
-
// Context lines appear in both old and new
|
|
113
|
-
expect(chunk.oldLines).toEqual([
|
|
114
|
-
" debug: false,",
|
|
115
|
-
" port: 3000,",
|
|
116
|
-
' host: "localhost",',
|
|
117
|
-
]);
|
|
118
|
-
expect(chunk.newLines).toEqual([
|
|
119
|
-
" debug: false,",
|
|
120
|
-
" port: 8080,",
|
|
121
|
-
' host: "localhost",',
|
|
122
|
-
]);
|
|
123
|
-
}
|
|
124
|
-
});
|
|
125
|
-
|
|
126
|
-
it("handles *** Move to: rename", () => {
|
|
127
|
-
const input = [
|
|
128
|
-
"*** Begin Patch",
|
|
129
|
-
"*** Update File: old-name.ts",
|
|
130
|
-
"*** Move to: new-name.ts",
|
|
131
|
-
"@@ ",
|
|
132
|
-
"-const OLD = true;",
|
|
133
|
-
"+const NEW = true;",
|
|
134
|
-
"*** End Patch",
|
|
135
|
-
].join("\n");
|
|
136
|
-
|
|
137
|
-
const { hunks } = parsePatchText(input);
|
|
138
|
-
expect(hunks).toHaveLength(1);
|
|
139
|
-
if (hunks[0]!.kind === "update") {
|
|
140
|
-
expect(hunks[0]!.path).toBe("old-name.ts");
|
|
141
|
-
expect(hunks[0]!.movePath).toBe("new-name.ts");
|
|
142
|
-
}
|
|
143
|
-
});
|
|
144
|
-
|
|
145
|
-
it("validates *** Begin Patch / *** End Patch boundaries", () => {
|
|
146
|
-
// Missing Begin Patch
|
|
147
|
-
expect(() => parsePatchText(["*** Delete File: foo.ts", "*** End Patch"].join("\n"))).toThrow(
|
|
148
|
-
"*** Begin Patch",
|
|
149
|
-
);
|
|
150
|
-
|
|
151
|
-
// Missing End Patch
|
|
152
|
-
expect(() =>
|
|
153
|
-
parsePatchText(["*** Begin Patch", "*** Delete File: foo.ts"].join("\n")),
|
|
154
|
-
).toThrow("*** End Patch");
|
|
155
|
-
|
|
156
|
-
// Valid boundaries should not throw
|
|
157
|
-
expect(() =>
|
|
158
|
-
parsePatchText(["*** Begin Patch", "*** Delete File: foo.ts", "*** End Patch"].join("\n")),
|
|
159
|
-
).not.toThrow();
|
|
160
|
-
});
|
|
161
|
-
|
|
162
|
-
it("rejects empty input", () => {
|
|
163
|
-
expect(() => parsePatchText("")).toThrow("input is empty");
|
|
164
|
-
expect(() => parsePatchText(" ")).toThrow("input is empty");
|
|
165
|
-
});
|
|
166
|
-
|
|
167
|
-
it("handles <<EOF heredoc wrapper", () => {
|
|
168
|
-
const input = [
|
|
169
|
-
"<<EOF",
|
|
170
|
-
"*** Begin Patch",
|
|
171
|
-
"*** Delete File: tmp.ts",
|
|
172
|
-
"*** End Patch",
|
|
173
|
-
"EOF",
|
|
174
|
-
].join("\n");
|
|
175
|
-
|
|
176
|
-
const { hunks } = parsePatchText(input);
|
|
177
|
-
expect(hunks).toHaveLength(1);
|
|
178
|
-
expect(hunks[0]!.kind).toBe("delete");
|
|
179
|
-
});
|
|
180
|
-
|
|
181
|
-
it("handles <<'EOF' (single-quoted) heredoc wrapper", () => {
|
|
182
|
-
const input = [
|
|
183
|
-
"<<'EOF'",
|
|
184
|
-
"*** Begin Patch",
|
|
185
|
-
"*** Delete File: tmp.ts",
|
|
186
|
-
"*** End Patch",
|
|
187
|
-
"EOF",
|
|
188
|
-
].join("\n");
|
|
189
|
-
|
|
190
|
-
const { hunks } = parsePatchText(input);
|
|
191
|
-
expect(hunks).toHaveLength(1);
|
|
192
|
-
});
|
|
193
|
-
|
|
194
|
-
it('handles <<"EOF" (double-quoted) heredoc wrapper', () => {
|
|
195
|
-
const input = [
|
|
196
|
-
'<<"EOF"',
|
|
197
|
-
"*** Begin Patch",
|
|
198
|
-
"*** Delete File: tmp.ts",
|
|
199
|
-
"*** End Patch",
|
|
200
|
-
"EOF",
|
|
201
|
-
].join("\n");
|
|
202
|
-
|
|
203
|
-
const { hunks } = parsePatchText(input);
|
|
204
|
-
expect(hunks).toHaveLength(1);
|
|
205
|
-
});
|
|
206
|
-
|
|
207
|
-
it("normalizes Unicode spaces in paths", () => {
|
|
208
|
-
// \u00A0 = non-breaking space
|
|
209
|
-
const result = normalizeUnicodeSpaces("src/my\u00A0file.ts");
|
|
210
|
-
expect(result).toBe("src/my file.ts");
|
|
211
|
-
|
|
212
|
-
// \u2003 = em space
|
|
213
|
-
const result2 = normalizeUnicodeSpaces("src/my\u2003file.ts");
|
|
214
|
-
expect(result2).toBe("src/my file.ts");
|
|
215
|
-
});
|
|
216
|
-
|
|
217
|
-
it("parses multiple hunks in one patch", () => {
|
|
218
|
-
const input = [
|
|
219
|
-
"*** Begin Patch",
|
|
220
|
-
"*** Add File: a.ts",
|
|
221
|
-
"+a",
|
|
222
|
-
"*** Delete File: b.ts",
|
|
223
|
-
"*** Add File: c.ts",
|
|
224
|
-
"+c",
|
|
225
|
-
"*** End Patch",
|
|
226
|
-
].join("\n");
|
|
227
|
-
|
|
228
|
-
const { hunks } = parsePatchText(input);
|
|
229
|
-
expect(hunks).toHaveLength(3);
|
|
230
|
-
expect(hunks[0]!.kind).toBe("add");
|
|
231
|
-
expect(hunks[1]!.kind).toBe("delete");
|
|
232
|
-
expect(hunks[2]!.kind).toBe("add");
|
|
233
|
-
});
|
|
234
|
-
|
|
235
|
-
it("handles empty @@ context marker", () => {
|
|
236
|
-
const input = [
|
|
237
|
-
"*** Begin Patch",
|
|
238
|
-
"*** Update File: src/main.ts",
|
|
239
|
-
"@@",
|
|
240
|
-
"-old",
|
|
241
|
-
"+new",
|
|
242
|
-
"*** End Patch",
|
|
243
|
-
].join("\n");
|
|
244
|
-
|
|
245
|
-
const { hunks } = parsePatchText(input);
|
|
246
|
-
expect(hunks).toHaveLength(1);
|
|
247
|
-
if (hunks[0]!.kind === "update") {
|
|
248
|
-
expect(hunks[0]!.chunks[0]!.changeContext).toBeUndefined();
|
|
249
|
-
}
|
|
250
|
-
});
|
|
251
|
-
|
|
252
|
-
it("handles *** End of File marker", () => {
|
|
253
|
-
const input = [
|
|
254
|
-
"*** Begin Patch",
|
|
255
|
-
"*** Update File: src/main.ts",
|
|
256
|
-
"@@",
|
|
257
|
-
"-old line",
|
|
258
|
-
"+new line",
|
|
259
|
-
"*** End of File",
|
|
260
|
-
"*** End Patch",
|
|
261
|
-
].join("\n");
|
|
262
|
-
|
|
263
|
-
const { hunks } = parsePatchText(input);
|
|
264
|
-
if (hunks[0]!.kind === "update") {
|
|
265
|
-
expect(hunks[0]!.chunks[0]!.isEndOfFile).toBe(true);
|
|
266
|
-
}
|
|
267
|
-
});
|
|
268
|
-
|
|
269
|
-
it("throws on invalid hunk header", () => {
|
|
270
|
-
const input = ["*** Begin Patch", "not a valid header", "*** End Patch"].join("\n");
|
|
271
|
-
|
|
272
|
-
expect(() => parsePatchText(input)).toThrow("not a valid hunk header");
|
|
273
|
-
});
|
|
274
|
-
|
|
275
|
-
it("throws on empty Update File hunk", () => {
|
|
276
|
-
const input = ["*** Begin Patch", "*** Update File: src/main.ts", "*** End Patch"].join("\n");
|
|
277
|
-
|
|
278
|
-
expect(() => parsePatchText(input)).toThrow("empty");
|
|
279
|
-
});
|
|
280
|
-
});
|
|
281
|
-
|
|
282
|
-
// ── Fuzzy Match ───────────────────────────────────────────────────────
|
|
283
|
-
|
|
284
|
-
describe("fuzzy-match", () => {
|
|
285
|
-
const mockReadFile = (content: string) => async (_path: string) => content;
|
|
286
|
-
|
|
287
|
-
it("matches exact lines (pass 1)", async () => {
|
|
288
|
-
const original = "line one\nline two\nline three\n";
|
|
289
|
-
const result = await applyUpdateHunk(
|
|
290
|
-
"test.txt",
|
|
291
|
-
[
|
|
292
|
-
{
|
|
293
|
-
oldLines: ["line two"],
|
|
294
|
-
newLines: ["line TWO replaced"],
|
|
295
|
-
isEndOfFile: false,
|
|
296
|
-
},
|
|
297
|
-
],
|
|
298
|
-
{ readFile: mockReadFile(original) },
|
|
299
|
-
);
|
|
300
|
-
|
|
301
|
-
expect(result).toContain("line TWO replaced");
|
|
302
|
-
expect(result).not.toContain("line two");
|
|
303
|
-
expect(result).toContain("line one");
|
|
304
|
-
expect(result).toContain("line three");
|
|
305
|
-
});
|
|
306
|
-
|
|
307
|
-
it("matches trimmed trailing whitespace (pass 2)", async () => {
|
|
308
|
-
// Original has trailing spaces on "line two "
|
|
309
|
-
const original = "line one\nline two \nline three\n";
|
|
310
|
-
const result = await applyUpdateHunk(
|
|
311
|
-
"test.txt",
|
|
312
|
-
[
|
|
313
|
-
{
|
|
314
|
-
// Pattern has no trailing spaces
|
|
315
|
-
oldLines: ["line two"],
|
|
316
|
-
newLines: ["replaced"],
|
|
317
|
-
isEndOfFile: false,
|
|
318
|
-
},
|
|
319
|
-
],
|
|
320
|
-
{ readFile: mockReadFile(original) },
|
|
321
|
-
);
|
|
322
|
-
|
|
323
|
-
expect(result).toContain("replaced");
|
|
324
|
-
expect(result).not.toContain("line two");
|
|
325
|
-
});
|
|
326
|
-
|
|
327
|
-
it("matches fully trimmed lines (pass 3)", async () => {
|
|
328
|
-
// Original has leading whitespace
|
|
329
|
-
const original = " line one\n line two\n line three\n";
|
|
330
|
-
const result = await applyUpdateHunk(
|
|
331
|
-
"test.txt",
|
|
332
|
-
[
|
|
333
|
-
{
|
|
334
|
-
// Pattern without leading whitespace
|
|
335
|
-
oldLines: ["line two"],
|
|
336
|
-
newLines: ["replaced"],
|
|
337
|
-
isEndOfFile: false,
|
|
338
|
-
},
|
|
339
|
-
],
|
|
340
|
-
{ readFile: mockReadFile(original) },
|
|
341
|
-
);
|
|
342
|
-
|
|
343
|
-
expect(result).toContain("replaced");
|
|
344
|
-
});
|
|
345
|
-
|
|
346
|
-
it("matches after Unicode punctuation normalization (pass 4)", async () => {
|
|
347
|
-
// \u201C and \u201D are "smart quotes"
|
|
348
|
-
const original = "const msg = \u201Chello world\u201D;\n";
|
|
349
|
-
const result = await applyUpdateHunk(
|
|
350
|
-
"test.txt",
|
|
351
|
-
[
|
|
352
|
-
{
|
|
353
|
-
oldLines: ['const msg = "hello world";'],
|
|
354
|
-
newLines: ['const msg = "goodbye world";'],
|
|
355
|
-
isEndOfFile: false,
|
|
356
|
-
},
|
|
357
|
-
],
|
|
358
|
-
{ readFile: mockReadFile(original) },
|
|
359
|
-
);
|
|
360
|
-
|
|
361
|
-
expect(result).toContain("goodbye world");
|
|
362
|
-
});
|
|
363
|
-
|
|
364
|
-
it("matches with Unicode dash normalization (pass 4)", async () => {
|
|
365
|
-
// \u2014 is em dash
|
|
366
|
-
const original = "value \u2014 default\n";
|
|
367
|
-
const result = await applyUpdateHunk(
|
|
368
|
-
"test.txt",
|
|
369
|
-
[
|
|
370
|
-
{
|
|
371
|
-
oldLines: ["value - default"],
|
|
372
|
-
newLines: ["value - override"],
|
|
373
|
-
isEndOfFile: false,
|
|
374
|
-
},
|
|
375
|
-
],
|
|
376
|
-
{ readFile: mockReadFile(original) },
|
|
377
|
-
);
|
|
378
|
-
|
|
379
|
-
expect(result).toContain("override");
|
|
380
|
-
});
|
|
381
|
-
|
|
382
|
-
it("returns null when no match found", async () => {
|
|
383
|
-
const original = "line one\nline two\nline three\n";
|
|
384
|
-
|
|
385
|
-
await expect(
|
|
386
|
-
applyUpdateHunk(
|
|
387
|
-
"test.txt",
|
|
388
|
-
[
|
|
389
|
-
{
|
|
390
|
-
oldLines: ["completely nonexistent line"],
|
|
391
|
-
newLines: ["replaced"],
|
|
392
|
-
isEndOfFile: false,
|
|
393
|
-
},
|
|
394
|
-
],
|
|
395
|
-
{ readFile: mockReadFile(original) },
|
|
396
|
-
),
|
|
397
|
-
).rejects.toThrow("Failed to find expected lines");
|
|
398
|
-
});
|
|
399
|
-
|
|
400
|
-
it("applies hunks in reverse order to preserve line numbers", async () => {
|
|
401
|
-
const original = "line 1\nline 2\nline 3\nline 4\nline 5\n";
|
|
402
|
-
|
|
403
|
-
// Two replacements: one near start, one near end
|
|
404
|
-
const result = await applyUpdateHunk(
|
|
405
|
-
"test.txt",
|
|
406
|
-
[
|
|
407
|
-
{
|
|
408
|
-
oldLines: ["line 2"],
|
|
409
|
-
newLines: ["LINE TWO"],
|
|
410
|
-
isEndOfFile: false,
|
|
411
|
-
},
|
|
412
|
-
{
|
|
413
|
-
oldLines: ["line 4"],
|
|
414
|
-
newLines: ["LINE FOUR"],
|
|
415
|
-
isEndOfFile: false,
|
|
416
|
-
},
|
|
417
|
-
],
|
|
418
|
-
{ readFile: mockReadFile(original) },
|
|
419
|
-
);
|
|
420
|
-
|
|
421
|
-
const lines = result.split("\n");
|
|
422
|
-
expect(lines).toContain("LINE TWO");
|
|
423
|
-
expect(lines).toContain("LINE FOUR");
|
|
424
|
-
expect(lines).not.toContain("line 2");
|
|
425
|
-
expect(lines).not.toContain("line 4");
|
|
426
|
-
});
|
|
427
|
-
|
|
428
|
-
it("handles insertion with empty oldLines (append)", async () => {
|
|
429
|
-
const original = "existing content\n";
|
|
430
|
-
|
|
431
|
-
const result = await applyUpdateHunk(
|
|
432
|
-
"test.txt",
|
|
433
|
-
[
|
|
434
|
-
{
|
|
435
|
-
oldLines: [],
|
|
436
|
-
newLines: ["appended line"],
|
|
437
|
-
isEndOfFile: false,
|
|
438
|
-
},
|
|
439
|
-
],
|
|
440
|
-
{ readFile: mockReadFile(original) },
|
|
441
|
-
);
|
|
442
|
-
|
|
443
|
-
expect(result).toContain("appended line");
|
|
444
|
-
expect(result).toContain("existing content");
|
|
445
|
-
});
|
|
446
|
-
|
|
447
|
-
it("handles changeContext to seek correct location", async () => {
|
|
448
|
-
const original = [
|
|
449
|
-
"function a() {",
|
|
450
|
-
" return 1;",
|
|
451
|
-
"}",
|
|
452
|
-
"function b() {",
|
|
453
|
-
" return 2;",
|
|
454
|
-
"}",
|
|
455
|
-
"",
|
|
456
|
-
].join("\n");
|
|
457
|
-
|
|
458
|
-
const result = await applyUpdateHunk(
|
|
459
|
-
"test.txt",
|
|
460
|
-
[
|
|
461
|
-
{
|
|
462
|
-
changeContext: "function b() {",
|
|
463
|
-
oldLines: [" return 2;"],
|
|
464
|
-
newLines: [" return 42;"],
|
|
465
|
-
isEndOfFile: false,
|
|
466
|
-
},
|
|
467
|
-
],
|
|
468
|
-
{ readFile: mockReadFile(original) },
|
|
469
|
-
);
|
|
470
|
-
|
|
471
|
-
expect(result).toContain("return 42");
|
|
472
|
-
expect(result).toContain("return 1"); // function a untouched
|
|
473
|
-
});
|
|
474
|
-
|
|
475
|
-
it("ensures output ends with newline", async () => {
|
|
476
|
-
const original = "line one\nline two\n";
|
|
477
|
-
|
|
478
|
-
const result = await applyUpdateHunk(
|
|
479
|
-
"test.txt",
|
|
480
|
-
[
|
|
481
|
-
{
|
|
482
|
-
oldLines: ["line two"],
|
|
483
|
-
newLines: ["replaced"],
|
|
484
|
-
isEndOfFile: false,
|
|
485
|
-
},
|
|
486
|
-
],
|
|
487
|
-
{ readFile: mockReadFile(original) },
|
|
488
|
-
);
|
|
489
|
-
|
|
490
|
-
expect(result.endsWith("\n")).toBe(true);
|
|
491
|
-
});
|
|
492
|
-
});
|
|
493
|
-
|
|
494
|
-
// ── Sandbox Paths ─────────────────────────────────────────────────────
|
|
495
|
-
|
|
496
|
-
describe("sandbox-paths", () => {
|
|
497
|
-
it("rejects path traversal with ..", () => {
|
|
498
|
-
expect(() =>
|
|
499
|
-
resolveSandboxPath({
|
|
500
|
-
filePath: "../../../etc/passwd",
|
|
501
|
-
cwd: "/home/user/project",
|
|
502
|
-
root: "/home/user/project",
|
|
503
|
-
}),
|
|
504
|
-
).toThrow("escapes sandbox root");
|
|
505
|
-
});
|
|
506
|
-
|
|
507
|
-
it("rejects absolute paths outside root", () => {
|
|
508
|
-
expect(() =>
|
|
509
|
-
resolveSandboxPath({
|
|
510
|
-
filePath: "/etc/passwd",
|
|
511
|
-
cwd: "/home/user/project",
|
|
512
|
-
root: "/home/user/project",
|
|
513
|
-
}),
|
|
514
|
-
).toThrow("escapes sandbox root");
|
|
515
|
-
});
|
|
516
|
-
|
|
517
|
-
it("allows paths within cwd", () => {
|
|
518
|
-
const result = resolveSandboxPath({
|
|
519
|
-
filePath: "src/main.ts",
|
|
520
|
-
cwd: "/home/user/project",
|
|
521
|
-
root: "/home/user/project",
|
|
522
|
-
});
|
|
523
|
-
|
|
524
|
-
expect(result.resolved).toBe("/home/user/project/src/main.ts");
|
|
525
|
-
expect(result.relative).toBe("src/main.ts");
|
|
526
|
-
});
|
|
527
|
-
|
|
528
|
-
it("allows the root path itself", () => {
|
|
529
|
-
const result = resolveSandboxPath({
|
|
530
|
-
filePath: ".",
|
|
531
|
-
cwd: "/home/user/project",
|
|
532
|
-
root: "/home/user/project",
|
|
533
|
-
});
|
|
534
|
-
|
|
535
|
-
expect(result.resolved).toBe("/home/user/project");
|
|
536
|
-
expect(result.relative).toBe("");
|
|
537
|
-
});
|
|
538
|
-
|
|
539
|
-
it("expands ~ to homedir", () => {
|
|
540
|
-
const os = require("node:os");
|
|
541
|
-
const home = os.homedir();
|
|
542
|
-
const root = home;
|
|
543
|
-
|
|
544
|
-
const result = resolveSandboxPath({
|
|
545
|
-
filePath: "~/somefile.ts",
|
|
546
|
-
cwd: home,
|
|
547
|
-
root,
|
|
548
|
-
});
|
|
549
|
-
|
|
550
|
-
expect(result.resolved).toBe(`${home}/somefile.ts`);
|
|
551
|
-
});
|
|
552
|
-
|
|
553
|
-
it("normalizes Unicode spaces in paths", () => {
|
|
554
|
-
// resolveSandboxPath goes through expandPath which normalizes Unicode spaces
|
|
555
|
-
const result = resolveSandboxPath({
|
|
556
|
-
filePath: "src/my\u00A0file.ts",
|
|
557
|
-
cwd: "/home/user/project",
|
|
558
|
-
root: "/home/user/project",
|
|
559
|
-
});
|
|
560
|
-
|
|
561
|
-
expect(result.resolved).toBe("/home/user/project/src/my file.ts");
|
|
562
|
-
});
|
|
563
|
-
|
|
564
|
-
it("assertSandboxPath checks symlink escapes", async () => {
|
|
565
|
-
// For a path that doesn't exist yet, assertNoSymlinkEscape returns
|
|
566
|
-
// early with ENOENT — so it should succeed (no symlink to check)
|
|
567
|
-
const result = await assertSandboxPath({
|
|
568
|
-
filePath: "nonexistent/path.ts",
|
|
569
|
-
cwd: "/tmp",
|
|
570
|
-
root: "/tmp",
|
|
571
|
-
});
|
|
572
|
-
|
|
573
|
-
expect(result.resolved).toBe("/tmp/nonexistent/path.ts");
|
|
574
|
-
});
|
|
575
|
-
|
|
576
|
-
it("rejects relative path that escapes via multiple segments", () => {
|
|
577
|
-
expect(() =>
|
|
578
|
-
resolveSandboxPath({
|
|
579
|
-
filePath: "a/b/../../../../../../etc/passwd",
|
|
580
|
-
cwd: "/home/user/project",
|
|
581
|
-
root: "/home/user/project",
|
|
582
|
-
}),
|
|
583
|
-
).toThrow("escapes sandbox root");
|
|
584
|
-
});
|
|
585
|
-
});
|
|
586
|
-
});
|