blockpatch 1.1.0 → 1.1.1
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 +1 -1
- package/dist/cli.js +71 -3
- package/docs/commands.md +4 -0
- package/docs/spec.md +11 -1
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
# blockpatch
|
|
2
2
|
|
|
3
3
|
[](https://github.com/cheese-melted/blockpatch/actions/workflows/ci.yml)
|
|
4
|
-
[](https://www.npmjs.com/package/blockpatch)
|
|
4
|
+
[](https://www.npmjs.com/package/blockpatch)
|
|
5
5
|
|
|
6
6
|
Cut/paste for agents: hash-verified block moves that read like unified diffs.
|
|
7
7
|
|
package/dist/cli.js
CHANGED
|
@@ -1953,7 +1953,8 @@ async function moveBlock(args, options = {}) {
|
|
|
1953
1953
|
selection
|
|
1954
1954
|
})
|
|
1955
1955
|
],
|
|
1956
|
-
patch
|
|
1956
|
+
patch,
|
|
1957
|
+
warnings: insertionBoundaryWarnings(normalized.dst, normalized.targetBefore, selection.payload, normalized.targetAfter)
|
|
1957
1958
|
};
|
|
1958
1959
|
}
|
|
1959
1960
|
function validateMoveArgs(value) {
|
|
@@ -2218,9 +2219,40 @@ async function insertPayload(args, validated, cwd, dryRun, options) {
|
|
|
2218
2219
|
insert_index: target.insertIndex
|
|
2219
2220
|
}
|
|
2220
2221
|
],
|
|
2221
|
-
patch: renderedPatch
|
|
2222
|
+
patch: renderedPatch,
|
|
2223
|
+
warnings: insertionBoundaryWarnings(args.dst, args.targetBefore, args.payload, args.targetAfter)
|
|
2222
2224
|
};
|
|
2223
2225
|
}
|
|
2226
|
+
function insertionBoundaryWarnings(path, targetBefore, payload, targetAfter) {
|
|
2227
|
+
const warnings = [];
|
|
2228
|
+
if (targetBefore.length > 0 && payload.length > 0 && !endsWithLf(targetBefore) && !startsWithLf(payload)) {
|
|
2229
|
+
warnings.push({
|
|
2230
|
+
code: "adjacent_bytes",
|
|
2231
|
+
message: "Insertion will place payload immediately after target_before with no newline or separator inserted by blockpatch",
|
|
2232
|
+
path,
|
|
2233
|
+
phase: "target",
|
|
2234
|
+
boundary: "target_before+payload",
|
|
2235
|
+
suggested_action: "include the intended newline in target_before or at the start of payload"
|
|
2236
|
+
});
|
|
2237
|
+
}
|
|
2238
|
+
if (payload.length > 0 && targetAfter.length > 0 && !endsWithLf(payload) && !startsWithLf(targetAfter)) {
|
|
2239
|
+
warnings.push({
|
|
2240
|
+
code: "adjacent_bytes",
|
|
2241
|
+
message: "Insertion will place target_after immediately after payload with no newline or separator inserted by blockpatch",
|
|
2242
|
+
path,
|
|
2243
|
+
phase: "target",
|
|
2244
|
+
boundary: "payload+target_after",
|
|
2245
|
+
suggested_action: "include the intended newline at the end of payload or at the start of target_after"
|
|
2246
|
+
});
|
|
2247
|
+
}
|
|
2248
|
+
return warnings.length > 0 ? warnings : undefined;
|
|
2249
|
+
}
|
|
2250
|
+
function startsWithLf(bytes) {
|
|
2251
|
+
return bytes[0] === 10;
|
|
2252
|
+
}
|
|
2253
|
+
function endsWithLf(bytes) {
|
|
2254
|
+
return bytes[bytes.length - 1] === 10;
|
|
2255
|
+
}
|
|
2224
2256
|
function verifyExpectedPayloadHash(args, payloadSha256) {
|
|
2225
2257
|
if (args.expected_payload_sha256 !== undefined && args.expected_payload_sha256 !== payloadSha256) {
|
|
2226
2258
|
fail("hash_mismatch", "expected_payload_sha256 does not match selected source payload", {
|
|
@@ -2671,12 +2703,25 @@ async function loadMoveArgs(options) {
|
|
|
2671
2703
|
}
|
|
2672
2704
|
const jsonBytes = options.moveJsonPath === "-" ? await readStdin() : await readFileChecked(resolve3(options.moveJsonPath), "move JSON file");
|
|
2673
2705
|
try {
|
|
2674
|
-
|
|
2706
|
+
const parsed = JSON.parse(jsonBytes.toString("utf8"));
|
|
2707
|
+
if (isEmptyObject(parsed)) {
|
|
2708
|
+
throw new BlockPatchError("invalid_move_args", "Move JSON cannot be empty; provide src plus source selectors or payload and target anchors", {
|
|
2709
|
+
field: "src",
|
|
2710
|
+
suggested_action: "Use fields like src, src_start, src_end, dst, target_before, target_after, payload, or run blockpatch help"
|
|
2711
|
+
});
|
|
2712
|
+
}
|
|
2713
|
+
return parsed;
|
|
2675
2714
|
} catch (error) {
|
|
2715
|
+
if (error instanceof BlockPatchError) {
|
|
2716
|
+
throw error;
|
|
2717
|
+
}
|
|
2676
2718
|
const message = error instanceof Error ? error.message : String(error);
|
|
2677
2719
|
throw new BlockPatchError("invalid_json", `Invalid move JSON: ${message}`);
|
|
2678
2720
|
}
|
|
2679
2721
|
}
|
|
2722
|
+
function isEmptyObject(value) {
|
|
2723
|
+
return typeof value === "object" && value !== null && !Array.isArray(value) && Object.keys(value).length === 0;
|
|
2724
|
+
}
|
|
2680
2725
|
function parseArgs(argv) {
|
|
2681
2726
|
const args = [...argv];
|
|
2682
2727
|
const outputFlags = takeLeadingOutputFlags(args);
|
|
@@ -2954,6 +2999,29 @@ Usage:
|
|
|
2954
2999
|
blockpatch move --src /dev/null --dst <path> --payload <text> --target-before <text>
|
|
2955
3000
|
blockpatch move --src <path> --src-start <text> --src-end <text> --dst /dev/null
|
|
2956
3001
|
blockpatch version
|
|
3002
|
+
|
|
3003
|
+
Move JSON fields:
|
|
3004
|
+
src, dst, src_start, src_end, payload, target_before, target_after,
|
|
3005
|
+
expected_payload_sha256, mode, dry_run
|
|
3006
|
+
|
|
3007
|
+
Move selection:
|
|
3008
|
+
src_start/src_end are byte-exact, newline-sensitive delimiters. The selected
|
|
3009
|
+
payload starts at src_start and ends after the first following src_end.
|
|
3010
|
+
|
|
3011
|
+
Target anchors:
|
|
3012
|
+
target_before is the exact context immediately before the insertion point,
|
|
3013
|
+
so insertion occurs after it. target_after is the exact context immediately
|
|
3014
|
+
after the insertion point, so insertion occurs before it. With both anchors,
|
|
3015
|
+
insertion occurs between them.
|
|
3016
|
+
|
|
3017
|
+
Newlines:
|
|
3018
|
+
blockpatch never adds separators. Include every intended newline in
|
|
3019
|
+
src_start/src_end, payload, target_before, or target_after.
|
|
3020
|
+
|
|
3021
|
+
Examples:
|
|
3022
|
+
blockpatch move --json - --diff <<'JSON'
|
|
3023
|
+
{"src":"src/a.ts","src_start":"function x() {\\n","src_end":"}\\n","dst":"src/b.ts","target_before":"class B {\\n"}
|
|
3024
|
+
JSON
|
|
2957
3025
|
`);
|
|
2958
3026
|
}
|
|
2959
3027
|
main(process.argv.slice(2)).then((code) => {
|
package/docs/commands.md
CHANGED
|
@@ -73,6 +73,10 @@ blockpatch move --src src/foo.ts --src-start $'\nfunction removeMe() {' --src-en
|
|
|
73
73
|
|
|
74
74
|
Each flag sets the move JSON field of the same name and cannot be combined with `--json`. `mode` has no flag form, so whole-file `create_file`/`remove_file` requests are JSON-only. JSON avoids shell quoting problems, so it is usually the more reliable form for agents.
|
|
75
75
|
|
|
76
|
+
`--target-before` means “this exact context is before the insertion point,” so the payload is inserted after that context. `--target-after` means “this exact context is after the insertion point,” so the payload is inserted before that context. These names describe the anchor's relationship to the insertion point; they do not mean “insert before this text” or “insert after this text.”
|
|
77
|
+
|
|
78
|
+
`--src-start`, `--src-end`, `--target-before`, and `--target-after` are byte-exact and newline-sensitive. `src_start`/`src_end` select bytes from the beginning of `src_start` through the end of the first following `src_end`. `blockpatch` does not add separators, so include the newlines you want moved or inserted. If `target_before` does not end with `\n` and the payload does not start with `\n`, they will be joined directly.
|
|
79
|
+
|
|
76
80
|
## Output
|
|
77
81
|
|
|
78
82
|
```sh
|
package/docs/spec.md
CHANGED
|
@@ -212,6 +212,8 @@ Request shapes:
|
|
|
212
212
|
- `payload` is only valid when `src` is `/dev/null`; it must be non-empty for in-file insertion.
|
|
213
213
|
- `expected_payload_sha256` is optional.
|
|
214
214
|
|
|
215
|
+
`src_start`, `src_end`, `target_before`, and `target_after` are byte-exact and newline-sensitive. Source selection starts at `src_start` and ends after the first following `src_end`; include any leading or trailing newline you want in the selected payload. `target_before` is the exact context before the insertion point, so insertion occurs after it. `target_after` is the exact context after the insertion point, so insertion occurs before it. `blockpatch` never inserts extra newlines or spacing between anchors and payload.
|
|
216
|
+
|
|
215
217
|
Insertion:
|
|
216
218
|
|
|
217
219
|
```json
|
|
@@ -272,6 +274,14 @@ type ApplyResult = {
|
|
|
272
274
|
status: "applied" | "noop" | "already_applied"
|
|
273
275
|
strip_components?: number
|
|
274
276
|
patch?: string
|
|
277
|
+
warnings?: Array<{
|
|
278
|
+
code: "adjacent_bytes"
|
|
279
|
+
message: string
|
|
280
|
+
path: string
|
|
281
|
+
phase: "target"
|
|
282
|
+
boundary: "target_before+payload" | "payload+target_after"
|
|
283
|
+
suggested_action: string
|
|
284
|
+
}>
|
|
275
285
|
moves: Array<{
|
|
276
286
|
id: string
|
|
277
287
|
src: string
|
|
@@ -289,7 +299,7 @@ type ApplyResult = {
|
|
|
289
299
|
|
|
290
300
|
`status` is `applied` for a normal computed move, `noop` for a computed move whose output bytes are identical, and `already_applied` when the command can prove the requested final state is already present. `strip_components` is present for `check` and `apply` JSON success output and reports the effective `-p` path-stripping count; it defaults to `1`.
|
|
291
301
|
|
|
292
|
-
`patch` is present when `move --diff --json-output` is used. In `already_applied` relocation results, `source_range` is `null` because the source block is no longer present. For target-only insertions, `source_range` is `null`. For source-only deletions, `target_range` and `insert_index` are `null`. For path creation/removal, `src` or `dst` is the string `/dev/null`.
|
|
302
|
+
`patch` is present when `move --diff --json-output` is used. `warnings` is present when a move validates but may surprise a caller, such as an insertion boundary where neither side contains a newline and the bytes will be joined directly. In `already_applied` relocation results, `source_range` is `null` because the source block is no longer present. For target-only insertions, `source_range` is `null`. For source-only deletions, `target_range` and `insert_index` are `null`. For path creation/removal, `src` or `dst` is the string `/dev/null`.
|
|
293
303
|
|
|
294
304
|
With `--json-output`, errors print:
|
|
295
305
|
|