@deepseek-ai/dsh-fs-patch 0.0.0-stage → 0.2.2-alpha.3

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/LICENSE ADDED
@@ -0,0 +1,235 @@
1
+ DeepSeek contributions are licensed under MIT below.
2
+
3
+ This package adapts patch parsing and text updates from OpenAI Codex
4
+ rust-v0.158.0 (codex-rs/apply-patch/src/parser.rs, streaming_parser.rs,
5
+ seek_sequence.rs, and file_update.rs), with text_file.rs as a reference
6
+ for the line-ending representations. Test fixtures also draw from
7
+ file_update_tests.rs and lib.rs in that directory.
8
+ Copyright 2025 OpenAI. Those portions remain under Apache License 2.0 below.
9
+ Modifications: pure TypeScript batch API, original-input line locations,
10
+ serializable records, explicit rejection of environment-routing headers,
11
+ and a filesystem-independent NormalizeToLf updater with structured failures.
12
+ Selected parser and text tests are adapted from the same Apache-licensed sources.
13
+
14
+ MIT License
15
+
16
+ Copyright (c) 2026 DeepSeek
17
+
18
+ Permission is hereby granted, free of charge, to any person obtaining a copy
19
+ of this software and associated documentation files (the "Software"), to deal
20
+ in the Software without restriction, including without limitation the rights
21
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
22
+ copies of the Software, and to permit persons to whom the Software is
23
+ furnished to do so, subject to the following conditions:
24
+
25
+ The above copyright notice and this permission notice shall be included in all
26
+ copies or substantial portions of the Software.
27
+
28
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
29
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
30
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
31
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
32
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
33
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
34
+ SOFTWARE.
35
+ Apache License
36
+ Version 2.0, January 2004
37
+ http://www.apache.org/licenses/
38
+
39
+ TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION
40
+
41
+ 1. Definitions.
42
+
43
+ "License" shall mean the terms and conditions for use, reproduction,
44
+ and distribution as defined by Sections 1 through 9 of this document.
45
+
46
+ "Licensor" shall mean the copyright owner or entity authorized by
47
+ the copyright owner that is granting the License.
48
+
49
+ "Legal Entity" shall mean the union of the acting entity and all
50
+ other entities that control, are controlled by, or are under common
51
+ control with that entity. For the purposes of this definition,
52
+ "control" means (i) the power, direct or indirect, to cause the
53
+ direction or management of such entity, whether by contract or
54
+ otherwise, or (ii) ownership of fifty percent (50%) or more of the
55
+ outstanding shares, or (iii) beneficial ownership of such entity.
56
+
57
+ "You" (or "Your") shall mean an individual or Legal Entity
58
+ exercising permissions granted by this License.
59
+
60
+ "Source" form shall mean the preferred form for making modifications,
61
+ including but not limited to software source code, documentation
62
+ source, and configuration files.
63
+
64
+ "Object" form shall mean any form resulting from mechanical
65
+ transformation or translation of a Source form, including but
66
+ not limited to compiled object code, generated documentation,
67
+ and conversions to other media types.
68
+
69
+ "Work" shall mean the work of authorship, whether in Source or
70
+ Object form, made available under the License, as indicated by a
71
+ copyright notice that is included in or attached to the work
72
+ (an example is provided in the Appendix below).
73
+
74
+ "Derivative Works" shall mean any work, whether in Source or Object
75
+ form, that is based on (or derived from) the Work and for which the
76
+ editorial revisions, annotations, elaborations, or other modifications
77
+ represent, as a whole, an original work of authorship. For the purposes
78
+ of this License, Derivative Works shall not include works that remain
79
+ separable from, or merely link (or bind by name) to the interfaces of,
80
+ the Work and Derivative Works thereof.
81
+
82
+ "Contribution" shall mean any work of authorship, including
83
+ the original version of the Work and any modifications or additions
84
+ to that Work or Derivative Works thereof, that is intentionally
85
+ submitted to Licensor for inclusion in the Work by the copyright owner
86
+ or by an individual or Legal Entity authorized to submit on behalf of
87
+ the copyright owner. For the purposes of this definition, "submitted"
88
+ means any form of electronic, verbal, or written communication sent
89
+ to the Licensor or its representatives, including but not limited to
90
+ communication on electronic mailing lists, source code control systems,
91
+ and issue tracking systems that are managed by, or on behalf of, the
92
+ Licensor for the purpose of discussing and improving the Work, but
93
+ excluding communication that is conspicuously marked or otherwise
94
+ designated in writing by the copyright owner as "Not a Contribution."
95
+
96
+ "Contributor" shall mean Licensor and any individual or Legal Entity
97
+ on behalf of whom a Contribution has been received by Licensor and
98
+ subsequently incorporated within the Work.
99
+
100
+ 2. Grant of Copyright License. Subject to the terms and conditions of
101
+ this License, each Contributor hereby grants to You a perpetual,
102
+ worldwide, non-exclusive, no-charge, royalty-free, irrevocable
103
+ copyright license to reproduce, prepare Derivative Works of,
104
+ publicly display, publicly perform, sublicense, and distribute the
105
+ Work and such Derivative Works in Source or Object form.
106
+
107
+ 3. Grant of Patent License. Subject to the terms and conditions of
108
+ this License, each Contributor hereby grants to You a perpetual,
109
+ worldwide, non-exclusive, no-charge, royalty-free, irrevocable
110
+ (except as stated in this section) patent license to make, have made,
111
+ use, offer to sell, sell, import, and otherwise transfer the Work,
112
+ where such license applies only to those patent claims licensable
113
+ by such Contributor that are necessarily infringed by their
114
+ Contribution(s) alone or by combination of their Contribution(s)
115
+ with the Work to which such Contribution(s) was submitted. If You
116
+ institute patent litigation against any entity (including a
117
+ cross-claim or counterclaim in a lawsuit) alleging that the Work
118
+ or a Contribution incorporated within the Work constitutes direct
119
+ or contributory patent infringement, then any patent licenses
120
+ granted to You under this License for that Work shall terminate
121
+ as of the date such litigation is filed.
122
+
123
+ 4. Redistribution. You may reproduce and distribute copies of the
124
+ Work or Derivative Works thereof in any medium, with or without
125
+ modifications, and in Source or Object form, provided that You
126
+ meet the following conditions:
127
+
128
+ (a) You must give any other recipients of the Work or
129
+ Derivative Works a copy of this License; and
130
+
131
+ (b) You must cause any modified files to carry prominent notices
132
+ stating that You changed the files; and
133
+
134
+ (c) You must retain, in the Source form of any Derivative Works
135
+ that You distribute, all copyright, patent, trademark, and
136
+ attribution notices from the Source form of the Work,
137
+ excluding those notices that do not pertain to any part of
138
+ the Derivative Works; and
139
+
140
+ (d) If the Work includes a "NOTICE" text file as part of its
141
+ distribution, then any Derivative Works that You distribute must
142
+ include a readable copy of the attribution notices contained
143
+ within such NOTICE file, excluding those notices that do not
144
+ pertain to any part of the Derivative Works, in at least one
145
+ of the following places: within a NOTICE text file distributed
146
+ as part of the Derivative Works; within the Source form or
147
+ documentation, if provided along with the Derivative Works; or,
148
+ within a display generated by the Derivative Works, if and
149
+ wherever such third-party notices normally appear. The contents
150
+ of the NOTICE file are for informational purposes only and
151
+ do not modify the License. You may add Your own attribution
152
+ notices within Derivative Works that You distribute, alongside
153
+ or as an addendum to the NOTICE text from the Work, provided
154
+ that such additional attribution notices cannot be construed
155
+ as modifying the License.
156
+
157
+ You may add Your own copyright statement to Your modifications and
158
+ may provide additional or different license terms and conditions
159
+ for use, reproduction, or distribution of Your modifications, or
160
+ for any such Derivative Works as a whole, provided Your use,
161
+ reproduction, and distribution of the Work otherwise complies with
162
+ the conditions stated in this License.
163
+
164
+ 5. Submission of Contributions. Unless You explicitly state otherwise,
165
+ any Contribution intentionally submitted for inclusion in the Work
166
+ by You to the Licensor shall be under the terms and conditions of
167
+ this License, without any additional terms or conditions.
168
+ Notwithstanding the above, nothing herein shall supersede or modify
169
+ the terms of any separate license agreement you may have executed
170
+ with Licensor regarding such Contributions.
171
+
172
+ 6. Trademarks. This License does not grant permission to use the trade
173
+ names, trademarks, service marks, or product names of the Licensor,
174
+ except as required for reasonable and customary use in describing the
175
+ origin of the Work and reproducing the content of the NOTICE file.
176
+
177
+ 7. Disclaimer of Warranty. Unless required by applicable law or
178
+ agreed to in writing, Licensor provides the Work (and each
179
+ Contributor provides its Contributions) on an "AS IS" BASIS,
180
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
181
+ implied, including, without limitation, any warranties or conditions
182
+ of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A
183
+ PARTICULAR PURPOSE. You are solely responsible for determining the
184
+ appropriateness of using or redistributing the Work and assume any
185
+ risks associated with Your exercise of permissions under this License.
186
+
187
+ 8. Limitation of Liability. In no event and under no legal theory,
188
+ whether in tort (including negligence), contract, or otherwise,
189
+ unless required by applicable law (such as deliberate and grossly
190
+ negligent acts) or agreed to in writing, shall any Contributor be
191
+ liable to You for damages, including any direct, indirect, special,
192
+ incidental, or consequential damages of any character arising as a
193
+ result of this License or out of the use or inability to use the
194
+ Work (including but not limited to damages for loss of goodwill,
195
+ work stoppage, computer failure or malfunction, or any and all
196
+ other commercial damages or losses), even if such Contributor
197
+ has been advised of the possibility of such damages.
198
+
199
+ 9. Accepting Warranty or Additional Liability. While redistributing
200
+ the Work or Derivative Works thereof, You may choose to offer,
201
+ and charge a fee for, acceptance of support, warranty, indemnity,
202
+ or other liability obligations and/or rights consistent with this
203
+ License. However, in accepting such obligations, You may act only
204
+ on Your own behalf and on Your sole responsibility, not on behalf
205
+ of any other Contributor, and only if You agree to indemnify,
206
+ defend, and hold each Contributor harmless for any liability
207
+ incurred by, or claims asserted against, such Contributor by reason
208
+ of your accepting any such warranty or additional liability.
209
+
210
+ END OF TERMS AND CONDITIONS
211
+
212
+ APPENDIX: How to apply the Apache License to your work.
213
+
214
+ To apply the Apache License to your work, attach the following
215
+ boilerplate notice, with the fields enclosed by brackets "[]"
216
+ replaced with your own identifying information. (Don't include
217
+ the brackets!) The text should be enclosed in the appropriate
218
+ comment syntax for the file format. We also recommend that a
219
+ file or class name and description of purpose be included on the
220
+ same "printed page" as the copyright notice for easier
221
+ identification within third-party archives.
222
+
223
+ Copyright 2025 OpenAI
224
+
225
+ Licensed under the Apache License, Version 2.0 (the "License");
226
+ you may not use this file except in compliance with the License.
227
+ You may obtain a copy of the License at
228
+
229
+ http://www.apache.org/licenses/LICENSE-2.0
230
+
231
+ Unless required by applicable law or agreed to in writing, software
232
+ distributed under the License is distributed on an "AS IS" BASIS,
233
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
234
+ See the License for the specific language governing permissions and
235
+ limitations under the License.
@@ -0,0 +1,40 @@
1
+ # Bilingual-pair consistency record for README.md (docs/i18n/README.md): per heading
2
+ # section, a hash of its English and Chinese blocks outside code blocks and generated regions.
3
+ # After editing either side, bring the other along and re-record with:
4
+ # pnpm run verify-translation-pairing --write packages/fs/fs-patch/README.md
5
+ /:
6
+ en: 80da396fec6cc3d9
7
+ zh: 35d4d8c713d9450c
8
+ /dsh-fs-patch:
9
+ en: ad2970c594c3b669
10
+ zh: fee247cad74b6191
11
+ /dsh-fs-patch/summary:
12
+ en: 614121759671f168
13
+ zh: 7cea460bb8940793
14
+ /dsh-fs-patch/table-of-contents:
15
+ en: 170f124543e5647a
16
+ zh: c30de483a11a1c09
17
+ /dsh-fs-patch/use-this-package:
18
+ en: 142b3b023512fc72
19
+ zh: bcfa70b710947eaf
20
+ /dsh-fs-patch/compatibility:
21
+ en: 70da6aede0649b32
22
+ zh: 2e3ed2964ae0461a
23
+ /dsh-fs-patch/compatibility/parsing:
24
+ en: 06644a9c38f7d569
25
+ zh: 91ca563d4e789087
26
+ /dsh-fs-patch/compatibility/matching-and-text-updates:
27
+ en: fcd630c121472565
28
+ zh: 35cc062e2f76b195
29
+ /dsh-fs-patch/understand-the-implementation:
30
+ en: 2ded4719a85a20bc
31
+ zh: e04f2614167a6c1c
32
+ /dsh-fs-patch/further-exploration:
33
+ en: d7d750bd71805956
34
+ zh: 8d33c76f5895d286
35
+ /dsh-fs-patch/known-limitations-and-deferred-work:
36
+ en: b18ead2791224bc2
37
+ zh: a4a8ab319c072445
38
+ /dsh-fs-patch/known-limitations-and-deferred-work/dev-note:
39
+ en: e67f633dc6fe7773
40
+ zh: e88aeb617ffb74ea
package/README.md CHANGED
@@ -1,3 +1,112 @@
1
- # Temporary Holding Version
1
+ ---
2
+ description: "Parse Codex-style patches and apply hunks to text without filesystem I/O."
3
+ kind: "package-library"
4
+ ---
2
5
 
3
- This version is a temporary placeholder for this package. An operational version to replace this has been submitted for review and is awaiting a staged release.
6
+ # dsh-fs-patch
7
+
8
+ English | [中文](README.zh.md)
9
+
10
+ ## Summary
11
+
12
+ Parse a complete Codex-style patch into ordered file operations with `parsePatch`, then apply update hunks to LF-normalized text with `applyHunks`. Both functions work on caller-supplied strings without filesystem access. Syntax errors throw with the original input line; matching returns new text or a structured failure.
13
+
14
+ ## Table of Contents
15
+
16
+ - [Use this package](#use-this-package)
17
+ - [Compatibility](#compatibility)
18
+ - [Understand the implementation](#understand-the-implementation)
19
+ - [Further Exploration](#further-exploration)
20
+ - [Known Limitations and Deferred Work](#known-limitations-and-deferred-work)
21
+ - [Dev Note](#dev-note)
22
+
23
+ <a id="use-this-package"></a>
24
+ ## Use this package
25
+
26
+ Operations and hunks contain only JSON-serializable values, and parsing retains repeated paths in input order as unresolved strings.
27
+
28
+ ```ts
29
+ import { applyHunks, parsePatch } from '@deepseek-ai/dsh-fs-patch'
30
+
31
+ const operations = parsePatch('*** Begin Patch\n*** Update File: hello.txt\n-hello\n+hi\n*** End Patch')
32
+ const operation = operations[0]
33
+ if (operation?.kind === 'update') {
34
+ const result = applyHunks('hello\n', operation.hunks) // { ok: true, after: 'hi\n' }
35
+ }
36
+ ```
37
+
38
+ Malformed input throws `PatchParseError` with a one-based `lineNumber` and a corrective `reason`. Locations count leading blank lines and heredoc wrappers in the original input.
39
+
40
+ `applyHunks` returns `{ ok: true, after }` or `{ ok: false, failure }` without mutating its inputs. A match failure reports the hunk's patch line, the `context` or `oldLines` stage, the sequential search cursor, and the original unmatched pattern; a trailing-empty-line retry still reports the original pattern. Returning rather than throwing lets callers collect match failures across file operations.
41
+
42
+ <a id="compatibility"></a>
43
+ ## Compatibility
44
+
45
+ The reference is [OpenAI Codex rust-v0.158.0](https://github.com/openai/codex/tree/rust-v0.158.0/codex-rs/apply-patch/src): `parser.rs` and `streaming_parser.rs` for syntax, and `seek_sequence.rs` and `file_update.rs` for matching and `NormalizeToLf` reconstruction. `text_file.rs` documents the separate line-preserving representation. The [parser tests](tests/parse.spec.ts) and [text tests](tests/apply.spec.ts) adapt selected upstream cases, including `file_update_tests.rs` and `lib.rs`. The [LICENSE](LICENSE) retains OpenAI attribution and Apache-2.0 terms alongside the MIT license for DeepSeek contributions.
46
+
47
+ ### Parsing
48
+
49
+ | Input | Parsed result |
50
+ |---|---|
51
+ | Begin/End Patch; Add/Delete/Update File headers | Ordered operations; an empty patch and an empty added file are accepted. |
52
+ | Added file lines prefixed by `+` | Prefix removed; each line contributes a final LF. |
53
+ | Update lines prefixed by space, `+`, or `-` | Context, insertion, or deletion; a bare empty line is empty context. |
54
+ | First update without `@@`; later `@@` or `@@ text` | Ordered hunks with an optional context anchor; empty hunks are rejected. |
55
+ | Move to immediately after Update File | Destination on the update; a move without a nonempty hunk is rejected. |
56
+ | End of File after a hunk | EOF flag; following blank lines are ignored and further content requires a new hunk or file header. |
57
+ | LF/CRLF, outer Unicode whitespace, indented headers | Codex trimming rules use Unicode `White_Space`, including U+0085 and excluding BOM (U+FEFF). Indented markers inside updates remain context. Content whitespace is retained. |
58
+ | Literal `<<EOF`, `<<'EOF'`, or `<<"EOF"` wrappers | Accepted without shell evaluation; the closing line must end in `EOF`, as in the reference. |
59
+ | Repeated paths, relative/absolute paths, Unicode text | Retained without path resolution or Unicode normalization. |
60
+ | Environment ID header | Dedicated unsupported-header error before the first operation; ordinary syntax errors elsewhere. |
61
+ | Unified diff, shell commands, malformed markers | Rejected with the original line number; diagnostic wording differs from Codex. |
62
+
63
+ EOF before any hunk has no effect, matching the reference; an update still requires a nonempty hunk. The reference also accepts a redundant final End Patch after an earlier End Patch and intervening whitespace.
64
+
65
+ ### Matching and text updates
66
+
67
+ | Input or rule | Result |
68
+ |---|---|
69
+ | Four matching passes | Exact, trailing `White_Space` trimmed on both sides, both ends trimmed, then trimmed Unicode punctuation mapped to ASCII. Each pass searches all candidates before the next pass. |
70
+ | Unicode punctuation | U+2010–2015 and U+2212 map to `-`; U+2018–201B to single quotes; U+201C–201F to double quotes; U+00A0, U+2002–200A, U+202F, U+205F, and U+3000 to space. No NFC/NFKC normalization. |
71
+ | `@@ text` and repeated content | Find the first matching anchor from the cursor, then search after it. Old lines use the first match from that cursor, which advances to the match end. All searches use the original text. |
72
+ | EOF hunk | Search only at `lineCount - patternLength`, even before the cursor. The pinned implementation does not fall back to earlier matches, despite its search-function comment. |
73
+ | Empty old lines | Insert at the original file's end, before its final empty source line when present. The insertion does not advance the cursor; an anchor must still match. Same-position insertions retain patch order. |
74
+ | Unmatched pattern ending in an empty line | Retry without that last line; also remove the last new line if it is empty. An empty retry pattern matches the cursor. |
75
+ | Reconstruction | Remove one terminal empty split element before matching; apply stably sorted replacements in reverse order. Add a terminal empty element unless one exists, then join with LF. Empty output stays empty; nonempty output ends in LF. |
76
+ | Context lines | Replace the complete matched region from patch text, including tolerated context differences. |
77
+ | No hunks | Still perform the same reconstruction, so a missing terminal LF is added and one terminal empty line can be consumed. |
78
+
79
+ The intentional differences from Codex are ownership and result reporting: the DSH provider preserves line-ending style, so the matcher sees only LF-normalized text and `PreserveLineEndings` is not implemented. CR is an ordinary character: callers must normalize CRLF first and restore the chosen style afterward. Match failures return structured data rather than Codex's path-bearing errors, so the caller owns file identification and user-facing diagnostics.
80
+
81
+ <a id="understand-the-implementation"></a>
82
+ ## Understand the implementation
83
+
84
+ <details>
85
+ <summary>Implementation internals — click to expand</summary>
86
+
87
+ The [parser](src/parse.ts) builds syntax records without a Session or provider dependency. The [text updater](src/apply.ts) locates every hunk against the original text before constructing a complete result. The [types](src/types.ts) carry syntax locations and match failures; the [filesystem reference](../../../docs/subsystems/filesystem.md#patch-syntax-records) owns the public type definitions.
88
+
89
+ </details>
90
+
91
+ <a id="further-exploration"></a>
92
+ ## Further Exploration
93
+
94
+ - [Filesystem package group](../README.md) — providers, policies, and file tools.
95
+ - [Apply patch file editing](../../../.agents/notes/implemented/feature/2026-09-28-apply-patch-file-editing.md) — decision record.
96
+
97
+ ## Known Limitations and Deferred Work
98
+
99
+ <a id="known-limitations-and-deferred-work"></a>
100
+
101
+ - Text updates implement only `NormalizeToLf`; callers own line-ending conversion.
102
+ - The public API accepts complete strings, not streaming deltas.
103
+
104
+ <a id="dev-note"></a>
105
+ ### Dev Note
106
+
107
+ <details>
108
+ <summary>Working context for maintainers — click to expand</summary>
109
+
110
+ None.
111
+
112
+ </details>
package/README.zh.md ADDED
@@ -0,0 +1,112 @@
1
+ ---
2
+ description: "解析 Codex 风格补丁并将 hunk 应用于文本,不执行文件系统 I/O。"
3
+ kind: "package-library"
4
+ ---
5
+
6
+ # dsh-fs-patch
7
+
8
+ [English](README.md) | 中文
9
+
10
+ ## 概述
11
+
12
+ 使用 `parsePatch` 将完整的 Codex 风格补丁解析为有序文件操作,再用 `applyHunks` 将更新 hunk 应用于 LF 规范化文本。两个函数只处理调用方传入的字符串,不访问文件系统。语法错误抛出并标明原始输入行号;匹配返回新文本或结构化失败。
13
+
14
+ ## 目录
15
+
16
+ - [使用此包](#use-this-package)
17
+ - [兼容性](#compatibility)
18
+ - [理解实现](#understand-the-implementation)
19
+ - [进一步探索](#further-exploration)
20
+ - [已知限制与后续工作](#known-limitations-and-deferred-work)
21
+ - [开发备注](#dev-note)
22
+
23
+ <a id="use-this-package"></a>
24
+ ## 使用此包
25
+
26
+ 操作和 hunk(修改块)只包含可 JSON 序列化的值;解析按输入顺序保留重复路径,路径保持为未解析的字符串。
27
+
28
+ ```ts
29
+ import { applyHunks, parsePatch } from '@deepseek-ai/dsh-fs-patch'
30
+
31
+ const operations = parsePatch('*** Begin Patch\n*** Update File: hello.txt\n-hello\n+hi\n*** End Patch')
32
+ const operation = operations[0]
33
+ if (operation?.kind === 'update') {
34
+ const result = applyHunks('hello\n', operation.hunks) // { ok: true, after: 'hi\n' }
35
+ }
36
+ ```
37
+
38
+ 非法输入抛出 `PatchParseError`,包含从 1 开始的 `lineNumber` 和说明如何修正的 `reason`。位置计入原始输入中的前导空行和 heredoc 包装。
39
+
40
+ `applyHunks` 返回 `{ ok: true, after }` 或 `{ ok: false, failure }`,不修改输入。匹配失败报告 hunk 在补丁中的行号、`context` 或 `oldLines` 阶段、顺序搜索游标和原始未匹配模式;移除末尾空行重试后仍报告原始模式。以返回值而非异常报告失败,便于调用方收集多个文件操作的匹配失败。
41
+
42
+ <a id="compatibility"></a>
43
+ ## 兼容性
44
+
45
+ 参照实现为 [OpenAI Codex rust-v0.158.0](https://github.com/openai/codex/tree/rust-v0.158.0/codex-rs/apply-patch/src):语法参照 `parser.rs` 和 `streaming_parser.rs`,匹配及 `NormalizeToLf` 重建参照 `seek_sequence.rs` 和 `file_update.rs`。`text_file.rs` 说明独立的换行保留表示。[解析器测试](tests/parse.spec.ts) 与[文本测试](tests/apply.spec.ts) 改编选定的上游用例,包括 `file_update_tests.rs` 和 `lib.rs`。[LICENSE](LICENSE) 在 DeepSeek 贡献的 MIT 许可证之外,保留 OpenAI 署名与 Apache-2.0 条款。
46
+
47
+ ### 解析
48
+
49
+ | 输入 | 解析结果 |
50
+ |---|---|
51
+ | Begin/End Patch;Add/Delete/Update File 头 | 有序操作;接受空补丁和空新增文件。 |
52
+ | 新增文件中以 `+` 为前缀的行 | 去除前缀;每行都附带末尾 LF。 |
53
+ | 更新中以空格、`+` 或 `-` 为前缀的行 | 上下文、插入或删除;没有前缀的空行视为空上下文。 |
54
+ | 首个更新省略 `@@`;后续使用 `@@` 或 `@@ text` | 带可选上下文锚点的有序 hunk;拒绝空 hunk。 |
55
+ | 紧跟 Update File 的 Move to | 更新操作携带目标路径;没有非空 hunk 的移动被拒绝。 |
56
+ | hunk 后的 End of File | 设置 EOF 标志;忽略后续空白行,更多内容需要新的 hunk 或文件头。 |
57
+ | LF/CRLF、外围 Unicode 空白、缩进的文件头 | Codex 裁剪规则使用 Unicode `White_Space`,包含 U+0085,不包含 BOM(U+FEFF)。更新内部缩进的标记仍作为上下文。保留内容空白。 |
58
+ | 字面量 `<<EOF`、`<<'EOF'` 或 `<<"EOF"` 包装 | 接受但不执行 shell;与参照实现相同,结束行必须以 `EOF` 结尾。 |
59
+ | 重复路径、相对/绝对路径、Unicode 文本 | 原样保留,不解析路径或进行 Unicode 归一化。 |
60
+ | Environment ID 头 | 首个操作前给出专用的不支持错误;其他位置给出普通语法错误。 |
61
+ | Unified diff、shell 命令、非法标记 | 拒绝并给出原始行号;诊断措辞与 Codex 不同。 |
62
+
63
+ 与参照实现相同,任何 hunk 之前的 EOF 没有效果;更新仍然需要非空 hunk。参照实现也接受在较早的 End Patch 和中间空白之后重复出现的最终 End Patch。
64
+
65
+ ### 匹配与文本更新
66
+
67
+ | 输入或规则 | 结果 |
68
+ |---|---|
69
+ | 四级匹配 | 精确匹配、两侧去尾部 `White_Space`、两侧去首尾空白,再将去首尾空白后的 Unicode 标点映射为 ASCII。每一级遍历所有候选后才进入下一级。 |
70
+ | Unicode 标点 | U+2010–2015 和 U+2212 映射为 `-`;U+2018–201B 映射为单引号;U+201C–201F 映射为双引号;U+00A0、U+2002–200A、U+202F、U+205F、U+3000 映射为空格。不做 NFC/NFKC 归一化。 |
71
+ | `@@ text` 与重复内容 | 从游标起找第一个匹配锚点,再从其后一行搜索。旧行取游标后的首个匹配,随后游标推进到匹配末尾。所有搜索都基于原文本。 |
72
+ | EOF hunk | 只从 `lineCount - patternLength` 搜索,即使该位置在游标之前。固定版本的实现不会回退到更早的匹配位置,尽管搜索函数的注释如此描述。 |
73
+ | 旧行为空 | 在原文件末尾插入;若末尾有空源行,则插在该行之前。插入不推进游标;存在锚点时仍须匹配。同位置插入保留补丁顺序。 |
74
+ | 未匹配且末行为空的模式 | 去掉最后一行重试;新行末尾若为空也去掉。重试模式为空时匹配游标位置。 |
75
+ | 文本重建 | 匹配前去掉按 LF 分割所得的一个末尾空元素;按起点稳定排序后反序应用替换。如果末尾没有空元素则补一个,再用 LF 连接。空结果保持空;非空结果以 LF 结尾。 |
76
+ | 上下文行 | 用补丁文本替换整个匹配区间,包括容错匹配的上下文差异。 |
77
+ | 没有 hunk | 仍执行相同的重建,因此会补齐缺少的末尾 LF,也可能消耗一个末尾空行。 |
78
+
79
+ 与 Codex 的刻意差异在于职责和结果报告:行尾风格由 DSH 提供方保留,因此匹配器只接收 LF 规范化文本,不实现 `PreserveLineEndings`。CR 是普通字符:调用方必须先规范化 CRLF,之后还原选定的行尾风格。匹配失败返回结构化数据而非 Codex 带路径的错误,因此调用方负责文件标识和面向用户的诊断。
80
+
81
+ <a id="understand-the-implementation"></a>
82
+ ## 理解实现
83
+
84
+ <details>
85
+ <summary>实现内部机制——点击展开</summary>
86
+
87
+ [解析器](src/parse.ts) 构建语法记录,不依赖 Session 或提供方。[文本更新器](src/apply.ts) 先在原文本中定位每个 hunk,再构建完整结果。[类型](src/types.ts) 携带语法位置与匹配失败;[文件系统参考](../../../docs/subsystems/filesystem.zh.md#patch-syntax-records) 维护公开类型定义。
88
+
89
+ </details>
90
+
91
+ <a id="further-exploration"></a>
92
+ ## 进一步探索
93
+
94
+ - [文件系统包组](../README.zh.md)——提供方、策略和文件工具。
95
+ - [Apply patch 文件编辑](../../../.agents/notes/implemented/feature/2026-09-28-apply-patch-file-editing.zh.md)——决策记录。
96
+
97
+ ## 已知限制与后续工作
98
+
99
+ <a id="known-limitations-and-deferred-work"></a>
100
+
101
+ - 文本更新只实现 `NormalizeToLf`;调用方负责行尾转换。
102
+ - 公开 API 接受完整字符串,不接收流式增量。
103
+
104
+ <a id="dev-note"></a>
105
+ ### 开发备注
106
+
107
+ <details>
108
+ <summary>维护者工作上下文——点击展开</summary>
109
+
110
+ 无。
111
+
112
+ </details>
package/lib/index.js ADDED
@@ -0,0 +1,275 @@
1
+ //#region lib/types/parse.js
2
+ /**
3
+ * Batch parser following OpenAI Codex rust-v0.158.0 parser.rs and streaming_parser.rs.
4
+ * Adapted into a pure TypeScript parser with original-input diagnostics and no environment routing.
5
+ * Copyright 2025 OpenAI. Apache-2.0 portions and modification notice: ../LICENSE.
6
+ */
7
+ const BEGIN = "*** Begin Patch";
8
+ const END = "*** End Patch";
9
+ const EOF = "*** End of File";
10
+ const MOVE = "*** Move to: ";
11
+ const ADD = "*** Add File: ";
12
+ const DELETE = "*** Delete File: ";
13
+ const UPDATE = "*** Update File: ";
14
+ /** Rust uses Unicode White_Space (all in the BMP); JavaScript trim also removes BOM. */
15
+ const WHITE_SPACE$1 = /\p{White_Space}/u;
16
+ /** Syntax failure with a one-based line in the original input, including any wrapper. */
17
+ var PatchParseError = class extends Error {
18
+ lineNumber;
19
+ reason;
20
+ constructor(lineNumber, reason) {
21
+ super(`Invalid patch at line ${lineNumber}: ${reason}`);
22
+ this.lineNumber = lineNumber;
23
+ this.reason = reason;
24
+ this.name = "PatchParseError";
25
+ }
26
+ };
27
+ /** Scan the ends to avoid regex backtracking over long interior whitespace. */
28
+ function trim(value) {
29
+ let start = 0;
30
+ while (start < value.length && WHITE_SPACE$1.test(value.charAt(start))) start++;
31
+ return trimEnd(value.slice(start));
32
+ }
33
+ function trimEnd(value) {
34
+ let end = value.length;
35
+ while (end > 0 && WHITE_SPACE$1.test(value.charAt(end - 1))) end--;
36
+ return value.slice(0, end);
37
+ }
38
+ function patchLines(patch) {
39
+ const offset = patch.slice(0, patch.length - patch.replace(/^\p{White_Space}+/u, "").length).split("\n").length - 1;
40
+ let lines = trim(patch).split(/\r?\n/u).map((text, index) => ({
41
+ text,
42
+ number: offset + index + 1
43
+ }));
44
+ const first = lines[0];
45
+ const last = lines.at(-1);
46
+ if ([
47
+ "<<EOF",
48
+ "<<'EOF'",
49
+ "<<\"EOF\""
50
+ ].includes(first.text) && last.text.endsWith("EOF") && lines.length >= 4) lines = lines.slice(1, -1);
51
+ const start = lines[0];
52
+ const end = lines.at(-1);
53
+ if (trim(start.text) !== BEGIN) throw new PatchParseError(start.number, `Expected '${BEGIN}'`);
54
+ if (trim(end.text) !== END) throw new PatchParseError(end.number, `Expected '${END}'`);
55
+ return lines.map((line, index) => index < lines.length - 1 ? {
56
+ ...line,
57
+ text: line.text.replace(/\r$/u, "")
58
+ } : line);
59
+ }
60
+ function emptyHunk(hunk) {
61
+ return hunk.oldLines.length === 0 && hunk.newLines.length === 0;
62
+ }
63
+ function ensureUpdate(operation, lineNumber) {
64
+ if (operation?.kind !== "update") return;
65
+ const last = operation.hunks.at(-1);
66
+ if (last === void 0) throw new PatchParseError(operation.lineNumber, `Update for '${operation.path}' requires at least one hunk`);
67
+ if (emptyHunk(last)) throw new PatchParseError(lineNumber, "Update hunk must contain context, added, or removed lines");
68
+ }
69
+ function newHunk(lineNumber) {
70
+ return {
71
+ lineNumber,
72
+ oldLines: [],
73
+ newLines: [],
74
+ isEndOfFile: false
75
+ };
76
+ }
77
+ function readUpdate(operation, line) {
78
+ const text = trimEnd(line.text);
79
+ const previous = operation.hunks.at(-1);
80
+ const marker = text === "@@" || text.startsWith("@@ ");
81
+ if (previous?.isEndOfFile) {
82
+ if (text === "") return;
83
+ if (!marker) throw new PatchParseError(line.number, "Expected '@@' after '*** End of File'");
84
+ }
85
+ if (previous === void 0 && operation.moveTo === void 0 && text.startsWith(MOVE)) {
86
+ operation.moveTo = text.slice(13);
87
+ return;
88
+ }
89
+ if (marker) {
90
+ if (previous !== void 0 && emptyHunk(previous)) throw new PatchParseError(line.number, "Update hunk must contain context, added, or removed lines");
91
+ const hunk = newHunk(line.number);
92
+ if (text.startsWith("@@ ")) hunk.context = text.slice(3);
93
+ operation.hunks.push(hunk);
94
+ return;
95
+ }
96
+ if (text === EOF) {
97
+ if (previous !== void 0) {
98
+ if (emptyHunk(previous)) throw new PatchParseError(line.number, "Update hunk must contain context, added, or removed lines");
99
+ previous.isEndOfFile = true;
100
+ }
101
+ return;
102
+ }
103
+ const prefix = line.text[0];
104
+ if (line.text !== "" && prefix !== " " && prefix !== "+" && prefix !== "-") throw new PatchParseError(line.number, "Expected a context (' '), added ('+'), removed ('-') line, or '@@'");
105
+ const hunk = previous ?? newHunk(line.number);
106
+ if (previous === void 0) operation.hunks.push(hunk);
107
+ const content = line.text.slice(1);
108
+ if (prefix === "+") hunk.newLines.push(content);
109
+ else if (prefix === "-") hunk.oldLines.push(content);
110
+ else {
111
+ hunk.oldLines.push(content);
112
+ hunk.newLines.push(content);
113
+ }
114
+ }
115
+ /**
116
+ * Parse a complete Codex-style patch without reading or modifying files.
117
+ * @param patch - patch text, optionally wrapped in a literal EOF heredoc.
118
+ * @returns ordered, JSON-serializable operations; an empty patch returns an empty array.
119
+ * @throws PatchParseError for malformed syntax or an unsupported Environment ID header.
120
+ */
121
+ function parsePatch(patch) {
122
+ const lines = patchLines(patch);
123
+ const operations = [];
124
+ let ended = false;
125
+ for (const line of lines.slice(1)) {
126
+ const operation = operations.at(-1);
127
+ const header = operation?.kind === "update" && line !== lines.at(-1) ? trimEnd(line.text) : trim(line.text);
128
+ if (ended && line !== lines.at(-1)) {
129
+ if (trim(line.text) !== "") throw new PatchParseError(line.number, `Unexpected content after '${END}'`);
130
+ continue;
131
+ }
132
+ if (header === END) {
133
+ ensureUpdate(operation, line.number);
134
+ ended = true;
135
+ } else if (header.startsWith(ADD)) {
136
+ ensureUpdate(operation, line.number);
137
+ operations.push({
138
+ kind: "add",
139
+ path: header.slice(14),
140
+ lineNumber: line.number,
141
+ content: ""
142
+ });
143
+ } else if (header.startsWith(DELETE)) {
144
+ ensureUpdate(operation, line.number);
145
+ operations.push({
146
+ kind: "delete",
147
+ path: header.slice(17),
148
+ lineNumber: line.number
149
+ });
150
+ } else if (header.startsWith(UPDATE)) {
151
+ ensureUpdate(operation, line.number);
152
+ operations.push({
153
+ kind: "update",
154
+ path: header.slice(17),
155
+ lineNumber: line.number,
156
+ hunks: []
157
+ });
158
+ } else if (operations.length === 0 && header.startsWith("*** Environment ID:")) throw new PatchParseError(line.number, "Environment ID headers are unsupported; the caller selects the filesystem provider");
159
+ else if (operation?.kind === "add" && line.text.startsWith("+")) operation.content += `${line.text.slice(1)}\n`;
160
+ else if (operation?.kind === "update") readUpdate(operation, line);
161
+ else throw new PatchParseError(line.number, "Expected an Add File, Delete File, or Update File header; added content must start with +");
162
+ }
163
+ return operations;
164
+ }
165
+ //#endregion
166
+ //#region lib/types/apply.js
167
+ /**
168
+ * Pure text updates following OpenAI Codex rust-v0.158.0 NormalizeToLf behavior.
169
+ * Adapted from seek_sequence.rs and file_update.rs with structured match failures.
170
+ * Copyright 2025 OpenAI. Apache-2.0 portions and modification notice: ../LICENSE.
171
+ */
172
+ const WHITE_SPACE = /\p{White_Space}/u;
173
+ function trimWhitespace(value, leading) {
174
+ let start = 0;
175
+ let end = value.length;
176
+ if (leading) while (start < end && WHITE_SPACE.test(value.charAt(start))) start++;
177
+ while (end > start && WHITE_SPACE.test(value.charAt(end - 1))) end--;
178
+ return value.slice(start, end);
179
+ }
180
+ function normalizePunctuation(value) {
181
+ return trimWhitespace(value, true).replace(/[\u2010-\u2015\u2212]/gu, "-").replace(/[\u2018-\u201B]/gu, "'").replace(/[\u201C-\u201F]/gu, "\"").replace(/[\u00A0\u2002-\u200A\u202F\u205F\u3000]/gu, " ");
182
+ }
183
+ const MATCH_PASSES = [
184
+ (value) => value,
185
+ (value) => trimWhitespace(value, false),
186
+ (value) => trimWhitespace(value, true),
187
+ normalizePunctuation
188
+ ];
189
+ /** NormalizeToLf checks only the tail candidate for EOF, even before the cursor. */
190
+ function seekSequence(lines, pattern, start, eof) {
191
+ if (pattern.length === 0) return start;
192
+ if (pattern.length > lines.length) return void 0;
193
+ const last = lines.length - pattern.length;
194
+ const first = eof ? last : start;
195
+ for (const normalize of MATCH_PASSES) {
196
+ const expected = pattern.map(normalize);
197
+ for (let index = first; index <= last; index++) if (expected.every((line, offset) => normalize(lines[index + offset]) === line)) return index;
198
+ }
199
+ }
200
+ /**
201
+ * Apply parsed hunks to LF-normalized text without I/O or input mutation.
202
+ * All searches use the original text; a failed search returns no partial text.
203
+ * Successful nonempty output ends in LF, including initially unterminated text.
204
+ * @param before - text normalized by the caller; CR characters receive no line-ending handling.
205
+ * @param hunks - ordered hunks from parsePatch; EOF searches follow NormalizeToLf tail semantics.
206
+ * @returns new text or the first unmatched hunk, without throwing for a match failure.
207
+ */
208
+ function applyHunks(before, hunks) {
209
+ const original = before.split("\n");
210
+ if (original.at(-1) === "") original.pop();
211
+ const replacements = [];
212
+ let lineIndex = 0;
213
+ for (const hunk of hunks) {
214
+ if (hunk.context !== void 0) {
215
+ const pattern = [hunk.context];
216
+ const found = seekSequence(original, pattern, lineIndex, false);
217
+ if (found === void 0) return {
218
+ ok: false,
219
+ failure: {
220
+ hunkLineNumber: hunk.lineNumber,
221
+ stage: "context",
222
+ searchStart: lineIndex,
223
+ pattern
224
+ }
225
+ };
226
+ lineIndex = found + 1;
227
+ }
228
+ if (hunk.oldLines.length === 0) {
229
+ const start = original.at(-1) === "" ? original.length - 1 : original.length;
230
+ replacements.push({
231
+ start,
232
+ oldLength: 0,
233
+ newLines: hunk.newLines
234
+ });
235
+ continue;
236
+ }
237
+ let pattern = hunk.oldLines;
238
+ let newLines = hunk.newLines;
239
+ let found = seekSequence(original, pattern, lineIndex, hunk.isEndOfFile);
240
+ if (found === void 0 && pattern.at(-1) === "") {
241
+ pattern = pattern.slice(0, -1);
242
+ if (newLines.at(-1) === "") newLines = newLines.slice(0, -1);
243
+ found = seekSequence(original, pattern, lineIndex, hunk.isEndOfFile);
244
+ }
245
+ if (found === void 0) return {
246
+ ok: false,
247
+ failure: {
248
+ hunkLineNumber: hunk.lineNumber,
249
+ stage: "oldLines",
250
+ searchStart: lineIndex,
251
+ pattern: [...hunk.oldLines]
252
+ }
253
+ };
254
+ replacements.push({
255
+ start: found,
256
+ oldLength: pattern.length,
257
+ newLines
258
+ });
259
+ lineIndex = found + pattern.length;
260
+ }
261
+ replacements.sort((left, right) => left.start - right.start);
262
+ let lines = original;
263
+ for (const replacement of replacements.reverse()) lines = [
264
+ ...lines.slice(0, replacement.start),
265
+ ...replacement.newLines,
266
+ ...lines.slice(replacement.start + replacement.oldLength)
267
+ ];
268
+ if (lines.at(-1) !== "") lines.push("");
269
+ return {
270
+ ok: true,
271
+ after: lines.join("\n")
272
+ };
273
+ }
274
+ //#endregion
275
+ export { PatchParseError, applyHunks, parsePatch };
@@ -0,0 +1,16 @@
1
+ /**
2
+ * Pure text updates following OpenAI Codex rust-v0.158.0 NormalizeToLf behavior.
3
+ * Adapted from seek_sequence.rs and file_update.rs with structured match failures.
4
+ * Copyright 2025 OpenAI. Apache-2.0 portions and modification notice: ../LICENSE.
5
+ */
6
+ import type { ApplyHunksResult, PatchHunk } from './types.ts';
7
+ /**
8
+ * Apply parsed hunks to LF-normalized text without I/O or input mutation.
9
+ * All searches use the original text; a failed search returns no partial text.
10
+ * Successful nonempty output ends in LF, including initially unterminated text.
11
+ * @param before - text normalized by the caller; CR characters receive no line-ending handling.
12
+ * @param hunks - ordered hunks from parsePatch; EOF searches follow NormalizeToLf tail semantics.
13
+ * @returns new text or the first unmatched hunk, without throwing for a match failure.
14
+ */
15
+ export declare function applyHunks(before: string, hunks: readonly PatchHunk[]): ApplyHunksResult;
16
+ //# sourceMappingURL=apply.d.ts.map
@@ -0,0 +1,5 @@
1
+ /** Pure patch parsing and text application, independent of filesystem providers. */
2
+ export { parsePatch, PatchParseError } from './parse.ts';
3
+ export { applyHunks } from './apply.ts';
4
+ export type { ApplyHunksResult, HunkApplyFailure, PatchHunk, PatchOperation } from './types.ts';
5
+ //# sourceMappingURL=index.d.ts.map
@@ -0,0 +1,20 @@
1
+ /**
2
+ * Batch parser following OpenAI Codex rust-v0.158.0 parser.rs and streaming_parser.rs.
3
+ * Adapted into a pure TypeScript parser with original-input diagnostics and no environment routing.
4
+ * Copyright 2025 OpenAI. Apache-2.0 portions and modification notice: ../LICENSE.
5
+ */
6
+ import type { PatchOperation } from './types.ts';
7
+ /** Syntax failure with a one-based line in the original input, including any wrapper. */
8
+ export declare class PatchParseError extends Error {
9
+ readonly lineNumber: number;
10
+ readonly reason: string;
11
+ constructor(lineNumber: number, reason: string);
12
+ }
13
+ /**
14
+ * Parse a complete Codex-style patch without reading or modifying files.
15
+ * @param patch - patch text, optionally wrapped in a literal EOF heredoc.
16
+ * @returns ordered, JSON-serializable operations; an empty patch returns an empty array.
17
+ * @throws PatchParseError for malformed syntax or an unsupported Environment ID header.
18
+ */
19
+ export declare function parsePatch(patch: string): PatchOperation[];
20
+ //# sourceMappingURL=parse.d.ts.map
@@ -0,0 +1,52 @@
1
+ /** Serializable patch syntax and text-application results; paths remain unresolved. */
2
+ /** One contiguous update. */
3
+ export interface PatchHunk {
4
+ /** One-based input line containing the marker or first implicit hunk line. */
5
+ lineNumber: number;
6
+ /** Text following `@@ `, used as a search anchor by the text matcher. */
7
+ context?: string;
8
+ /** Context and removed lines, without patch prefixes or line endings. */
9
+ oldLines: string[];
10
+ /** Context and added lines, without patch prefixes or line endings. */
11
+ newLines: string[];
12
+ /** Whether `*** End of File` follows this hunk. */
13
+ isEndOfFile: boolean;
14
+ }
15
+ /** Ordered file operation; a move is an update with a destination path. */
16
+ export type PatchOperation = {
17
+ kind: 'add';
18
+ path: string;
19
+ lineNumber: number;
20
+ /** Added lines joined with LF, including a final LF for every added line. */
21
+ content: string;
22
+ } | {
23
+ kind: 'delete';
24
+ path: string;
25
+ lineNumber: number;
26
+ } | {
27
+ kind: 'update';
28
+ path: string;
29
+ lineNumber: number;
30
+ moveTo?: string;
31
+ hunks: PatchHunk[];
32
+ };
33
+ /** The first hunk whose anchor or old lines could not be located in the original text. */
34
+ export interface HunkApplyFailure {
35
+ /** One-based line of the failing hunk in the patch input. */
36
+ hunkLineNumber: number;
37
+ /** The failed anchor or old-content search. */
38
+ stage: 'context' | 'oldLines';
39
+ /** Zero-based sequential search cursor; EOF searches override it with the tail candidate. */
40
+ searchStart: number;
41
+ /** Original anchor or old lines, including any trailing empty line retried without its sentinel. */
42
+ pattern: string[];
43
+ }
44
+ /** Complete new text or a match failure; failures contain no partially updated text. */
45
+ export type ApplyHunksResult = {
46
+ ok: true;
47
+ after: string;
48
+ } | {
49
+ ok: false;
50
+ failure: HunkApplyFailure;
51
+ };
52
+ //# sourceMappingURL=types.d.ts.map
package/package.json CHANGED
@@ -1,6 +1,35 @@
1
1
  {
2
2
  "name": "@deepseek-ai/dsh-fs-patch",
3
- "version": "0.0.0-stage",
4
- "stub": true,
5
- "description": "Temporary package placeholder for staged publishing"
3
+ "description": "Pure Codex-style patch parser and LF text updater with structured match failures",
4
+ "version": "0.2.2-alpha.3",
5
+ "publishConfig": {
6
+ "access": "public"
7
+ },
8
+ "repository": {
9
+ "type": "git",
10
+ "url": "git+https://github.com/deepseek-ai/deepseek-harness.git",
11
+ "directory": "packages/fs/fs-patch"
12
+ },
13
+ "type": "module",
14
+ "main": "lib/index.js",
15
+ "types": "lib/types/index.d.ts",
16
+ "exports": {
17
+ ".": {
18
+ "types": "./lib/types/index.d.ts",
19
+ "default": "./lib/index.js"
20
+ },
21
+ "./src/*": "./src/*",
22
+ "./package.json": "./package.json"
23
+ },
24
+ "files": [
25
+ "lib/index.js",
26
+ "lib/types/**/*.d.ts"
27
+ ],
28
+ "license": "MIT",
29
+ "peerDependencies": {
30
+ "@deepseek-ai/cordis": "~4.0.6-alpha.2"
31
+ },
32
+ "devDependencies": {
33
+ "@deepseek-ai/cordis": "~4.0.6-alpha.2"
34
+ }
6
35
  }