@aefree/pi-plastic 0.7.6 → 0.8.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/CHANGELOG.md +43 -0
- package/README.md +48 -174
- package/docs/2026-10-06-development.md +101 -0
- package/docs/2026-10-06-diffs-and-patches.md +136 -0
- package/docs/2026-10-06-discovery.md +135 -0
- package/docs/2026-10-06-integrations.md +49 -0
- package/docs/2026-10-06-merging.md +153 -0
- package/docs/2026-10-06-safety-and-receipts.md +95 -0
- package/docs/2026-10-06-setup.md +101 -0
- package/docs/2026-10-06-shelvesets-and-reviews.md +113 -0
- package/docs/2026-10-06-status.md +130 -0
- package/docs/2026-10-06-tools-and-loading.md +64 -0
- package/docs/2026-10-06-workspace-operations.md +142 -0
- package/docs/README.md +65 -0
- package/extensions/bash-cm-diff-guard.ts +1 -1
- package/index.ts +4 -687
- package/package.json +10 -4
- package/skills/using-plastic/SKILL.md +11 -5
- package/skills/using-plastic/references/branch-management.md +5 -1
- package/skills/using-plastic/references/changeset-operations.md +9 -8
- package/skills/using-plastic/references/code-review-creation.md +25 -18
- package/skills/using-plastic/references/integration.md +9 -5
- package/skills/using-plastic/references/quick-reference.md +4 -4
- package/skills/using-plastic/references/reviewing-changes.md +10 -10
- package/skills/using-plastic/references/shelving.md +26 -11
- package/skills/using-plastic/references/troubleshooting.md +4 -4
- package/src/diff/local-snapshot.ts +74 -0
- package/src/diff/text.ts +187 -0
- package/src/domain/add-contract.ts +24 -0
- package/src/domain/branch-create-contract.ts +38 -0
- package/src/domain/branch-delete-contract.ts +15 -0
- package/src/domain/branches.ts +263 -0
- package/src/domain/checkin-contract.ts +70 -0
- package/src/domain/closeout-contract.ts +30 -0
- package/src/domain/copied-merge.ts +42 -0
- package/src/domain/diff-base-xml.ts +65 -0
- package/src/domain/diff-pending-xml.ts +34 -0
- package/src/domain/merge-output.ts +327 -0
- package/src/domain/paths.ts +253 -0
- package/src/domain/pending.ts +499 -0
- package/src/domain/removal-contract.ts +25 -0
- package/src/domain/revisions.ts +301 -0
- package/src/domain/server-merge-contract.ts +45 -0
- package/src/domain/server-merge-resolutions.ts +61 -0
- package/src/domain/status-xml.ts +122 -0
- package/src/domain/switch-contract.ts +60 -0
- package/src/domain/switch-policy.ts +95 -0
- package/src/domain/undo-contract.ts +24 -0
- package/src/domain/update-contract.ts +16 -0
- package/src/domain/workspace-merge-contract.ts +41 -0
- package/src/execution/add-command.ts +118 -0
- package/src/execution/branch-create-command.ts +118 -0
- package/src/execution/branch-list-command.ts +75 -0
- package/src/execution/checkin-command.ts +116 -0
- package/src/execution/cli-version.ts +31 -0
- package/src/execution/cm.ts +59 -0
- package/src/execution/code-review-find-command.ts +75 -0
- package/src/execution/context.ts +20 -0
- package/src/execution/diff-revisions-command.ts +74 -0
- package/src/execution/process.ts +260 -0
- package/src/execution/removal-command.ts +118 -0
- package/src/execution/server-merge-command.ts +92 -0
- package/src/execution/shelveset-list-command.ts +75 -0
- package/src/execution/status-command.ts +29 -0
- package/src/execution/status-xml-command.ts +72 -0
- package/src/execution/status-xml-limits.ts +1 -0
- package/src/execution/switch-command.ts +118 -0
- package/src/execution/test-seams.ts +10 -0
- package/src/execution/undo-command.ts +118 -0
- package/src/execution/update-command.ts +118 -0
- package/src/execution/workspace-list-command.ts +75 -0
- package/src/execution/workspace-merge-command.ts +118 -0
- package/src/operations/add-receipt.ts +37 -0
- package/src/operations/arguments.ts +3 -0
- package/src/operations/branch-create.ts +74 -0
- package/src/operations/branch-delete-receipt.ts +25 -0
- package/src/operations/branch-list.ts +94 -0
- package/src/operations/branch-reads.ts +94 -0
- package/src/operations/branches.ts +111 -0
- package/src/operations/checkin-receipt.ts +140 -0
- package/src/operations/checkin.ts +102 -0
- package/src/operations/closeout-receipt.ts +74 -0
- package/src/operations/closeout.ts +19 -0
- package/src/operations/code-review-find.ts +97 -0
- package/src/operations/consolidated-diff.ts +144 -0
- package/src/operations/diff-revisions.ts +157 -0
- package/src/operations/diff.ts +53 -0
- package/src/operations/merge.ts +26 -0
- package/src/operations/object-delete-receipt.ts +21 -0
- package/src/operations/object-write-receipt.ts +105 -0
- package/src/operations/patch-receipt.ts +96 -0
- package/src/operations/patch-support.ts +375 -0
- package/src/operations/patch.ts +25 -0
- package/src/operations/removal-receipt.ts +42 -0
- package/src/operations/reviews.ts +90 -0
- package/src/operations/server-merge.ts +130 -0
- package/src/operations/shelveset-list.ts +90 -0
- package/src/operations/shelvesets.ts +90 -0
- package/src/operations/status.ts +129 -0
- package/src/operations/switch-receipt.ts +85 -0
- package/src/operations/switch.ts +25 -0
- package/src/operations/undo-receipt.ts +37 -0
- package/src/operations/update-receipt.ts +35 -0
- package/src/operations/workspace-list.ts +92 -0
- package/src/operations/workspace-merge-receipt.ts +50 -0
- package/src/operations/workspace.ts +120 -0
- package/src/pi/add-output.ts +52 -0
- package/src/pi/arguments.ts +298 -0
- package/src/pi/branch-create-output.ts +83 -0
- package/src/pi/branch-delete-output.ts +46 -0
- package/src/pi/branch-list-output.ts +59 -0
- package/src/pi/branch-output.ts +63 -0
- package/src/pi/capability-diagnostics.ts +23 -0
- package/src/pi/checkin-output.ts +68 -0
- package/src/pi/closeout-output.ts +87 -0
- package/src/pi/code-review-find-output.ts +50 -0
- package/src/pi/diff-output.ts +66 -0
- package/src/pi/diff-revisions-output.ts +55 -0
- package/src/pi/object-delete-output.ts +15 -0
- package/src/pi/object-write-output.ts +54 -0
- package/src/pi/patch-output.ts +69 -0
- package/src/pi/register.ts +188 -0
- package/src/pi/removal-output.ts +59 -0
- package/src/pi/schemas.ts +146 -0
- package/src/pi/server-merge-output.ts +83 -0
- package/src/pi/shelveset-list-output.ts +50 -0
- package/src/pi/status-output.ts +204 -0
- package/src/pi/switch-output.ts +80 -0
- package/src/pi/tool-registry.ts +72 -0
- package/src/pi/undo-output.ts +52 -0
- package/src/pi/update-output.ts +48 -0
- package/src/pi/workspace-list-output.ts +53 -0
- package/src/pi/workspace-merge-output.ts +46 -0
- package/src/plastic-core.ts +14 -5775
- package/src/plastic-renderers.ts +102 -22
- package/src/plastic-tool-loading.ts +7 -12
- package/src/presentation/add-results.ts +5 -0
- package/src/presentation/branch-create.ts +7 -0
- package/src/presentation/branch-delete-results.ts +7 -0
- package/src/presentation/checkin-results.ts +14 -0
- package/src/presentation/closeout-results.ts +2 -0
- package/src/presentation/diff-results.ts +237 -0
- package/src/presentation/removal-results.ts +7 -0
- package/src/presentation/results.ts +84 -0
- package/src/presentation/switch-results.ts +7 -0
- package/src/presentation/undo-results.ts +5 -0
- package/src/presentation/update-results.ts +5 -0
- package/src/presentation/workspace-merge-results.ts +6 -0
|
@@ -0,0 +1,298 @@
|
|
|
1
|
+
import type { PlasticExportName } from "./tool-registry";
|
|
2
|
+
|
|
3
|
+
type ToolConfig = {
|
|
4
|
+
prepareArguments?: (args: unknown) => Record<string, unknown>;
|
|
5
|
+
};
|
|
6
|
+
|
|
7
|
+
export const TOOL_CONFIG: Partial<Record<PlasticExportName, ToolConfig>> = {
|
|
8
|
+
status: {
|
|
9
|
+
prepareArguments(args) {
|
|
10
|
+
const input = normalizeOutputFormatAlias(normalizeArgs(args));
|
|
11
|
+
normalizeWorkdirAliases(input);
|
|
12
|
+
assignAlias(input, "includeRevId", ["include_rev_id"]);
|
|
13
|
+
assignAlias(input, "machineReadable", ["machine_readable", "machine"]);
|
|
14
|
+
assignAlias(input, "maxItems", ["max_items"]);
|
|
15
|
+
assignAlias(input, "includeRaw", ["include_raw"]);
|
|
16
|
+
return input;
|
|
17
|
+
},
|
|
18
|
+
},
|
|
19
|
+
update: {
|
|
20
|
+
prepareArguments(args) {
|
|
21
|
+
const input = normalizeArgs(args);
|
|
22
|
+
normalizeWorkdirAliases(input);
|
|
23
|
+
return input;
|
|
24
|
+
},
|
|
25
|
+
},
|
|
26
|
+
add: {
|
|
27
|
+
prepareArguments(args) {
|
|
28
|
+
const input = normalizeArgs(args);
|
|
29
|
+
normalizeWorkdirAliases(input);
|
|
30
|
+
promoteSinglePath(input);
|
|
31
|
+
assignAlias(input, "paths", ["items"]);
|
|
32
|
+
return input;
|
|
33
|
+
},
|
|
34
|
+
},
|
|
35
|
+
checkin: {
|
|
36
|
+
prepareArguments(args) {
|
|
37
|
+
const input = normalizeOutputFormatAlias(normalizeArgs(args));
|
|
38
|
+
normalizeWorkdirAliases(input);
|
|
39
|
+
assignAlias(input, "message", ["comment", "comments"]);
|
|
40
|
+
promoteSinglePath(input);
|
|
41
|
+
assignAlias(input, "paths", ["items", "files"]);
|
|
42
|
+
assignAlias(input, "applyChanged", ["apply_changed"]);
|
|
43
|
+
assignAlias(input, "includePrivate", ["include_private"]);
|
|
44
|
+
assignAlias(input, "includeAll", ["include_all"]);
|
|
45
|
+
assignAlias(input, "updateAfter", ["update_after"]);
|
|
46
|
+
return input;
|
|
47
|
+
},
|
|
48
|
+
},
|
|
49
|
+
undo: {
|
|
50
|
+
prepareArguments(args) {
|
|
51
|
+
const input = normalizeArgs(args);
|
|
52
|
+
normalizeWorkdirAliases(input);
|
|
53
|
+
promoteSinglePath(input);
|
|
54
|
+
assignAlias(input, "paths", ["items"]);
|
|
55
|
+
// Consumed aliases must not become unsupported extra options in the selected producer.
|
|
56
|
+
for (const key of ["cwd", "workingDirectory", "working_directory", "path", "file", "items"]) delete input[key];
|
|
57
|
+
return input;
|
|
58
|
+
},
|
|
59
|
+
},
|
|
60
|
+
resolveDeleteChangeConflict: {
|
|
61
|
+
prepareArguments(args) {
|
|
62
|
+
const input = normalizeOutputFormatAlias(normalizeArgs(args));
|
|
63
|
+
normalizeWorkdirAliases(input);
|
|
64
|
+
promoteSinglePath(input);
|
|
65
|
+
assignAlias(input, "paths", ["items", "files"]);
|
|
66
|
+
assignAlias(input, "keepOnDisk", ["keep_on_disk", "keepOnDisk", "nodisk"]);
|
|
67
|
+
for (const key of ["cwd", "workingDirectory", "working_directory", "path", "file", "items", "files", "keep_on_disk", "nodisk"]) delete input[key];
|
|
68
|
+
return input;
|
|
69
|
+
},
|
|
70
|
+
},
|
|
71
|
+
diff: {
|
|
72
|
+
prepareArguments(args) {
|
|
73
|
+
const input=normalizeOutputFormatAlias(normalizeArgs(args));
|
|
74
|
+
normalizeWorkdirAliases(input);
|
|
75
|
+
return input;
|
|
76
|
+
},
|
|
77
|
+
},
|
|
78
|
+
patch: {
|
|
79
|
+
prepareArguments(args) {
|
|
80
|
+
const input = normalizeOutputFormatAlias(normalizeArgs(args));
|
|
81
|
+
normalizeWorkdirAliases(input);
|
|
82
|
+
assignAlias(input, "output", ["output_file", "outputFile"]);
|
|
83
|
+
assignAlias(input, "toolPath", ["tool_path", "tool"]);
|
|
84
|
+
for (const key of ["cwd", "workingDirectory", "working_directory", "output_file", "outputFile", "tool_path", "tool"]) delete input[key];
|
|
85
|
+
return input;
|
|
86
|
+
},
|
|
87
|
+
},
|
|
88
|
+
branchCreate: {
|
|
89
|
+
prepareArguments(args) {
|
|
90
|
+
const input = normalizeArgs(args);
|
|
91
|
+
normalizeWorkdirAliases(input);
|
|
92
|
+
assignAlias(input, "commentsFile", ["comments_file"]);
|
|
93
|
+
return input;
|
|
94
|
+
},
|
|
95
|
+
},
|
|
96
|
+
switchBranch: {
|
|
97
|
+
prepareArguments(args) {
|
|
98
|
+
const input = normalizeOutputFormatAlias(normalizeArgs(args));
|
|
99
|
+
normalizeWorkdirAliases(input);
|
|
100
|
+
assignAlias(input, "pendingChanges", ["pending_changes"]);
|
|
101
|
+
return input;
|
|
102
|
+
},
|
|
103
|
+
},
|
|
104
|
+
merge: {
|
|
105
|
+
prepareArguments(args) {
|
|
106
|
+
const input = normalizeOutputFormatAlias(normalizeArgs(args));
|
|
107
|
+
normalizeWorkdirAliases(input);
|
|
108
|
+
assignAlias(input, "cherrypicking", ["cherry_picking", "cherryPicking"]);
|
|
109
|
+
for (const key of ["cwd", "workingDirectory", "working_directory", "cherry_picking", "cherryPicking"]) delete input[key];
|
|
110
|
+
return input;
|
|
111
|
+
},
|
|
112
|
+
},
|
|
113
|
+
mergeBranches: {
|
|
114
|
+
prepareArguments(args) {
|
|
115
|
+
return normalizeOutputFormatAlias(normalizeArgs(args));
|
|
116
|
+
},
|
|
117
|
+
},
|
|
118
|
+
mergeToBranch: {
|
|
119
|
+
prepareArguments(args) {
|
|
120
|
+
const input = normalizeOutputFormatAlias(normalizeArgs(args));
|
|
121
|
+
normalizeWorkdirAliases(input);
|
|
122
|
+
assignAlias(input, "source", ["sourceBranch", "source_branch", "branch"]);
|
|
123
|
+
assignAlias(input, "target", ["targetBranch", "target_branch", "destination", "destinationBranch", "destination_branch"]);
|
|
124
|
+
assignAlias(input, "cardRef", ["card", "cardCode", "card_code", "codecksCard", "codecks_card"]);
|
|
125
|
+
assignAlias(input, "updateTarget", ["update_target", "update"]);
|
|
126
|
+
assignAlias(input, "includePrivate", ["include_private"]);
|
|
127
|
+
for (const key of ["cwd", "workingDirectory", "working_directory", "sourceBranch", "source_branch", "branch", "targetBranch", "target_branch", "destination", "destinationBranch", "destination_branch", "card", "cardCode", "card_code", "codecksCard", "codecks_card", "update_target", "update", "include_private"]) delete input[key];
|
|
128
|
+
return input;
|
|
129
|
+
},
|
|
130
|
+
},
|
|
131
|
+
finalizeMerge: {
|
|
132
|
+
prepareArguments(args) {
|
|
133
|
+
const input = normalizeOutputFormatAlias(normalizeArgs(args));
|
|
134
|
+
normalizeWorkdirAliases(input);
|
|
135
|
+
assignAlias(input, "source", ["mergeSource", "merge_source"]);
|
|
136
|
+
for (const key of ["cwd", "workingDirectory", "working_directory", "mergeSource", "merge_source"]) delete input[key];
|
|
137
|
+
return input;
|
|
138
|
+
},
|
|
139
|
+
},
|
|
140
|
+
currentBranch: {
|
|
141
|
+
prepareArguments(args) {
|
|
142
|
+
const input = normalizeOutputFormatAlias(normalizeArgs(args));
|
|
143
|
+
normalizeWorkdirAliases(input);
|
|
144
|
+
return input;
|
|
145
|
+
},
|
|
146
|
+
},
|
|
147
|
+
branchList: {
|
|
148
|
+
prepareArguments(args) {
|
|
149
|
+
const input = normalizeArgs(args);
|
|
150
|
+
normalizeWorkdirAliases(input);
|
|
151
|
+
assignAlias(input, "nameLike", ["name_like"]);
|
|
152
|
+
assignAlias(input, "includeHidden", ["include_hidden"]);
|
|
153
|
+
assignAlias(input, "orderBy", ["order_by"]);
|
|
154
|
+
return input;
|
|
155
|
+
},
|
|
156
|
+
},
|
|
157
|
+
branchExists: {
|
|
158
|
+
prepareArguments(args) {
|
|
159
|
+
const input = normalizeArgs(args);
|
|
160
|
+
normalizeWorkdirAliases(input);
|
|
161
|
+
return input;
|
|
162
|
+
},
|
|
163
|
+
},
|
|
164
|
+
branchDelete: {
|
|
165
|
+
prepareArguments(args) {
|
|
166
|
+
const input = normalizeOutputFormatAlias(normalizeArgs(args));
|
|
167
|
+
normalizeWorkdirAliases(input);
|
|
168
|
+
assignAlias(input, "deleteChangesets", ["delete_changesets"]);
|
|
169
|
+
for (const key of ["cwd", "workingDirectory", "working_directory", "delete_changesets"]) delete input[key];
|
|
170
|
+
return input;
|
|
171
|
+
},
|
|
172
|
+
},
|
|
173
|
+
shelvesetCreate: {
|
|
174
|
+
prepareArguments(args) {
|
|
175
|
+
const input = normalizeArgs(args);
|
|
176
|
+
normalizeWorkdirAliases(input);
|
|
177
|
+
promoteSinglePath(input);
|
|
178
|
+
assignAlias(input, "paths", ["items"]);
|
|
179
|
+
assignAlias(input, "commentsFile", ["comments_file"]);
|
|
180
|
+
assignAlias(input, "summaryFormat", ["summary_format"]);
|
|
181
|
+
for (const key of ["cwd","workingDirectory","working_directory","path","file","items","comments_file","summary_format"]) delete input[key];
|
|
182
|
+
return input;
|
|
183
|
+
},
|
|
184
|
+
},
|
|
185
|
+
shelvesetApply: {
|
|
186
|
+
prepareArguments(args) {
|
|
187
|
+
const input = normalizeArgs(args);
|
|
188
|
+
normalizeWorkdirAliases(input);
|
|
189
|
+
assignAlias(input, "changePaths", ["change_paths"]);
|
|
190
|
+
assignAlias(input, "dontCheckout", ["dont_checkout"]);
|
|
191
|
+
assignAlias(input, "comparisonMethod", ["comparison_method"]);
|
|
192
|
+
for (const key of ["cwd","workingDirectory","working_directory","change_paths","dont_checkout","comparison_method"]) delete input[key];
|
|
193
|
+
return input;
|
|
194
|
+
},
|
|
195
|
+
},
|
|
196
|
+
shelvesetDelete: {
|
|
197
|
+
prepareArguments(args) {
|
|
198
|
+
const input = normalizeOutputFormatAlias(normalizeArgs(args));
|
|
199
|
+
normalizeWorkdirAliases(input);
|
|
200
|
+
for (const key of ["cwd","workingDirectory","working_directory"]) delete input[key];
|
|
201
|
+
return input;
|
|
202
|
+
},
|
|
203
|
+
},
|
|
204
|
+
shelvesetList: {
|
|
205
|
+
prepareArguments(args) {
|
|
206
|
+
const input = normalizeArgs(args);
|
|
207
|
+
normalizeWorkdirAliases(input);
|
|
208
|
+
assignAlias(input, "commentLike", ["comment_like"]);
|
|
209
|
+
assignAlias(input, "dateFrom", ["date_from"]);
|
|
210
|
+
assignAlias(input, "dateFormat", ["date_format"]);
|
|
211
|
+
return input;
|
|
212
|
+
},
|
|
213
|
+
},
|
|
214
|
+
codeReviewCreate: {
|
|
215
|
+
prepareArguments(args) {
|
|
216
|
+
const input = normalizeArgs(args);
|
|
217
|
+
normalizeWorkdirAliases(input);
|
|
218
|
+
for (const key of ["cwd","workingDirectory","working_directory"]) delete input[key];
|
|
219
|
+
return input;
|
|
220
|
+
},
|
|
221
|
+
},
|
|
222
|
+
codeReviewUpdate: {
|
|
223
|
+
prepareArguments(args) {
|
|
224
|
+
const input = normalizeArgs(args);
|
|
225
|
+
normalizeWorkdirAliases(input);
|
|
226
|
+
assignAlias(input, "reviewId", ["review_id"]);
|
|
227
|
+
if (input.reviewId !== undefined && input.id === undefined) input.id = input.reviewId;
|
|
228
|
+
for (const key of ["cwd","workingDirectory","working_directory","reviewId","review_id"]) delete input[key];
|
|
229
|
+
return input;
|
|
230
|
+
},
|
|
231
|
+
},
|
|
232
|
+
codeReviewDelete: {
|
|
233
|
+
prepareArguments(args) {
|
|
234
|
+
const input = normalizeOutputFormatAlias(normalizeArgs(args));
|
|
235
|
+
normalizeWorkdirAliases(input);
|
|
236
|
+
if (input.id !== undefined && input.ids === undefined) input.ids = [String(input.id)];
|
|
237
|
+
assignAlias(input, "ids", ["review_ids"]);
|
|
238
|
+
for (const key of ["cwd","workingDirectory","working_directory","id","review_ids"]) delete input[key];
|
|
239
|
+
return input;
|
|
240
|
+
},
|
|
241
|
+
},
|
|
242
|
+
codeReviewFind: {
|
|
243
|
+
prepareArguments(args) {
|
|
244
|
+
const input = normalizeOutputFormatAlias(normalizeArgs(args));
|
|
245
|
+
normalizeWorkdirAliases(input);
|
|
246
|
+
assignAlias(input, "targetType", ["target_type"]);
|
|
247
|
+
assignAlias(input, "titleLike", ["title_like"]);
|
|
248
|
+
assignAlias(input, "orderBy", ["order_by"]);
|
|
249
|
+
assignAlias(input, "dateFormat", ["date_format"]);
|
|
250
|
+
assignAlias(input, "output", ["output_format", "response_format"]);
|
|
251
|
+
return input;
|
|
252
|
+
},
|
|
253
|
+
},
|
|
254
|
+
workspaceList: {
|
|
255
|
+
prepareArguments(args) {
|
|
256
|
+
const input = normalizeOutputFormatAlias(normalizeArgs(args));
|
|
257
|
+
normalizeWorkdirAliases(input);
|
|
258
|
+
assignAlias(input, "output", ["output_format", "response_format"]);
|
|
259
|
+
return input;
|
|
260
|
+
},
|
|
261
|
+
},
|
|
262
|
+
};
|
|
263
|
+
|
|
264
|
+
export function normalizeArgs(args: unknown): Record<string, unknown> {
|
|
265
|
+
return args && typeof args === "object" ? { ...(args as Record<string, unknown>) } : {};
|
|
266
|
+
}
|
|
267
|
+
|
|
268
|
+
function assignAlias(target: Record<string, unknown>, key: string, aliases: string[]): void {
|
|
269
|
+
if (target[key] !== undefined) return;
|
|
270
|
+
for (const alias of aliases) {
|
|
271
|
+
if (target[alias] !== undefined) {
|
|
272
|
+
target[key] = target[alias];
|
|
273
|
+
return;
|
|
274
|
+
}
|
|
275
|
+
}
|
|
276
|
+
}
|
|
277
|
+
|
|
278
|
+
function normalizeWorkdirAliases(input: Record<string, unknown>): void {
|
|
279
|
+
assignAlias(input, "workdir", ["cwd", "workingDirectory", "working_directory"]);
|
|
280
|
+
}
|
|
281
|
+
|
|
282
|
+
function normalizeOutputFormatAlias(input: Record<string, unknown>): Record<string, unknown> {
|
|
283
|
+
if (typeof input.format === "string" && input.format.trim().toLowerCase() === "markdown") {
|
|
284
|
+
input.format = "text";
|
|
285
|
+
}
|
|
286
|
+
if (typeof input.output === "string" && input.output.trim().toLowerCase() === "markdown") {
|
|
287
|
+
input.output = "text";
|
|
288
|
+
}
|
|
289
|
+
return input;
|
|
290
|
+
}
|
|
291
|
+
|
|
292
|
+
function promoteSinglePath(input: Record<string, unknown>): void {
|
|
293
|
+
if (input.paths !== undefined) return;
|
|
294
|
+
const singlePath = input.path ?? input.file;
|
|
295
|
+
if (typeof singlePath === "string" && singlePath.trim().length > 0) {
|
|
296
|
+
input.paths = [singlePath];
|
|
297
|
+
}
|
|
298
|
+
}
|
|
@@ -0,0 +1,83 @@
|
|
|
1
|
+
import { Type, type TProperties } from "typebox";
|
|
2
|
+
import { Check } from "typebox/value";
|
|
3
|
+
import { assembleBranchCreateReceipt, presentBranchCreateReceipt, safeBranchCreateText, type BranchCreateReceipt, type BranchCreateAttempt, type BranchCreateCapture } from "../operations/branch-create";
|
|
4
|
+
const object = <P extends TProperties>(p: P) => Type.Object(p, { additionalProperties: false });
|
|
5
|
+
const enumeration = <const V extends readonly string[]>(v: V) => Type.Unsafe<V[number]>({ anyOf: v.map(value => ({ type: "string", const: value })) });
|
|
6
|
+
const str = (max = 4096) => Type.String({ maxLength: max });
|
|
7
|
+
const uint = (max = Number.MAX_SAFE_INTEGER) => Type.Integer({ minimum: 0, maximum: max });
|
|
8
|
+
const nullable = <T extends ReturnType<typeof Type.Object> | ReturnType<typeof Type.String> | ReturnType<typeof Type.Array>>(s: T) => Type.Union([s, Type.Null()]);
|
|
9
|
+
const attempt = object({ state: enumeration(["not-attempted", "not-started", "started", "unknown"]), terminal: enumeration(["not-observed", "observed"]), exitCode: Type.Union([uint(2147483647), Type.Null()]), aborted: Type.Boolean(), timedOut: Type.Boolean() });
|
|
10
|
+
const capture = object({ stdoutBytes: uint(), stderrBytes: uint(), stdoutRetainedBytes: uint(65536), stderrRetainedBytes: uint(16384), truncated: Type.Boolean(), complete: Type.Boolean(), validUtf8: Type.Boolean() });
|
|
11
|
+
const argv = Type.Array(str(8192), { maxItems: 8, minItems: 1 });
|
|
12
|
+
const data = {
|
|
13
|
+
requested: nullable(object({ branch: str(), parent: nullable(str()), changeset: nullable(str()), label: nullable(str()), comment: nullable(str()), commentsFile: nullable(str()), allowRootBranch: Type.Boolean() })),
|
|
14
|
+
resolvedTarget: nullable(str()), parentResolution: object({ basis: enumeration(["not-required", "explicit", "status", "unresolved"]), observedBranch: nullable(str()), repositoryVerification: Type.Literal("unverified") }),
|
|
15
|
+
parentCommands: Type.Array(object({ argv, attempt, capture }), { maxItems: 2 }),
|
|
16
|
+
command: nullable(argv), attempt, capture: nullable(capture), observedCreatedIdentity: Type.Null(), repositoryVerification: Type.Literal("unverified"), workspaceSwitch: Type.Literal("not-requested"),
|
|
17
|
+
};
|
|
18
|
+
const header = { schemaVersion: Type.Literal(1), action: Type.Literal("branch-create"), provenance: object({ source: Type.Literal("plastic"), producer: Type.Literal("@aefree/pi-plastic"), contentTrust: Type.Literal("external") }), completeness: object({ capture: enumeration(["complete", "incomplete", "unknown"]), projection: Type.Literal(true) }) };
|
|
19
|
+
const error = object({ code: enumeration(["invalid_request", "unsupported_source", "ambiguous_qualifier", "parent_unresolved", "launch_failed", "aborted", "uncertain", "producer_failed"]), stage: enumeration(["input", "source", "parent", "create", "producer"]), message: str(256) });
|
|
20
|
+
export const branchCreateOutputSchema = Type.Unsafe<BranchCreateReceipt>(Type.Union([
|
|
21
|
+
object({ ...header, ok: Type.Literal(true), outcome: Type.Literal("command-completed"), data: object({ ...data, effect: Type.Literal("not-proven") }) }),
|
|
22
|
+
object({ ...header, ok: Type.Literal(false), outcome: enumeration(["failed", "unsupported"]), error, data: object({ ...data, effect: Type.Literal("not-attempted") }) }),
|
|
23
|
+
object({ ...header, ok: Type.Literal(false), outcome: Type.Literal("uncertain"), error, data: object({ ...data, effect: Type.Literal("uncertain") }) }),
|
|
24
|
+
]));
|
|
25
|
+
const validAttempt = (a: BranchCreateAttempt) => !(a.exitCode !== null && a.terminal !== "observed") && !(a.state === "not-attempted" && (a.terminal !== "not-observed" || a.exitCode !== null || a.timedOut)) && !(a.state === "not-started" && a.exitCode !== null);
|
|
26
|
+
const validCapture = (c: BranchCreateCapture, a: BranchCreateAttempt) => c.stdoutRetainedBytes <= c.stdoutBytes && c.stderrRetainedBytes <= c.stderrBytes && (!c.complete || c.validUtf8 && !c.truncated && c.stdoutBytes === c.stdoutRetainedBytes && c.stderrBytes === c.stderrRetainedBytes && a.state === "started" && a.terminal === "observed" && a.exitCode !== null && !a.aborted && !a.timedOut);
|
|
27
|
+
export function validateBranchCreateOutput(value: unknown): value is BranchCreateReceipt {
|
|
28
|
+
if (!Check(branchCreateOutputSchema, value) || Buffer.byteLength(JSON.stringify(value), "utf8") > 131072) return false;
|
|
29
|
+
const dto = value as BranchCreateReceipt, d = dto.data, a = d.attempt, p = d.parentResolution, r = d.requested;
|
|
30
|
+
if (!validAttempt(a) || d.capture && !validCapture(d.capture, a)) return false;
|
|
31
|
+
if (!dto.ok && !safeBranchCreateText(dto.error.message)) return false;
|
|
32
|
+
if (r && (!r.branch.trim() || Object.values(r).some(v => typeof v === "string" && !safeBranchCreateText(v)) || r.changeset && r.label || r.comment && r.commentsFile || r.comment !== null && !r.comment.trim())) return false;
|
|
33
|
+
if (d.resolvedTarget !== null && (!safeBranchCreateText(d.resolvedTarget) || !d.resolvedTarget.trim())) return false;
|
|
34
|
+
if (p.observedBranch !== null && (!safeBranchCreateText(p.observedBranch) || !/^\/(?:[^/@\\]+)(?:\/[^/@\\]+)*$/u.test(p.observedBranch) || /[?\uFFFD]/u.test(p.observedBranch))) return false;
|
|
35
|
+
if ((p.basis === "status") !== (p.observedBranch !== null)) return false;
|
|
36
|
+
for (let i = 0; i < d.parentCommands.length; i++) {
|
|
37
|
+
const c = d.parentCommands[i]!;
|
|
38
|
+
if (JSON.stringify(c.argv) !== JSON.stringify(i === 0 ? ["status"] : ["status", "--compact"]) || !validAttempt(c.attempt) || !validCapture(c.capture, c.attempt)) return false;
|
|
39
|
+
}
|
|
40
|
+
if (p.basis === "status" && (d.parentCommands.length !== 1 || !d.parentCommands[0]!.capture.complete || d.parentCommands[0]!.capture.stderrBytes || d.parentCommands[0]!.attempt.exitCode !== 0)) return false;
|
|
41
|
+
if (["explicit", "not-required"].includes(p.basis) && d.parentCommands.length) return false;
|
|
42
|
+
const norm = (s: string) => s.trim().replace(/^br:/i, "").split("@")[0]!.replace(/\\/g, "/");
|
|
43
|
+
if (r) {
|
|
44
|
+
const relative = !norm(r.branch).startsWith("/");
|
|
45
|
+
if (!relative && p.basis !== "not-required" || relative && p.basis === "explicit" && r.parent === null || relative && p.basis === "status" && r.parent !== null) return false;
|
|
46
|
+
if (d.command && relative && (r.branch.includes("@") || r.parent?.includes("@"))) return false;
|
|
47
|
+
}
|
|
48
|
+
if (d.command) {
|
|
49
|
+
if (!r || !d.resolvedTarget || Buffer.byteLength(JSON.stringify(d.command), "utf8") > 65536 || !d.command.every(s => !/[\u0000-\u001f\u007f-\u009f]|[\uD800-\uDBFF](?![\uDC00-\uDFFF])|(?<![\uD800-\uDBFF])[\uDC00-\uDFFF]/u.test(s))) return false;
|
|
50
|
+
let expectedTarget = r.branch.trim();
|
|
51
|
+
if (!norm(r.branch).startsWith("/")) {
|
|
52
|
+
const parent = p.basis === "status" ? p.observedBranch : r.parent;
|
|
53
|
+
if (!parent || !["status", "explicit"].includes(p.basis)) return false;
|
|
54
|
+
const normalizedParent = norm(parent).replace(/\/$/, "");
|
|
55
|
+
if (!normalizedParent.startsWith("/") || normalizedParent === "/") return false;
|
|
56
|
+
expectedTarget = normalizedParent + "/" + norm(r.branch);
|
|
57
|
+
} else if (norm(r.branch).split("/").filter(Boolean).length === 1 && !r.allowRootBranch) return false;
|
|
58
|
+
if (d.resolvedTarget !== expectedTarget) return false;
|
|
59
|
+
const expected = ["branch", "create", expectedTarget];
|
|
60
|
+
if (r.changeset) expected.push(`--changeset=${r.changeset}`);
|
|
61
|
+
if (r.label) expected.push(`--label=${r.label}`);
|
|
62
|
+
if (r.comment) expected.push(`-c=${r.comment}`);
|
|
63
|
+
if (r.commentsFile) expected.push(`-commentsfile=${r.commentsFile}`);
|
|
64
|
+
if (JSON.stringify(expected) !== JSON.stringify(d.command)) return false;
|
|
65
|
+
} else if (d.resolvedTarget !== null || !["not-attempted", "unknown"].includes(a.state) || d.capture) return false;
|
|
66
|
+
if (dto.outcome === "command-completed" && (!d.command || !d.capture?.complete || d.capture.stderrBytes || a.state !== "started" || a.terminal !== "observed" || a.exitCode !== 0 || a.aborted || a.timedOut)) return false;
|
|
67
|
+
if (dto.outcome === "uncertain" && !["started", "unknown"].includes(a.state)) return false;
|
|
68
|
+
if (["failed", "unsupported"].includes(dto.outcome) && !["not-attempted", "not-started"].includes(a.state)) return false;
|
|
69
|
+
const lastCapture = d.capture ?? d.parentCommands.at(-1)?.capture;
|
|
70
|
+
if (dto.completeness.capture !== (lastCapture ? lastCapture.complete ? "complete" : "incomplete" : "unknown")) return false;
|
|
71
|
+
return true;
|
|
72
|
+
}
|
|
73
|
+
export async function executeBranchCreateOutput(args: unknown) {
|
|
74
|
+
let dto = await assembleBranchCreateReceipt(args);
|
|
75
|
+
const valid: boolean = validateBranchCreateOutput(dto);
|
|
76
|
+
if (!valid) {
|
|
77
|
+
const mayHaveStarted = !["not-attempted", "not-started"].includes(dto.data.attempt.state);
|
|
78
|
+
const safe = await assembleBranchCreateReceipt({});
|
|
79
|
+
dto = { ...safe, ok: false, outcome: mayHaveStarted ? "uncertain" : "failed", data: { ...safe.data, attempt: { ...safe.data.attempt, state: mayHaveStarted ? "unknown" : "not-attempted" }, effect: mayHaveStarted ? "uncertain" : "not-attempted" }, error: { code: "producer_failed", stage: "producer", message: "Branch creation receipt could not be validated; do not retry automatically." } } as BranchCreateReceipt;
|
|
80
|
+
if (!validateBranchCreateOutput(dto)) throw new Error("Safe branch creation receipt could not be constructed.");
|
|
81
|
+
}
|
|
82
|
+
return { content: [{ type: "text" as const, text: presentBranchCreateReceipt(dto) }], details: {}, structuredContent: dto, isError: !dto.ok };
|
|
83
|
+
}
|
|
@@ -0,0 +1,46 @@
|
|
|
1
|
+
import { Type } from "typebox";
|
|
2
|
+
import { Check } from "typebox/value";
|
|
3
|
+
import { assembleBranchDeleteReceipt, branchDeleteArgv, branchDeleteFailure, emptyBranchDeleteData, presentBranchDeleteReceipt, safeBranchDeleteText, validBranchDeleteOperand, type BranchDeleteReceipt } from "../operations/branch-delete-receipt";
|
|
4
|
+
const obj=(p:Record<string,any>)=>Type.Object(p,{additionalProperties:false}),enm=(v:string[])=>Type.Union(v.map(x=>Type.Literal(x))),nil=(s:any)=>Type.Union([s,Type.Null()]),uint=(max=Number.MAX_SAFE_INTEGER)=>Type.Integer({minimum:0,maximum:max}),text=Type.String({minLength:1,maxLength:4096});
|
|
5
|
+
const attempt=obj({state:enm(["not-attempted","not-started","started","unknown"]),terminal:enm(["not-observed","observed"]),exitCode:nil(uint(2147483647)),aborted:Type.Boolean(),timedOut:Type.Boolean()});
|
|
6
|
+
const capture=obj({stdoutBytes:uint(),stderrBytes:uint(),stdoutRetainedBytes:uint(65536),stderrRetainedBytes:uint(16384),truncated:Type.Boolean(),complete:Type.Boolean(),validUtf8:Type.Boolean()});
|
|
7
|
+
const data={intendedArgv:Type.Array(text,{minItems:2,maxItems:4}),requestedBranch:nil(text),workingDirectory:nil(text),deleteChangesetsRequested:nil(Type.Boolean()),previewRequested:nil(Type.Boolean()),outputFormatRequested:nil(enm(["text","json"])),attempt,capture:nil(capture),observedDeletedBranch:Type.Null(),observedDeletedChangesets:Type.Null(),observedRepositoryIdentity:Type.Null(),observedWorkspaceIdentity:Type.Null(),verification:Type.Literal("unverified"),rollback:Type.Literal("not-proven")};
|
|
8
|
+
const header={schemaVersion:Type.Literal(1),action:Type.Literal("branch-delete"),provenance:obj({source:Type.Literal("plastic"),producer:Type.Literal("@aefree/pi-plastic"),contentTrust:Type.Literal("external")}),completeness:obj({capture:enm(["complete","incomplete","unknown"]),projection:Type.Literal(true)})};
|
|
9
|
+
const error=obj({code:enm(["invalid_request","launch_failed","aborted","uncertain","producer_failed"]),message:Type.String({minLength:1,maxLength:256})});
|
|
10
|
+
export const branchDeleteOutputSchema=Type.Unsafe<BranchDeleteReceipt>(Type.Union([
|
|
11
|
+
obj({...header,ok:Type.Literal(true),outcome:Type.Literal("preflight"),data:obj({...data,effect:Type.Literal("not-attempted")})}),
|
|
12
|
+
obj({...header,ok:Type.Literal(true),outcome:Type.Literal("command-completed"),data:obj({...data,effect:Type.Literal("not-proven")})}),
|
|
13
|
+
obj({...header,ok:Type.Literal(false),outcome:Type.Literal("failed"),data:obj({...data,effect:Type.Literal("not-attempted")}),error}),
|
|
14
|
+
obj({...header,ok:Type.Literal(false),outcome:Type.Literal("uncertain"),data:obj({...data,effect:Type.Literal("uncertain")}),error})
|
|
15
|
+
]));
|
|
16
|
+
export function validateBranchDeleteOutput(v:unknown):v is BranchDeleteReceipt {
|
|
17
|
+
if(!Check(branchDeleteOutputSchema,v)||Buffer.byteLength(JSON.stringify(v),"utf8")>131072)return false;
|
|
18
|
+
const dto=v as BranchDeleteReceipt,d=dto.data,a=d.attempt,c=d.capture;
|
|
19
|
+
if(d.requestedBranch===null){
|
|
20
|
+
if(JSON.stringify(d.intendedArgv)!==JSON.stringify(["branch","delete"])||[d.workingDirectory,d.deleteChangesetsRequested,d.previewRequested,d.outputFormatRequested].some(x=>x!==null)||c||a.terminal!=="not-observed"||a.exitCode!==null||a.aborted||a.timedOut||!["not-attempted","unknown"].includes(a.state))return false;
|
|
21
|
+
}else{
|
|
22
|
+
if(!validBranchDeleteOperand(d.requestedBranch)||!safeBranchDeleteText(d.workingDirectory)||typeof d.deleteChangesetsRequested!=="boolean"||typeof d.previewRequested!=="boolean"||d.outputFormatRequested===null||JSON.stringify(d.intendedArgv)!==JSON.stringify(branchDeleteArgv(d.requestedBranch,d.deleteChangesetsRequested)))return false;
|
|
23
|
+
}
|
|
24
|
+
if(a.exitCode!==null&&a.terminal!=="observed"||["not-attempted","not-started"].includes(a.state)&&(a.terminal!=="not-observed"||a.exitCode!==null)||a.state==="not-attempted"&&a.timedOut)return false;
|
|
25
|
+
if(c){
|
|
26
|
+
if(d.requestedBranch===null||c.stdoutRetainedBytes>c.stdoutBytes||c.stderrRetainedBytes>c.stderrBytes)return false;
|
|
27
|
+
if(c.complete&&(!c.validUtf8||c.truncated||c.stdoutBytes!==c.stdoutRetainedBytes||c.stderrBytes!==c.stderrRetainedBytes||a.state!=="started"||a.terminal!=="observed"||a.exitCode===null||a.aborted||a.timedOut))return false;
|
|
28
|
+
if(a.state==="not-attempted"&&(!a.aborted||c.stdoutBytes||c.stderrBytes||c.validUtf8||c.complete))return false;
|
|
29
|
+
}
|
|
30
|
+
if(dto.completeness.capture!==(c?c.complete?"complete":"incomplete":"unknown"))return false;
|
|
31
|
+
if(d.previewRequested===true&&(!dto.ok||dto.outcome!=="preflight"||d.requestedBranch===null||c||a.state!=="not-attempted"||a.terminal!=="not-observed"||a.exitCode!==null||a.aborted||a.timedOut))return false;
|
|
32
|
+
if(dto.ok&&dto.outcome==="preflight"&&(d.previewRequested!==true||d.requestedBranch===null))return false;
|
|
33
|
+
if(dto.ok&&dto.outcome==="command-completed"&&(d.previewRequested!==false||d.requestedBranch===null||!c?.complete||a.state!=="started"||a.exitCode!==0))return false;
|
|
34
|
+
if(!dto.ok){
|
|
35
|
+
if(!safeBranchDeleteText(dto.error.message))return false;
|
|
36
|
+
if(dto.outcome==="uncertain"&&!["started","unknown"].includes(a.state)||dto.outcome==="failed"&&!["not-attempted","not-started"].includes(a.state))return false;
|
|
37
|
+
if(dto.error.code==="invalid_request"&&(d.requestedBranch!==null||c||a.state!=="not-attempted"||a.aborted))return false;
|
|
38
|
+
if(dto.error.code==="aborted"&&!a.aborted||dto.error.code==="launch_failed"&&!["not-attempted","not-started"].includes(a.state))return false;
|
|
39
|
+
}
|
|
40
|
+
return true;
|
|
41
|
+
}
|
|
42
|
+
export async function executeBranchDeleteOutput(args:unknown) {
|
|
43
|
+
let dto=await assembleBranchDeleteReceipt(args);const valid:boolean=validateBranchDeleteOutput(dto);
|
|
44
|
+
if(!valid){const possible=["started","unknown"].includes(dto.data.attempt.state),d=emptyBranchDeleteData();if(possible){d.attempt.state="unknown";d.effect="uncertain";}dto=branchDeleteFailure(d,"producer_failed","Branch-delete receipt validation failed; effects remain possible, no automatic retry.");if(!validateBranchDeleteOutput(dto))throw Error("Safe branch-delete receipt unavailable.");}
|
|
45
|
+
return {content:[{type:"text" as const,text:presentBranchDeleteReceipt(dto)}],details:{},structuredContent:dto,isError:!dto.ok};
|
|
46
|
+
}
|
|
@@ -0,0 +1,59 @@
|
|
|
1
|
+
import { Type, type Static, type TSchema } from "typebox";
|
|
2
|
+
import { Check } from "typebox/value";
|
|
3
|
+
import { assembleBranchListObservation, presentBranchListObservation, branchListPayload, BranchListError, validBranchListIdentity, validBranchQueryString, type BranchListArgs, BRANCH_LIST_MAX_BYTES } from "../operations/branch-list";
|
|
4
|
+
import { getActiveAbortSignal } from "../execution/context";
|
|
5
|
+
import { BranchListCommandError } from "../execution/branch-list-command";
|
|
6
|
+
|
|
7
|
+
const object = <T extends Record<string, TSchema>>(fields: T) => Type.Object(fields, { additionalProperties: false });
|
|
8
|
+
const nullable = <T extends TSchema>(schema: T) => Type.Union([schema, Type.Null()]);
|
|
9
|
+
const integer = (maximum = Number.MAX_SAFE_INTEGER) => Type.Integer({ minimum: 0, maximum });
|
|
10
|
+
const queryString = nullable(Type.String({ maxLength: 4096, pattern: "^[^\\u0000-\\u001f\\u007f-\\u009f]*$" }));
|
|
11
|
+
const identity = Type.Intersect([
|
|
12
|
+
Type.String({ minLength: 1, maxLength: 4096, pattern: process.platform === "win32" ? "^[^\\u0000-\\u001f\\u007f-\\u009f\\uFFFD?]+$" : "^[^\\u0000-\\u001f\\u007f-\\u009f\\uFFFD]+$" }),
|
|
13
|
+
Type.String({ pattern: "^/[^/@\\\\]+(?:/[^/@\\\\]+)*$" }),
|
|
14
|
+
]);
|
|
15
|
+
const query = object({ nameLike: queryString, parent: queryString, owner: queryString, includeHidden: Type.Boolean(), orderBy: nullable(Type.Union([Type.Literal("date"), Type.Literal("branchname")])), descending: Type.Boolean(), limit: nullable(Type.Integer({ minimum: 1, maximum: Number.MAX_SAFE_INTEGER })) });
|
|
16
|
+
const limits = object({ maxItems: Type.Integer({ minimum: 1, maximum: 500 }), identityCodeUnits: Type.Literal(4096), compactUtf8Bytes: Type.Literal(131072) });
|
|
17
|
+
const provenance = { source: "plastic", producer: "@aefree/pi-plastic", contentTrust: "external" } as const;
|
|
18
|
+
const common = { schemaVersion: Type.Literal(1), action: Type.Literal("branch-list"), provenance: object({ source: Type.Literal("plastic"), producer: Type.Literal("@aefree/pi-plastic"), contentTrust: Type.Literal("external") }) };
|
|
19
|
+
const dataCommon = { scope: Type.Literal("workspace_repository"), qualifierVerified: Type.Literal(false), query, limits };
|
|
20
|
+
const errorCode = Type.Union([Type.Literal("command_failed"), Type.Literal("aborted"), Type.Literal("capture_incomplete"), Type.Literal("malformed_output"), Type.Literal("invalid_identity"), Type.Literal("unsupported_query"), Type.Literal("invalid_producer_data"), Type.Literal("output_overflow")]);
|
|
21
|
+
export const branchListOutputSchema = Type.Union([
|
|
22
|
+
object({ ...common, ok: Type.Literal(true), completeness: object({ read: Type.Literal("complete"), capture: Type.Literal("complete"), projection: Type.Boolean() }), data: object({ ...dataCommon, mode: Type.Literal("names"), basis: Type.Literal("find_branch_name_utf8"), rows: Type.Array(object({ branch: identity }), { maxItems: 500 }), countBasis: Type.Literal("observed_query_rows"), counts: object({ observed: integer(20000), returned: integer(500), omitted: integer(20000), excluded: Type.Literal(0) }) }) }),
|
|
23
|
+
object({ ...common, ok: Type.Literal(true), completeness: object({ read: Type.Literal("unknown"), capture: Type.Literal("unknown"), projection: Type.Literal(false) }), data: object({ ...dataCommon, mode: Type.Literal("native"), basis: Type.Literal("find_branch_native"), rows: Type.Null(), countBasis: Type.Literal("unavailable"), counts: Type.Null() }) }),
|
|
24
|
+
object({ ...common, ok: Type.Literal(false), completeness: object({ read: Type.Literal("incomplete"), capture: Type.Literal("unknown"), projection: Type.Literal(false) }), error: object({ code: errorCode, message: Type.String({ minLength: 1, maxLength: 256 }) }) }),
|
|
25
|
+
]);
|
|
26
|
+
export type BranchListOutput = Static<typeof branchListOutputSchema>;
|
|
27
|
+
export type BranchListErrorCode = Extract<BranchListOutput, { ok: false }>["error"]["code"];
|
|
28
|
+
const failure = (code: BranchListErrorCode): BranchListOutput => ({ schemaVersion: 1, action: "branch-list", provenance, ok: false, completeness: { read: "incomplete", capture: "unknown", projection: false }, error: { code, message: `Branch list read failed (${code}); no reusable rows are available.` } });
|
|
29
|
+
export function validateBranchListOutput(value: unknown): BranchListOutput {
|
|
30
|
+
if (!Check(branchListOutputSchema, value)) return failure("invalid_producer_data");
|
|
31
|
+
const dto = value as BranchListOutput;
|
|
32
|
+
if (dto.ok) {
|
|
33
|
+
if ([dto.data.query.nameLike, dto.data.query.parent, dto.data.query.owner].some(value => value !== null && !validBranchQueryString(value))) return failure("invalid_producer_data");
|
|
34
|
+
if (dto.data.mode === "names") {
|
|
35
|
+
const { counts, rows, limits, query } = dto.data;
|
|
36
|
+
if (query.includeHidden || rows.some(row => !validBranchListIdentity(row.branch)) || new Set(rows.map(row => row.branch)).size !== rows.length
|
|
37
|
+
|| counts.returned !== rows.length || counts.observed !== counts.returned + counts.omitted
|
|
38
|
+
|| counts.returned !== Math.min(counts.observed, limits.maxItems)
|
|
39
|
+
|| (query.limit !== null && counts.observed > query.limit)
|
|
40
|
+
|| dto.completeness.projection !== (counts.omitted === 0)) return failure("invalid_producer_data");
|
|
41
|
+
}
|
|
42
|
+
}
|
|
43
|
+
if (Buffer.byteLength(JSON.stringify(dto), "utf8") > BRANCH_LIST_MAX_BYTES) return failure("output_overflow");
|
|
44
|
+
return dto;
|
|
45
|
+
}
|
|
46
|
+
export async function executeBranchListOutput(args: BranchListArgs) {
|
|
47
|
+
let dto: BranchListOutput;
|
|
48
|
+
let rawResult: string | undefined;
|
|
49
|
+
try {
|
|
50
|
+
const observation = await assembleBranchListObservation(args, true);
|
|
51
|
+
if (getActiveAbortSignal()?.aborted) throw new BranchListCommandError("aborted");
|
|
52
|
+
dto = validateBranchListOutput(branchListPayload(observation));
|
|
53
|
+
if (dto.ok) rawResult = args.format === "json" ? JSON.stringify(dto) : presentBranchListObservation(observation);
|
|
54
|
+
} catch (error) {
|
|
55
|
+
dto = failure(error instanceof BranchListError || error instanceof BranchListCommandError ? error.code : "invalid_producer_data");
|
|
56
|
+
}
|
|
57
|
+
dto = validateBranchListOutput(dto);
|
|
58
|
+
return { content: [{ type: "text" as const, text: dto.ok ? rawResult! : dto.error.message }], structuredContent: dto, isError: !dto.ok, details: { exportName: "branchList", rawResult: dto.ok ? rawResult : undefined, ...(args.workdir ? { workdir: args.workdir } : {}) } };
|
|
59
|
+
}
|
|
@@ -0,0 +1,63 @@
|
|
|
1
|
+
import { Type, type Static, type TSchema } from "typebox";
|
|
2
|
+
import { Check } from "typebox/value";
|
|
3
|
+
import { assembleCurrentBranchObservation, assembleBranchExistsObservation, presentCurrentBranchObservation, presentBranchExistsObservation, BranchReadError, validBranchIdentity, type BranchReadObservation } from "../operations/branch-reads";
|
|
4
|
+
|
|
5
|
+
export const BRANCH_OUTPUT_MAX_BYTES = 16384;
|
|
6
|
+
const object = <T extends Record<string, TSchema>>(fields: T) => Type.Object(fields, { additionalProperties: false });
|
|
7
|
+
const identity = Type.String({ minLength: 1, maxLength: 4096, pattern: process.platform === "win32" ? "^[^\\u0000-\\u001f\\u007f-\\u009f\\uFFFD?]+$" : "^[^\\u0000-\\u001f\\u007f-\\u009f\\uFFFD]+$" });
|
|
8
|
+
const observedPath = Type.Intersect([identity, Type.String({ pattern: "^/[^/@\\\\]+(?:/[^/@\\\\]+)*$" })]);
|
|
9
|
+
const provenance = { source: "plastic", producer: "@aefree/pi-plastic", contentTrust: "external" } as const;
|
|
10
|
+
const provenanceSchema = object({ source: Type.Literal("plastic"), producer: Type.Literal("@aefree/pi-plastic"), contentTrust: Type.Literal("external") });
|
|
11
|
+
const errorSchema = object({ code: Type.Union([Type.Literal("command_failed"), Type.Literal("aborted"), Type.Literal("capture_incomplete"), Type.Literal("invalid_identity"), Type.Literal("malformed_output"), Type.Literal("invalid_producer_data"), Type.Literal("output_overflow")]), message: Type.String({ minLength: 1, maxLength: 256 }) });
|
|
12
|
+
const completeness = (success: boolean) => object({ read: Type.Literal(success ? "complete" : "incomplete"), capture: Type.Literal(success ? "complete" : "unknown"), projection: Type.Literal(success) });
|
|
13
|
+
const common = <A extends string>(action: A) => ({ schemaVersion: Type.Literal(1), action: Type.Literal(action), provenance: provenanceSchema });
|
|
14
|
+
export const currentBranchOutputSchema = Type.Union([
|
|
15
|
+
object({ ...common("current-branch"), ok: Type.Literal(true), completeness: completeness(true), data: object({ branch: observedPath, basis: Type.Union([Type.Literal("status"), Type.Literal("compact_status"), Type.Literal("changeset_lookup")]), scope: Type.Literal("workspace") }) }),
|
|
16
|
+
object({ ...common("current-branch"), ok: Type.Literal(false), completeness: completeness(false), error: errorSchema }),
|
|
17
|
+
]);
|
|
18
|
+
export const branchExistsOutputSchema = Type.Union([
|
|
19
|
+
object({ ...common("branch-exists"), ok: Type.Literal(true), completeness: completeness(true), data: object({ requestedBranch: identity, comparisonBranch: identity, scope: Type.Literal("workspace_repository"), qualifierVerified: Type.Optional(Type.Literal(false)), exists: Type.Boolean() }) }),
|
|
20
|
+
object({ ...common("branch-exists"), ok: Type.Literal(false), completeness: completeness(false), error: errorSchema }),
|
|
21
|
+
]);
|
|
22
|
+
export type CurrentBranchOutput = Static<typeof currentBranchOutputSchema>;
|
|
23
|
+
export type BranchExistsOutput = Static<typeof branchExistsOutputSchema>;
|
|
24
|
+
export type BranchOutput = CurrentBranchOutput | BranchExistsOutput;
|
|
25
|
+
type Action = BranchOutput["action"];
|
|
26
|
+
const failure = (action: Action, code: Extract<BranchOutput, { ok: false }>["error"]["code"], message: string): BranchOutput => ({ schemaVersion: 1, action, provenance, ok: false, completeness: { read: "incomplete", capture: "unknown", projection: false }, error: { code, message } });
|
|
27
|
+
export function validateBranchOutput(action: Action, value: unknown): BranchOutput {
|
|
28
|
+
if (!Check(action === "current-branch" ? currentBranchOutputSchema : branchExistsOutputSchema, value)) return failure(action, "invalid_producer_data", "Branch producer data did not match the output contract.");
|
|
29
|
+
const dto = value as BranchOutput;
|
|
30
|
+
if (dto.ok) {
|
|
31
|
+
const identities = dto.action === "current-branch" ? [dto.data.branch] : [dto.data.requestedBranch, dto.data.comparisonBranch];
|
|
32
|
+
if (identities.some(identity => !validBranchIdentity(identity))) return failure(action, "invalid_producer_data", "Branch producer identity was invalid or lossy.");
|
|
33
|
+
}
|
|
34
|
+
if (Buffer.byteLength(JSON.stringify(value), "utf8") > BRANCH_OUTPUT_MAX_BYTES) return failure(action, "output_overflow", "Branch output exceeded the compact UTF-8 byte limit.");
|
|
35
|
+
return value as BranchOutput;
|
|
36
|
+
}
|
|
37
|
+
export function projectBranchOutput(observation: BranchReadObservation): BranchOutput {
|
|
38
|
+
const { action, ...data } = observation;
|
|
39
|
+
return validateBranchOutput(action, { schemaVersion: 1, action, provenance, ok: true, completeness: { read: "complete", capture: "complete", projection: true }, data });
|
|
40
|
+
}
|
|
41
|
+
export async function executeBranchOutput(exportName: "currentBranch" | "branchExists", args: { branch?: string; format?: "text" | "json"; workdir?: string }) {
|
|
42
|
+
const action = exportName === "currentBranch" ? "current-branch" : "branch-exists";
|
|
43
|
+
let dto: BranchOutput;
|
|
44
|
+
let rawResult: string | undefined;
|
|
45
|
+
try {
|
|
46
|
+
if (exportName === "currentBranch") {
|
|
47
|
+
const observation = await assembleCurrentBranchObservation(args);
|
|
48
|
+
dto = projectBranchOutput(observation);
|
|
49
|
+
if (dto.ok) rawResult = await presentCurrentBranchObservation(observation, args);
|
|
50
|
+
} else {
|
|
51
|
+
if (typeof args.branch !== "string") throw new BranchReadError("invalid_identity");
|
|
52
|
+
const observation = await assembleBranchExistsObservation({ ...args, branch: args.branch });
|
|
53
|
+
dto = projectBranchOutput(observation);
|
|
54
|
+
if (dto.ok) rawResult = presentBranchExistsObservation(observation);
|
|
55
|
+
}
|
|
56
|
+
} catch (error) {
|
|
57
|
+
dto = failure(action, error instanceof BranchReadError ? error.code : "invalid_producer_data", error instanceof BranchReadError ? error.message : "Branch producer failed to produce valid data.");
|
|
58
|
+
}
|
|
59
|
+
// Validate even failures immediately before return.
|
|
60
|
+
dto = validateBranchOutput(action, dto);
|
|
61
|
+
return { content: [{ type: "text" as const, text: !dto.ok ? dto.error.message : dto.action === "current-branch" ? dto.data.branch : `${dto.data.exists} (workspace repository path comparison${dto.data.qualifierVerified === false ? "; requested repository qualifier NOT verified" : ""}).` }], structuredContent: dto, isError: !dto.ok,
|
|
62
|
+
details: { exportName, rawResult, ...(args.workdir ? { workdir: args.workdir } : {}) } };
|
|
63
|
+
}
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
import { getPatchBackendCapabilityWarning } from "../operations/patch";
|
|
2
|
+
|
|
3
|
+
export type CapabilityDiagnosticContext = {
|
|
4
|
+
hasUI?: boolean;
|
|
5
|
+
ui?: { notify?: (message: string, level: "warning") => void };
|
|
6
|
+
};
|
|
7
|
+
|
|
8
|
+
type PatchCapabilityWarningGetter = () => Promise<string | undefined>;
|
|
9
|
+
|
|
10
|
+
export const __plasticCapabilityDiagnosticInternals = {
|
|
11
|
+
createPatchCapabilityWarningNotifier(getWarning: PatchCapabilityWarningGetter = () => getPatchBackendCapabilityWarning()) {
|
|
12
|
+
let emitted = false;
|
|
13
|
+
return async (ctx: CapabilityDiagnosticContext): Promise<void> => {
|
|
14
|
+
if (emitted || !ctx.hasUI || typeof ctx.ui?.notify !== "function") return;
|
|
15
|
+
const warning = await getWarning();
|
|
16
|
+
if (!warning) return;
|
|
17
|
+
emitted = true;
|
|
18
|
+
ctx.ui.notify(warning, "warning");
|
|
19
|
+
};
|
|
20
|
+
},
|
|
21
|
+
};
|
|
22
|
+
|
|
23
|
+
export const notifyPatchCapabilityWarning = __plasticCapabilityDiagnosticInternals.createPatchCapabilityWarningNotifier();
|