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.
Files changed (135) hide show
  1. package/README.md +126 -489
  2. package/agents/audit.md +4 -2
  3. package/agents/build.md +3 -2
  4. package/agents/craft.md +3 -3
  5. package/agents/debug.md +4 -3
  6. package/agents/ideas.md +5 -4
  7. package/agents/scout.md +3 -1
  8. package/dist/batch/apply-patch.d.ts +60 -0
  9. package/dist/batch/apply-patch.d.ts.map +1 -0
  10. package/dist/batch/apply-patch.js +477 -0
  11. package/dist/batch/apply-patch.js.map +1 -0
  12. package/dist/batch/batch-bash.d.ts +0 -6
  13. package/dist/batch/batch-bash.d.ts.map +1 -1
  14. package/dist/batch/batch-bash.js +25 -10
  15. package/dist/batch/batch-bash.js.map +1 -1
  16. package/dist/batch/constants.d.ts +33 -4
  17. package/dist/batch/constants.d.ts.map +1 -1
  18. package/dist/batch/constants.js +26 -4
  19. package/dist/batch/constants.js.map +1 -1
  20. package/dist/batch/execute.d.ts +8 -2
  21. package/dist/batch/execute.d.ts.map +1 -1
  22. package/dist/batch/execute.js +221 -66
  23. package/dist/batch/execute.js.map +1 -1
  24. package/dist/batch/fuzzy-edit.d.ts +4 -1
  25. package/dist/batch/fuzzy-edit.d.ts.map +1 -1
  26. package/dist/batch/fuzzy-edit.js +7 -2
  27. package/dist/batch/fuzzy-edit.js.map +1 -1
  28. package/dist/batch/index.d.ts +3 -15
  29. package/dist/batch/index.d.ts.map +1 -1
  30. package/dist/batch/index.js +48 -78
  31. package/dist/batch/index.js.map +1 -1
  32. package/dist/batch/render.d.ts.map +1 -1
  33. package/dist/batch/render.js +30 -7
  34. package/dist/batch/render.js.map +1 -1
  35. package/dist/batch/summary.d.ts.map +1 -1
  36. package/dist/batch/summary.js +4 -0
  37. package/dist/batch/summary.js.map +1 -1
  38. package/dist/core/depth.d.ts +3 -3
  39. package/dist/core/depth.d.ts.map +1 -1
  40. package/dist/core/depth.js +5 -5
  41. package/dist/core/depth.js.map +1 -1
  42. package/dist/core/executor.d.ts +3 -3
  43. package/dist/core/executor.d.ts.map +1 -1
  44. package/dist/core/executor.js +2 -2
  45. package/dist/core/executor.js.map +1 -1
  46. package/dist/core/flow.d.ts +10 -3
  47. package/dist/core/flow.d.ts.map +1 -1
  48. package/dist/core/flow.js +76 -33
  49. package/dist/core/flow.js.map +1 -1
  50. package/dist/core/{delegation.d.ts → transition.d.ts} +8 -8
  51. package/dist/core/{delegation.d.ts.map → transition.d.ts.map} +1 -1
  52. package/dist/core/{delegation.js → transition.js} +12 -12
  53. package/dist/core/{delegation.js.map → transition.js.map} +1 -1
  54. package/dist/flow/auto-warp.d.ts +1 -1
  55. package/dist/flow/auto-warp.js +1 -1
  56. package/dist/flow/continuation.js +2 -2
  57. package/dist/flow/continuation.js.map +1 -1
  58. package/dist/flow/index.d.ts +2 -2
  59. package/dist/flow/index.d.ts.map +1 -1
  60. package/dist/flow/index.js +3 -3
  61. package/dist/flow/index.js.map +1 -1
  62. package/dist/flow/loop-command.d.ts.map +1 -1
  63. package/dist/flow/loop-command.js +3 -0
  64. package/dist/flow/loop-command.js.map +1 -1
  65. package/dist/flow/settings-command.d.ts.map +1 -1
  66. package/dist/flow/settings-command.js +4 -1
  67. package/dist/flow/settings-command.js.map +1 -1
  68. package/dist/flow/warp.d.ts +15 -0
  69. package/dist/flow/warp.d.ts.map +1 -0
  70. package/dist/flow/warp.js +207 -0
  71. package/dist/flow/warp.js.map +1 -0
  72. package/dist/index.d.ts.map +1 -1
  73. package/dist/index.js +168 -21
  74. package/dist/index.js.map +1 -1
  75. package/dist/snapshot/runner-events.d.ts +5 -0
  76. package/dist/snapshot/runner-events.d.ts.map +1 -1
  77. package/dist/snapshot/runner-events.js +89 -15
  78. package/dist/snapshot/runner-events.js.map +1 -1
  79. package/dist/snapshot/snapshot.d.ts.map +1 -1
  80. package/dist/snapshot/snapshot.js +51 -18
  81. package/dist/snapshot/snapshot.js.map +1 -1
  82. package/dist/steering/flow-prompt.d.ts +2 -2
  83. package/dist/steering/flow-prompt.d.ts.map +1 -1
  84. package/dist/steering/flow-prompt.js +4 -4
  85. package/dist/steering/flow-prompt.js.map +1 -1
  86. package/dist/steering/sliding-prompt.js +6 -6
  87. package/dist/steering/sliding-prompt.js.map +1 -1
  88. package/dist/steering/tool-utils.d.ts +31 -8
  89. package/dist/steering/tool-utils.d.ts.map +1 -1
  90. package/dist/steering/tool-utils.js +63 -30
  91. package/dist/steering/tool-utils.js.map +1 -1
  92. package/dist/tools/ask-user.d.ts +0 -17
  93. package/dist/tools/ask-user.d.ts.map +1 -1
  94. package/dist/tools/ask-user.js +13 -37
  95. package/dist/tools/ask-user.js.map +1 -1
  96. package/dist/tools/timed-bash.d.ts +1 -1
  97. package/dist/tools/timed-bash.d.ts.map +1 -1
  98. package/dist/tools/timed-bash.js +10 -8
  99. package/dist/tools/timed-bash.js.map +1 -1
  100. package/dist/tools/web-tool.d.ts.map +1 -1
  101. package/dist/tools/web-tool.js +11 -13
  102. package/dist/tools/web-tool.js.map +1 -1
  103. package/dist/tui/render-utils.d.ts.map +1 -1
  104. package/dist/tui/render-utils.js +2 -4
  105. package/dist/tui/render-utils.js.map +1 -1
  106. package/dist/tui/render.d.ts +9 -0
  107. package/dist/tui/render.d.ts.map +1 -1
  108. package/dist/tui/render.js +78 -39
  109. package/dist/tui/render.js.map +1 -1
  110. package/dist/tui/scramble/index.d.ts +1 -1
  111. package/dist/tui/scramble/index.d.ts.map +1 -1
  112. package/dist/tui/scramble/index.js +1 -1
  113. package/dist/tui/scramble/index.js.map +1 -1
  114. package/dist/tui/scramble/manager.d.ts +1 -1
  115. package/dist/tui/scramble/manager.d.ts.map +1 -1
  116. package/dist/tui/scramble/manager.js +14 -2
  117. package/dist/tui/scramble/manager.js.map +1 -1
  118. package/dist/tui/scramble/utils.js +1 -1
  119. package/dist/tui/scramble/utils.js.map +1 -1
  120. package/dist/types/flow.d.ts.map +1 -1
  121. package/dist/types/flow.js +11 -2
  122. package/dist/types/flow.js.map +1 -1
  123. package/package.json +2 -2
  124. package/dist/flow/perform-warp.d.ts +0 -28
  125. package/dist/flow/perform-warp.d.ts.map +0 -1
  126. package/dist/flow/perform-warp.js +0 -127
  127. package/dist/flow/perform-warp.js.map +0 -1
  128. package/dist/flow/warp-command.d.ts +0 -8
  129. package/dist/flow/warp-command.d.ts.map +0 -1
  130. package/dist/flow/warp-command.js +0 -144
  131. package/dist/flow/warp-command.js.map +0 -1
  132. package/dist/flow/warp-utils.d.ts +0 -11
  133. package/dist/flow/warp-utils.d.ts.map +0 -1
  134. package/dist/flow/warp-utils.js +0 -187
  135. 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: commit push monitor CI fix failures until green
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 handoff to build
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 cited 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
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 orchestrator confirms if applicable update relevant docs, runbooks, or troubleshooting notes after finishing Documentation-only updates are required after finishing the work
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 explain why
15
- 4 Package: present choices clearly enough for planning or implementation handoff
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