pi-agent-flow 2.0.1 → 2.0.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/README.md +126 -489
- package/agents/audit.md +4 -2
- package/agents/build.md +3 -2
- package/agents/craft.md +3 -3
- package/agents/debug.md +4 -3
- package/agents/ideas.md +5 -4
- package/agents/scout.md +3 -1
- package/dist/batch/apply-patch.d.ts +60 -0
- package/dist/batch/apply-patch.d.ts.map +1 -0
- package/dist/batch/apply-patch.js +477 -0
- package/dist/batch/apply-patch.js.map +1 -0
- package/dist/batch/batch-bash.d.ts +0 -6
- package/dist/batch/batch-bash.d.ts.map +1 -1
- package/dist/batch/batch-bash.js +25 -10
- package/dist/batch/batch-bash.js.map +1 -1
- package/dist/batch/constants.d.ts +33 -4
- package/dist/batch/constants.d.ts.map +1 -1
- package/dist/batch/constants.js +26 -4
- package/dist/batch/constants.js.map +1 -1
- package/dist/batch/execute.d.ts +8 -2
- package/dist/batch/execute.d.ts.map +1 -1
- package/dist/batch/execute.js +221 -66
- package/dist/batch/execute.js.map +1 -1
- package/dist/batch/fuzzy-edit.d.ts +4 -1
- package/dist/batch/fuzzy-edit.d.ts.map +1 -1
- package/dist/batch/fuzzy-edit.js +7 -2
- package/dist/batch/fuzzy-edit.js.map +1 -1
- package/dist/batch/index.d.ts +3 -15
- package/dist/batch/index.d.ts.map +1 -1
- package/dist/batch/index.js +48 -78
- package/dist/batch/index.js.map +1 -1
- package/dist/batch/render.d.ts.map +1 -1
- package/dist/batch/render.js +30 -7
- package/dist/batch/render.js.map +1 -1
- package/dist/batch/summary.d.ts.map +1 -1
- package/dist/batch/summary.js +4 -0
- package/dist/batch/summary.js.map +1 -1
- package/dist/core/depth.d.ts +3 -3
- package/dist/core/depth.d.ts.map +1 -1
- package/dist/core/depth.js +5 -5
- package/dist/core/depth.js.map +1 -1
- package/dist/core/executor.d.ts +3 -3
- package/dist/core/executor.d.ts.map +1 -1
- package/dist/core/executor.js +2 -2
- package/dist/core/executor.js.map +1 -1
- package/dist/core/flow.d.ts +10 -3
- package/dist/core/flow.d.ts.map +1 -1
- package/dist/core/flow.js +76 -33
- package/dist/core/flow.js.map +1 -1
- package/dist/core/{delegation.d.ts → transition.d.ts} +8 -8
- package/dist/core/{delegation.d.ts.map → transition.d.ts.map} +1 -1
- package/dist/core/{delegation.js → transition.js} +12 -12
- package/dist/core/{delegation.js.map → transition.js.map} +1 -1
- package/dist/flow/auto-warp.d.ts +1 -1
- package/dist/flow/auto-warp.js +1 -1
- package/dist/flow/continuation.js +2 -2
- package/dist/flow/continuation.js.map +1 -1
- package/dist/flow/index.d.ts +2 -2
- package/dist/flow/index.d.ts.map +1 -1
- package/dist/flow/index.js +3 -3
- package/dist/flow/index.js.map +1 -1
- package/dist/flow/loop-command.d.ts.map +1 -1
- package/dist/flow/loop-command.js +3 -0
- package/dist/flow/loop-command.js.map +1 -1
- package/dist/flow/settings-command.d.ts.map +1 -1
- package/dist/flow/settings-command.js +4 -1
- package/dist/flow/settings-command.js.map +1 -1
- package/dist/flow/warp.d.ts +15 -0
- package/dist/flow/warp.d.ts.map +1 -0
- package/dist/flow/warp.js +207 -0
- package/dist/flow/warp.js.map +1 -0
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +168 -21
- package/dist/index.js.map +1 -1
- package/dist/snapshot/runner-events.d.ts +5 -0
- package/dist/snapshot/runner-events.d.ts.map +1 -1
- package/dist/snapshot/runner-events.js +89 -15
- package/dist/snapshot/runner-events.js.map +1 -1
- package/dist/snapshot/snapshot.d.ts.map +1 -1
- package/dist/snapshot/snapshot.js +51 -18
- package/dist/snapshot/snapshot.js.map +1 -1
- package/dist/steering/flow-prompt.d.ts +2 -2
- package/dist/steering/flow-prompt.d.ts.map +1 -1
- package/dist/steering/flow-prompt.js +4 -4
- package/dist/steering/flow-prompt.js.map +1 -1
- package/dist/steering/sliding-prompt.js +6 -6
- package/dist/steering/sliding-prompt.js.map +1 -1
- package/dist/steering/tool-utils.d.ts +31 -8
- package/dist/steering/tool-utils.d.ts.map +1 -1
- package/dist/steering/tool-utils.js +63 -30
- package/dist/steering/tool-utils.js.map +1 -1
- package/dist/tools/ask-user.d.ts +0 -17
- package/dist/tools/ask-user.d.ts.map +1 -1
- package/dist/tools/ask-user.js +13 -37
- package/dist/tools/ask-user.js.map +1 -1
- package/dist/tools/timed-bash.d.ts +1 -1
- package/dist/tools/timed-bash.d.ts.map +1 -1
- package/dist/tools/timed-bash.js +10 -8
- package/dist/tools/timed-bash.js.map +1 -1
- package/dist/tools/web-tool.d.ts.map +1 -1
- package/dist/tools/web-tool.js +11 -13
- package/dist/tools/web-tool.js.map +1 -1
- package/dist/tui/render-utils.d.ts.map +1 -1
- package/dist/tui/render-utils.js +2 -4
- package/dist/tui/render-utils.js.map +1 -1
- package/dist/tui/render.d.ts +9 -0
- package/dist/tui/render.d.ts.map +1 -1
- package/dist/tui/render.js +78 -39
- package/dist/tui/render.js.map +1 -1
- package/dist/tui/scramble/index.d.ts +1 -1
- package/dist/tui/scramble/index.d.ts.map +1 -1
- package/dist/tui/scramble/index.js +1 -1
- package/dist/tui/scramble/index.js.map +1 -1
- package/dist/tui/scramble/manager.d.ts +1 -1
- package/dist/tui/scramble/manager.d.ts.map +1 -1
- package/dist/tui/scramble/manager.js +14 -2
- package/dist/tui/scramble/manager.js.map +1 -1
- package/dist/tui/scramble/utils.js +1 -1
- package/dist/tui/scramble/utils.js.map +1 -1
- package/dist/types/flow.d.ts.map +1 -1
- package/dist/types/flow.js +11 -2
- package/dist/types/flow.js.map +1 -1
- package/package.json +2 -2
- package/dist/flow/perform-warp.d.ts +0 -28
- package/dist/flow/perform-warp.d.ts.map +0 -1
- package/dist/flow/perform-warp.js +0 -127
- package/dist/flow/perform-warp.js.map +0 -1
- package/dist/flow/warp-command.d.ts +0 -8
- package/dist/flow/warp-command.d.ts.map +0 -1
- package/dist/flow/warp-command.js +0 -144
- package/dist/flow/warp-command.js.map +0 -1
- package/dist/flow/warp-utils.d.ts +0 -11
- package/dist/flow/warp-utils.d.ts.map +0 -1
- package/dist/flow/warp-utils.js +0 -187
- package/dist/flow/warp-utils.js.map +0 -1
package/agents/audit.md
CHANGED
|
@@ -11,16 +11,18 @@ mission: During this audit flow your mission is to verify and remediate quality
|
|
|
11
11
|
workflow:
|
|
12
12
|
1 Scope: identify the files behavior or change set to audit
|
|
13
13
|
2 Inspect: review security correctness maintainability and performance risks use batch with o read s offset l limit for targeted file reading instead of bash sed head tail
|
|
14
|
-
3 Classify: assign severity and explain the impact of each issue found
|
|
14
|
+
3 Classify: assign severity and explain the impact of each issue found use P0 critical immediate action P1 serious fix soon P2 moderate schedule fix P3 minor note and track
|
|
15
15
|
4 Fix: apply safe localized fixes directly with available tools
|
|
16
16
|
5 Verify: run relevant tests or checks after fixes when practical
|
|
17
|
-
6 Report: distinguish fixed issues from remaining risks
|
|
17
|
+
6 Report: distinguish fixed issues from remaining risks give overall verdict CLEAN CAUTION or AT_RISK with confidence 0.0-1.0
|
|
18
18
|
|
|
19
19
|
rules:
|
|
20
|
+
A real issue causes data loss exposes secrets crashes produces wrong results violates contracts regresses perf >20% blocks CI or introduces a race style-only without behavioral impact is not an issue
|
|
20
21
|
Be specific cite exact file paths and line numbers
|
|
21
22
|
If code is clean say so do not invent issues
|
|
22
23
|
Fix issues autonomously when the fix is safe and localized
|
|
23
24
|
Do not apply risky rewrites or broad redesigns from audit flag them with severity instead
|
|
25
|
+
Enumerate exhaustively before judging completeness do not stop at the first few issues
|
|
24
26
|
If a fix requires broader redesign recommend craft in next steps
|
|
25
27
|
If root cause is unclear recommend debug rather than guessing
|
|
26
28
|
Markers: Prefix substantive claims with [V] verified, [I] inferred, [A] assumed, or [U] unknown.
|
package/agents/build.md
CHANGED
|
@@ -12,9 +12,9 @@ workflow:
|
|
|
12
12
|
1 Analyze: read existing code for context
|
|
13
13
|
2 Plan: outline approach before modifying
|
|
14
14
|
3 Test: write or identify a failing test when practical
|
|
15
|
-
4 Execute: implement changes following core principles
|
|
15
|
+
4 Execute: implement changes following core principles size gate 800 lines or 500 complex lines per stage if exceeded split or recommend craft guard breaking changes with explicit migration note
|
|
16
16
|
5 Verify: run tests and checks refactor only if working
|
|
17
|
-
6 Ship:
|
|
17
|
+
6 Ship: self-review changes and rate confidence 0.0-1.0 before committing push monitor CI fix failures until green
|
|
18
18
|
7 Cleanup: delete old branch local and remote if requested otherwise leave the branch for the user to merge
|
|
19
19
|
|
|
20
20
|
rules:
|
|
@@ -24,6 +24,7 @@ Commit with conventional messages feat fix refactor
|
|
|
24
24
|
Do not merge to main unless the user explicitly requests it
|
|
25
25
|
If merging use squash merge
|
|
26
26
|
Update relevant docs if none changed, state why
|
|
27
|
+
Prefer integration tests for coverage over unit tests alone when behavior crosses module boundaries
|
|
27
28
|
Unexpected errors recommend debug do not guess
|
|
28
29
|
Markers: Prefix substantive claims with [V] verified, [I] inferred, [A] assumed, or [U] unknown.
|
|
29
30
|
Bite-first: Output raw evidence (code, paths, logs) before any prose explanation.
|
package/agents/craft.md
CHANGED
|
@@ -10,10 +10,10 @@ mission: During this craft flow your mission is to design a clear well structure
|
|
|
10
10
|
|
|
11
11
|
workflow:
|
|
12
12
|
1 Understand: define the problem constraints existing behavior and success criteria
|
|
13
|
-
2 Explore: map relevant patterns dependencies and existing architecture use batch with o read s offset l limit for targeted file reading instead of bash sed head tail
|
|
13
|
+
2 Explore: map relevant patterns dependencies and existing architecture use batch with o read s offset l limit for targeted file reading instead of bash sed head tail enumerate exhaustively
|
|
14
14
|
3 Evaluate: assess whether the change fits existing patterns or requires a clean migration prefer incremental improvement when safe endorse full cut redesign when the architecture demands it
|
|
15
|
-
4 Design: produce a concrete plan with ordered tasks data flow module boundaries and interface contracts
|
|
16
|
-
5 Review: check risks edge cases test strategy migration path and
|
|
15
|
+
4 Design: produce a concrete plan with ordered tasks data flow module boundaries and interface contracts tag priorities P0-P3 cap each stage at 800 lines or 500 complex design bounded context buffers for large reads
|
|
16
|
+
5 Review: check risks edge cases test strategy migration path and transfer to build include breaking-changes checklist require integration tests and give architecture soundness verdict
|
|
17
17
|
|
|
18
18
|
rules:
|
|
19
19
|
Follow SOLID (Single Responsibility Open Closed Liskov Substitution Interface Segregation Dependency Inversion) DRY (Do Not Repeat Yourself) KISS (Keep It Simple Stupid)
|
package/agents/debug.md
CHANGED
|
@@ -13,16 +13,17 @@ workflow:
|
|
|
13
13
|
2 Hypothesize: list three to five concrete causes subsystem branch timing data shape each must be falsifiable
|
|
14
14
|
3 Instrument: add minimal temporary logs or probes so one run can support or reject several hypotheses in parallel tag each log with hypothesisId prefer existing test hooks or stderr over noisy prints
|
|
15
15
|
4 Run once: clear prior logs if applicable reproduce read the evidence before editing logic
|
|
16
|
-
5 Conclude: for each hypothesis state CONFIRMED REJECTED or INCONCLUSIVE with
|
|
17
|
-
6 Fix: only after a hypothesis is confirmed by evidence no speculative guards revert any change tied to a rejected hypothesis
|
|
16
|
+
5 Conclude: for each hypothesis state CONFIRMED REJECTED or INCONCLUSIVE with confidence HIGH 0.8-1.0 MEDIUM 0.5-0.8 or LOW <0.5 and cite lines log stack trace assertion
|
|
17
|
+
6 Fix: only after a hypothesis is confirmed by evidence no speculative guards revert any change tied to a rejected hypothesis if fix exceeds 50 lines recommend craft instead of a broad patch
|
|
18
18
|
7 Verify: same repro plus tests compare before and after evidence no sleep or polling hacks as fixes unless the product contract truly requires delay
|
|
19
|
-
8 Finalize: remove temporary instrumentation after verification or when
|
|
19
|
+
8 Finalize: remove temporary instrumentation after verification or when root state confirms if applicable update relevant docs, runbooks, or troubleshooting notes after finishing Documentation-only updates are required after finishing the work give overall verdict FIXED CONFIRMED INCONCLUSIVE or CANNOT_REPRODUCE with confidence 0.0-1.0
|
|
20
20
|
|
|
21
21
|
rules:
|
|
22
22
|
Evidence before edits read errors failing tests and traces before wide code reads
|
|
23
23
|
Targeted reads use batch with o read s and l avoid bash sed head tail for source
|
|
24
24
|
No fix without proof do not ship guesses if blocked report what evidence is still missing
|
|
25
25
|
Keep instrumentation through verification do not strip logs until the post fix run proves the fix or the user confirms
|
|
26
|
+
Prefer integration tests or existing test reproduction over manual steps when available
|
|
26
27
|
State missing evidence or environment gaps do not invent a fix
|
|
27
28
|
Markers: Prefix substantive claims with [V] verified, [I] inferred, [A] assumed, or [U] unknown.
|
|
28
29
|
Bite-first: Output raw evidence (code, paths, logs) before any prose explanation.
|
package/agents/ideas.md
CHANGED
|
@@ -9,16 +9,17 @@ tier: full
|
|
|
9
9
|
mission: During this ideas flow your mission is to generate and compare possible directions. Use inherited context as background and constraints but avoid anchoring too tightly on prior solutions.
|
|
10
10
|
|
|
11
11
|
workflow:
|
|
12
|
-
1 Diverge: explore many possibilities without judging too early
|
|
13
|
-
2 Evaluate: compare trade offs risks effort and reversibility
|
|
14
|
-
3 Recommend: identify the strongest options and
|
|
15
|
-
4 Package: present choices clearly enough for planning or implementation
|
|
12
|
+
1 Diverge: explore many possibilities without judging too early enumerate exhaustively before narrowing
|
|
13
|
+
2 Evaluate: compare trade offs risks effort and reversibility tag each option P0-P3 and assess change size against 800/500 line caps plus breaking-changes risk
|
|
14
|
+
3 Recommend: identify the strongest options explain why and give overall verdict with justification and confidence_score 0.0-1.0
|
|
15
|
+
4 Package: present choices clearly enough for planning or implementation transfer
|
|
16
16
|
|
|
17
17
|
rules:
|
|
18
18
|
Stay focused on the requested intent creativity should still serve the objective
|
|
19
19
|
Prefer several distinct options over variations of the same idea
|
|
20
20
|
Make assumptions explicit when evidence is limited
|
|
21
21
|
If file context is needed use batch with o read s offset l limit for targeted reading instead of bash sed head tail
|
|
22
|
+
Prefer integration-testable ideas over brittle manual-only designs
|
|
22
23
|
Do not implement changes from this flow
|
|
23
24
|
Markers: Prefix substantive claims with [V] verified, [I] inferred, [A] assumed, or [U] unknown.
|
|
24
25
|
Bite-first: Output raw evidence (code, paths, logs) before any prose explanation.
|
package/agents/scout.md
CHANGED
|
@@ -12,16 +12,18 @@ workflow:
|
|
|
12
12
|
1 Survey: use ls find and grep to locate relevant files and symbols before reading whole files
|
|
13
13
|
2 Inspect: use batch with o read s offset l limit for targeted file reading instead of bash sed head tail
|
|
14
14
|
3 Trace: if batch returns a context map for a large file use the reported line ranges for targeted follow up reads then follow code paths dependencies configuration and tests that explain the requested area
|
|
15
|
-
4 Report: cite concrete evidence with precise file paths and line ranges stop when the requested context is mapped
|
|
15
|
+
4 Report: cite concrete evidence with precise file paths and line ranges stop when the requested context is mapped enumerate exhaustively before declaring completeness
|
|
16
16
|
5 Validate: cross-check evidence, verify inferences against source, mark gaps with [U], and confirm sufficiency.
|
|
17
17
|
|
|
18
18
|
rules:
|
|
19
|
+
Keep context reads bounded cap grep ls find output highlight any artifact over 1k tokens as [P0] and read in chunks
|
|
19
20
|
This is a read oriented flow do not modify files
|
|
20
21
|
Cite every finding with a precise file path and line number or range
|
|
21
22
|
Include relevant snippets or evidence inline so citations are verifiable
|
|
22
23
|
Show actual code or data not excessive summaries
|
|
23
24
|
If something is not found say so directly do not guess
|
|
24
25
|
Workflow: Scouts must complete 5 steps (Survey → Inspect → Trace → Report → Validate). Reject and resend if Validate is missing.
|
|
26
|
+
When tracing code paths surface any breaking-changes footprint deprecated APIs or altered contracts
|
|
25
27
|
Snap mode: when session mode is snap, prioritize Survey and Inspect. Skip deep Trace. Emit partial findings fast — incomplete maps are acceptable.
|
|
26
28
|
Markers: Preserve exactly ([V] Verified, [I] Inferred, [A] Assumed, [U] Unknown). Never present [A] or [U] as facts to the user. Dispatch a validation scout if critical claims are [A]/[U].
|
|
27
29
|
Output: Zero preamble or filler. Start immediately with the answer or tool call.
|
|
@@ -0,0 +1,60 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* apply-patch — 1:1 port of OpenAI Codex CLI apply_patch tool.
|
|
3
|
+
*
|
|
4
|
+
* Parses patch text into hunks, locates context via 4-stage fuzzy matching,
|
|
5
|
+
* computes replacements, and applies them to the filesystem.
|
|
6
|
+
*/
|
|
7
|
+
export interface UpdateFileChunk {
|
|
8
|
+
changeContext?: string;
|
|
9
|
+
oldLines: string[];
|
|
10
|
+
newLines: string[];
|
|
11
|
+
isEndOfFile: boolean;
|
|
12
|
+
}
|
|
13
|
+
export type Hunk = {
|
|
14
|
+
type: "add";
|
|
15
|
+
path: string;
|
|
16
|
+
contents: string;
|
|
17
|
+
} | {
|
|
18
|
+
type: "delete";
|
|
19
|
+
path: string;
|
|
20
|
+
} | {
|
|
21
|
+
type: "update";
|
|
22
|
+
path: string;
|
|
23
|
+
movePath?: string;
|
|
24
|
+
chunks: UpdateFileChunk[];
|
|
25
|
+
};
|
|
26
|
+
export interface ApplyPatchArgs {
|
|
27
|
+
patch: string;
|
|
28
|
+
hunks: Hunk[];
|
|
29
|
+
environmentId?: string;
|
|
30
|
+
}
|
|
31
|
+
export declare class ParseError extends Error {
|
|
32
|
+
readonly kind: "invalid-patch" | "invalid-hunk";
|
|
33
|
+
readonly lineNumber?: number | undefined;
|
|
34
|
+
constructor(kind: "invalid-patch" | "invalid-hunk", message: string, lineNumber?: number | undefined);
|
|
35
|
+
}
|
|
36
|
+
export declare class ComputeReplacementsError extends Error {
|
|
37
|
+
constructor(message: string);
|
|
38
|
+
}
|
|
39
|
+
export declare function parsePatch(patch: string): ApplyPatchArgs;
|
|
40
|
+
export declare function seekSequence(lines: string[], pattern: string[], start: number, eof: boolean): number | undefined;
|
|
41
|
+
export declare function computeReplacements(originalLines: string[], filePath: string, chunks: UpdateFileChunk[]): Array<{
|
|
42
|
+
startIdx: number;
|
|
43
|
+
oldLen: number;
|
|
44
|
+
newLines: string[];
|
|
45
|
+
}>;
|
|
46
|
+
export declare function applyReplacements(lines: string[], replacements: Array<{
|
|
47
|
+
startIdx: number;
|
|
48
|
+
oldLen: number;
|
|
49
|
+
newLines: string[];
|
|
50
|
+
}>): string[];
|
|
51
|
+
export interface AffectedPaths {
|
|
52
|
+
added: string[];
|
|
53
|
+
modified: string[];
|
|
54
|
+
deleted: string[];
|
|
55
|
+
}
|
|
56
|
+
export declare function applyPatch(patchText: string, cwd: string): Promise<{
|
|
57
|
+
affected: AffectedPaths;
|
|
58
|
+
exact: boolean;
|
|
59
|
+
}>;
|
|
60
|
+
//# sourceMappingURL=apply-patch.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"apply-patch.d.ts","sourceRoot":"","sources":["../../src/batch/apply-patch.ts"],"names":[],"mappings":"AAAA;;;;;GAKG;AAwBH,MAAM,WAAW,eAAe;IAC/B,aAAa,CAAC,EAAE,MAAM,CAAC;IACvB,QAAQ,EAAE,MAAM,EAAE,CAAC;IACnB,QAAQ,EAAE,MAAM,EAAE,CAAC;IACnB,WAAW,EAAE,OAAO,CAAC;CACrB;AAED,MAAM,MAAM,IAAI,GACb;IAAE,IAAI,EAAE,KAAK,CAAC;IAAC,IAAI,EAAE,MAAM,CAAC;IAAC,QAAQ,EAAE,MAAM,CAAA;CAAE,GAC/C;IAAE,IAAI,EAAE,QAAQ,CAAC;IAAC,IAAI,EAAE,MAAM,CAAA;CAAE,GAChC;IAAE,IAAI,EAAE,QAAQ,CAAC;IAAC,IAAI,EAAE,MAAM,CAAC;IAAC,QAAQ,CAAC,EAAE,MAAM,CAAC;IAAC,MAAM,EAAE,eAAe,EAAE,CAAA;CAAE,CAAC;AAElF,MAAM,WAAW,cAAc;IAC9B,KAAK,EAAE,MAAM,CAAC;IACd,KAAK,EAAE,IAAI,EAAE,CAAC;IACd,aAAa,CAAC,EAAE,MAAM,CAAC;CACvB;AAED,qBAAa,UAAW,SAAQ,KAAK;aAEnB,IAAI,EAAE,eAAe,GAAG,cAAc;aAEtC,UAAU,CAAC,EAAE,MAAM;gBAFnB,IAAI,EAAE,eAAe,GAAG,cAAc,EACtD,OAAO,EAAE,MAAM,EACC,UAAU,CAAC,EAAE,MAAM,YAAA;CAKpC;AAED,qBAAa,wBAAyB,SAAQ,KAAK;gBACtC,OAAO,EAAE,MAAM;CAI3B;AA6ND,wBAAgB,UAAU,CAAC,KAAK,EAAE,MAAM,GAAG,cAAc,CAsBxD;AAMD,wBAAgB,YAAY,CAC3B,KAAK,EAAE,MAAM,EAAE,EACf,OAAO,EAAE,MAAM,EAAE,EACjB,KAAK,EAAE,MAAM,EACb,GAAG,EAAE,OAAO,GACV,MAAM,GAAG,SAAS,CA4GpB;AAMD,wBAAgB,mBAAmB,CAClC,aAAa,EAAE,MAAM,EAAE,EACvB,QAAQ,EAAE,MAAM,EAChB,MAAM,EAAE,eAAe,EAAE,GACvB,KAAK,CAAC;IAAE,QAAQ,EAAE,MAAM,CAAC;IAAC,MAAM,EAAE,MAAM,CAAC;IAAC,QAAQ,EAAE,MAAM,EAAE,CAAA;CAAE,CAAC,CAkDjE;AAED,wBAAgB,iBAAiB,CAChC,KAAK,EAAE,MAAM,EAAE,EACf,YAAY,EAAE,KAAK,CAAC;IAAE,QAAQ,EAAE,MAAM,CAAC;IAAC,MAAM,EAAE,MAAM,CAAC;IAAC,QAAQ,EAAE,MAAM,EAAE,CAAA;CAAE,CAAC,GAC3E,MAAM,EAAE,CAOV;AA8BD,MAAM,WAAW,aAAa;IAC7B,KAAK,EAAE,MAAM,EAAE,CAAC;IAChB,QAAQ,EAAE,MAAM,EAAE,CAAC;IACnB,OAAO,EAAE,MAAM,EAAE,CAAC;CAClB;AAED,wBAAsB,UAAU,CAC/B,SAAS,EAAE,MAAM,EACjB,GAAG,EAAE,MAAM,GACT,OAAO,CAAC;IAAE,QAAQ,EAAE,aAAa,CAAC;IAAC,KAAK,EAAE,OAAO,CAAA;CAAE,CAAC,CAmEtD"}
|
|
@@ -0,0 +1,477 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* apply-patch — 1:1 port of OpenAI Codex CLI apply_patch tool.
|
|
3
|
+
*
|
|
4
|
+
* Parses patch text into hunks, locates context via 4-stage fuzzy matching,
|
|
5
|
+
* computes replacements, and applies them to the filesystem.
|
|
6
|
+
*/
|
|
7
|
+
import * as fs from "node:fs/promises";
|
|
8
|
+
import * as path from "node:path";
|
|
9
|
+
// ---------------------------------------------------------------------------
|
|
10
|
+
// Parser constants
|
|
11
|
+
// ---------------------------------------------------------------------------
|
|
12
|
+
const BEGIN_PATCH_MARKER = "*** Begin Patch";
|
|
13
|
+
const END_PATCH_MARKER = "*** End Patch";
|
|
14
|
+
const ENVIRONMENT_ID_MARKER = "*** Environment ID: ";
|
|
15
|
+
const ADD_FILE_MARKER = "*** Add File: ";
|
|
16
|
+
const DELETE_FILE_MARKER = "*** Delete File: ";
|
|
17
|
+
const UPDATE_FILE_MARKER = "*** Update File: ";
|
|
18
|
+
const MOVE_TO_MARKER = "*** Move to: ";
|
|
19
|
+
const EOF_MARKER = "*** End of File";
|
|
20
|
+
const CHANGE_CONTEXT_MARKER = "@@ ";
|
|
21
|
+
const EMPTY_CHANGE_CONTEXT_MARKER = "@@";
|
|
22
|
+
export class ParseError extends Error {
|
|
23
|
+
kind;
|
|
24
|
+
lineNumber;
|
|
25
|
+
constructor(kind, message, lineNumber) {
|
|
26
|
+
super(message);
|
|
27
|
+
this.kind = kind;
|
|
28
|
+
this.lineNumber = lineNumber;
|
|
29
|
+
this.name = "ParseError";
|
|
30
|
+
}
|
|
31
|
+
}
|
|
32
|
+
export class ComputeReplacementsError extends Error {
|
|
33
|
+
constructor(message) {
|
|
34
|
+
super(message);
|
|
35
|
+
this.name = "ComputeReplacementsError";
|
|
36
|
+
}
|
|
37
|
+
}
|
|
38
|
+
// ---------------------------------------------------------------------------
|
|
39
|
+
// Patch parser
|
|
40
|
+
// ---------------------------------------------------------------------------
|
|
41
|
+
function checkPatchBoundariesStrict(lines) {
|
|
42
|
+
if (lines.length === 0) {
|
|
43
|
+
throw new ParseError("invalid-patch", "The first line of the patch must be '*** Begin Patch'");
|
|
44
|
+
}
|
|
45
|
+
const first = lines[0].trim();
|
|
46
|
+
const last = lines[lines.length - 1].trim();
|
|
47
|
+
if (first !== BEGIN_PATCH_MARKER) {
|
|
48
|
+
throw new ParseError("invalid-patch", "The first line of the patch must be '*** Begin Patch'");
|
|
49
|
+
}
|
|
50
|
+
if (last !== END_PATCH_MARKER) {
|
|
51
|
+
throw new ParseError("invalid-patch", "The last line of the patch must be '*** End Patch'");
|
|
52
|
+
}
|
|
53
|
+
return { patchLines: lines, hunkLines: lines.slice(1, -1) };
|
|
54
|
+
}
|
|
55
|
+
function checkPatchBoundariesLenient(lines) {
|
|
56
|
+
try {
|
|
57
|
+
return checkPatchBoundariesStrict(lines);
|
|
58
|
+
}
|
|
59
|
+
catch (strictErr) {
|
|
60
|
+
if (lines.length >= 4) {
|
|
61
|
+
const first = lines[0].trim();
|
|
62
|
+
const last = lines[lines.length - 1].trim();
|
|
63
|
+
if ((first === "<<EOF" || first === "<<'EOF'" || first === '<<"EOF"') &&
|
|
64
|
+
last.endsWith("EOF")) {
|
|
65
|
+
const inner = lines.slice(1, -1);
|
|
66
|
+
try {
|
|
67
|
+
return checkPatchBoundariesStrict(inner);
|
|
68
|
+
}
|
|
69
|
+
catch {
|
|
70
|
+
/* fall through to return original error */
|
|
71
|
+
}
|
|
72
|
+
}
|
|
73
|
+
}
|
|
74
|
+
throw strictErr;
|
|
75
|
+
}
|
|
76
|
+
}
|
|
77
|
+
function parseEnvironmentIdPreamble(lines) {
|
|
78
|
+
if (lines.length === 0) {
|
|
79
|
+
return { remaining: lines, lineNumber: 2 };
|
|
80
|
+
}
|
|
81
|
+
const first = lines[0].trimStart();
|
|
82
|
+
if (first.startsWith(ENVIRONMENT_ID_MARKER)) {
|
|
83
|
+
const id = first.slice(ENVIRONMENT_ID_MARKER.length).trim();
|
|
84
|
+
if (id === "") {
|
|
85
|
+
throw new ParseError("invalid-patch", "apply_patch environment_id cannot be empty");
|
|
86
|
+
}
|
|
87
|
+
return { environmentId: id, remaining: lines.slice(1), lineNumber: 3 };
|
|
88
|
+
}
|
|
89
|
+
return { remaining: lines, lineNumber: 2 };
|
|
90
|
+
}
|
|
91
|
+
function parseUpdateFileChunk(lines, lineNumber, allowMissingContext) {
|
|
92
|
+
if (lines.length === 0) {
|
|
93
|
+
throw new ParseError("invalid-hunk", "Update hunk does not contain any lines", lineNumber);
|
|
94
|
+
}
|
|
95
|
+
let changeContext;
|
|
96
|
+
let startIndex = 0;
|
|
97
|
+
if (lines[0] === EMPTY_CHANGE_CONTEXT_MARKER) {
|
|
98
|
+
changeContext = undefined;
|
|
99
|
+
startIndex = 1;
|
|
100
|
+
}
|
|
101
|
+
else if (lines[0].startsWith(CHANGE_CONTEXT_MARKER)) {
|
|
102
|
+
changeContext = lines[0].slice(CHANGE_CONTEXT_MARKER.length);
|
|
103
|
+
startIndex = 1;
|
|
104
|
+
}
|
|
105
|
+
else {
|
|
106
|
+
if (!allowMissingContext) {
|
|
107
|
+
throw new ParseError("invalid-hunk", `Expected update hunk to start with a @@ context marker, got: '${lines[0]}'`, lineNumber);
|
|
108
|
+
}
|
|
109
|
+
changeContext = undefined;
|
|
110
|
+
startIndex = 0;
|
|
111
|
+
}
|
|
112
|
+
if (startIndex >= lines.length) {
|
|
113
|
+
throw new ParseError("invalid-hunk", "Update hunk does not contain any lines", lineNumber + 1);
|
|
114
|
+
}
|
|
115
|
+
const oldLines = [];
|
|
116
|
+
const newLines = [];
|
|
117
|
+
let isEndOfFile = false;
|
|
118
|
+
let parsedLines = 0;
|
|
119
|
+
for (let i = startIndex; i < lines.length; i++) {
|
|
120
|
+
const line = lines[i];
|
|
121
|
+
if (line === EOF_MARKER) {
|
|
122
|
+
if (parsedLines === 0) {
|
|
123
|
+
throw new ParseError("invalid-hunk", "Update hunk does not contain any lines", lineNumber + 1);
|
|
124
|
+
}
|
|
125
|
+
isEndOfFile = true;
|
|
126
|
+
parsedLines += 1;
|
|
127
|
+
break;
|
|
128
|
+
}
|
|
129
|
+
const firstChar = line.charAt(0);
|
|
130
|
+
if (firstChar === "") {
|
|
131
|
+
oldLines.push("");
|
|
132
|
+
newLines.push("");
|
|
133
|
+
}
|
|
134
|
+
else if (firstChar === " ") {
|
|
135
|
+
oldLines.push(line.slice(1));
|
|
136
|
+
newLines.push(line.slice(1));
|
|
137
|
+
}
|
|
138
|
+
else if (firstChar === "+") {
|
|
139
|
+
newLines.push(line.slice(1));
|
|
140
|
+
}
|
|
141
|
+
else if (firstChar === "-") {
|
|
142
|
+
oldLines.push(line.slice(1));
|
|
143
|
+
}
|
|
144
|
+
else {
|
|
145
|
+
if (parsedLines === 0) {
|
|
146
|
+
throw new ParseError("invalid-hunk", `Unexpected line found in update hunk: '${line}'. Every line should start with ' ' (context line), '+' (added line), or '-' (removed line)`, lineNumber + 1);
|
|
147
|
+
}
|
|
148
|
+
// Assume start of next hunk.
|
|
149
|
+
break;
|
|
150
|
+
}
|
|
151
|
+
parsedLines += 1;
|
|
152
|
+
}
|
|
153
|
+
return {
|
|
154
|
+
chunk: { changeContext, oldLines, newLines, isEndOfFile },
|
|
155
|
+
parsedLines: parsedLines + startIndex,
|
|
156
|
+
};
|
|
157
|
+
}
|
|
158
|
+
function parseOneHunk(lines, lineNumber) {
|
|
159
|
+
const firstLine = lines[0].trim();
|
|
160
|
+
if (firstLine.startsWith(ADD_FILE_MARKER)) {
|
|
161
|
+
const filePath = firstLine.slice(ADD_FILE_MARKER.length);
|
|
162
|
+
let contents = "";
|
|
163
|
+
let parsedLines = 1;
|
|
164
|
+
for (let i = 1; i < lines.length; i++) {
|
|
165
|
+
const line = lines[i];
|
|
166
|
+
if (line.startsWith("+")) {
|
|
167
|
+
contents += line.slice(1) + "\n";
|
|
168
|
+
parsedLines += 1;
|
|
169
|
+
}
|
|
170
|
+
else {
|
|
171
|
+
break;
|
|
172
|
+
}
|
|
173
|
+
}
|
|
174
|
+
return { hunk: { type: "add", path: filePath, contents }, parsedLines };
|
|
175
|
+
}
|
|
176
|
+
if (firstLine.startsWith(DELETE_FILE_MARKER)) {
|
|
177
|
+
const filePath = firstLine.slice(DELETE_FILE_MARKER.length);
|
|
178
|
+
return { hunk: { type: "delete", path: filePath }, parsedLines: 1 };
|
|
179
|
+
}
|
|
180
|
+
if (firstLine.startsWith(UPDATE_FILE_MARKER)) {
|
|
181
|
+
const filePath = firstLine.slice(UPDATE_FILE_MARKER.length);
|
|
182
|
+
let remaining = lines.slice(1);
|
|
183
|
+
let parsedLines = 1;
|
|
184
|
+
let movePath;
|
|
185
|
+
if (remaining.length > 0 && remaining[0].startsWith(MOVE_TO_MARKER)) {
|
|
186
|
+
movePath = remaining[0].slice(MOVE_TO_MARKER.length);
|
|
187
|
+
remaining = remaining.slice(1);
|
|
188
|
+
parsedLines += 1;
|
|
189
|
+
}
|
|
190
|
+
const chunks = [];
|
|
191
|
+
while (remaining.length > 0) {
|
|
192
|
+
if (remaining[0].trim() === "") {
|
|
193
|
+
parsedLines += 1;
|
|
194
|
+
remaining = remaining.slice(1);
|
|
195
|
+
continue;
|
|
196
|
+
}
|
|
197
|
+
if (remaining[0].startsWith("*")) {
|
|
198
|
+
break;
|
|
199
|
+
}
|
|
200
|
+
const { chunk, parsedLines: chunkLines } = parseUpdateFileChunk(remaining, lineNumber + parsedLines, chunks.length === 0);
|
|
201
|
+
chunks.push(chunk);
|
|
202
|
+
parsedLines += chunkLines;
|
|
203
|
+
remaining = remaining.slice(chunkLines);
|
|
204
|
+
}
|
|
205
|
+
if (chunks.length === 0) {
|
|
206
|
+
throw new ParseError("invalid-hunk", `Update file hunk for path '${filePath}' is empty`, lineNumber);
|
|
207
|
+
}
|
|
208
|
+
return { hunk: { type: "update", path: filePath, movePath, chunks }, parsedLines };
|
|
209
|
+
}
|
|
210
|
+
throw new ParseError("invalid-hunk", `'${firstLine}' is not a valid hunk header. Valid hunk headers: '*** Add File: {path}', '*** Delete File: {path}', '*** Update File: {path}'`, lineNumber);
|
|
211
|
+
}
|
|
212
|
+
export function parsePatch(patch) {
|
|
213
|
+
const trimmed = patch.trim();
|
|
214
|
+
const lines = trimmed.split("\n");
|
|
215
|
+
const { patchLines, hunkLines } = checkPatchBoundariesLenient(lines);
|
|
216
|
+
const { environmentId, remaining, lineNumber } = parseEnvironmentIdPreamble(hunkLines);
|
|
217
|
+
let remainingLines = remaining;
|
|
218
|
+
let currentLine = lineNumber;
|
|
219
|
+
const hunks = [];
|
|
220
|
+
while (remainingLines.length > 0) {
|
|
221
|
+
const { hunk, parsedLines } = parseOneHunk(remainingLines, currentLine);
|
|
222
|
+
hunks.push(hunk);
|
|
223
|
+
currentLine += parsedLines;
|
|
224
|
+
remainingLines = remainingLines.slice(parsedLines);
|
|
225
|
+
}
|
|
226
|
+
return {
|
|
227
|
+
patch: patchLines.join("\n"),
|
|
228
|
+
hunks,
|
|
229
|
+
environmentId,
|
|
230
|
+
};
|
|
231
|
+
}
|
|
232
|
+
// ---------------------------------------------------------------------------
|
|
233
|
+
// seek_sequence — 4-stage fuzzy matching
|
|
234
|
+
// ---------------------------------------------------------------------------
|
|
235
|
+
export function seekSequence(lines, pattern, start, eof) {
|
|
236
|
+
if (pattern.length === 0) {
|
|
237
|
+
return start;
|
|
238
|
+
}
|
|
239
|
+
if (pattern.length > lines.length) {
|
|
240
|
+
return undefined;
|
|
241
|
+
}
|
|
242
|
+
const searchStart = eof && lines.length >= pattern.length ? lines.length - pattern.length : start;
|
|
243
|
+
// Stage 1: exact match
|
|
244
|
+
for (let i = searchStart; i <= lines.length - pattern.length; i++) {
|
|
245
|
+
let ok = true;
|
|
246
|
+
for (let j = 0; j < pattern.length; j++) {
|
|
247
|
+
if (lines[i + j] !== pattern[j]) {
|
|
248
|
+
ok = false;
|
|
249
|
+
break;
|
|
250
|
+
}
|
|
251
|
+
}
|
|
252
|
+
if (ok)
|
|
253
|
+
return i;
|
|
254
|
+
}
|
|
255
|
+
// Stage 2: rstrip match (ignore trailing whitespace)
|
|
256
|
+
for (let i = searchStart; i <= lines.length - pattern.length; i++) {
|
|
257
|
+
let ok = true;
|
|
258
|
+
for (let j = 0; j < pattern.length; j++) {
|
|
259
|
+
if (lines[i + j].trimEnd() !== pattern[j].trimEnd()) {
|
|
260
|
+
ok = false;
|
|
261
|
+
break;
|
|
262
|
+
}
|
|
263
|
+
}
|
|
264
|
+
if (ok)
|
|
265
|
+
return i;
|
|
266
|
+
}
|
|
267
|
+
// Stage 3: trim match (ignore leading and trailing whitespace)
|
|
268
|
+
for (let i = searchStart; i <= lines.length - pattern.length; i++) {
|
|
269
|
+
let ok = true;
|
|
270
|
+
for (let j = 0; j < pattern.length; j++) {
|
|
271
|
+
if (lines[i + j].trim() !== pattern[j].trim()) {
|
|
272
|
+
ok = false;
|
|
273
|
+
break;
|
|
274
|
+
}
|
|
275
|
+
}
|
|
276
|
+
if (ok)
|
|
277
|
+
return i;
|
|
278
|
+
}
|
|
279
|
+
// Stage 4: Unicode normalisation
|
|
280
|
+
function normalise(s) {
|
|
281
|
+
return s
|
|
282
|
+
.trim()
|
|
283
|
+
.split("")
|
|
284
|
+
.map((c) => {
|
|
285
|
+
switch (c) {
|
|
286
|
+
// Dashes / hyphens → ASCII '-'
|
|
287
|
+
case "\u2010":
|
|
288
|
+
case "\u2011":
|
|
289
|
+
case "\u2012":
|
|
290
|
+
case "\u2013":
|
|
291
|
+
case "\u2014":
|
|
292
|
+
case "\u2015":
|
|
293
|
+
case "\u2212":
|
|
294
|
+
return "-";
|
|
295
|
+
// Fancy single quotes → '\''
|
|
296
|
+
case "\u2018":
|
|
297
|
+
case "\u2019":
|
|
298
|
+
case "\u201A":
|
|
299
|
+
case "\u201B":
|
|
300
|
+
return "'";
|
|
301
|
+
// Fancy double quotes → '"'
|
|
302
|
+
case "\u201C":
|
|
303
|
+
case "\u201D":
|
|
304
|
+
case "\u201E":
|
|
305
|
+
case "\u201F":
|
|
306
|
+
return '"';
|
|
307
|
+
// Odd spaces → normal space
|
|
308
|
+
case "\u00A0":
|
|
309
|
+
case "\u2002":
|
|
310
|
+
case "\u2003":
|
|
311
|
+
case "\u2004":
|
|
312
|
+
case "\u2005":
|
|
313
|
+
case "\u2006":
|
|
314
|
+
case "\u2007":
|
|
315
|
+
case "\u2008":
|
|
316
|
+
case "\u2009":
|
|
317
|
+
case "\u200A":
|
|
318
|
+
case "\u202F":
|
|
319
|
+
case "\u205F":
|
|
320
|
+
case "\u3000":
|
|
321
|
+
return " ";
|
|
322
|
+
default:
|
|
323
|
+
return c;
|
|
324
|
+
}
|
|
325
|
+
})
|
|
326
|
+
.join("");
|
|
327
|
+
}
|
|
328
|
+
for (let i = searchStart; i <= lines.length - pattern.length; i++) {
|
|
329
|
+
let ok = true;
|
|
330
|
+
for (let j = 0; j < pattern.length; j++) {
|
|
331
|
+
if (normalise(lines[i + j]) !== normalise(pattern[j])) {
|
|
332
|
+
ok = false;
|
|
333
|
+
break;
|
|
334
|
+
}
|
|
335
|
+
}
|
|
336
|
+
if (ok)
|
|
337
|
+
return i;
|
|
338
|
+
}
|
|
339
|
+
return undefined;
|
|
340
|
+
}
|
|
341
|
+
// ---------------------------------------------------------------------------
|
|
342
|
+
// compute_replacements + apply_replacements
|
|
343
|
+
// ---------------------------------------------------------------------------
|
|
344
|
+
export function computeReplacements(originalLines, filePath, chunks) {
|
|
345
|
+
const replacements = [];
|
|
346
|
+
let lineIndex = 0;
|
|
347
|
+
for (const chunk of chunks) {
|
|
348
|
+
if (chunk.changeContext) {
|
|
349
|
+
const idx = seekSequence(originalLines, [chunk.changeContext], lineIndex, false);
|
|
350
|
+
if (idx === undefined) {
|
|
351
|
+
throw new ComputeReplacementsError(`Failed to find context '${chunk.changeContext}' in ${filePath}`);
|
|
352
|
+
}
|
|
353
|
+
lineIndex = idx + 1;
|
|
354
|
+
}
|
|
355
|
+
if (chunk.oldLines.length === 0) {
|
|
356
|
+
// Pure addition — insert at end (or just before the trailing empty line).
|
|
357
|
+
const insertionIdx = originalLines.length > 0 && originalLines[originalLines.length - 1] === ""
|
|
358
|
+
? originalLines.length - 1
|
|
359
|
+
: originalLines.length;
|
|
360
|
+
replacements.push({ startIdx: insertionIdx, oldLen: 0, newLines: chunk.newLines });
|
|
361
|
+
continue;
|
|
362
|
+
}
|
|
363
|
+
let pattern = chunk.oldLines;
|
|
364
|
+
let found = seekSequence(originalLines, pattern, lineIndex, chunk.isEndOfFile);
|
|
365
|
+
let newSlice = chunk.newLines;
|
|
366
|
+
if (found === undefined && pattern.length > 0 && pattern[pattern.length - 1] === "") {
|
|
367
|
+
// Retry without the trailing empty sentinel.
|
|
368
|
+
pattern = pattern.slice(0, -1);
|
|
369
|
+
if (newSlice.length > 0 && newSlice[newSlice.length - 1] === "") {
|
|
370
|
+
newSlice = newSlice.slice(0, -1);
|
|
371
|
+
}
|
|
372
|
+
found = seekSequence(originalLines, pattern, lineIndex, chunk.isEndOfFile);
|
|
373
|
+
}
|
|
374
|
+
if (found === undefined) {
|
|
375
|
+
throw new ComputeReplacementsError(`Failed to find expected lines in ${filePath}:\n${chunk.oldLines.join("\n")}`);
|
|
376
|
+
}
|
|
377
|
+
replacements.push({ startIdx: found, oldLen: pattern.length, newLines: newSlice });
|
|
378
|
+
lineIndex = found + pattern.length;
|
|
379
|
+
}
|
|
380
|
+
replacements.sort((a, b) => a.startIdx - b.startIdx);
|
|
381
|
+
return replacements;
|
|
382
|
+
}
|
|
383
|
+
export function applyReplacements(lines, replacements) {
|
|
384
|
+
const result = [...lines];
|
|
385
|
+
for (let i = replacements.length - 1; i >= 0; i--) {
|
|
386
|
+
const { startIdx, oldLen, newLines } = replacements[i];
|
|
387
|
+
result.splice(startIdx, oldLen, ...newLines);
|
|
388
|
+
}
|
|
389
|
+
return result;
|
|
390
|
+
}
|
|
391
|
+
function deriveNewContentsFromChunks(originalContent, chunks, filePath) {
|
|
392
|
+
let originalLines = originalContent.split("\n").map((l) => l);
|
|
393
|
+
// Drop the trailing empty element that results from the final newline
|
|
394
|
+
// so that line counts match the behaviour of standard diff.
|
|
395
|
+
if (originalLines.length > 0 && originalLines[originalLines.length - 1] === "") {
|
|
396
|
+
originalLines.pop();
|
|
397
|
+
}
|
|
398
|
+
const replacements = computeReplacements(originalLines, filePath, chunks);
|
|
399
|
+
let newLines = applyReplacements(originalLines, replacements);
|
|
400
|
+
// Ensure file terminates with a newline.
|
|
401
|
+
if (newLines.length === 0 || newLines[newLines.length - 1] !== "") {
|
|
402
|
+
newLines.push("");
|
|
403
|
+
}
|
|
404
|
+
return newLines.join("\n");
|
|
405
|
+
}
|
|
406
|
+
export async function applyPatch(patchText, cwd) {
|
|
407
|
+
const args = parsePatch(patchText);
|
|
408
|
+
const added = [];
|
|
409
|
+
const modified = [];
|
|
410
|
+
const deleted = [];
|
|
411
|
+
let exact = true;
|
|
412
|
+
for (const hunk of args.hunks) {
|
|
413
|
+
switch (hunk.type) {
|
|
414
|
+
case "add": {
|
|
415
|
+
const targetPath = path.resolve(cwd, hunk.path);
|
|
416
|
+
await fs.mkdir(path.dirname(targetPath), { recursive: true });
|
|
417
|
+
await fs.writeFile(targetPath, hunk.contents, "utf-8");
|
|
418
|
+
added.push(hunk.path);
|
|
419
|
+
break;
|
|
420
|
+
}
|
|
421
|
+
case "delete": {
|
|
422
|
+
const targetPath = path.resolve(cwd, hunk.path);
|
|
423
|
+
try {
|
|
424
|
+
await fs.unlink(targetPath);
|
|
425
|
+
}
|
|
426
|
+
catch (err) {
|
|
427
|
+
if (err.code === "ENOENT") {
|
|
428
|
+
exact = false;
|
|
429
|
+
}
|
|
430
|
+
else {
|
|
431
|
+
throw err;
|
|
432
|
+
}
|
|
433
|
+
}
|
|
434
|
+
deleted.push(hunk.path);
|
|
435
|
+
break;
|
|
436
|
+
}
|
|
437
|
+
case "update": {
|
|
438
|
+
const targetPath = path.resolve(cwd, hunk.path);
|
|
439
|
+
let originalContent;
|
|
440
|
+
try {
|
|
441
|
+
originalContent = await fs.readFile(targetPath, "utf-8");
|
|
442
|
+
}
|
|
443
|
+
catch (err) {
|
|
444
|
+
if (err.code === "ENOENT") {
|
|
445
|
+
throw new Error(`File not found: ${hunk.path}`);
|
|
446
|
+
}
|
|
447
|
+
throw err;
|
|
448
|
+
}
|
|
449
|
+
const newContent = deriveNewContentsFromChunks(originalContent, hunk.chunks, hunk.path);
|
|
450
|
+
if (hunk.movePath) {
|
|
451
|
+
const destPath = path.resolve(cwd, hunk.movePath);
|
|
452
|
+
await fs.mkdir(path.dirname(destPath), { recursive: true });
|
|
453
|
+
await fs.writeFile(destPath, newContent, "utf-8");
|
|
454
|
+
try {
|
|
455
|
+
await fs.unlink(targetPath);
|
|
456
|
+
}
|
|
457
|
+
catch (err) {
|
|
458
|
+
if (err.code === "ENOENT") {
|
|
459
|
+
exact = false;
|
|
460
|
+
}
|
|
461
|
+
else {
|
|
462
|
+
throw err;
|
|
463
|
+
}
|
|
464
|
+
}
|
|
465
|
+
modified.push(hunk.movePath);
|
|
466
|
+
}
|
|
467
|
+
else {
|
|
468
|
+
await fs.writeFile(targetPath, newContent, "utf-8");
|
|
469
|
+
modified.push(hunk.path);
|
|
470
|
+
}
|
|
471
|
+
break;
|
|
472
|
+
}
|
|
473
|
+
}
|
|
474
|
+
}
|
|
475
|
+
return { affected: { added, modified, deleted }, exact };
|
|
476
|
+
}
|
|
477
|
+
//# sourceMappingURL=apply-patch.js.map
|