@osovv/vv-opencode 1.4.5 → 1.6.0
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/CHANGELOG.md +23 -0
- package/README.md +47 -16
- package/dist/commands/patch-provider.d.ts +5 -5
- package/dist/commands/patch-provider.js +10 -6
- package/dist/commands/patch-provider.js.map +1 -1
- package/dist/lib/agent-tool-catalog.d.ts +144 -0
- package/dist/lib/agent-tool-catalog.js +2551 -0
- package/dist/lib/agent-tool-catalog.js.map +1 -0
- package/dist/lib/agent-tool-contract.d.ts +200 -0
- package/dist/lib/agent-tool-contract.js +407 -0
- package/dist/lib/agent-tool-contract.js.map +1 -0
- package/dist/lib/opencode/agent-registrations.d.ts +33 -0
- package/dist/lib/opencode/agent-registrations.js +469 -0
- package/dist/lib/opencode/agent-registrations.js.map +1 -0
- package/dist/lib/opencode/inspection.d.ts +84 -0
- package/dist/lib/opencode/inspection.js +367 -0
- package/dist/lib/opencode/inspection.js.map +1 -0
- package/dist/lib/opencode/model-overrides.d.ts +19 -0
- package/dist/lib/opencode/model-overrides.js +221 -0
- package/dist/lib/opencode/model-overrides.js.map +1 -0
- package/dist/lib/opencode/paths.d.ts +21 -0
- package/dist/lib/opencode/paths.js +62 -0
- package/dist/lib/opencode/paths.js.map +1 -0
- package/dist/lib/opencode/plugin-registration.d.ts +17 -0
- package/dist/lib/opencode/plugin-registration.js +210 -0
- package/dist/lib/opencode/plugin-registration.js.map +1 -0
- package/dist/lib/opencode/shared-utils.d.ts +30 -0
- package/dist/lib/opencode/shared-utils.js +183 -0
- package/dist/lib/opencode/shared-utils.js.map +1 -0
- package/dist/lib/opencode/vvoc-config-io.d.ts +9 -0
- package/dist/lib/opencode/vvoc-config-io.js +58 -0
- package/dist/lib/opencode/vvoc-config-io.js.map +1 -0
- package/dist/lib/opencode.d.ts +15 -174
- package/dist/lib/opencode.js +19 -1387
- package/dist/lib/opencode.js.map +1 -1
- package/dist/lib/spec-lint/cross-file-rules.d.ts +2 -0
- package/dist/lib/spec-lint/cross-file-rules.js +33 -0
- package/dist/lib/spec-lint/cross-file-rules.js.map +1 -0
- package/dist/lib/spec-lint/delegated-extraction.d.ts +8 -0
- package/dist/lib/spec-lint/delegated-extraction.js +112 -0
- package/dist/lib/spec-lint/delegated-extraction.js.map +1 -0
- package/dist/lib/spec-lint/lint.d.ts +7 -0
- package/dist/lib/spec-lint/lint.js +92 -0
- package/dist/lib/spec-lint/lint.js.map +1 -0
- package/dist/lib/spec-lint/parser.d.ts +101 -0
- package/dist/lib/spec-lint/parser.js +482 -0
- package/dist/lib/spec-lint/parser.js.map +1 -0
- package/dist/lib/spec-lint/plan-rules.d.ts +38 -0
- package/dist/lib/spec-lint/plan-rules.js +745 -0
- package/dist/lib/spec-lint/plan-rules.js.map +1 -0
- package/dist/lib/spec-lint/spec-rules.d.ts +8 -0
- package/dist/lib/spec-lint/spec-rules.js +188 -0
- package/dist/lib/spec-lint/spec-rules.js.map +1 -0
- package/dist/lib/spec-lint.d.ts +4 -63
- package/dist/lib/spec-lint.js +13 -1505
- package/dist/lib/spec-lint.js.map +1 -1
- package/dist/lib/vvoc-preset-registry.d.ts +7 -7
- package/dist/lib/vvoc-preset-registry.js +8 -8
- package/dist/lib/vvoc-preset-registry.js.map +1 -1
- package/dist/lib/workflow-contract.d.ts +12 -0
- package/dist/lib/workflow-contract.js +71 -18
- package/dist/lib/workflow-contract.js.map +1 -1
- package/dist/plugins/hashline-edit/index.js +102 -97
- package/dist/plugins/hashline-edit/index.js.map +1 -1
- package/dist/plugins/hashline-edit/normalize-edits.d.ts +2 -8
- package/dist/plugins/hashline-edit/normalize-edits.js +32 -17
- package/dist/plugins/hashline-edit/normalize-edits.js.map +1 -1
- package/dist/plugins/hashline-edit/schemas.d.ts +246 -0
- package/dist/plugins/hashline-edit/schemas.js +659 -0
- package/dist/plugins/hashline-edit/schemas.js.map +1 -0
- package/dist/plugins/hashline-edit/str-replace-editor.d.ts +6 -17
- package/dist/plugins/hashline-edit/str-replace-editor.js +19 -21
- package/dist/plugins/hashline-edit/str-replace-editor.js.map +1 -1
- package/dist/plugins/hashline-edit/tool-description.d.ts +1 -0
- package/dist/plugins/hashline-edit/tool-description.js +20 -4
- package/dist/plugins/hashline-edit/tool-description.js.map +1 -1
- package/dist/plugins/web-tools/fetch-service.d.ts +8 -5
- package/dist/plugins/web-tools/fetch-service.js +30 -45
- package/dist/plugins/web-tools/fetch-service.js.map +1 -1
- package/dist/plugins/web-tools/index.js +23 -5
- package/dist/plugins/web-tools/index.js.map +1 -1
- package/dist/plugins/web-tools/schemas.d.ts +469 -0
- package/dist/plugins/web-tools/schemas.js +523 -0
- package/dist/plugins/web-tools/schemas.js.map +1 -0
- package/dist/plugins/web-tools/search-service.d.ts +6 -3
- package/dist/plugins/web-tools/search-service.js +24 -29
- package/dist/plugins/web-tools/search-service.js.map +1 -1
- package/dist/plugins/workflow/authority.d.ts +6 -0
- package/dist/plugins/workflow/authority.js +45 -10
- package/dist/plugins/workflow/authority.js.map +1 -1
- package/dist/plugins/workflow/authorization.d.ts +30 -0
- package/dist/plugins/workflow/authorization.js +147 -0
- package/dist/plugins/workflow/authorization.js.map +1 -0
- package/dist/plugins/workflow/checkpoints.d.ts +41 -2
- package/dist/plugins/workflow/checkpoints.js +176 -47
- package/dist/plugins/workflow/checkpoints.js.map +1 -1
- package/dist/plugins/workflow/delegated.d.ts +15 -1
- package/dist/plugins/workflow/delegated.js +65 -35
- package/dist/plugins/workflow/delegated.js.map +1 -1
- package/dist/plugins/workflow/execution.d.ts +15 -30
- package/dist/plugins/workflow/execution.js +133 -33
- package/dist/plugins/workflow/execution.js.map +1 -1
- package/dist/plugins/workflow/index.js +197 -435
- package/dist/plugins/workflow/index.js.map +1 -1
- package/dist/plugins/workflow/input-validation.d.ts +48 -0
- package/dist/plugins/workflow/input-validation.js +536 -0
- package/dist/plugins/workflow/input-validation.js.map +1 -0
- package/dist/plugins/workflow/inspection.d.ts +60 -0
- package/dist/plugins/workflow/inspection.js +316 -0
- package/dist/plugins/workflow/inspection.js.map +1 -0
- package/dist/plugins/workflow/protocol.d.ts +2 -0
- package/dist/plugins/workflow/protocol.js +26 -4
- package/dist/plugins/workflow/protocol.js.map +1 -1
- package/dist/plugins/workflow/recovery.d.ts +18 -0
- package/dist/plugins/workflow/recovery.js +217 -0
- package/dist/plugins/workflow/recovery.js.map +1 -0
- package/dist/plugins/workflow/repair.d.ts +1 -1
- package/dist/plugins/workflow/repair.js +9 -10
- package/dist/plugins/workflow/repair.js.map +1 -1
- package/dist/plugins/workflow/results.d.ts +2566 -0
- package/dist/plugins/workflow/results.js +1111 -0
- package/dist/plugins/workflow/results.js.map +1 -0
- package/dist/plugins/workflow/schemas.d.ts +398 -0
- package/dist/plugins/workflow/schemas.js +399 -0
- package/dist/plugins/workflow/schemas.js.map +1 -0
- package/dist/plugins/workflow/state.d.ts +3 -2
- package/dist/plugins/workflow/state.js +15 -8
- package/dist/plugins/workflow/state.js.map +1 -1
- package/dist/plugins/workflow/system-instruction.md +17 -1
- package/dist/plugins/workflow/tooling.d.ts +11 -82
- package/dist/plugins/workflow/tooling.js +478 -461
- package/dist/plugins/workflow/tooling.js.map +1 -1
- package/dist/plugins/workflow/transactions.d.ts +13 -0
- package/dist/plugins/workflow/transactions.js +13 -3
- package/dist/plugins/workflow/transactions.js.map +1 -1
- package/package.json +6 -3
- package/schemas/vvoc/v3.json +1 -1
- package/templates/agents/vv-code-reviewer.md +13 -4
- package/templates/agents/vv-implementer.md +16 -9
- package/templates/agents/vv-spec-reviewer.md +13 -4
- package/templates/skills/vv-execute/SKILL.md +3 -3
- package/templates/skills/vv-execute/references/tool-contracts.md +1750 -0
- package/templates/skills/vv-review/SKILL.md +1 -1
|
@@ -0,0 +1,659 @@
|
|
|
1
|
+
// FILE: src/plugins/hashline-edit/schemas.ts
|
|
2
|
+
// VERSION: 1.0.0
|
|
3
|
+
// START_MODULE_CONTRACT
|
|
4
|
+
// PURPOSE: Single source of the two vvoc-owned edit tool contracts: registered raw argument maps, closed nested edit/command structures, strict runtime schemas with args-only branch validation, model-facing descriptions, operation examples, and concrete metadata/result producer schemas.
|
|
5
|
+
// SCOPE: Pure contract declarations and args-only validation for hashline_edit and str_replace_editor: structural parsing (unknown keys, enums, types), operation/command allowed/required field matrices, delete/rename/edits branch rules, anchor-conflict detection, and view_range exact-shape rules. No filesystem, session cache, editor execution, plugin registration, or anchor-versus-current-file validation; the normalizer and editor keep literal-content, three-part-anchor, and current-file authority.
|
|
6
|
+
// DEPENDS: [@opencode-ai/plugin, zod (types), src/lib/agent-tool-contract.ts, src/plugins/hashline-edit/routing.ts, src/plugins/hashline-edit/tool-description.ts]
|
|
7
|
+
// LINKS: [M-PLUGIN-HASHLINE-EDIT, M-AGENT-TOOL-CONTRACT]
|
|
8
|
+
// ROLE: RUNTIME
|
|
9
|
+
// MAP_MODE: EXPORTS
|
|
10
|
+
// END_MODULE_CONTRACT
|
|
11
|
+
//
|
|
12
|
+
// START_MODULE_MAP
|
|
13
|
+
// HASHLINE_EDIT_TOOL_ID - Registered tool id for the hash-anchored editor.
|
|
14
|
+
// STR_REPLACE_EDITOR_TOOL_ID - Registered tool id for the dsh str_replace_editor.
|
|
15
|
+
// HASHLINE_EDIT_OPS - Canonical hash-anchored operation vocabulary.
|
|
16
|
+
// HashlineToolOp - Union of hash-anchored operation identifiers.
|
|
17
|
+
// RawHashlineEdit - Loose tool-facing edit input accepted by the normalizer.
|
|
18
|
+
// STR_REPLACE_EDITOR_COMMANDS - Canonical str_replace_editor command vocabulary.
|
|
19
|
+
// StrReplaceEditorCommand - Union of str_replace_editor command identifiers.
|
|
20
|
+
// HashlineEditToolArgs - Schema-derived hashline_edit argument shape.
|
|
21
|
+
// RawHashlineEditEntry - Schema-derived shape of one raw hash-anchored edit entry.
|
|
22
|
+
// StrReplaceEditorToolArgs - Schema-derived str_replace_editor argument shape.
|
|
23
|
+
// StrReplaceEditorResult - Str editor ok/error result shape (producer/test contract).
|
|
24
|
+
// hashlineEditEntrySchema - Closed single-edit shape shared with the normalizer.
|
|
25
|
+
// hashlineEditArgs - Registered raw argument map for hashline_edit.
|
|
26
|
+
// strReplaceEditorArgs - Registered raw argument map for str_replace_editor.
|
|
27
|
+
// hashlineEditContract - Owned contract for hashline_edit.
|
|
28
|
+
// strReplaceEditorContract - Owned contract for str_replace_editor.
|
|
29
|
+
// editToolContracts - Owned contracts for both edit tools (definition adapter input).
|
|
30
|
+
// HashlineEditValidation - Successful parsed hashline args or bounded issues.
|
|
31
|
+
// StrReplaceEditorValidation - Successful parsed str editor args or bounded issues.
|
|
32
|
+
// validateRawHashlineEditEntry - Closed single-edit shape validation reused by the direct normalizer.
|
|
33
|
+
// validateHashlineEditToolInput - Structural plus branch validation for hashline_edit.
|
|
34
|
+
// validateStrReplaceEditorToolInput - Structural plus command validation for str_replace_editor.
|
|
35
|
+
// hashlineEditFilediffSchema - Concrete filediff metadata envelope schema.
|
|
36
|
+
// hashlineEditMetadataSchema - Concrete hashline success metadata schema (no blanket records).
|
|
37
|
+
// strReplaceEditorMetadataSchema - Concrete str editor success metadata schema.
|
|
38
|
+
// strReplaceEditorResultSchema - Closed ok/error result schema for producer checks.
|
|
39
|
+
// END_MODULE_MAP
|
|
40
|
+
//
|
|
41
|
+
// START_CHANGE_SUMMARY
|
|
42
|
+
// LAST_CHANGE: [C-AGENT-TOOL-CONTRACTS T-005 - Initial single-source edit-tool contracts plus a correction cycle: non-empty path checks for filePath/rename/path, representable insert_line min(0) and view_range length(2) bounds, and a shared closed single-edit shape validation for the direct normalizer.]
|
|
43
|
+
// END_CHANGE_SUMMARY
|
|
44
|
+
import { tool } from "@opencode-ai/plugin";
|
|
45
|
+
import { MAX_CONTRACT_ISSUES, MAX_ISSUE_MESSAGE_CHARS, MAX_ISSUE_PATH_CHARS, MAX_ISSUE_VALUE_CHARS, defineOwnedToolContract, formatIssuePath, strictObject, toContractIssues, } from "../../lib/agent-tool-contract.js";
|
|
46
|
+
import { EDIT_MODES } from "./routing.js";
|
|
47
|
+
import { HASHLINE_EDIT_DESCRIPTION, STR_REPLACE_EDITOR_DESCRIPTION } from "./tool-description.js";
|
|
48
|
+
const schema = tool.schema;
|
|
49
|
+
// START_BLOCK_TOOL_IDS
|
|
50
|
+
/** Registered tool id for the hash-anchored editor. */
|
|
51
|
+
export const HASHLINE_EDIT_TOOL_ID = "hashline_edit";
|
|
52
|
+
/** Registered tool id for the dsh str_replace_editor. */
|
|
53
|
+
export const STR_REPLACE_EDITOR_TOOL_ID = "str_replace_editor";
|
|
54
|
+
/** Canonical hash-anchored operation vocabulary. */
|
|
55
|
+
export const HASHLINE_EDIT_OPS = ["replace", "replace_range", "append", "prepend"];
|
|
56
|
+
/** Canonical str_replace_editor command vocabulary. */
|
|
57
|
+
export const STR_REPLACE_EDITOR_COMMANDS = ["view", "create", "str_replace", "insert"];
|
|
58
|
+
// END_BLOCK_TOOL_IDS
|
|
59
|
+
// START_BLOCK_ARG_MAPS
|
|
60
|
+
const hashlineEditEntryShape = {
|
|
61
|
+
op: schema
|
|
62
|
+
.enum(HASHLINE_EDIT_OPS)
|
|
63
|
+
.describe("Operation: replace one line, replace a pos..end range, append, or prepend."),
|
|
64
|
+
pos: schema
|
|
65
|
+
.string()
|
|
66
|
+
.optional()
|
|
67
|
+
.describe("Primary anchor in LINE#HASH#ANCHOR three-part format. Required for replace/replace_range; optional for append/prepend (no anchor inserts at the file boundary)."),
|
|
68
|
+
end: schema
|
|
69
|
+
.string()
|
|
70
|
+
.optional()
|
|
71
|
+
.describe("Optional inclusive range end anchor. With end, replace covers pos..end; required for replace_range. For append/prepend it is only a fallback when pos is omitted."),
|
|
72
|
+
lines: schema
|
|
73
|
+
.union([schema.array(schema.string()), schema.string(), schema.null()])
|
|
74
|
+
.describe('Replacement or inserted lines as plain text content. null or [] deletes for replace/replace_range; [""] is rejected as ambiguous.'),
|
|
75
|
+
};
|
|
76
|
+
/** Closed single-edit shape shared by registration, args validation, and the direct normalizer. */
|
|
77
|
+
export const hashlineEditEntrySchema = strictObject(hashlineEditEntryShape);
|
|
78
|
+
/** Registered raw argument map for hashline_edit (single source of the tool surface). */
|
|
79
|
+
export const hashlineEditArgs = {
|
|
80
|
+
filePath: schema
|
|
81
|
+
.string()
|
|
82
|
+
.describe("Absolute path to the file to edit (non-empty; spaces inside the name are preserved)"),
|
|
83
|
+
delete: schema.boolean().optional().describe("Delete the file instead of editing it"),
|
|
84
|
+
rename: schema
|
|
85
|
+
.string()
|
|
86
|
+
.optional()
|
|
87
|
+
.describe("Rename the file after edits are applied (non-empty when provided)"),
|
|
88
|
+
edits: schema
|
|
89
|
+
.array(hashlineEditEntrySchema)
|
|
90
|
+
.describe("Hash-anchored edit operations to apply to the file"),
|
|
91
|
+
};
|
|
92
|
+
const hashlineEditArgsObject = strictObject(hashlineEditArgs);
|
|
93
|
+
/** Registered raw argument map for str_replace_editor (single source of the tool surface). */
|
|
94
|
+
export const strReplaceEditorArgs = {
|
|
95
|
+
command: schema
|
|
96
|
+
.enum(STR_REPLACE_EDITOR_COMMANDS)
|
|
97
|
+
.describe("The command to run: view, create, str_replace, or insert"),
|
|
98
|
+
path: schema.string().describe("Absolute path to file or directory"),
|
|
99
|
+
file_text: schema
|
|
100
|
+
.string()
|
|
101
|
+
.optional()
|
|
102
|
+
.describe("create only: content of the new file; an explicitly empty string is allowed"),
|
|
103
|
+
old_str: schema
|
|
104
|
+
.string()
|
|
105
|
+
.optional()
|
|
106
|
+
.describe("str_replace only: the exact, non-empty text to replace (whitespace is significant)"),
|
|
107
|
+
new_str: schema
|
|
108
|
+
.string()
|
|
109
|
+
.optional()
|
|
110
|
+
.describe("str_replace replacement text (omitted defaults to empty deletion; explicit empty is valid); required for insert"),
|
|
111
|
+
insert_line: schema
|
|
112
|
+
.number()
|
|
113
|
+
.int()
|
|
114
|
+
.min(0)
|
|
115
|
+
.optional()
|
|
116
|
+
.describe("insert only: integer line index >= 0; new_str is inserted AFTER this line (the file-dependent upper bound is enforced by the editor)"),
|
|
117
|
+
view_range: schema
|
|
118
|
+
.array(schema.number().int())
|
|
119
|
+
.length(2)
|
|
120
|
+
.optional()
|
|
121
|
+
.describe("view only: exact [start, end] line range. The length is fixed at 2; start >= 1 and end is -1 (end of file) or >= start are runtime-checked."),
|
|
122
|
+
};
|
|
123
|
+
const strReplaceEditorArgsObject = strictObject(strReplaceEditorArgs);
|
|
124
|
+
// END_BLOCK_ARG_MAPS
|
|
125
|
+
// START_BLOCK_RESULT_SCHEMAS
|
|
126
|
+
/** Concrete filediff metadata envelope published after a successful hashline edit. */
|
|
127
|
+
export const hashlineEditFilediffSchema = strictObject({
|
|
128
|
+
file: schema.string(),
|
|
129
|
+
path: schema.string(),
|
|
130
|
+
filePath: schema.string(),
|
|
131
|
+
before: schema.string(),
|
|
132
|
+
after: schema.string(),
|
|
133
|
+
});
|
|
134
|
+
/** Concrete success metadata envelope for hashline_edit (known fields, no blanket records). */
|
|
135
|
+
export const hashlineEditMetadataSchema = strictObject({
|
|
136
|
+
filePath: schema.string(),
|
|
137
|
+
path: schema.string(),
|
|
138
|
+
file: schema.string(),
|
|
139
|
+
noopEdits: schema.number().int(),
|
|
140
|
+
deduplicatedEdits: schema.number().int(),
|
|
141
|
+
firstChangedLine: schema.number().int().optional(),
|
|
142
|
+
editMode: schema.enum(EDIT_MODES),
|
|
143
|
+
providerID: schema.string().optional(),
|
|
144
|
+
modelID: schema.string().optional(),
|
|
145
|
+
filediff: hashlineEditFilediffSchema,
|
|
146
|
+
});
|
|
147
|
+
/** Concrete success metadata envelope for str_replace_editor. */
|
|
148
|
+
export const strReplaceEditorMetadataSchema = strictObject({
|
|
149
|
+
filePath: schema.string(),
|
|
150
|
+
path: schema.string(),
|
|
151
|
+
file: schema.string(),
|
|
152
|
+
editMode: schema.enum(EDIT_MODES),
|
|
153
|
+
providerID: schema.string().optional(),
|
|
154
|
+
modelID: schema.string().optional(),
|
|
155
|
+
});
|
|
156
|
+
/**
|
|
157
|
+
* Closed ok/error result schema for str_replace_editor producer/test checks.
|
|
158
|
+
* This is never applied as a throw-after-side-effect execute wrapper.
|
|
159
|
+
*/
|
|
160
|
+
export const strReplaceEditorResultSchema = schema.union([
|
|
161
|
+
strictObject({ ok: schema.literal(true), output: schema.string() }),
|
|
162
|
+
strictObject({ ok: schema.literal(false), error: schema.string() }),
|
|
163
|
+
]);
|
|
164
|
+
// END_BLOCK_RESULT_SCHEMAS
|
|
165
|
+
// START_BLOCK_OPERATION_EXAMPLES
|
|
166
|
+
const hashlineEditExamples = [
|
|
167
|
+
{
|
|
168
|
+
operation: "replace",
|
|
169
|
+
label: "single-line replace at pos",
|
|
170
|
+
expect: "accept",
|
|
171
|
+
input: { filePath: "/tmp/a.ts", edits: [{ op: "replace", pos: "2#VK#ZZ", lines: ["x"] }] },
|
|
172
|
+
},
|
|
173
|
+
{
|
|
174
|
+
operation: "replace",
|
|
175
|
+
label: "replace with end is an inclusive range",
|
|
176
|
+
expect: "accept",
|
|
177
|
+
input: {
|
|
178
|
+
filePath: "/tmp/a.ts",
|
|
179
|
+
edits: [{ op: "replace", pos: "2#VK#ZZ", end: "3#MB#ZZ", lines: ["x"] }],
|
|
180
|
+
},
|
|
181
|
+
},
|
|
182
|
+
{
|
|
183
|
+
operation: "replace_range",
|
|
184
|
+
label: "replace_range alias requires both anchors",
|
|
185
|
+
expect: "accept",
|
|
186
|
+
input: {
|
|
187
|
+
filePath: "/tmp/a.ts",
|
|
188
|
+
edits: [{ op: "replace_range", pos: "2#VK#ZZ", end: "3#MB#ZZ", lines: ["x"] }],
|
|
189
|
+
},
|
|
190
|
+
},
|
|
191
|
+
{
|
|
192
|
+
operation: "replace_range",
|
|
193
|
+
label: "deletion via lines null",
|
|
194
|
+
expect: "accept",
|
|
195
|
+
input: {
|
|
196
|
+
filePath: "/tmp/a.ts",
|
|
197
|
+
edits: [{ op: "replace_range", pos: "2#VK#ZZ", end: "3#MB#ZZ", lines: null }],
|
|
198
|
+
},
|
|
199
|
+
},
|
|
200
|
+
{
|
|
201
|
+
operation: "append",
|
|
202
|
+
label: "boundary append without an anchor creates a missing file",
|
|
203
|
+
expect: "accept",
|
|
204
|
+
input: { filePath: "/tmp/a.ts", edits: [{ op: "append", lines: ["x"] }] },
|
|
205
|
+
},
|
|
206
|
+
{
|
|
207
|
+
operation: "prepend",
|
|
208
|
+
label: "end-anchor fallback when pos is omitted",
|
|
209
|
+
expect: "accept",
|
|
210
|
+
input: { filePath: "/tmp/a.ts", edits: [{ op: "prepend", end: "3#MB#ZZ", lines: ["x"] }] },
|
|
211
|
+
},
|
|
212
|
+
{
|
|
213
|
+
operation: "delete",
|
|
214
|
+
label: "delete mode with empty edits",
|
|
215
|
+
expect: "accept",
|
|
216
|
+
input: { filePath: "/tmp/a.ts", delete: true, edits: [] },
|
|
217
|
+
},
|
|
218
|
+
{
|
|
219
|
+
operation: "delete",
|
|
220
|
+
label: "delete and rename conflict",
|
|
221
|
+
expect: "reject",
|
|
222
|
+
input: { filePath: "/tmp/a.ts", delete: true, rename: "/tmp/b.ts", edits: [] },
|
|
223
|
+
},
|
|
224
|
+
{
|
|
225
|
+
operation: "delete",
|
|
226
|
+
label: "delete with non-empty edits",
|
|
227
|
+
expect: "reject",
|
|
228
|
+
input: {
|
|
229
|
+
filePath: "/tmp/a.ts",
|
|
230
|
+
delete: true,
|
|
231
|
+
edits: [{ op: "replace", pos: "2#VK#ZZ", lines: ["x"] }],
|
|
232
|
+
},
|
|
233
|
+
},
|
|
234
|
+
{
|
|
235
|
+
operation: "append",
|
|
236
|
+
label: "conflicting insert anchors",
|
|
237
|
+
expect: "reject",
|
|
238
|
+
input: {
|
|
239
|
+
filePath: "/tmp/a.ts",
|
|
240
|
+
edits: [{ op: "append", pos: "2#VK#ZZ", end: "3#MB#ZZ", lines: ["x"] }],
|
|
241
|
+
},
|
|
242
|
+
},
|
|
243
|
+
{
|
|
244
|
+
operation: "replace_range",
|
|
245
|
+
label: "replace_range missing end",
|
|
246
|
+
expect: "reject",
|
|
247
|
+
input: {
|
|
248
|
+
filePath: "/tmp/a.ts",
|
|
249
|
+
edits: [{ op: "replace_range", pos: "2#VK#ZZ", lines: ["x"] }],
|
|
250
|
+
},
|
|
251
|
+
},
|
|
252
|
+
{
|
|
253
|
+
operation: "replace",
|
|
254
|
+
label: "non-empty edits required when not deleting",
|
|
255
|
+
expect: "reject",
|
|
256
|
+
input: { filePath: "/tmp/a.ts", edits: [] },
|
|
257
|
+
},
|
|
258
|
+
];
|
|
259
|
+
const strReplaceEditorExamples = [
|
|
260
|
+
{
|
|
261
|
+
operation: "view",
|
|
262
|
+
label: "view with an end-of-file range",
|
|
263
|
+
expect: "accept",
|
|
264
|
+
input: { command: "view", path: "/tmp/a.ts", view_range: [2, -1] },
|
|
265
|
+
},
|
|
266
|
+
{
|
|
267
|
+
operation: "create",
|
|
268
|
+
label: "create with empty file_text",
|
|
269
|
+
expect: "accept",
|
|
270
|
+
input: { command: "create", path: "/tmp/a.ts", file_text: "" },
|
|
271
|
+
},
|
|
272
|
+
{
|
|
273
|
+
operation: "str_replace",
|
|
274
|
+
label: "str_replace with explicit empty new_str (deletion)",
|
|
275
|
+
expect: "accept",
|
|
276
|
+
input: { command: "str_replace", path: "/tmp/a.ts", old_str: "x", new_str: "" },
|
|
277
|
+
},
|
|
278
|
+
{
|
|
279
|
+
operation: "str_replace",
|
|
280
|
+
label: "str_replace with omitted new_str defaults to deletion",
|
|
281
|
+
expect: "accept",
|
|
282
|
+
input: { command: "str_replace", path: "/tmp/a.ts", old_str: "x" },
|
|
283
|
+
},
|
|
284
|
+
{
|
|
285
|
+
operation: "insert",
|
|
286
|
+
label: "insert requires an integer insert_line and new_str",
|
|
287
|
+
expect: "accept",
|
|
288
|
+
input: { command: "insert", path: "/tmp/a.ts", insert_line: 0, new_str: "" },
|
|
289
|
+
},
|
|
290
|
+
{
|
|
291
|
+
operation: "view",
|
|
292
|
+
label: "unsupported field for the selected command",
|
|
293
|
+
expect: "reject",
|
|
294
|
+
input: { command: "view", path: "/tmp/a.ts", old_str: "x" },
|
|
295
|
+
},
|
|
296
|
+
{
|
|
297
|
+
operation: "str_replace",
|
|
298
|
+
label: "old_str must be non-empty",
|
|
299
|
+
expect: "reject",
|
|
300
|
+
input: { command: "str_replace", path: "/tmp/a.ts", old_str: "", new_str: "x" },
|
|
301
|
+
},
|
|
302
|
+
{
|
|
303
|
+
operation: "insert",
|
|
304
|
+
label: "insert requires insert_line",
|
|
305
|
+
expect: "reject",
|
|
306
|
+
input: { command: "insert", path: "/tmp/a.ts", new_str: "x" },
|
|
307
|
+
},
|
|
308
|
+
{
|
|
309
|
+
operation: "view",
|
|
310
|
+
label: "view_range must be exactly two integers",
|
|
311
|
+
expect: "reject",
|
|
312
|
+
input: { command: "view", path: "/tmp/a.ts", view_range: [1] },
|
|
313
|
+
},
|
|
314
|
+
{
|
|
315
|
+
operation: "view",
|
|
316
|
+
label: "view_range end must be -1 or >= start",
|
|
317
|
+
expect: "reject",
|
|
318
|
+
input: { command: "view", path: "/tmp/a.ts", view_range: [3, 2] },
|
|
319
|
+
},
|
|
320
|
+
];
|
|
321
|
+
// END_BLOCK_OPERATION_EXAMPLES
|
|
322
|
+
// START_BLOCK_CONTRACTS
|
|
323
|
+
/** Owned contract for hashline_edit. */
|
|
324
|
+
export const hashlineEditContract = defineOwnedToolContract({
|
|
325
|
+
toolId: HASHLINE_EDIT_TOOL_ID,
|
|
326
|
+
description: HASHLINE_EDIT_DESCRIPTION,
|
|
327
|
+
registeredArgs: hashlineEditArgs,
|
|
328
|
+
examples: hashlineEditExamples,
|
|
329
|
+
});
|
|
330
|
+
/** Owned contract for str_replace_editor. */
|
|
331
|
+
export const strReplaceEditorContract = defineOwnedToolContract({
|
|
332
|
+
toolId: STR_REPLACE_EDITOR_TOOL_ID,
|
|
333
|
+
description: STR_REPLACE_EDITOR_DESCRIPTION,
|
|
334
|
+
registeredArgs: strReplaceEditorArgs,
|
|
335
|
+
examples: strReplaceEditorExamples,
|
|
336
|
+
});
|
|
337
|
+
/** Owned contracts for both edit tools (input to the owned definition/pre-execute adapters). */
|
|
338
|
+
export const editToolContracts = [
|
|
339
|
+
hashlineEditContract,
|
|
340
|
+
strReplaceEditorContract,
|
|
341
|
+
];
|
|
342
|
+
// END_BLOCK_CONTRACTS
|
|
343
|
+
// START_BLOCK_ISSUE_HELPERS
|
|
344
|
+
function truncateChars(value, max) {
|
|
345
|
+
if (value.length <= max)
|
|
346
|
+
return value;
|
|
347
|
+
return `${value.slice(0, Math.max(0, max - 1))}…`;
|
|
348
|
+
}
|
|
349
|
+
function boundedIssue(issue) {
|
|
350
|
+
return {
|
|
351
|
+
code: issue.code,
|
|
352
|
+
path: truncateChars(issue.path, MAX_ISSUE_PATH_CHARS),
|
|
353
|
+
message: truncateChars(issue.message, MAX_ISSUE_MESSAGE_CHARS),
|
|
354
|
+
...(issue.expected !== undefined
|
|
355
|
+
? { expected: truncateChars(issue.expected, MAX_ISSUE_MESSAGE_CHARS) }
|
|
356
|
+
: {}),
|
|
357
|
+
...(issue.received !== undefined
|
|
358
|
+
? { received: truncateChars(issue.received, MAX_ISSUE_VALUE_CHARS) }
|
|
359
|
+
: {}),
|
|
360
|
+
};
|
|
361
|
+
}
|
|
362
|
+
function pushIssue(issues, issue) {
|
|
363
|
+
if (issues.length >= MAX_CONTRACT_ISSUES)
|
|
364
|
+
return;
|
|
365
|
+
issues.push(boundedIssue(issue));
|
|
366
|
+
}
|
|
367
|
+
/** Trim an anchor exactly like the normalizer does before comparing references. */
|
|
368
|
+
function normalizeAnchor(value) {
|
|
369
|
+
if (typeof value !== "string")
|
|
370
|
+
return undefined;
|
|
371
|
+
const trimmed = value.trim();
|
|
372
|
+
return trimmed === "" ? undefined : trimmed;
|
|
373
|
+
}
|
|
374
|
+
function validateHashlineEditBranches(issues, data) {
|
|
375
|
+
if (data.filePath.trim().length === 0) {
|
|
376
|
+
pushIssue(issues, {
|
|
377
|
+
code: "missing_value",
|
|
378
|
+
path: "filePath",
|
|
379
|
+
message: "filePath must be a non-empty path",
|
|
380
|
+
expected: "a non-empty file path (spaces inside the name are preserved)",
|
|
381
|
+
});
|
|
382
|
+
}
|
|
383
|
+
if (data.rename !== undefined && data.rename.trim().length === 0) {
|
|
384
|
+
pushIssue(issues, {
|
|
385
|
+
code: "missing_value",
|
|
386
|
+
path: "rename",
|
|
387
|
+
message: "rename must be a non-empty path when provided",
|
|
388
|
+
expected: "a non-empty destination path, or omit rename",
|
|
389
|
+
});
|
|
390
|
+
}
|
|
391
|
+
if (data.delete === true && data.rename !== undefined) {
|
|
392
|
+
pushIssue(issues, {
|
|
393
|
+
code: "invalid_value",
|
|
394
|
+
path: "rename",
|
|
395
|
+
message: "delete and rename cannot be used together",
|
|
396
|
+
expected: "either delete:true or rename, not both",
|
|
397
|
+
});
|
|
398
|
+
}
|
|
399
|
+
if (data.delete === true && data.edits.length > 0) {
|
|
400
|
+
pushIssue(issues, {
|
|
401
|
+
code: "invalid_value",
|
|
402
|
+
path: "edits",
|
|
403
|
+
message: "delete mode requires edits to be an empty array",
|
|
404
|
+
expected: "[]",
|
|
405
|
+
});
|
|
406
|
+
}
|
|
407
|
+
if (data.delete !== true && data.edits.length === 0) {
|
|
408
|
+
pushIssue(issues, {
|
|
409
|
+
code: "missing_value",
|
|
410
|
+
path: "edits",
|
|
411
|
+
message: "edits parameter must be a non-empty array",
|
|
412
|
+
expected: "at least one edit operation",
|
|
413
|
+
});
|
|
414
|
+
}
|
|
415
|
+
data.edits.forEach((edit, index) => {
|
|
416
|
+
if (edit.pos !== undefined && edit.pos.trim() === "") {
|
|
417
|
+
pushIssue(issues, {
|
|
418
|
+
code: "invalid_value",
|
|
419
|
+
path: formatIssuePath(["edits", index, "pos"]),
|
|
420
|
+
message: "pos was provided but is blank",
|
|
421
|
+
expected: "a full three-part anchor, or omit pos",
|
|
422
|
+
});
|
|
423
|
+
}
|
|
424
|
+
if (edit.end !== undefined && edit.end.trim() === "") {
|
|
425
|
+
pushIssue(issues, {
|
|
426
|
+
code: "invalid_value",
|
|
427
|
+
path: formatIssuePath(["edits", index, "end"]),
|
|
428
|
+
message: "end was provided but is blank",
|
|
429
|
+
expected: "a full three-part anchor, or omit end",
|
|
430
|
+
});
|
|
431
|
+
}
|
|
432
|
+
const pos = normalizeAnchor(edit.pos);
|
|
433
|
+
const end = normalizeAnchor(edit.end);
|
|
434
|
+
if (edit.op === "replace" && pos === undefined) {
|
|
435
|
+
pushIssue(issues, {
|
|
436
|
+
code: "missing_value",
|
|
437
|
+
path: formatIssuePath(["edits", index, "pos"]),
|
|
438
|
+
message: "replace requires a pos anchor",
|
|
439
|
+
expected: "a three-part LINE#HASH#ANCHOR reference",
|
|
440
|
+
});
|
|
441
|
+
return;
|
|
442
|
+
}
|
|
443
|
+
if (edit.op === "replace_range") {
|
|
444
|
+
if (pos === undefined) {
|
|
445
|
+
pushIssue(issues, {
|
|
446
|
+
code: "missing_value",
|
|
447
|
+
path: formatIssuePath(["edits", index, "pos"]),
|
|
448
|
+
message: "replace_range requires both pos and end anchors",
|
|
449
|
+
expected: "a three-part pos anchor",
|
|
450
|
+
});
|
|
451
|
+
}
|
|
452
|
+
if (end === undefined) {
|
|
453
|
+
pushIssue(issues, {
|
|
454
|
+
code: "missing_value",
|
|
455
|
+
path: formatIssuePath(["edits", index, "end"]),
|
|
456
|
+
message: "replace_range requires both pos and end anchors",
|
|
457
|
+
expected: "a three-part end anchor",
|
|
458
|
+
});
|
|
459
|
+
}
|
|
460
|
+
return;
|
|
461
|
+
}
|
|
462
|
+
if ((edit.op === "append" || edit.op === "prepend") &&
|
|
463
|
+
pos !== undefined &&
|
|
464
|
+
end !== undefined &&
|
|
465
|
+
pos !== end) {
|
|
466
|
+
pushIssue(issues, {
|
|
467
|
+
code: "invalid_value",
|
|
468
|
+
path: formatIssuePath(["edits", index, "end"]),
|
|
469
|
+
message: "pos and end anchors reference different lines for an insert operation",
|
|
470
|
+
expected: "one anchor, or identical pos and end",
|
|
471
|
+
});
|
|
472
|
+
}
|
|
473
|
+
});
|
|
474
|
+
}
|
|
475
|
+
/**
|
|
476
|
+
* Closed single-edit shape validation reused by the direct normalizer.
|
|
477
|
+
* Rejects unknown properties, malformed provided optional types, a provided
|
|
478
|
+
* non-canonical operation name, and (through the shared schema) missing
|
|
479
|
+
* op/lines. Blank `pos`/`end` are valid strings here and are rejected by the
|
|
480
|
+
* normalizer's anchor step so blank-versus-absent stays distinguishable.
|
|
481
|
+
*/
|
|
482
|
+
export function validateRawHashlineEditEntry(raw, index) {
|
|
483
|
+
const issues = [];
|
|
484
|
+
if (raw !== null && typeof raw === "object" && !Array.isArray(raw)) {
|
|
485
|
+
const op = raw.op;
|
|
486
|
+
if (typeof op === "string" && !HASHLINE_EDIT_OPS.includes(op)) {
|
|
487
|
+
pushIssue(issues, {
|
|
488
|
+
code: "invalid_value",
|
|
489
|
+
path: formatIssuePath(["edits", index, "op"]),
|
|
490
|
+
message: `unsupported op "${op}". Use replace, replace_range, append, or prepend.`,
|
|
491
|
+
expected: HASHLINE_EDIT_OPS.join(" | "),
|
|
492
|
+
});
|
|
493
|
+
return { ok: false, issues };
|
|
494
|
+
}
|
|
495
|
+
}
|
|
496
|
+
const parsed = hashlineEditEntrySchema.safeParse(raw);
|
|
497
|
+
if (parsed.success) {
|
|
498
|
+
return { ok: true, data: parsed.data };
|
|
499
|
+
}
|
|
500
|
+
const prefix = formatIssuePath(["edits", index]);
|
|
501
|
+
for (const issue of toContractIssues(parsed.error)) {
|
|
502
|
+
pushIssue(issues, {
|
|
503
|
+
...issue,
|
|
504
|
+
path: issue.path === "(root)" ? prefix : `${prefix}.${issue.path}`,
|
|
505
|
+
});
|
|
506
|
+
}
|
|
507
|
+
return { ok: false, issues };
|
|
508
|
+
}
|
|
509
|
+
/**
|
|
510
|
+
* Strict structural plus args-only branch validation for hashline_edit.
|
|
511
|
+
* Unknown keys, enums, and nested shapes fail first; then non-empty paths,
|
|
512
|
+
* delete/rename/edits combinations, and per-operation anchor requirements are
|
|
513
|
+
* enforced. Current-file anchor validation, literal payload rejection, and
|
|
514
|
+
* creation remain in the normalizer/executor and stay authoritative.
|
|
515
|
+
*/
|
|
516
|
+
export function validateHashlineEditToolInput(raw) {
|
|
517
|
+
const parsed = hashlineEditContract.safeParse(raw);
|
|
518
|
+
if (!parsed.success) {
|
|
519
|
+
return { ok: false, issues: parsed.issues };
|
|
520
|
+
}
|
|
521
|
+
const issues = [];
|
|
522
|
+
validateHashlineEditBranches(issues, parsed.data);
|
|
523
|
+
if (issues.length > 0) {
|
|
524
|
+
return { ok: false, issues };
|
|
525
|
+
}
|
|
526
|
+
return { ok: true, data: parsed.data };
|
|
527
|
+
}
|
|
528
|
+
const STR_REPLACE_EDITOR_COMMAND_FIELDS = {
|
|
529
|
+
view: ["command", "path", "view_range"],
|
|
530
|
+
create: ["command", "path", "file_text"],
|
|
531
|
+
str_replace: ["command", "path", "old_str", "new_str"],
|
|
532
|
+
insert: ["command", "path", "new_str", "insert_line"],
|
|
533
|
+
};
|
|
534
|
+
function validateViewRangeShape(issues, viewRange) {
|
|
535
|
+
if (viewRange.length !== 2 || !viewRange.every((value) => Number.isInteger(value))) {
|
|
536
|
+
pushIssue(issues, {
|
|
537
|
+
code: "invalid_value",
|
|
538
|
+
path: "view_range",
|
|
539
|
+
message: "view_range must be a list of exactly two integers",
|
|
540
|
+
expected: "[start, end] with integer start and end",
|
|
541
|
+
});
|
|
542
|
+
return;
|
|
543
|
+
}
|
|
544
|
+
const start = viewRange[0];
|
|
545
|
+
const end = viewRange[1];
|
|
546
|
+
if (start < 1) {
|
|
547
|
+
pushIssue(issues, {
|
|
548
|
+
code: "too_small",
|
|
549
|
+
path: "view_range",
|
|
550
|
+
message: `view_range start ${start} must be >= 1`,
|
|
551
|
+
expected: "start >= 1",
|
|
552
|
+
});
|
|
553
|
+
}
|
|
554
|
+
if (end !== -1 && end < start) {
|
|
555
|
+
pushIssue(issues, {
|
|
556
|
+
code: "invalid_value",
|
|
557
|
+
path: "view_range",
|
|
558
|
+
message: `view_range end ${end} must be -1 or >= start ${start}`,
|
|
559
|
+
expected: "end = -1 or end >= start",
|
|
560
|
+
});
|
|
561
|
+
}
|
|
562
|
+
}
|
|
563
|
+
/**
|
|
564
|
+
* Strict structural plus command-specific validation for str_replace_editor.
|
|
565
|
+
* Unknown keys, enums, and types fail first; then recognized fields that the
|
|
566
|
+
* selected command does not consume are rejected rather than dropped, the
|
|
567
|
+
* command's required fields are enforced, and view_range is required to be an
|
|
568
|
+
* exact two-integer range. Path existence, directory checks, the file-length
|
|
569
|
+
* insert bound, exact-match behavior, and freshness checks remain in the editor.
|
|
570
|
+
*/
|
|
571
|
+
export function validateStrReplaceEditorToolInput(raw) {
|
|
572
|
+
const parsed = strReplaceEditorContract.safeParse(raw);
|
|
573
|
+
if (!parsed.success) {
|
|
574
|
+
return { ok: false, issues: parsed.issues };
|
|
575
|
+
}
|
|
576
|
+
const data = parsed.data;
|
|
577
|
+
const issues = [];
|
|
578
|
+
const allowed = new Set(STR_REPLACE_EDITOR_COMMAND_FIELDS[data.command]);
|
|
579
|
+
for (const key of Object.keys(data)) {
|
|
580
|
+
const value = data[key];
|
|
581
|
+
if (value === undefined || allowed.has(key))
|
|
582
|
+
continue;
|
|
583
|
+
pushIssue(issues, {
|
|
584
|
+
code: "invalid_value",
|
|
585
|
+
path: key,
|
|
586
|
+
message: `${key} is not consumed by command ${data.command}`,
|
|
587
|
+
expected: `only ${[...allowed].join(", ")}`,
|
|
588
|
+
});
|
|
589
|
+
}
|
|
590
|
+
if (data.path.trim().length === 0) {
|
|
591
|
+
pushIssue(issues, {
|
|
592
|
+
code: "missing_value",
|
|
593
|
+
path: "path",
|
|
594
|
+
message: "path must be a non-empty path",
|
|
595
|
+
expected: "a non-empty file or directory path (spaces inside the name are preserved)",
|
|
596
|
+
});
|
|
597
|
+
}
|
|
598
|
+
if (data.command === "create" && data.file_text === undefined) {
|
|
599
|
+
pushIssue(issues, {
|
|
600
|
+
code: "missing_value",
|
|
601
|
+
path: "file_text",
|
|
602
|
+
message: "create requires file_text (an explicitly empty string is allowed)",
|
|
603
|
+
expected: 'a string, including ""',
|
|
604
|
+
});
|
|
605
|
+
}
|
|
606
|
+
if (data.command === "str_replace") {
|
|
607
|
+
if (data.old_str === undefined) {
|
|
608
|
+
pushIssue(issues, {
|
|
609
|
+
code: "missing_value",
|
|
610
|
+
path: "old_str",
|
|
611
|
+
message: "str_replace requires old_str",
|
|
612
|
+
expected: "the exact non-empty text to replace",
|
|
613
|
+
});
|
|
614
|
+
}
|
|
615
|
+
else if (data.old_str.length === 0) {
|
|
616
|
+
pushIssue(issues, {
|
|
617
|
+
code: "invalid_value",
|
|
618
|
+
path: "old_str",
|
|
619
|
+
message: "old_str must be a non-empty exact string",
|
|
620
|
+
expected: "a non-empty literal string",
|
|
621
|
+
});
|
|
622
|
+
}
|
|
623
|
+
}
|
|
624
|
+
if (data.command === "insert") {
|
|
625
|
+
if (data.new_str === undefined) {
|
|
626
|
+
pushIssue(issues, {
|
|
627
|
+
code: "missing_value",
|
|
628
|
+
path: "new_str",
|
|
629
|
+
message: "insert requires new_str (an explicitly empty string is allowed)",
|
|
630
|
+
expected: 'a string, including ""',
|
|
631
|
+
});
|
|
632
|
+
}
|
|
633
|
+
if (data.insert_line === undefined) {
|
|
634
|
+
pushIssue(issues, {
|
|
635
|
+
code: "missing_value",
|
|
636
|
+
path: "insert_line",
|
|
637
|
+
message: "insert requires an integer insert_line >= 0",
|
|
638
|
+
expected: "an integer >= 0",
|
|
639
|
+
});
|
|
640
|
+
}
|
|
641
|
+
else if (!Number.isInteger(data.insert_line) || data.insert_line < 0) {
|
|
642
|
+
pushIssue(issues, {
|
|
643
|
+
code: "too_small",
|
|
644
|
+
path: "insert_line",
|
|
645
|
+
message: "insert_line must be an integer >= 0",
|
|
646
|
+
expected: "an integer >= 0",
|
|
647
|
+
});
|
|
648
|
+
}
|
|
649
|
+
}
|
|
650
|
+
if (data.view_range !== undefined) {
|
|
651
|
+
validateViewRangeShape(issues, data.view_range);
|
|
652
|
+
}
|
|
653
|
+
if (issues.length > 0) {
|
|
654
|
+
return { ok: false, issues };
|
|
655
|
+
}
|
|
656
|
+
return { ok: true, data };
|
|
657
|
+
}
|
|
658
|
+
// END_BLOCK_STR_REPLACE_VALIDATION
|
|
659
|
+
//# sourceMappingURL=schemas.js.map
|